From d871c008fbf0832e502f8ae1367ecf8ab6796aa5 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Sat, 5 Aug 2017 21:18:57 +0100 Subject: [PATCH] devmem3 --- buildroot_config_fragment | 29 ++++++++++ kernel_config_fragment | 5 +- kernel_module/user/devmem3.c | 103 ++++++++++++++++++++++++++++++++++ kernel_module/user/uio_read.c | 30 ++++++++-- qemu | 2 +- 5 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 kernel_module/user/devmem3.c diff --git a/buildroot_config_fragment b/buildroot_config_fragment index 153164d..598ed87 100644 --- a/buildroot_config_fragment +++ b/buildroot_config_fragment @@ -70,3 +70,32 @@ BR2_PACKAGE_HOST_DTC=y # TODO: have a look at: https://github.com/kaiwan/device-memory-readwrite # BR2_PACKAGE_DEVMEM2=y + +# Provides setpci and a lspci more advanced than Busybox's +# +# setpci can read and write to PCI configuration registers. +# +# Read is possible from Linux with: +# +# hexdump /sys/bus/pci/devices/0000:00:05.0/config +# +# but setpci provies nice human readable register names, e.g.: +# +# setpci --dumpregs +# +# then and then get the values with either bus or device id: +# +# setpci -s 0000:00:05.0 BASE_ADDRESS_0 +# setpci -d 1234:11e9 BASE_ADDRESS_0 +# +# Note however that BASE_ADDRESS_0 also appears when you do: +# +# lspci -v +# +# Then you can try messing with that address with: +# +# devmem2 0xfeb52000 w 0x12345678 +# +# which for our pci_min device fires interrupts. +# +BR2_PACKAGE_PCIUTILS=y diff --git a/kernel_config_fragment b/kernel_config_fragment index 41f5db1..5bfc604 100644 --- a/kernel_config_fragment +++ b/kernel_config_fragment @@ -138,11 +138,12 @@ CONFIG_ARM64_PTDUMP=y ## UIO -# Userspace drivers. +# Userspace drivers: allow you to handle IRQs and do memory IO from userland through a /dev file. # # Superseded by the more featureful VFIO. # -# Documentation/DocBook/uio-howto.tmpl +# Documentation/DocBook/uio-howto.tmpl contains actual userland examples +# for the generic examples under drivers/uio # # UIO interface in a nutshell: # diff --git a/kernel_module/user/devmem3.c b/kernel_module/user/devmem3.c new file mode 100644 index 0000000..eb1d8e5 --- /dev/null +++ b/kernel_module/user/devmem3.c @@ -0,0 +1,103 @@ +/* +Adapted from: http://free-electrons.com/pub/mirror/devmem2.c +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \ + __LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0) + +#define MAP_SIZE 4096UL +#define MAP_MASK (MAP_SIZE - 1) + +int main(int argc, char **argv) { + int fd; + void *map_base, *virt_addr; + uintmax_t read_result, writeval; + off_t target; + int access_type = 'w'; + + if(argc < 2) { + fprintf(stderr, "\nUsage:\t%s { address } [ type [ data ] ]\n" + "\taddress : memory address to act upon\n" + "\ttype : access operation type : [b]yte, [h]alfword, [w]ord\n" + "\tdata : data to be written\n\n", + argv[0]); + exit(1); + } + target = strtoul(argv[1], 0, 0); + + if(argc > 2) + access_type = tolower(argv[2][0]); + + + if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) FATAL; + printf("/dev/mem opened.\n"); + fflush(stdout); + + /* Map one page */ + map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK); + if(map_base == (void *) -1) FATAL; + printf("Memory mapped at address %p.\n", map_base); + fflush(stdout); + + virt_addr = map_base + (target & MAP_MASK); + switch(access_type) { + case 'b': + read_result = *((uint8_t *) virt_addr); + break; + case 'h': + read_result = *((uint16_t *) virt_addr); + break; + case 'w': + read_result = *((uint32_t *) virt_addr); + break; + case 'q': + read_result = *((uint64_t *) virt_addr); + break; + default: + fprintf(stderr, "Illegal data type '%c'.\n", access_type); + exit(2); + } + printf("Value at address 0x%jX (%p): 0x%jX\n", (uintmax_t)target, virt_addr, (uintmax_t)read_result); + fflush(stdout); + + if(argc > 3) { + writeval = strtoul(argv[3], 0, 0); + switch(access_type) { + case 'b': + *((uint8_t *) virt_addr) = writeval; + read_result = *((uint8_t *) virt_addr); + break; + case 'h': + *((uint16_t *) virt_addr) = writeval; + read_result = *((uint16_t *) virt_addr); + break; + case 'w': + *((uint32_t *) virt_addr) = writeval; + read_result = *((uint32_t *) virt_addr); + break; + case 'q': + *((uint64_t *) virt_addr) = writeval; + read_result = *((uint64_t *) virt_addr); + break; + } + printf("Written 0x%X; readback 0x%jX\n", (unsigned int)writeval, (unsigned int)read_result); + fflush(stdout); + } + + if(munmap(map_base, MAP_SIZE) == -1) FATAL; + close(fd); + return 0; +} diff --git a/kernel_module/user/uio_read.c b/kernel_module/user/uio_read.c index 4e8a414..bfac6e1 100644 --- a/kernel_module/user/uio_read.c +++ b/kernel_module/user/uio_read.c @@ -1,9 +1,15 @@ /* Adapted from: https://yurovsky.github.io/2014/10/10/linux-uio-gpio-interrupt/ - modprobe uio_pdrv_genirq + modprobe uio_pci_generic + echo '1234 11e9' > /sys/bus/pci/drivers/uio_pci_generic/new_id + /uio_read.out & -TODO get working. +TODO get working. The problem now is how to generate IRQs to test our IRQ handling: + +- a synchronous IRQ needs regwrites to be kicked off, but mmap does not seem to work with this driver +- a custom PCI periodic timer device would also need to be initiazed by some regwrite, otherwise + early interrupts before the OS is setup would crash everything? Handle interrupts from userland and print a message to stdout. */ @@ -17,17 +23,31 @@ Handle interrupts from userland and print a message to stdout. #include #include /* write */ +#include +#include + int main(int argc, char **argv) { - char *dev; - if (argc < 2) { + char *dev = "/dev/uio0"; + if (argc > 1) { + dev = argv[1]; + exit(EXIT_FAILURE); } - int fd = open("/dev/uio", O_RDWR); + int fd = open(dev, O_RDWR); if (fd < 0) { perror("open"); exit(EXIT_FAILURE); } + + /* TODO not supported by this kernel module? */ + /*int *addr = mmap(NULL, sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);*/ + /*if (addr == MAP_FAILED) {*/ + /*perror("mmap");*/ + /*assert(0);*/ + /*}*/ + /**addr = 0x12345678;*/ + while (1) { uint32_t info = 1; size_t nb = write(fd, &info, sizeof(info)); diff --git a/qemu b/qemu index 6ba2bd0..019bbe5 160000 --- a/qemu +++ b/qemu @@ -1 +1 @@ -Subproject commit 6ba2bd0c907963321d47cde1ac654cb6d04e793e +Subproject commit 019bbe59d603cb3470f65d4a4a5e3a72a41d823b