| // SPDX-License-Identifier: GPL-2.0-only |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| |
| #include <asm/papr-vpd.h> |
| |
| #include "utils.h" |
| |
| #define DEVPATH "/dev/papr-vpd" |
| |
| static int dev_papr_vpd_open_close(void) |
| { |
| const int devfd = open(DEVPATH, O_RDONLY); |
| |
| SKIP_IF_MSG(devfd < 0 && errno == ENOENT, |
| DEVPATH " not present"); |
| |
| FAIL_IF(devfd < 0); |
| FAIL_IF(close(devfd) != 0); |
| |
| return 0; |
| } |
| |
| static int dev_papr_vpd_get_handle_all(void) |
| { |
| const int devfd = open(DEVPATH, O_RDONLY); |
| struct papr_location_code lc = { .str = "", }; |
| off_t size; |
| int fd; |
| |
| SKIP_IF_MSG(devfd < 0 && errno == ENOENT, |
| DEVPATH " not present"); |
| |
| FAIL_IF(devfd < 0); |
| |
| errno = 0; |
| fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc); |
| FAIL_IF(errno != 0); |
| FAIL_IF(fd < 0); |
| |
| FAIL_IF(close(devfd) != 0); |
| |
| size = lseek(fd, 0, SEEK_END); |
| FAIL_IF(size <= 0); |
| |
| void *buf = malloc((size_t)size); |
| FAIL_IF(!buf); |
| |
| ssize_t consumed = pread(fd, buf, size, 0); |
| FAIL_IF(consumed != size); |
| |
| /* Ensure EOF */ |
| FAIL_IF(read(fd, buf, size) != 0); |
| FAIL_IF(close(fd)); |
| |
| /* Verify that the buffer looks like VPD */ |
| static const char needle[] = "System VPD"; |
| FAIL_IF(!memmem(buf, size, needle, strlen(needle))); |
| |
| return 0; |
| } |
| |
| static int dev_papr_vpd_get_handle_byte_at_a_time(void) |
| { |
| const int devfd = open(DEVPATH, O_RDONLY); |
| struct papr_location_code lc = { .str = "", }; |
| int fd; |
| |
| SKIP_IF_MSG(devfd < 0 && errno == ENOENT, |
| DEVPATH " not present"); |
| |
| FAIL_IF(devfd < 0); |
| |
| errno = 0; |
| fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc); |
| FAIL_IF(errno != 0); |
| FAIL_IF(fd < 0); |
| |
| FAIL_IF(close(devfd) != 0); |
| |
| size_t consumed = 0; |
| while (1) { |
| ssize_t res; |
| char c; |
| |
| errno = 0; |
| res = read(fd, &c, sizeof(c)); |
| FAIL_IF(res > sizeof(c)); |
| FAIL_IF(res < 0); |
| FAIL_IF(errno != 0); |
| consumed += res; |
| if (res == 0) |
| break; |
| } |
| |
| FAIL_IF(consumed != lseek(fd, 0, SEEK_END)); |
| |
| FAIL_IF(close(fd)); |
| |
| return 0; |
| } |
| |
| |
| static int dev_papr_vpd_unterm_loc_code(void) |
| { |
| const int devfd = open(DEVPATH, O_RDONLY); |
| struct papr_location_code lc = {}; |
| int fd; |
| |
| SKIP_IF_MSG(devfd < 0 && errno == ENOENT, |
| DEVPATH " not present"); |
| |
| FAIL_IF(devfd < 0); |
| |
| /* |
| * Place a non-null byte in every element of loc_code; the |
| * driver should reject this input. |
| */ |
| memset(lc.str, 'x', ARRAY_SIZE(lc.str)); |
| |
| errno = 0; |
| fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc); |
| FAIL_IF(fd != -1); |
| FAIL_IF(errno != EINVAL); |
| |
| FAIL_IF(close(devfd) != 0); |
| return 0; |
| } |
| |
| static int dev_papr_vpd_null_handle(void) |
| { |
| const int devfd = open(DEVPATH, O_RDONLY); |
| int rc; |
| |
| SKIP_IF_MSG(devfd < 0 && errno == ENOENT, |
| DEVPATH " not present"); |
| |
| FAIL_IF(devfd < 0); |
| |
| errno = 0; |
| rc = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, NULL); |
| FAIL_IF(rc != -1); |
| FAIL_IF(errno != EFAULT); |
| |
| FAIL_IF(close(devfd) != 0); |
| return 0; |
| } |
| |
| static int papr_vpd_close_handle_without_reading(void) |
| { |
| const int devfd = open(DEVPATH, O_RDONLY); |
| struct papr_location_code lc = { .str = "", }; |
| int fd; |
| |
| SKIP_IF_MSG(devfd < 0 && errno == ENOENT, |
| DEVPATH " not present"); |
| |
| FAIL_IF(devfd < 0); |
| |
| errno = 0; |
| fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc); |
| FAIL_IF(errno != 0); |
| FAIL_IF(fd < 0); |
| |
| /* close the handle without reading it */ |
| FAIL_IF(close(fd) != 0); |
| |
| FAIL_IF(close(devfd) != 0); |
| return 0; |
| } |
| |
| static int papr_vpd_reread(void) |
| { |
| const int devfd = open(DEVPATH, O_RDONLY); |
| struct papr_location_code lc = { .str = "", }; |
| int fd; |
| |
| SKIP_IF_MSG(devfd < 0 && errno == ENOENT, |
| DEVPATH " not present"); |
| |
| FAIL_IF(devfd < 0); |
| |
| errno = 0; |
| fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc); |
| FAIL_IF(errno != 0); |
| FAIL_IF(fd < 0); |
| |
| FAIL_IF(close(devfd) != 0); |
| |
| const off_t size = lseek(fd, 0, SEEK_END); |
| FAIL_IF(size <= 0); |
| |
| char *bufs[2]; |
| |
| for (size_t i = 0; i < ARRAY_SIZE(bufs); ++i) { |
| bufs[i] = malloc(size); |
| FAIL_IF(!bufs[i]); |
| ssize_t consumed = pread(fd, bufs[i], size, 0); |
| FAIL_IF(consumed != size); |
| } |
| |
| FAIL_IF(memcmp(bufs[0], bufs[1], size)); |
| |
| FAIL_IF(close(fd) != 0); |
| |
| return 0; |
| } |
| |
| static int get_system_loc_code(struct papr_location_code *lc) |
| { |
| static const char system_id_path[] = "/sys/firmware/devicetree/base/system-id"; |
| static const char model_path[] = "/sys/firmware/devicetree/base/model"; |
| char *system_id; |
| char *model; |
| int err = -1; |
| |
| if (read_file_alloc(model_path, &model, NULL)) |
| return err; |
| |
| if (read_file_alloc(system_id_path, &system_id, NULL)) |
| goto free_model; |
| |
| char *mtm; |
| int sscanf_ret = sscanf(model, "IBM,%ms", &mtm); |
| if (sscanf_ret != 1) |
| goto free_system_id; |
| |
| char *plant_and_seq; |
| if (sscanf(system_id, "IBM,%*c%*c%ms", &plant_and_seq) != 1) |
| goto free_mtm; |
| /* |
| * Replace - with . to build location code. |
| */ |
| char *sep = strchr(mtm, '-'); |
| if (!sep) |
| goto free_mtm; |
| else |
| *sep = '.'; |
| |
| snprintf(lc->str, sizeof(lc->str), |
| "U%s.%s", mtm, plant_and_seq); |
| err = 0; |
| |
| free(plant_and_seq); |
| free_mtm: |
| free(mtm); |
| free_system_id: |
| free(system_id); |
| free_model: |
| free(model); |
| return err; |
| } |
| |
| static int papr_vpd_system_loc_code(void) |
| { |
| struct papr_location_code lc; |
| const int devfd = open(DEVPATH, O_RDONLY); |
| off_t size; |
| int fd; |
| |
| SKIP_IF_MSG(devfd < 0 && errno == ENOENT, |
| DEVPATH " not present"); |
| SKIP_IF_MSG(get_system_loc_code(&lc), |
| "Cannot determine system location code"); |
| |
| FAIL_IF(devfd < 0); |
| |
| errno = 0; |
| fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc); |
| FAIL_IF(errno != 0); |
| FAIL_IF(fd < 0); |
| |
| FAIL_IF(close(devfd) != 0); |
| |
| size = lseek(fd, 0, SEEK_END); |
| FAIL_IF(size <= 0); |
| |
| void *buf = malloc((size_t)size); |
| FAIL_IF(!buf); |
| |
| ssize_t consumed = pread(fd, buf, size, 0); |
| FAIL_IF(consumed != size); |
| |
| /* Ensure EOF */ |
| FAIL_IF(read(fd, buf, size) != 0); |
| FAIL_IF(close(fd)); |
| |
| /* Verify that the buffer looks like VPD */ |
| static const char needle[] = "System VPD"; |
| FAIL_IF(!memmem(buf, size, needle, strlen(needle))); |
| |
| return 0; |
| } |
| |
| struct vpd_test { |
| int (*function)(void); |
| const char *description; |
| }; |
| |
| static const struct vpd_test vpd_tests[] = { |
| { |
| .function = dev_papr_vpd_open_close, |
| .description = "open/close " DEVPATH, |
| }, |
| { |
| .function = dev_papr_vpd_unterm_loc_code, |
| .description = "ensure EINVAL on unterminated location code", |
| }, |
| { |
| .function = dev_papr_vpd_null_handle, |
| .description = "ensure EFAULT on bad handle addr", |
| }, |
| { |
| .function = dev_papr_vpd_get_handle_all, |
| .description = "get handle for all VPD" |
| }, |
| { |
| .function = papr_vpd_close_handle_without_reading, |
| .description = "close handle without consuming VPD" |
| }, |
| { |
| .function = dev_papr_vpd_get_handle_byte_at_a_time, |
| .description = "read all VPD one byte at a time" |
| }, |
| { |
| .function = papr_vpd_reread, |
| .description = "ensure re-read yields same results" |
| }, |
| { |
| .function = papr_vpd_system_loc_code, |
| .description = "get handle for system VPD" |
| }, |
| }; |
| |
| int main(void) |
| { |
| size_t fails = 0; |
| |
| for (size_t i = 0; i < ARRAY_SIZE(vpd_tests); ++i) { |
| const struct vpd_test *t = &vpd_tests[i]; |
| |
| if (test_harness(t->function, t->description)) |
| ++fails; |
| } |
| |
| return fails == 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |