diff --git a/base-sysroot.tar.gz b/base-sysroot.tar.gz index f30cf5e6..457c9904 100644 Binary files a/base-sysroot.tar.gz and b/base-sysroot.tar.gz differ diff --git a/userspace/CMakeLists.txt b/userspace/CMakeLists.txt index c10c321d..31fdebae 100644 --- a/userspace/CMakeLists.txt +++ b/userspace/CMakeLists.txt @@ -11,6 +11,7 @@ set(USERSPACE_PROJECTS echo id init + image ls meminfo mkdir diff --git a/userspace/image/CMakeLists.txt b/userspace/image/CMakeLists.txt new file mode 100644 index 00000000..49431bfc --- /dev/null +++ b/userspace/image/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.26) + +project(image CXX) + +set(SOURCES + main.cpp + Image.cpp + Netbpm.cpp +) + +add_executable(image ${SOURCES}) +target_compile_options(image PUBLIC -O2 -g) +target_link_libraries(image PUBLIC libc ban) + +add_custom_target(image-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/image ${BANAN_BIN}/ + DEPENDS image +) diff --git a/userspace/image/Image.cpp b/userspace/image/Image.cpp new file mode 100644 index 00000000..20152909 --- /dev/null +++ b/userspace/image/Image.cpp @@ -0,0 +1,149 @@ +#include "Image.h" +#include "Netbpm.h" + +#include +#include +#include +#include + +BAN::UniqPtr Image::load_from_file(BAN::StringView path) +{ + int fd = -1; + + if (path.data()[path.size()] == '\0') + { + fd = open(path.data(), O_RDONLY); + } + else + { + char* buffer = (char*)malloc(path.size() + 1); + if (!buffer) + { + perror("malloc"); + return {}; + } + memcpy(buffer, path.data(), path.size()); + buffer[path.size()] = '\0'; + + fd = open(path.data(), O_RDONLY); + + free(buffer); + } + + if (fd == -1) + { + perror("open"); + return {}; + } + + struct stat st; + if (fstat(fd, &st) == -1) + { + perror("fstat"); + close(fd); + return {}; + } + + if (st.st_size < 2) + { + fprintf(stderr, "invalid image (too small)\n"); + close(fd); + return {}; + } + + void* addr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + { + perror("mmap"); + close(fd); + return {}; + } + + BAN::UniqPtr image; + + uint16_t u16_signature = *reinterpret_cast(addr); + switch (u16_signature) + { + case 0x3650: + case 0x3550: + case 0x3450: + case 0x3350: + case 0x3250: + case 0x3150: + if (auto res = Netbpm::create(addr, st.st_size); res.is_error()) + fprintf(stderr, "%s\n", strerror(res.error().get_error_code())); + else + image = res.release_value(); + break; + default: + fprintf(stderr, "unrecognized image format\n"); + break; + } + + munmap(addr, st.st_size); + close(fd); + + return image; +} + +bool Image::render_to_framebuffer() +{ + int fd = open("/dev/fb0", O_RDWR); + if (fd == -1) + { + perror("open"); + return false; + } + + framebuffer_info_t fb_info; + if (pread(fd, &fb_info, sizeof(fb_info), -1) == -1) + { + perror("pread"); + close(fd); + return false; + } + + if (fb_info.bpp != 24 && fb_info.bpp != 32) + { + fprintf(stderr, "unsupported framebuffer bpp\n"); + close(fd); + return false; + } + + size_t mmap_size = fb_info.height * fb_info.width * fb_info.bpp / 8; + + void* mmap_addr = mmap(nullptr, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (mmap_addr == MAP_FAILED) + { + perror("mmap"); + close(fd); + return false; + } + + uint8_t* u8_fb = reinterpret_cast(mmap_addr); + + for (uint64_t y = 0; y < BAN::Math::min(height(), fb_info.height); y++) + { + for (uint64_t x = 0; x < BAN::Math::min(width(), fb_info.width); x++) + { + u8_fb[(y * fb_info.width + x) * fb_info.bpp / 8 + 0] = m_bitmap[y * width() + x].r; + u8_fb[(y * fb_info.width + x) * fb_info.bpp / 8 + 1] = m_bitmap[y * width() + x].g; + u8_fb[(y * fb_info.width + x) * fb_info.bpp / 8 + 2] = m_bitmap[y * width() + x].b; + if (fb_info.bpp == 32) + u8_fb[(y * fb_info.width + x) * fb_info.bpp / 8 + 3] = m_bitmap[y * width() + x].a; + } + } + + if (msync(mmap_addr, mmap_size, MS_SYNC) == -1) + { + perror("msync"); + munmap(mmap_addr, mmap_size); + close(fd); + return false; + } + + munmap(mmap_addr, mmap_size); + close(fd); + + return true; +} diff --git a/userspace/image/Image.h b/userspace/image/Image.h new file mode 100644 index 00000000..613ff5a4 --- /dev/null +++ b/userspace/image/Image.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +class Image +{ +public: + struct Color + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; + +public: + static BAN::UniqPtr load_from_file(BAN::StringView path); + + uint64_t width() const { return m_width; } + uint64_t height() const { return m_height; } + + bool render_to_framebuffer(); + +protected: + Image(uint64_t width, uint64_t height, BAN::Vector&& bitmap) + : m_width(width) + , m_height(height) + , m_bitmap(BAN::move(bitmap)) + { } + +protected: + const uint64_t m_width; + const uint64_t m_height; + const BAN::Vector m_bitmap; +}; diff --git a/userspace/image/Netbpm.cpp b/userspace/image/Netbpm.cpp new file mode 100644 index 00000000..6d65b0ac --- /dev/null +++ b/userspace/image/Netbpm.cpp @@ -0,0 +1,108 @@ +#include "Netbpm.h" + +#include + +#include +#include + +BAN::Optional parse_u64(const uint8_t*& data, size_t data_size) +{ + uint64_t result = 0; + + // max supported size 10^20 - 1 + for (size_t i = 0; i < 19; i++) + { + if (i >= data_size) + { + if (isdigit(*data)) + return {}; + return result; + } + + if (!isdigit(*data)) + return result; + + result = (result * 10) + (*data - '0'); + data++; + } + + return {}; +} + +BAN::ErrorOr> Netbpm::create(const void* mmap_addr, size_t size) +{ + if (size < 11) + { + fprintf(stderr, "invalid Netbpm image (too small)\n"); + return BAN::Error::from_errno(EINVAL); + } + + const uint8_t* u8_ptr = reinterpret_cast(mmap_addr); + + if (u8_ptr[0] != 'P') + { + fprintf(stderr, "not Netbpm image\n"); + return BAN::Error::from_errno(EINVAL); + } + if (u8_ptr[1] != '6') + { + fprintf(stderr, "unsupported Netbpm image\n"); + return BAN::Error::from_errno(EINVAL); + } + if (u8_ptr[2] != '\n') + { + fprintf(stderr, "invalid Netbpm image (invalid header)\n"); + return BAN::Error::from_errno(EINVAL); + } + u8_ptr += 3; + + auto width = parse_u64(u8_ptr, size - (u8_ptr - reinterpret_cast(mmap_addr))); + if (!width.has_value() || *u8_ptr != ' ') + { + fprintf(stderr, "invalid Netbpm image (invalid width)\n"); + return BAN::Error::from_errno(EINVAL); + } + u8_ptr++; + + auto height = parse_u64(u8_ptr, size - (u8_ptr - reinterpret_cast(mmap_addr))); + if (!height.has_value() || *u8_ptr != '\n') + { + fprintf(stderr, "invalid Netbpm image (invalid height)\n"); + return BAN::Error::from_errno(EINVAL); + } + u8_ptr++; + + auto header_end = parse_u64(u8_ptr, size - (u8_ptr - reinterpret_cast(mmap_addr))); + if (!header_end.has_value() || *header_end != 255 || *u8_ptr != '\n') + { + fprintf(stderr, "invalid Netbpm image (invalid header end)\n"); + return BAN::Error::from_errno(EINVAL); + } + u8_ptr++; + + if (size - (u8_ptr - reinterpret_cast(mmap_addr)) < *width * *height * 3) + { + fprintf(stderr, "invalid Netbpm image (too small file size)\n"); + return BAN::Error::from_errno(EINVAL); + } + + printf("Netbpm image %llux%llu\n", *width, *height); + + BAN::Vector bitmap; + TRY(bitmap.resize(*width * *height)); + + // Fill bitmap + for (uint64_t y = 0; y < *height; y++) + { + for (uint64_t x = 0; x < *width; x++) + { + auto& pixel = bitmap[y * *width + x]; + pixel.r = *u8_ptr++; + pixel.g = *u8_ptr++; + pixel.b = *u8_ptr++; + pixel.a = 0xFF; + } + } + + return TRY(BAN::UniqPtr::create(*width, *height, BAN::move(bitmap))); +} diff --git a/userspace/image/Netbpm.h b/userspace/image/Netbpm.h new file mode 100644 index 00000000..325d85fa --- /dev/null +++ b/userspace/image/Netbpm.h @@ -0,0 +1,14 @@ +#include "Image.h" + +class Netbpm : public Image +{ +public: + static BAN::ErrorOr> create(const void* mmap_addr, size_t size); + +private: + Netbpm(uint64_t width, uint64_t height, BAN::Vector&& bitmap) + : Image(width, height, BAN::move(bitmap)) + { } + + friend class BAN::UniqPtr; +}; diff --git a/userspace/image/main.cpp b/userspace/image/main.cpp new file mode 100644 index 00000000..a82d8715 --- /dev/null +++ b/userspace/image/main.cpp @@ -0,0 +1,29 @@ +#include "Image.h" + +#include +#include + +int usage(char* arg0, int ret) +{ + FILE* out = (ret == 0) ? stdout : stderr; + fprintf(out, "usage: %s IMAGE_PATH\n", arg0); + return ret; +} + +int main(int argc, char** argv) +{ + if (argc != 2) + return usage(argv[0], 1); + + auto image = Image::load_from_file(argv[1]); + if (!image) + return 1; + + if (!image->render_to_framebuffer()) + return 1; + + for (;;) + sleep(1); + + return 0; +}