Add linux PCI devices used under the hood to make real PCI devices work. Signed-off-by: Piotr Jaroszyński <p.jaroszyn...@gmail.com> --- src/arch/x86/core/linux/linux_api.c | 15 + src/drivers/linux/lpci.c | 552 +++++++++++++++++++++++++++++++++++ src/include/gpxe/errfile.h | 1 + src/include/gpxe/lpci.h | 87 ++++++ src/include/linux_api.h | 6 + 5 files changed, 661 insertions(+), 0 deletions(-) create mode 100644 src/drivers/linux/lpci.c create mode 100644 src/include/gpxe/lpci.h
diff --git a/src/arch/x86/core/linux/linux_api.c b/src/arch/x86/core/linux/linux_api.c index e578687..c557bb1 100644 --- a/src/arch/x86/core/linux/linux_api.c +++ b/src/arch/x86/core/linux/linux_api.c @@ -29,6 +29,11 @@ FILE_LICENCE(GPL2_OR_LATER); #include <asm/unistd.h> #include <string.h> +int linux_access(const char *pathname, int mode) +{ + return linux_syscall(__NR_access, pathname, mode); +} + int linux_open(const char *pathname, int flags) { return linux_syscall(__NR_open, pathname, flags); @@ -112,3 +117,13 @@ int linux_munmap(void *addr, size_t length) { return linux_syscall(__NR_munmap, addr, length); } + +int linux_iopl(int level) +{ + return linux_syscall(__NR_iopl, level); +} + +uid_t linux_getuid(void) +{ + return linux_syscall(__NR_getuid); +} diff --git a/src/drivers/linux/lpci.c b/src/drivers/linux/lpci.c new file mode 100644 index 0000000..347796e --- /dev/null +++ b/src/drivers/linux/lpci.c @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2010 Piotr Jaroszyński <p.jaroszyn...@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +FILE_LICENCE(GPL2_OR_LATER); + +#include <gpxe/lpci.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <linux_api.h> +#include <gpxe/linux.h> +#include <gpxe/pci.h> +#include <gpxe/malloc.h> +#include <gpxe/netdevice.h> +#include <gpxe/init.h> + +/** + * @file + * + * Implementation of the linux PCI devices + * + * Most of the work is accessing /sys/ intefaces exposed by the linux PCI subsystem. + * See linux/Documentation/filesystems/sysfs-pci.txt for details. + */ + +#define UIO_DMA_MEM_SIZE (128 * 1024) + +#define UIO_DMA_ID_LEN 10 + +LIST_HEAD(lpci_devices); + +int lpci_ioports_ready = 0; + +/** Allocate and initialize an lpci device */ +static struct lpci_device *alloc_lpci_device() +{ + struct lpci_device *lpci; + + lpci = zalloc(sizeof(*lpci)); + + if (lpci) { + lpci->pci_config_fd = -1; + INIT_LIST_HEAD(&lpci->iomems); + INIT_LIST_HEAD(&lpci->ioports); + } + + return lpci; +} + +/** Open the PCI config available via the /sys/ interface */ +static int lpci_open_config(struct lpci_device *lpci) +{ + char path[SYS_PATH_LEN]; + + if (lpci->pci_config_fd != -1) { + DBGC(lpci, "lpci %p config already open('%s') failed (%s)\n", lpci, path, linux_strerror(linux_errno)); + return -1; + } + + snprintf(path, SYS_PATH_LEN, "%sconfig", lpci->sys_path); + lpci->pci_config_fd = linux_open(path, O_RDWR); + + if (lpci->pci_config_fd == -1) { + DBGC(lpci, "lpci %p open('%s') failed (%s)\n", lpci, path, linux_strerror(linux_errno)); + return -1; + } + + return 0; +} + +/** Close the PCI config */ +static void lpci_close_config(struct lpci_device *lpci) +{ + linux_close(lpci->pci_config_fd); +} + +/** + * Map a specific I/O resource available via the /sys/ interface + * + * @v lpci lpci device + * @v iores_list List to add the newly mapped I/O resource to + * @v rind Index of the resource necessary to find it in /sys/ + * @v start System address of the resouce + * @v len Length of the I/O resource in bytes + * @ret rc 0 on success + */ +static int lpci_map_iores(struct lpci_device *lpci, struct list_head *iores_list, + int rind, unsigned long long start, size_t len) +{ + int fd; + char path[SYS_PATH_LEN]; + struct lpci_iores * iores; + int rc = 0; + + iores = malloc(sizeof(*iores)); + + if (! iores) { + return -ENOMEM; + } + + iores->start = start; + iores->len = len; + + snprintf(path, SYS_PATH_LEN, "%sresource%d", lpci->sys_path, rind); + fd = linux_open(path, O_RDWR); + + if (fd == -1) { + DBGC(lpci, "lpci %p open('%s', O_RDWR) failed (%s)\n", lpci, path, linux_strerror(linux_errno)); + rc = fd; + goto err_open; + } + DBGC(lpci, "lpci %p mmapping iores [0x%016llx, 0x%016llx)\n", lpci, start, start + len); + + iores->mapped = linux_mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + if (iores->mapped == MAP_FAILED) { + DBGC(lpci, "lpci %p mmap failed (%s)\n", lpci, linux_strerror(linux_errno)); + rc = -1; + goto err_mmap; + } + + list_add(&iores->list, iores_list); + + linux_close(fd); + + DBGC(lpci, "lpci %p mapped iores [0x%016llx, 0x%016llx) to %p\n", lpci, start, start + len, iores->mapped); + + return 0; + +err_mmap: + linux_close(fd); +err_open: + free(iores); + + return rc; +} + +/** Unmap all I/O resources on the list */ +static void lpci_unmap_iores(struct list_head *iores_list) +{ + struct lpci_iores * iores; + struct lpci_iores * tmp; + + list_for_each_entry_safe(iores, tmp, iores_list, list) { + linux_munmap(iores->mapped, iores->len); + list_del(&iores->list); + free(iores); + } +} + +/** Map I/O resources of the device available via the /sys/ interface */ +static int lpci_map_resources(struct lpci_device *lpci) +{ + int fd; + char path[SYS_PATH_LEN]; + char line[SYS_PCI_RESOURCE_LINE_WIDTH]; + unsigned long long r_start, r_end, r_flags; + int r_ind = 0; + char * end = line; + + strcpy(path, lpci->sys_path); + strcat(path, "resource"); + + fd = linux_open(path, O_RDONLY); + + if (fd == -1) { + DBGC(lpci, "lpci %p open('%s', O_RDONLY) = %d (%s)\n", lpci, path, fd, linux_strerror(linux_errno)); + return -1; + } + + while (r_ind <= PCI_STD_RESOURCE_END && + linux_read(fd, line, SYS_PCI_RESOURCE_LINE_WIDTH) == SYS_PCI_RESOURCE_LINE_WIDTH) { + line[SYS_PCI_RESOURCE_LINE_WIDTH - 1] = 0; + + r_start = strtoull(end, &end, 0); + r_end = strtoull(end, &end, 0); + r_flags = strtoull(end, &end, 0); + end = line; + + if (r_flags & IORESOURCE_MEM) { + lpci_map_iores(lpci, &lpci->iomems, r_ind, r_start, r_end - r_start + 1); + } + /* Doesn't work on x86, see pci_mmap_page_range() in linux/arch/x86/pci/i386.c */ +#if 0 + if (r_flags & IORESOURCE_IO) { + lpci_map_iores(lpci, &lpci->ioports, r_ind, r_start, r_end - r_start + 1); + } +#endif + + ++r_ind; + } + + linux_close(fd); + + return 0; +} + +/** + * Parse a device id into domain, bus, device and function number + * + * Accept ids in the following format [domain_hex:]bus_hex:device_hex:function_decimal + * hex numbers w/o the '0x' prefix + * : can be any string not containing hex digits + */ +static int lpci_parse_dev_id(char *id, uint16_t *domain, uint8_t *bus, uint8_t *devfn) +{ + unsigned long parts[4]; + char * p; + int i = 0; + + for (p = id ; *p && i < 4 ; ) { + if (! isxdigit(*p)) + ++p; + else + parts[i++] = strtoul(p, &p, 16); + } + + /* parsed all? */ + if (*p) + return -1; + + /* domain present? */ + if (i == 3) { + *domain = 0; + } else if (i == 4) { + *domain = parts[0]; + } else { + return -1; + } + + /* bus < 256 ? */ + if (parts[i - 3] >= 256) + return -1; + + *bus = parts[i - 3]; + + /* device < 32 ? */ + if (parts[i - 2] >= 32) + return -1; + + /* function < 8 ? */ + if (parts[i - 1] >= 8) + return -1; + + *devfn = PCI_DEVFN(parts[i - 2], parts[i - 1]); + + return 0; +} + +/** Used to save the old DMA pool to restore it on remove */ +static struct memory_pool *old_dma_pool; + +/** The pool to be used while lpci devices are active */ +static struct memory_pool lpci_dma_pool = { + .free_blocks = LIST_HEAD_INIT(lpci_dma_pool.free_blocks), +}; + +struct uio_dma_mapping *lpci_dma_mapping; + +static int uio_dma_fd = -1; +static struct uio_dma_area *uio_dma_area; + +/** Do some basic sanity checks */ +static int lpci_precheck(struct lpci_device *lpci) +{ + if (linux_getuid() != 0) { + printf("You need to be root to use lpci devices\n"); + return -EPERM; + } + + if (linux_access(lpci->sys_path, O_RDONLY) != 0) { + printf("Cannot access '%s': (%s)\n", lpci->sys_path, linux_strerror(linux_errno)); + return -ENODEV; + } + + return 0; +} + +/** Get the UIO-DMA device id from /sys */ +static int lpci_get_uio_dma_id(struct lpci_device *lpci) +{ + char path[SYS_PATH_LEN]; + char buf[UIO_DMA_ID_LEN + 1]; + int fd; + int rc = 0; + + snprintf(path, SYS_PATH_LEN, "%suio_dma_id", lpci->sys_path); + fd = linux_open(path, O_RDONLY); + if (fd == -1) { + DBGC(lpci, "lpci %p open('%s') failed (%s)\n", lpci, path, linux_strerror(linux_errno)); + return -1; + } + + if (linux_read(fd, buf, UIO_DMA_ID_LEN) != UIO_DMA_ID_LEN) { + DBGC(lpci, "lpci %p read('%s') failed (%s)\n", lpci, path, linux_strerror(linux_errno)); + rc = -1; + goto close; + } + + buf[UIO_DMA_ID_LEN] = '\0'; + + lpci->uio_dma_id = strtoul(buf, NULL, 0); + +close: + linux_close(fd); + + return rc; +} + +/** + * Initialize the DMA mappings with UIO-DMA and switch the memory_pool used + * by DMA allocations. + */ +static int lpci_init_dma(struct lpci_device *lpci) +{ + if (uio_dma_fd != -1) { + DBG("uio_dma_fd already open\n"); + return -1; + } + + uio_dma_fd = uio_dma_open(); + + if (uio_dma_fd == -1) { + DBG("lpci uio_dma_open() failed (%s)\n", linux_strerror(linux_errno)); + goto err_open; + } + + /* Use a 32bit mask as some drivers might be assuming it */ + uio_dma_area = uio_dma_alloc(uio_dma_fd, UIO_DMA_MEM_SIZE, UIO_DMA_CACHE_DISABLE, UIO_DMA_MASK(32), 0); + if (! uio_dma_area) { + DBG("lpci uio_dma_alloc() failed (%s)\n", linux_strerror(linux_errno)); + goto err_alloc; + } + + lpci_dma_mapping = uio_dma_map(uio_dma_fd, uio_dma_area, lpci->uio_dma_id, UIO_DMA_BIDIRECTIONAL); + if (! lpci_dma_mapping) { + DBG("lpci uio_dma_map() failed (%s)\n", linux_strerror(linux_errno)); + goto err_map; + } + + if (lpci_dma_mapping->chunk_count != 1) { + DBG("lpci uio_dma_map() returned %d chunks, we only support 1\n", lpci_dma_mapping->chunk_count); + goto err_chunks; + } + + mpopulate(&lpci_dma_pool, lpci_dma_mapping->addr, UIO_DMA_MEM_SIZE); + old_dma_pool = set_dma_pool(&lpci_dma_pool); + + return 0; + +err_chunks: + uio_dma_unmap(uio_dma_fd, lpci_dma_mapping); +err_map: + uio_dma_free(uio_dma_fd, uio_dma_area); +err_alloc: + uio_dma_close(uio_dma_fd); + uio_dma_fd = -1; +err_open: + return -1; +} + +/** + * Clean up the UIO-DMA mappings and restore the old memory_pool for DMA + * allocations. + */ +static void lpci_clean_dma() +{ + set_dma_pool(old_dma_pool); + + uio_dma_unmap(uio_dma_fd, lpci_dma_mapping); + uio_dma_free(uio_dma_fd, uio_dma_area); + uio_dma_close(uio_dma_fd); +} + +/** Initialize access to the I/O ports */ +static int lpci_init_ioports() +{ + if (lpci_ioports_ready) + return 0; + + if (linux_iopl(3) != 0) { + DBG("lpci iopl(3) failed (%s)\n", linux_strerror(linux_errno)); + return -EPERM; + } + lpci_ioports_ready = 1; + + return 0; +} + +struct linux_driver lpci_driver __linux_driver; + +/** Handle a request for an lpci device */ +static int lpci_probe(struct linux_device *device, struct linux_device_request *request) +{ + struct linux_setting *dev_setting; + uint16_t domain = 0; + uint8_t bus = 0; + uint8_t devfn = 0; + struct lpci_device *lpci; + int rc; + + /* Look for the mandatory dev setting */ + dev_setting = linux_find_setting("dev", &request->settings); + + if (! dev_setting) { + printf("lpci missing mandatory dev setting\n"); + return -EINVAL; + } + + dev_setting->applied = 1; + + if (lpci_parse_dev_id(dev_setting->value, &domain, &bus, &devfn)) { + printf("lpci missing mandatory dev setting\n"); + return -ENODEV; + } + + lpci = alloc_lpci_device(); + if (! lpci) + return -ENOMEM; + + lpci->settings = &request->settings; + lpci->domain = domain; + lpci->bus = bus; + lpci->devfn = devfn; + sprintf(lpci->sys_path, SYS_PCI_DEVICES_PATH "%04x:%02x:%02x.%d/", + domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); + + linux_set_drvdata(device, lpci); + + rc = lpci_precheck(lpci); + if (rc) { + goto err_precheck; + } + + rc = lpci_get_uio_dma_id(lpci); + if (rc) { + printf("lpci failed to get UIO-DMA device id. Is the uio-dma-pci driver bound to the device?\n"); + goto err_uio_dma_id; + } + + rc = lpci_open_config(lpci); + if (rc) { + printf("lpci failed to open PCI config\n"); + goto err_config; + } + + rc = lpci_map_resources(lpci); + if (rc) { + printf("lpci failed to map I/O resources\n"); + goto err_map; + } + + rc = lpci_init_ioports(); + if (rc) { + printf("lpci ioprts not ready\n"); + goto err_init_ioports; + } + + rc = lpci_init_dma(lpci); + if (rc) { + printf("lpci DMA memory not ready\n"); + goto err_init_dma; + } + + list_add(&lpci->list, &lpci_devices); + + DBGC(lpci, "lpci %p added device [%04x:%02x:%02x.%d]\n", lpci, domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); + + /* + * We can handle only one device at the same time. + * To support multiple devices time malloc_dma() and phys_to_bus() + * conversion would have to know for which device they are being done. + */ + lpci_driver.can_probe = 0; + + return 0; + +err_init_dma: +err_init_ioports: +err_map: + lpci_unmap_iores(&lpci->iomems); + lpci_unmap_iores(&lpci->ioports); + lpci_close_config(lpci); +err_uio_dma_id: +err_precheck: +err_config: + free(lpci); + + return rc; +} + +/** Remove the lpci device */ +static void lpci_remove(struct linux_device *device) +{ + struct lpci_device *lpci = linux_get_drvdata(device); + + lpci_clean_dma(); + + lpci_unmap_iores(&lpci->iomems); + lpci_unmap_iores(&lpci->ioports); + lpci_close_config(lpci); + list_del(&lpci->list); + + free(lpci); +} + +/** lpci linux_driver */ +struct linux_driver lpci_driver __linux_driver = { + .name = "lpci", + .probe = lpci_probe, + .remove = lpci_remove, + .can_probe = 1, +}; + +/** Apply lpci settings to corresponding net devices */ +void lpci_apply_settings(void) +{ + struct lpci_device *lpci; + struct net_device *ndev; + + /* Look for the net_device corresponding to each lpci device */ + for_each_lpci(lpci) { + for_each_netdev(ndev) { + if (ndev->dev->desc.bus_type == BUS_TYPE_PCI && + ndev->dev->desc.location == (unsigned int)PCI_BUSDEVFN(lpci->bus, lpci->devfn)) { + /* Apply the lpci settings to the net device */ + linux_apply_settings(lpci->settings, &ndev->settings.settings); + } + } + } +} + +struct startup_fn lpci_apply_settings_startup_fn __startup_fn(STARTUP_LATE) = { + .startup = lpci_apply_settings, +}; diff --git a/src/include/gpxe/errfile.h b/src/include/gpxe/errfile.h index 9f92470..14f6d91 100644 --- a/src/include/gpxe/errfile.h +++ b/src/include/gpxe/errfile.h @@ -129,6 +129,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ERRFILE_jme ( ERRFILE_DRIVER | 0x005b0000 ) #define ERRFILE_virtio_net ( ERRFILE_DRIVER | 0x005c0000 ) #define ERRFILE_tap ( ERRFILE_DRIVER | 0x005d0000 ) +#define ERRFILE_lpci ( ERRFILE_DRIVER | 0x005e0000 ) #define ERRFILE_scsi ( ERRFILE_DRIVER | 0x00700000 ) #define ERRFILE_arbel ( ERRFILE_DRIVER | 0x00710000 ) diff --git a/src/include/gpxe/lpci.h b/src/include/gpxe/lpci.h new file mode 100644 index 0000000..370d629 --- /dev/null +++ b/src/include/gpxe/lpci.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010 Piotr Jaroszyński <p.jaroszyn...@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _GPXE_LPCI_H +#define _GPXE_LPCI_H + +FILE_LICENCE(GPL2_OR_LATER); + +#include <gpxe/list.h> + +#define SYS_PATH_LEN 64 /* enough to contain any path under /sys we need */ +#define SYS_PCI_DEVICES_PATH "/sys/bus/pci/devices/" +#define SYS_PCI_RESOURCE_LINE_WIDTH (18 * 3 + 1 * 3) + +#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */ +#define IORESOURCE_IO 0x00000100 +#define IORESOURCE_MEM 0x00000200 +#define IORESOURCE_IRQ 0x00000400 +#define IORESOURCE_DMA 0x00000800 +#define IORESOURCE_BUS 0x00001000 + +#define PCI_STD_RESOURCE_END 5 + +/** A linux PCI device + * + * It is used under the hood to provide I/O and PCI API on linux + */ +struct lpci_device { + /** Domain number */ + uint16_t domain; + /** Bus number */ + uint8_t bus; + /** Device and function number */ + uint8_t devfn; + /** Base path of the device under /sys/ */ + char sys_path[SYS_PATH_LEN]; + /** File descriptor of an opened PCI config file under /sys/ */ + int pci_config_fd; + /** List node */ + struct list_head list; + /** List of mmaped I/O memory */ + struct list_head iomems; + /** List of mmaped I/O ports */ + struct list_head ioports; + /** UIO-DMA device id */ + uint32_t uio_dma_id; + /** Settings list */ + struct list_head *settings; +}; + +/** List of all linux PCI devices */ +extern struct list_head lpci_devices; + +/** Iterate over all lpci devices */ +#define for_each_lpci( lpci ) \ + list_for_each_entry ( (lpci), &lpci_devices, list ) + +/** An mmaped() I/O resource */ +struct lpci_iores { + unsigned long long start; + size_t len; + void * mapped; + struct list_head list; +}; + +/** Are ioports ready to be used? */ +extern int lpci_ioports_ready; + +/** lpci DMA mapping */ +extern struct uio_dma_mapping * lpci_dma_mapping; + +#endif /* _GPXE_LPCI_H */ diff --git a/src/include/linux_api.h b/src/include/linux_api.h index 6f591d8..1bc1f73 100644 --- a/src/include/linux_api.h +++ b/src/include/linux_api.h @@ -47,6 +47,7 @@ typedef uint32_t useconds_t; extern long linux_syscall(int number, ...); +extern int linux_access(const char *pathname, int mode); extern int linux_open(const char *pathname, int flags); extern int linux_close(int fd); extern ssize_t linux_read(int fd, void *buf, size_t count); @@ -67,6 +68,11 @@ extern void *linux_mmap(void *addr, size_t length, int prot, int flags, int fd, extern void *linux_mremap(void *old_address, size_t old_size, size_t new_size, int flags); extern int linux_munmap(void *addr, size_t length); +extern int linux_iopl(int level); + +typedef __kernel_uid_t uid_t; +extern uid_t linux_getuid(void); + extern const char *linux_strerror(int errnum); #endif /* _LINUX_API_H */ -- 1.7.1 _______________________________________________ gPXE-devel mailing list gPXE-devel@etherboot.org http://etherboot.org/mailman/listinfo/gpxe-devel