/* * The PCI Library -- Hurd access via RPCs * * Copyright (c) 2017 Joan Lledó <jlledom@member.fsf.org> * * Can be freely distributed and used under the terms of the GNU GPL. */ #define _GNU_SOURCE #include "internal.h" #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <dirent.h> #include <fcntl.h> #include <string.h> #include <hurd.h> #include <hurd/pci.h> #include <hurd/paths.h> /* Server path */ #define _SERVERS_BUS_PCI _SERVERS_BUS "/pci" /* File names */ #define FILE_CONFIG_NAME "config" #define FILE_ROM_NAME "rom" /* Level in the fs tree */ typedef enum { LEVEL_NONE, LEVEL_DOMAIN, LEVEL_BUS, LEVEL_DEV, LEVEL_FUNC } tree_level; /* Check whether there's a pci server */ static int hurd_detect(struct pci_access *a) { int err; struct stat st; err = stat(_SERVERS_BUS_PCI, &st); if (err) { a->error("Could not open file `%s'", _SERVERS_BUS_PCI); return 0; } /* The node must be a directory and a translator */ return S_ISDIR(st.st_mode) && ((st.st_mode & S_ITRANS) == S_IROOT); } /* Empty callbacks, we don't need any special init or cleanup */ static void hurd_init(struct pci_access *a UNUSED) { } static void hurd_cleanup(struct pci_access *a UNUSED) { } /* Each device has its own server path. Allocate space for the port. */ static void hurd_init_dev(struct pci_dev *d) { d->aux = pci_malloc(d->access, sizeof(mach_port_t)); } /* Deallocate the port and free its space */ static void hurd_cleanup_dev(struct pci_dev *d) { mach_port_t device_port; device_port = *((mach_port_t *) d->aux); mach_port_deallocate(mach_task_self(), device_port); pci_mfree(d->aux); } /* Walk through the FS tree to see what is allowed for us */ static void enum_devices(const char *parent, struct pci_access *a, int domain, int bus, int dev, int func, tree_level lev) { int ret; DIR *dir; struct dirent *entry; char path[NAME_MAX]; char server[NAME_MAX]; uint32_t vd; uint8_t ht; mach_port_t device_port; struct pci_dev *d; dir = opendir(parent); if (!dir) { if (errno == EPERM || errno == EACCES) /* The client lacks the permissions to access this function, skip */ return; else a->error("Cannot open directory: %s (%s)", parent, strerror(errno)); } while ((entry = readdir(dir)) != 0) { snprintf(path, NAME_MAX, "%s/%s", parent, entry->d_name); if (entry->d_type == DT_DIR) { if (!strncmp(entry->d_name, ".", NAME_MAX) || !strncmp(entry->d_name, "..", NAME_MAX)) continue; errno = 0; ret = strtol(entry->d_name, 0, 16); if (errno) { if (closedir(dir) < 0) a->warning("Cannot close directory: %s (%s)", parent, strerror(errno)); a->error("Wrong directory name: %s (number expected) probably \ not connected to an arbiter", entry->d_name); } /* * We found a valid directory. * Update the address and switch to the next level. */ switch (lev) { case LEVEL_DOMAIN: domain = ret; break; case LEVEL_BUS: bus = ret; break; case LEVEL_DEV: dev = ret; break; case LEVEL_FUNC: func = ret; break; default: if (closedir(dir) < 0) a->warning("Cannot close directory: %s (%s)", parent, strerror(errno)); a->error("Wrong directory tree, probably not connected \ to an arbiter"); } enum_devices(path, a, domain, bus, dev, func, lev + 1); } else { if (strncmp(entry->d_name, FILE_CONFIG_NAME, NAME_MAX)) /* We are looking for the config file */ continue; /* We found an available virtual device, add it to our list */ snprintf(server, NAME_MAX, "%s/%04x/%02x/%02x/%01u/%s", _SERVERS_BUS_PCI, domain, bus, dev, func, entry->d_name); device_port = file_name_lookup(server, 0, 0); if (device_port == MACH_PORT_NULL) { if (closedir(dir) < 0) a->warning("Cannot close directory: %s (%s)", parent, strerror(errno)); a->error("Cannot open %s", server); } d = pci_alloc_dev(a); *((mach_port_t *) d->aux) = device_port; d->bus = bus; d->dev = dev; d->func = func; pci_link_dev(a, d); vd = pci_read_long(d, PCI_VENDOR_ID); ht = pci_read_byte(d, PCI_HEADER_TYPE); d->vendor_id = vd & 0xffff; d->device_id = vd >> 16U; d->known_fields = PCI_FILL_IDENT; d->hdrtype = ht; } } if (closedir(dir) < 0) a->error("Cannot close directory: %s (%s)", parent, strerror(errno)); } /* Enumerate devices */ static void hurd_scan(struct pci_access *a) { enum_devices(_SERVERS_BUS_PCI, a, -1, -1, -1, -1, LEVEL_DOMAIN); } /* * Read `len' bytes to `buf'. * * Returns error when the number of read bytes does not match `len'. */ static int hurd_read(struct pci_dev *d, int pos, byte * buf, int len) { int err; size_t nread; char *data; mach_port_t device_port; nread = len; device_port = *((mach_port_t *) d->aux); if (len > 4) err = !pci_generic_block_read(d, pos, buf, nread); else { data = (char *) buf; err = pci_conf_read(device_port, pos, &data, &nread, len); if (data != (char *) buf) { if (nread > (size_t) len) /* Sanity check for bogus server. */ { vm_deallocate(mach_task_self(), (vm_address_t) data, nread); return 0; } memcpy(buf, data, nread); vm_deallocate(mach_task_self(), (vm_address_t) data, nread); } } if (err) return 0; return nread == (size_t) len; } /* * Write `len' bytes from `buf'. * * Returns error when the number of written bytes does not match `len'. */ static int hurd_write(struct pci_dev *d, int pos, byte * buf, int len) { int err; size_t nwrote; mach_port_t device_port; nwrote = len; device_port = *((mach_port_t *) d->aux); if (len > 4) err = !pci_generic_block_write(d, pos, buf, len); else err = pci_conf_write(device_port, pos, (char *) buf, len, &nwrote); if (err) return 0; return nwrote == (size_t) len; } /* Get requested info from the server */ static void hurd_fill_regions(struct pci_dev *d) { mach_port_t device_port = *((mach_port_t *) d->aux); struct pci_bar regions[6]; char *buf = (char *) ®ions; size_t size = sizeof(regions); int err = pci_get_dev_regions(device_port, &buf, &size); if (err) return; if ((char *) ®ions != buf) { /* Sanity check for bogus server. */ if (size > sizeof(regions)) { vm_deallocate(mach_task_self(), (vm_address_t) buf, size); return; } memcpy(®ions, buf, size); vm_deallocate(mach_task_self(), (vm_address_t) buf, size); } for (int i = 0; i < 6; i++) { if (regions[i].size == 0) continue; d->base_addr[i] = regions[i].base_addr; d->base_addr[i] |= regions[i].is_IO; d->base_addr[i] |= regions[i].is_64 << 2; d->base_addr[i] |= regions[i].is_prefetchable << 3; if (flags & PCI_FILL_SIZES) d->size[i] = regions[i].size; } } static void hurd_fill_rom(struct pci_dev *d) { struct pci_xrom_bar rom; mach_port_t device_port = *((mach_port_t *) d->aux); char *buf = (char *) &rom; size_t size = sizeof(rom); int err = pci_get_dev_rom(device_port, &buf, &size); if (err) return; if ((char *) &rom != buf) { /* Sanity check for bogus server. */ if (size > sizeof(rom)) { vm_deallocate(mach_task_self(), (vm_address_t) buf, size); return; } memcpy(&rom, buf, size); vm_deallocate(mach_task_self(), (vm_address_t) buf, size); } d->rom_base_addr = rom.base_addr; d->rom_size = rom.size; } static unsigned int hurd_fill_info(struct pci_dev *d, unsigned int flags) { unsigned int done = 0; if (!d->access->buscentric) { if (flags & (PCI_FILL_BASES | PCI_FILL_SIZES)) { hurd_fill_regions(d); done |= PCI_FILL_BASES | PCI_FILL_SIZES; } if (flags & PCI_FILL_ROM_BASE) { hurd_fill_rom(d); done |= PCI_FILL_ROM_BASE; } } return done | pci_generic_fill_info(d, flags & ~done); } struct pci_methods pm_hurd = { "hurd", "Hurd access using RPCs", NULL, /* config */ hurd_detect, hurd_init, hurd_cleanup, hurd_scan, hurd_fill_info, hurd_read, hurd_write, NULL, /* read_vpd */ hurd_init_dev, hurd_cleanup_dev };