[ppc64] VIO support, from Dave Boutcher, Hollis Blanchard and Santiago Leon Add virtual I/O support. These routines provide the infrastructure needed by virtual SCSI, virtual ethernet, virtual serial on IBM pSeries servers --- arch/ppc64/kernel/Makefile | 2 arch/ppc64/kernel/pci_dma.c | 10 arch/ppc64/kernel/vio.c | 535 ++++++++++++++++++++++++++++++++++++++++++++ include/asm-ppc64/vio.h | 130 ++++++++++ 4 files changed, 671 insertions(+), 6 deletions(-) diff -puN arch/ppc64/kernel/Makefile~ppc64-addvio arch/ppc64/kernel/Makefile --- 25/arch/ppc64/kernel/Makefile~ppc64-addvio 2004-01-13 23:22:58.000000000 -0800 +++ 25-akpm/arch/ppc64/kernel/Makefile 2004-01-13 23:22:58.000000000 -0800 @@ -29,7 +29,7 @@ obj-$(CONFIG_PPC_ISERIES) += iSeries_irq obj-$(CONFIG_PPC_PSERIES) += pSeries_pci.o pSeries_lpar.o pSeries_hvCall.o \ eeh.o nvram.o rtasd.o ras.o \ open_pic.o xics.o pSeries_htab.o rtas.o \ - chrp_setup.o i8259.o prom.o + chrp_setup.o i8259.o prom.o vio.o obj-$(CONFIG_PROC_FS) += proc_ppc64.o obj-$(CONFIG_RTAS_FLASH) += rtas_flash.o diff -puN arch/ppc64/kernel/pci_dma.c~ppc64-addvio arch/ppc64/kernel/pci_dma.c --- 25/arch/ppc64/kernel/pci_dma.c~ppc64-addvio 2004-01-13 23:22:58.000000000 -0800 +++ 25-akpm/arch/ppc64/kernel/pci_dma.c 2004-01-13 23:22:58.000000000 -0800 @@ -100,7 +100,7 @@ void free_tce_range_nolock(struct TceTab unsigned order ); /* allocates a range of tces and sets them to the pages */ -static inline dma_addr_t get_tces( struct TceTable *, +inline dma_addr_t get_tces( struct TceTable *, unsigned order, void *page, unsigned numPages, @@ -212,7 +212,7 @@ static void tce_build_pSeries(struct Tce * Build a TceTable structure. This contains a multi-level bit map which * is used to manage allocation of the tce space. */ -static struct TceTable *build_tce_table( struct TceTable * tbl ) +struct TceTable *build_tce_table( struct TceTable * tbl ) { unsigned long bits, bytes, totalBytes; unsigned long numBits[NUM_TCE_LEVELS], numBytes[NUM_TCE_LEVELS]; @@ -520,7 +520,7 @@ static long test_tce_range( struct TceTa return retval; } -static inline dma_addr_t get_tces( struct TceTable *tbl, unsigned order, void *page, unsigned numPages, int direction ) +inline dma_addr_t get_tces( struct TceTable *tbl, unsigned order, void *page, unsigned numPages, int direction ) { long tcenum; unsigned long uaddr; @@ -555,7 +555,7 @@ static inline dma_addr_t get_tces( struc } #ifdef CONFIG_PPC_ISERIES -static void tce_free_one_iSeries( struct TceTable *tbl, long tcenum ) +void tce_free_one_iSeries( struct TceTable *tbl, long tcenum ) { u64 set_tce_rc; union Tce tce; @@ -583,7 +583,7 @@ static void tce_free_one_pSeries( struct } #endif -static void tce_free(struct TceTable *tbl, dma_addr_t dma_addr, +void tce_free(struct TceTable *tbl, dma_addr_t dma_addr, unsigned order, unsigned num_pages) { long tcenum, total_tces, free_tce; diff -puN /dev/null arch/ppc64/kernel/vio.c --- /dev/null 2002-08-30 16:31:37.000000000 -0700 +++ 25-akpm/arch/ppc64/kernel/vio.c 2004-01-13 23:22:58.000000000 -0800 @@ -0,0 +1,535 @@ +/* + * IBM PowerPC Virtual I/O Infrastructure Support. + * + * Copyright (c) 2003 IBM Corp. + * Dave Engebretsen engebret@us.ibm.com + * Santiago Leon santil@us.ibm.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 (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "open_pic.h" /* temporary, until we merge large irq support */ + +extern struct TceTable *build_tce_table(struct TceTable *tbl); + +extern dma_addr_t get_tces(struct TceTable *, unsigned order, + void *page, unsigned numPages, int direction); +extern void tce_free(struct TceTable *tbl, dma_addr_t dma_addr, + unsigned order, unsigned num_pages); + + +static struct vio_bus vio_bus; +static LIST_HEAD(registered_vio_drivers); +int vio_num_address_cells; +EXPORT_SYMBOL(vio_num_address_cells); + +/* TODO: + * really fit into driver model (see include/linux/device.h) + * locking around list accesses + */ + +/** + * vio_register_driver: - Register a new vio driver + * @drv: The vio_driver structure to be registered. + * + * Adds the driver structure to the list of registered drivers + * Returns the number of vio devices which were claimed by the driver + * during registration. The driver remains registered even if the + * return value is zero. + */ +int vio_register_driver(struct vio_driver *drv) +{ + int count = 0; + struct vio_dev *dev; + + printk(KERN_DEBUG "%s: driver %s/%s registering\n", __FUNCTION__, + drv->id_table[0].type, drv->id_table[0].type); + + /* find matching devices not already claimed by other drivers and pass + * them to probe() */ + list_for_each_entry(dev, &vio_bus.devices, devices_list) { + const struct vio_device_id* id; + + if (dev->driver) + continue; /* this device is already owned */ + + id = vio_match_device(drv->id_table, dev); + if (drv && id) { + if (0 == drv->probe(dev, id)) { + printk(KERN_DEBUG " took device %p\n", dev); + dev->driver = drv; + count++; + } + } + } + + list_add_tail(&drv->node, ®istered_vio_drivers); + + return count; +} +EXPORT_SYMBOL(vio_register_driver); + +/** + * vio_unregister_driver - Remove registration of vio driver. + * @driver: The vio_driver struct to be removed form registration + * + * Searches for devices that are assigned to the driver and calls + * driver->remove() for each one. Removes the driver from the list + * of registered drivers. Returns the number of devices that were + * assigned to that driver. + */ +int vio_unregister_driver(struct vio_driver *driver) +{ + struct vio_dev *dev; + int devices_found = 0; + + list_for_each_entry(dev, &vio_bus.devices, devices_list) { + if (dev->driver == driver) { + driver->remove(dev); + dev->driver = NULL; + devices_found++; + } + } + + list_del(&driver->node); + + return devices_found; +} +EXPORT_SYMBOL(vio_unregister_driver); + +/** + * vio_match_device: - Tell if a VIO device has a matching VIO device id structure. + * @ids: array of VIO device id structures to search in + * @dev: the VIO device structure to match against + * + * Used by a driver to check whether a VIO device present in the + * system is in its list of supported devices. Returns the matching + * vio_device_id structure or NULL if there is no match. + */ +const struct vio_device_id * +vio_match_device(const struct vio_device_id *ids, const struct vio_dev *dev) +{ + while (ids->type) { + if ((strncmp(dev->archdata->type, ids->type, strlen(ids->type)) == 0) && + device_is_compatible((struct device_node*)dev->archdata, ids->compat)) + return ids; + ids++; + } + return NULL; +} + +/** + * vio_bus_init: - Initialize the virtual IO bus + */ +int __init +vio_bus_init(void) +{ + struct device_node *node_vroot, *node_vdev; + + INIT_LIST_HEAD(&vio_bus.devices); + + /* + * Create device node entries for each virtual device + * identified in the device tree. + * Functionally takes the place of pci_scan_bus + */ + node_vroot = find_devices("vdevice"); + if (!node_vroot) { + printk(KERN_WARNING "%s: no /vdevice node\n", __FUNCTION__); + return 0; + } + + vio_num_address_cells = prom_n_addr_cells(node_vroot->child); + + for (node_vdev = node_vroot->child; + node_vdev != NULL; + node_vdev = node_vdev->sibling) { + printk(KERN_DEBUG "%s: processing %p\n", __FUNCTION__, node_vdev); + + vio_register_device(node_vdev); + } + + return 0; +} + +__initcall(vio_bus_init); + + +/** + * vio_probe_device - attach dev to appropriate driver + * @dev: device to find a driver for + * + * Walks the list of registered VIO drivers looking for one to take this + * device. + * + * Returns a pointer to the matched driver or NULL if driver is not + * found. + */ +struct vio_driver * __devinit vio_probe_device(struct vio_dev* dev) +{ + struct vio_driver *driver; + + list_for_each_entry(driver, ®istered_vio_drivers, node) { + const struct vio_device_id* id; + + id = vio_match_device(driver->id_table, dev); + if (id && (0 < driver->probe(dev, id))) { + printk(KERN_DEBUG "%s: driver %s/%s took device %p\n", + __FUNCTION__, id->type, id->compat, dev); + dev->driver = driver; + return driver; + } + } + + printk(KERN_DEBUG "%s: device %p found no driver\n", __FUNCTION__, dev); + return NULL; +} + +/** + * vio_register_device: - Register a new vio device. + * @archdata: The OF node for this device. + * + * Creates and initializes a vio_dev structure from the data in + * node_vdev (archdata) and adds it to the list of virtual devices. + * Returns a pointer to the created vio_dev or NULL if node has + * NULL device_type or compatible fields. + */ +struct vio_dev * __devinit vio_register_device(struct device_node *node_vdev) +{ + struct vio_dev *dev; + unsigned int *unit_address; + unsigned int *irq_p; + + /* guarantee all vio_devs have 'device_type' field*/ + if ((NULL == node_vdev->type)) { + printk(KERN_WARNING + "%s: node %s missing 'device_type'\n", __FUNCTION__, + node_vdev->name ? node_vdev->name : ""); + return NULL; + } + + unit_address = (unsigned int *)get_property(node_vdev, "reg", NULL); + if (!unit_address) { + printk(KERN_WARNING "%s: node %s missing 'reg'\n", __FUNCTION__, + node_vdev->name ? node_vdev->name : ""); + return NULL; + } + + /* allocate a vio_dev for this node */ + dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + memset(dev, 0, sizeof(*dev)); + + dev->archdata = (void*)of_node_get(node_vdev); + dev->bus = &vio_bus; + dev->unit_address = *unit_address; + dev->tce_table = vio_build_tce_table(dev); + + irq_p = (unsigned int *) get_property(node_vdev, "interrupts", 0); + if(irq_p) { + dev->irq = openpic_to_irq(virt_irq_create_mapping(*irq_p)); + } else { + dev->irq = (unsigned int) -1; + } + + list_add_tail(&dev->devices_list, &vio_bus.devices); + + vio_probe_device(dev); /* finally, assign it to a driver */ + + return dev; +} +EXPORT_SYMBOL(vio_register_device); + +int __devinit vio_unregister_device(struct vio_dev *dev) +{ + list_del(&dev->devices_list); + of_node_put(dev->archdata); + + return 0; +} +EXPORT_SYMBOL(vio_unregister_device); + +/** + * vio_get_attribute: - get attribute for virtual device + * @vdev: The vio device to get property. + * @which: The property/attribute to be extracted. + * @length: Pointer to length of returned data size (unused if NULL). + * + * Calls prom.c's get_property() to return the value of the + * attribute specified by the preprocessor constant @which +*/ +const void * vio_get_attribute(struct vio_dev *vdev, void* which, int* length) +{ + return get_property((struct device_node *)vdev->archdata, (char*)which, length); +} +EXPORT_SYMBOL(vio_get_attribute); + +/** + * vio_build_tce_table: - gets the dma information from OF and builds the TCE tree. + * @dev: the virtual device. + * + * Returns a pointer to the built tce tree, or NULL if it can't + * find property. +*/ +struct TceTable * vio_build_tce_table(struct vio_dev *dev) +{ + unsigned int *dma_window; + struct TceTable *newTceTable; + unsigned long offset; + unsigned long size; + int dma_window_property_size; + + dma_window = (unsigned int *) get_property((struct device_node *)dev->archdata, "ibm,my-dma-window", &dma_window_property_size); + if(!dma_window) { + return NULL; + } + + newTceTable = (struct TceTable *) kmalloc(sizeof(struct TceTable), GFP_KERNEL); + + /* RPA docs say that #address-cells is always 1 for virtual + devices, but some older boxes' OF returns 2. This should + be removed by GA, unless there is legacy OFs that still + have 2 for #address-cells */ + size = ((dma_window[1+vio_num_address_cells] + >> PAGE_SHIFT) << 3) >> PAGE_SHIFT; + + /* This is just an ugly kludge. Remove as soon as the OF for all + machines actually follow the spec and encodes the offset field + as phys-encode (that is, #address-cells wide)*/ + if (dma_window_property_size == 12) { + size = ((dma_window[1] >> PAGE_SHIFT) << 3) >> PAGE_SHIFT; + } else if (dma_window_property_size == 20) { + size = ((dma_window[4] >> PAGE_SHIFT) << 3) >> PAGE_SHIFT; + } else { + printk(KERN_WARNING "vio_build_tce_table: Invalid size of ibm,my-dma-window=%i, using 0x80 for size\n", dma_window_property_size); + size = 0x80; + } + + /* There should be some code to extract the phys-encoded offset + using prom_n_addr_cells(). However, according to a comment + on earlier versions, it's always zero, so we don't bother */ + offset = dma_window[1] >> PAGE_SHIFT; + + /* TCE table size - measured in units of pages of tce table */ + newTceTable->size = size; + /* offset for VIO should always be 0 */ + newTceTable->startOffset = offset; + newTceTable->busNumber = 0; + newTceTable->index = (unsigned long)dma_window[0]; + newTceTable->tceType = TCE_VB; + + return build_tce_table(newTceTable); +} + +int vio_enable_interrupts(struct vio_dev *dev) +{ + int rc = h_vio_signal(dev->unit_address, VIO_IRQ_ENABLE); + if (rc != H_Success) { + printk(KERN_ERR "vio: Error 0x%x enabling interrupts\n", rc); + } + return rc; +} +EXPORT_SYMBOL(vio_enable_interrupts); + +int vio_disable_interrupts(struct vio_dev *dev) +{ + int rc = h_vio_signal(dev->unit_address, VIO_IRQ_DISABLE); + if (rc != H_Success) { + printk(KERN_ERR "vio: Error 0x%x disabling interrupts\n", rc); + } + return rc; +} +EXPORT_SYMBOL(vio_disable_interrupts); + + +dma_addr_t vio_map_single(struct vio_dev *dev, void *vaddr, + size_t size, int direction ) +{ + struct TceTable * tbl; + dma_addr_t dma_handle = NO_TCE; + unsigned long uaddr; + unsigned order, nPages; + + if(direction == PCI_DMA_NONE) BUG(); + + uaddr = (unsigned long)vaddr; + nPages = PAGE_ALIGN( uaddr + size ) - ( uaddr & PAGE_MASK ); + order = get_order( nPages & PAGE_MASK ); + nPages >>= PAGE_SHIFT; + + /* Client asked for way to much space. This is checked later anyway */ + /* It is easier to debug here for the drivers than in the tce tables.*/ + if(order >= NUM_TCE_LEVELS) { + printk("VIO_DMA: vio_map_single size to large: 0x%lx \n",size); + return NO_TCE; + } + + tbl = dev->tce_table; + + if(tbl) { + dma_handle = get_tces(tbl, order, vaddr, nPages, direction); + dma_handle |= (uaddr & ~PAGE_MASK); + } + + return dma_handle; +} +EXPORT_SYMBOL(vio_map_single); + +void vio_unmap_single(struct vio_dev *dev, dma_addr_t dma_handle, + size_t size, int direction) +{ + struct TceTable * tbl; + unsigned order, nPages; + + if (direction == PCI_DMA_NONE) BUG(); + + nPages = PAGE_ALIGN( dma_handle + size ) - ( dma_handle & PAGE_MASK ); + order = get_order( nPages & PAGE_MASK ); + nPages >>= PAGE_SHIFT; + + /* Client asked for way to much space. This is checked later anyway */ + /* It is easier to debug here for the drivers than in the tce tables.*/ + if(order >= NUM_TCE_LEVELS) { + printk("VIO_DMA: vio_unmap_single 0x%lx size to large: 0x%lx \n",(unsigned long)dma_handle,(unsigned long)size); + return; + } + + tbl = dev->tce_table; + if(tbl) tce_free(tbl, dma_handle, order, nPages); +} +EXPORT_SYMBOL(vio_unmap_single); + +int vio_map_sg(struct vio_dev *vdev, struct scatterlist *sglist, int nelems, + int direction) +{ + int i; + + for (i = 0; i < nelems; i++) { + + /* 2.4 scsi scatterlists use address field. + Not sure about other subsystems. */ + void *vaddr; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,5,0) + if (sglist->address) + vaddr = sglist->address; + else +#endif + vaddr = page_address(sglist->page) + sglist->offset; + + sglist->dma_address = vio_map_single(vdev, vaddr, + sglist->length, + direction); + sglist->dma_length = sglist->length; + sglist++; + } + + return nelems; +} +EXPORT_SYMBOL(vio_map_sg); + +void vio_unmap_sg(struct vio_dev *vdev, struct scatterlist *sglist, int nelems, + int direction) +{ + while (nelems--) { + vio_unmap_single(vdev, sglist->dma_address, + sglist->dma_length, direction); + sglist++; + } +} + +void *vio_alloc_consistent(struct vio_dev *dev, size_t size, + dma_addr_t *dma_handle) +{ + struct TceTable * tbl; + void *ret = NULL; + unsigned order, nPages; + dma_addr_t tce; + + size = PAGE_ALIGN(size); + order = get_order(size); + nPages = 1 << order; + + /* Client asked for way to much space. This is checked later anyway */ + /* It is easier to debug here for the drivers than in the tce tables.*/ + if(order >= NUM_TCE_LEVELS) { + printk("VIO_DMA: vio_alloc_consistent size to large: 0x%lx \n",size); + return (void *)NO_TCE; + } + + tbl = dev->tce_table; + + if ( tbl ) { + /* Alloc enough pages (and possibly more) */ + ret = (void *)__get_free_pages( GFP_ATOMIC, order ); + if ( ret ) { + /* Page allocation succeeded */ + memset(ret, 0, nPages << PAGE_SHIFT); + /* Set up tces to cover the allocated range */ + tce = get_tces( tbl, order, ret, nPages, PCI_DMA_BIDIRECTIONAL ); + if ( tce == NO_TCE ) { + PPCDBG(PPCDBG_TCE, "vio_alloc_consistent: get_tces failed\n" ); + free_pages( (unsigned long)ret, order ); + ret = NULL; + } + else + { + *dma_handle = tce; + } + } + else PPCDBG(PPCDBG_TCE, "vio_alloc_consistent: __get_free_pages failed for order = %d\n", order); + } + else PPCDBG(PPCDBG_TCE, "vio_alloc_consistent: get_tce_table failed for 0x%016lx\n", dev); + + PPCDBG(PPCDBG_TCE, "\tvio_alloc_consistent: dma_handle = 0x%16.16lx\n", *dma_handle); + PPCDBG(PPCDBG_TCE, "\tvio_alloc_consistent: return = 0x%16.16lx\n", ret); + return ret; +} +EXPORT_SYMBOL(vio_alloc_consistent); + +void vio_free_consistent(struct vio_dev *dev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + struct TceTable * tbl; + unsigned order, nPages; + + PPCDBG(PPCDBG_TCE, "vio_free_consistent:\n"); + PPCDBG(PPCDBG_TCE, "\tdev = 0x%16.16lx, size = 0x%16.16lx, dma_handle = 0x%16.16lx, vaddr = 0x%16.16lx\n", dev, size, dma_handle, vaddr); + + size = PAGE_ALIGN(size); + order = get_order(size); + nPages = 1 << order; + + /* Client asked for way to much space. This is checked later anyway */ + /* It is easier to debug here for the drivers than in the tce tables.*/ + if(order >= NUM_TCE_LEVELS) { + printk("PCI_DMA: pci_free_consistent size to large: 0x%lx \n",size); + return; + } + + tbl = dev->tce_table; + + if ( tbl ) { + tce_free(tbl, dma_handle, order, nPages); + free_pages( (unsigned long)vaddr, order ); + } +} +EXPORT_SYMBOL(vio_free_consistent); + +EXPORT_SYMBOL(plpar_hcall_norets); +EXPORT_SYMBOL(plpar_hcall_8arg_2ret); diff -puN /dev/null include/asm-ppc64/vio.h --- /dev/null 2002-08-30 16:31:37.000000000 -0700 +++ 25-akpm/include/asm-ppc64/vio.h 2004-01-13 23:22:58.000000000 -0800 @@ -0,0 +1,130 @@ +/* + * IBM PowerPC Virtual I/O Infrastructure Support. + * + * Copyright (c) 2003 IBM Corp. + * Dave Engebretsen engebret@us.ibm.com + * Santiago Leon santil@us.ibm.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 (at your option) any later version. + */ + +#ifndef _VIO_H +#define _VIO_H + +#include +#include +#include +#include +#include +/* + * Architecture-specific constants for drivers to + * extract attributes of the device using vio_get_attribute() +*/ +#define VETH_MAC_ADDR "local-mac-address" +#define VETH_MCAST_FILTER_SIZE "ibm,mac-address-filters" + +/* End architecture-specific constants */ + +#define h_vio_signal(ua, mode) \ + plpar_hcall_norets(H_VIO_SIGNAL, ua, mode) + +#define VIO_IRQ_DISABLE 0UL +#define VIO_IRQ_ENABLE 1UL + +struct vio_dev; +struct vio_driver; +struct vio_device_id; +struct TceTable; + +int vio_register_driver(struct vio_driver *drv); +int vio_unregister_driver(struct vio_driver *drv); +const struct vio_device_id * vio_match_device(const struct vio_device_id *ids, + const struct vio_dev *dev); +struct vio_dev * __devinit vio_register_device(struct device_node *node_vdev); +int __devinit vio_unregister_device(struct vio_dev *dev); +const void * vio_get_attribute(struct vio_dev *vdev, void* which, int* length); +int vio_get_irq(struct vio_dev *dev); +struct TceTable * vio_build_tce_table(struct vio_dev *dev); +int vio_enable_interrupts(struct vio_dev *dev); +int vio_disable_interrupts(struct vio_dev *dev); + +dma_addr_t vio_map_single(struct vio_dev *dev, void *vaddr, + size_t size, int direction); +void vio_unmap_single(struct vio_dev *dev, dma_addr_t dma_handle, + size_t size, int direction); +int vio_map_sg(struct vio_dev *vdev, struct scatterlist *sglist, + int nelems, int direction); +void vio_unmap_sg(struct vio_dev *vdev, struct scatterlist *sglist, + int nelems, int direction); +void *vio_alloc_consistent(struct vio_dev *dev, size_t size, + dma_addr_t *dma_handle); +void vio_free_consistent(struct vio_dev *dev, size_t size, void *vaddr, + dma_addr_t dma_handle); + +struct vio_device_id { + char *type; + char *compat; +/* I don't think we need this + unsigned long driver_data; */ /* Data private to the driver */ +}; + +struct vio_driver { + struct list_head node; + char *name; + const struct vio_device_id *id_table; /* NULL if wants all devices */ + int (*probe) (struct vio_dev *dev, const struct vio_device_id *id); /* New device inserted */ + void (*remove) (struct vio_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */ + unsigned long driver_data; +}; + +struct vio_bus; +/* + * The vio_dev structure is used to describe virtual I/O devices. + */ +struct vio_dev { + struct list_head devices_list; /* node in list of all vio devices */ + struct device_node *archdata; /* Open Firmware node */ + struct vio_bus *bus; /* bus this device is on */ + struct vio_driver *driver; /* owning driver */ + void *driver_data; /* data private to the driver */ + unsigned long unit_address; + + struct TceTable *tce_table; /* vio_map_* uses this */ + unsigned int irq; + struct proc_dir_entry *procent; /* device entry in /proc/bus/vio */ +}; + +struct vio_bus { + struct list_head devices; /* list of virtual devices */ +}; + + +static inline int vio_module_init(struct vio_driver *drv) +{ + int rc = vio_register_driver (drv); + + if (rc > 0) + return 0; + + /* iff CONFIG_HOTPLUG and built into kernel, we should + * leave the driver around for future hotplug events. + * For the module case, a hotplug daemon of some sort + * should load a module in response to an insert event. */ +#if defined(CONFIG_HOTPLUG) && !defined(MODULE) + if (rc == 0) + return 0; +#else + if (rc == 0) + rc = -ENODEV; +#endif + + /* if we get here, we need to clean up vio driver instance + * and return some sort of error */ + + return rc; +} + +#endif /* _PHYP_H */ _