194 lines
4.8 KiB
C
194 lines
4.8 KiB
C
|
|
// SPDX-License-Identifier: MPL-2.0
|
||
|
|
|
||
|
|
#define _GNU_SOURCE
|
||
|
|
#include <errno.h>
|
||
|
|
#include <fcntl.h>
|
||
|
|
#include <linux/fb.h>
|
||
|
|
#include <stdbool.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <sys/ioctl.h>
|
||
|
|
#include <sys/mman.h>
|
||
|
|
#include <sys/types.h>
|
||
|
|
#include <sys/wait.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
|
||
|
|
#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()
|