Test ioctl

#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "tst_test.h"
#include "hwbus-io.h"

#define DEV_PATH "/dev/hwbusc"
#define SYSFS_PCI_PATH "/sys/bus/pci/devices/0000:02:00.0/"

static int fd = -1;

static uint16_t read_sysfs_hex16(const char *rel_path)
{
   FILE *fp;
   uint16_t val = 0;
   char full_path[256];

   snprintf(full_path, sizeof(full_path), "%s%s", SYSFS_PCI_PATH, rel_path);
   fp = fopen(full_path, "r");
   if (!fp)
      tst_brk(TBROK | TERRNO, "Failed to open %s", full_path);

   if (fscanf(fp, "0x%" SCNx16, &val) != 1) {
      fclose(fp);
      tst_brk(TBROK, "Failed to parse hex value from %s", full_path);
   }

   fclose(fp);
   return val;
}

static uint8_t read_sysfs_hex8(const char *rel_path)
{
   FILE *fp;
   unsigned int val = 0;
   char full_path[256];

   snprintf(full_path, sizeof(full_path), "%s%s", SYSFS_PCI_PATH, rel_path);
   fp = fopen(full_path, "r");
   if (!fp)
      tst_brk(TBROK | TERRNO, "Failed to open %s", full_path);

   if (fscanf(fp, "0x%x", &val) != 1) {
      fclose(fp);
      tst_brk(TBROK, "Failed to parse hex value from %s", full_path);
   }

   fclose(fp);
   return (uint8_t)val;
}

static void setup(void)
{
   fd = open(DEV_PATH, O_RDWR);
   if (fd < 0)
      fd = open(DEV_PATH, O_RDONLY);

   if (fd < 0)
      tst_brk(TBROK | TERRNO, "Failed to open %s", DEV_PATH);
}

static void cleanup(void)
{
   if (fd >= 0)
      close(fd);
}

static void test_pci_ids_ioctl(void)
{
   uint16_t exp_vendor = read_sysfs_hex16("vendor");
   uint16_t exp_device = read_sysfs_hex16("device");
   uint16_t act_vendor = 0, act_device = 0;

   if (ioctl(fd, HWBUS_IOC_READ_VENDOR, &act_vendor) < 0) {
      tst_res(TFAIL | TERRNO, "HWBUS_IOC_READ_VENDOR ioctl failed");
      return;
   }

   if (ioctl(fd, HWBUS_IOC_READ_DEVICE, &act_device) < 0) {
      tst_res(TFAIL | TERRNO, "HWBUS_IOC_READ_DEVICE ioctl failed");
      return;
   }

   if (act_vendor == exp_vendor && act_device == exp_device) {
      tst_res(TPASS, "IOCTL PCI IDs match SysFS (Vendor: 0x%04x, Device: 0x%04x)",
         act_vendor, act_device);
   } else {
      tst_res(TFAIL, "IOCTL PCI IDs mismatch: got Vendor=0x%04x Device=0x%04x, exp Vendor=0x%04x Device=0x%04x",
         act_vendor, act_device, exp_vendor, exp_device);
   }
}

static void test_data_widths(void)
{
   uint8_t exp_rev = read_sysfs_hex8("revision");
   uint8_t act_rev = 0;
   uint16_t cmd_reg = 0;
   uint32_t bar0_reg = 0;

   /* 8-bit read (Revision ID at offset 0x08) */
   if (ioctl(fd, HWBUS_IOC_READ_REVISION, &act_rev) < 0) {
      tst_res(TFAIL | TERRNO, "8-bit IOCTL read failed");
   } else if (act_rev == exp_rev) {
      tst_res(TPASS, "8-bit IOCTL read (Revision ID: 0x%02x) matches SysFS", act_rev);
   } else {
      tst_res(TFAIL, "8-bit IOCTL read mismatch: got 0x%02x, expected 0x%02x", act_rev, exp_rev);
   }

   /* 16-bit read (Command Register at offset 0x04) */
   if (ioctl(fd, _IOR('L', 0x04, uint16_t), &cmd_reg) < 0) {
      tst_res(TFAIL | TERRNO, "16-bit IOCTL read (Command Reg) failed");
   } else {
      tst_res(TPASS, "16-bit IOCTL read executed successfully (Cmd: 0x%04x)", cmd_reg);
   }

   /* 32-bit read (BAR0 Register at offset 0x10) */
   if (ioctl(fd, _IOR('L', 0x10, uint32_t), &bar0_reg) < 0) {
      tst_res(TFAIL | TERRNO, "32-bit IOCTL read (BAR0) failed");
   } else {
      tst_res(TPASS, "32-bit IOCTL read executed successfully (BAR0: 0x%08x)", bar0_reg);
   }
}

static void test_invalid_ioctl(void)
{
   uint32_t dummy = 0;
   unsigned int invalid_cmd = _IOR('L', 0xFF, uint32_t); // Offset fuera de rango (> 0x3c)

   TST_EXP_FAIL(ioctl(fd, invalid_cmd, &dummy), ENOTTY, "Invalid IOCTL command rejection");
}

static void run_tests(void)
{
   test_pci_ids_ioctl();
   test_data_widths();
   test_invalid_ioctl();
}

static struct tst_test test = {
   .setup = setup,
   .cleanup = cleanup,
   .test_all = run_tests,
};