diff --git a/test/src/apps/devfs/framebuffer.c b/test/src/apps/devfs/framebuffer.c new file mode 100644 index 000000000..3e28ec908 --- /dev/null +++ b/test/src/apps/devfs/framebuffer.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../test.h" + +#define FB_DEVICE "/dev/fb0" +#define PAGE_SIZE 4096 +#define CMAP_LEN 4 + +static int fb_fd = -1; +static size_t fb_smem_len; + +FN_SETUP(open_framebuffer) +{ + struct fb_fix_screeninfo fix_info; + struct fb_var_screeninfo var_info; + + fb_fd = open(FB_DEVICE, O_RDWR); + if (fb_fd < 0) { + if (errno == ENOENT || errno == ENODEV || errno == ENXIO) { + fprintf(stderr, "framebuffer tests skipped: %s (%s)\n", + FB_DEVICE, strerror(errno)); + exit(EXIT_SUCCESS); + } + fprintf(stderr, + "fatal error: setup_open_framebuffer: open('%s') failed: %s\n", + FB_DEVICE, strerror(errno)); + exit(EXIT_FAILURE); + } + + CHECK_WITH(ioctl(fb_fd, FBIOGET_FSCREENINFO, &fix_info), + _ret == 0 && fix_info.smem_len != 0); + CHECK_WITH(ioctl(fb_fd, FBIOGET_VSCREENINFO, &var_info), + _ret == 0 && var_info.xres != 0); + + fb_smem_len = fix_info.smem_len; +} +END_SETUP() + +FN_TEST(color_map) +{ + uint16_t red_expected[CMAP_LEN]; + uint16_t green_expected[CMAP_LEN]; + uint16_t blue_expected[CMAP_LEN]; + + for (size_t i = 0; i < CMAP_LEN; ++i) { + red_expected[i] = 0x0100 + i; + green_expected[i] = 0x0200 + i; + blue_expected[i] = 0x0300 + i; + } + + uint16_t red[CMAP_LEN]; + uint16_t green[CMAP_LEN]; + uint16_t blue[CMAP_LEN]; + + memcpy(red, red_expected, sizeof(red)); + memcpy(green, green_expected, sizeof(green)); + memcpy(blue, blue_expected, sizeof(blue)); + + struct fb_cmap set_cmap = { + .start = 0, + .len = CMAP_LEN, + .red = red, + .green = green, + .blue = blue, + .transp = NULL, + }; + + TEST_SUCC(ioctl(fb_fd, FBIOPUTCMAP, &set_cmap)); + + memset(red, 0, sizeof(red)); + memset(green, 0, sizeof(green)); + memset(blue, 0, sizeof(blue)); + + struct fb_cmap get_cmap = { + .start = 0, + .len = CMAP_LEN, + .red = red, + .green = green, + .blue = blue, + .transp = NULL, + }; + + TEST_SUCC(ioctl(fb_fd, FBIOGETCMAP, &get_cmap)); + TEST_RES(memcmp(red, red_expected, sizeof(red)), _ret == 0); + TEST_RES(memcmp(green, green_expected, sizeof(green)), _ret == 0); + TEST_RES(memcmp(blue, blue_expected, sizeof(blue)), _ret == 0); + + // Test invalid color map: use a start index that's definitely out of range + // Most devices have 256 entries or less, so start=0x10000 should always fail + struct fb_cmap invalid_cmap = { + .start = 0x10000, + .len = 1, + .red = red, + .green = green, + .blue = blue, + .transp = NULL, + }; + + TEST_ERRNO(ioctl(fb_fd, FBIOPUTCMAP, &invalid_cmap), EINVAL); +} +END_TEST() + +FN_TEST(write_enospc) +{ + const unsigned char test_pattern = 0xff; + + // First, read the last byte to preserve it + unsigned char original_byte = 0; + TEST_RES(lseek(fb_fd, (off_t)(fb_smem_len - 1), SEEK_SET), + _ret == (off_t)(fb_smem_len - 1)); + TEST_RES(read(fb_fd, &original_byte, 1), _ret == 1); + + // Now seek to the end and try to write beyond the end of the framebuffer + TEST_RES(lseek(fb_fd, (off_t)fb_smem_len, SEEK_SET), + _ret == (off_t)fb_smem_len); + TEST_ERRNO(write(fb_fd, &test_pattern, sizeof(test_pattern)), ENOSPC); + + // Finally, verify that the last byte is unchanged + TEST_RES(lseek(fb_fd, (off_t)(fb_smem_len - 1), SEEK_SET), + _ret == (off_t)(fb_smem_len - 1)); + unsigned char check_byte = 0; + TEST_RES(read(fb_fd, &check_byte, 1), _ret == 1); + TEST_RES(memcmp(&check_byte, &original_byte, sizeof(check_byte)), + _ret == 0); +} +END_TEST() + +FN_TEST(mmap_mremap_and_fork) +{ + static uint8_t pattern[PAGE_SIZE]; + static uint8_t fork_pattern[PAGE_SIZE]; + + size_t map_len = fb_smem_len; + if (map_len > sizeof(pattern)) { + map_len = sizeof(pattern); + } + + for (size_t i = 0; i < map_len; ++i) { + pattern[i] = (uint8_t)(i & 0xff); + fork_pattern[i] = (uint8_t)(0xaa ^ (i & 0xff)); + } + + uint8_t *mapped = TEST_SUCC((uint8_t *)mmap( + NULL, map_len, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0)); + memcpy(mapped, pattern, map_len); + TEST_RES(memcmp(mapped, pattern, map_len), _ret == 0); + + uint8_t *remapped = TEST_SUCC( + (uint8_t *)mremap(mapped, map_len, map_len, MREMAP_MAYMOVE)); + if (remapped != mapped) { + mapped = remapped; + } + + TEST_RES(memcmp(mapped, pattern, map_len), _ret == 0); + + pid_t pid = TEST_SUCC(fork()); + if (pid == 0) { + // Child process: avoid TEST_* so the parent sees the status via waitpid + CHECK_WITH(memcmp(mapped, pattern, map_len), _ret == 0); + memcpy(mapped, fork_pattern, map_len); + _exit(EXIT_SUCCESS); + } + + // Parent process + int status = 0; + TEST_RES(waitpid(pid, &status, 0), + _ret == pid && WIFEXITED(status) && WEXITSTATUS(status) == 0); + TEST_RES(memcmp(mapped, fork_pattern, map_len), _ret == 0); + + TEST_RES(munmap(mapped, map_len), _ret == 0); +} +END_TEST() + +FN_SETUP(close_framebuffer) +{ + CHECK(close(fb_fd)); +} +END_SETUP() diff --git a/test/src/apps/scripts/fs.sh b/test/src/apps/scripts/fs.sh index 22b866f57..f2f2cd10d 100755 --- a/test/src/apps/scripts/fs.sh +++ b/test/src/apps/scripts/fs.sh @@ -106,4 +106,5 @@ file_io/access_err file_io/iovec_err devfs/full devfs/random +devfs/framebuffer devfs/evdev