UML usb host controller 2.5.44-1 ~63K
From: | "James McMechan" <james_mcmechan@hotmail.com> | |
To: | <doug@lathi.net> | |
Subject: | [linux-usb-devel] UML usb host controller 2.5.44-1 ~63K | |
Date: | Wed, 23 Oct 2002 07:10:45 -0700 | |
Cc: | <linux-usb-devel@lists.sourceforge.net>, <user-mode-linux-devel@lists.sourceforge.net> |
And here is the patch for 2.5.44-1, which is against vanilla linux-2.5.44 + uml-patch-2.5.44-1 This one enumerates the devices in the host's /proc/bus/usb and builds up the device bus structures seemingly ok, but none of the indiviual devices seem to appear on the UML side /proc/bus/usb, I think what is wrong is that it depended on some automatic device creation in the 2.4 HCD model which is no longer present This has even more debug printks as I have been attempting to trace down what is happening. It is nice to be able to single step through the host controller and examine all the data structures with gdb while it is running :) I also suspect that the useage of hcpriv has changed and my most recent attempt to try and follow the current model may make this version even more confusing than normal. Alas, while this version does not work, pretty much all of the comments about the 2.4 version still apply, this module is named "uml-hcd.o" and its support function "uml-pci.o" the noop pci functions from ARCH=i386 I suppose that putting these up on either sourceforge or the uml-user site would be reasonable. I hope this is helpful, it does seem to make a long email though :( James McMechan diff -Nur uml-2.5.44-1/arch/um/config.in work-2.5.44-1/arch/um/config.in --- uml-2.5.44-1/arch/um/config.in 2002-10-18 03:35:50.000000000 -0400 +++ work-2.5.44-1/arch/um/config.in 2002-10-21 10:38:01.000000000 -0400 @@ -75,6 +75,8 @@ fi endmenu +source drivers/usb/Config.in + source drivers/md/Config.in source drivers/mtd/Config.in @@ -91,3 +93,6 @@ dep_bool 'Enable gprof support' CONFIG_GPROF $CONFIG_DEBUGSYM dep_bool 'Enable gcov support' CONFIG_GCOV $CONFIG_DEBUGSYM endmenu + +# this is needed for the zlib build to occur properly +source lib/Config.in diff -Nur uml-2.5.44-1/arch/um/kernel/ksyms.c work-2.5.44-1/arch/um/kernel/ksyms.c --- uml-2.5.44-1/arch/um/kernel/ksyms.c 2002-10-20 22:35:55.000000000 -0400 +++ work-2.5.44-1/arch/um/kernel/ksyms.c 2002-10-21 10:38:01.000000000 -0400 @@ -85,3 +85,5 @@ EXPORT_SYMBOL(kmap_atomic_to_page); #endif +EXPORT_SYMBOL(page_to_pfn); +EXPORT_SYMBOL(um_kmalloc); diff -Nur uml-2.5.44-1/arch/um/kernel/Makefile work-2.5.44-1/arch/um/kernel/Makefile --- uml-2.5.44-1/arch/um/kernel/Makefile 2002-10-20 22:35:55.000000000 -0400 +++ work-2.5.44-1/arch/um/kernel/Makefile 2002-10-21 00:44:44.000000000 -0400 @@ -6,7 +6,7 @@ sigio_kern.o signal_kern.o signal_user.o smp.o syscall_kern.o \ syscall_user.o sysrq.o sys_call_table.o tempfile.o time.o \ time_kern.o tlb.o trap_kern.o trap_user.o uaccess_user.o um_arch.o \ - umid.o user_util.o + umid.o user_util.o user_syms.o obj-$(CONFIG_BLK_DEV_INITRD) += initrd_kern.o initrd_user.o @@ -42,7 +42,7 @@ include $(TOPDIR)/Rules.make $(USER_OBJS) : %.o: %.c - $(CC) $(CFLAGS_$@) $(USER_CFLAGS) -c -o $@ $< + $(CC) $(CFLAGS_$(@F)) $(USER_CFLAGS) -c -o $@ $< arch/um/kernel/unmap.o: arch/um/kernel/unmap.c $(CC) $(UNMAP_CFLAGS) -c -o $@ $< diff -Nur uml-2.5.44-1/drivers/usb/Config.in work-2.5.44-1/drivers/usb/Config.in --- uml-2.5.44-1/drivers/usb/Config.in 2002-08-27 19:53:35.000000000 -0400 +++ work-2.5.44-1/drivers/usb/Config.in 2002-10-21 10:38:01.000000000 -0400 @@ -4,14 +4,9 @@ mainmenu_option next_comment comment 'USB support' -# ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface. -if [ "$CONFIG_PCI" = "y" -o "$CONFIG_SA1111" = "y" ]; then - tristate 'Support for USB' CONFIG_USB -else - define_bool CONFIG_USB n -fi +tristate 'Support for USB' CONFIG_USB -if [ "$CONFIG_USB" = "y" -o "$CONFIG_USB" = "m" ]; then +if [ "$CONFIG_USB" != "n" ]; then source drivers/usb/core/Config.in source drivers/usb/host/Config.in diff -Nur uml-2.5.44-1/drivers/usb/host/Config.help work-2.5.44-1/drivers/usb/host/Config.help --- uml-2.5.44-1/drivers/usb/host/Config.help 2002-08-10 21:41:40.000000000 -0400 +++ work-2.5.44-1/drivers/usb/host/Config.help 2002-10-21 10:38:01.000000000 -0400 @@ -61,3 +61,13 @@ inserted in and removed from the running kernel whenever you want). The module will be called hc_sl811.o. If you want to compile it as a module, say M here and read <file:Documentation/modules.txt>. + +CONFIG_USB_UML_HCD + Say Y here if you want a user mode USB host controller in your system. + + If you do not know what this is, please say N. + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called hc_sl811.o. If you want to compile it + as a module, say M here and read <file:Documentation/modules.txt>. diff -Nur uml-2.5.44-1/drivers/usb/host/Config.in work-2.5.44-1/drivers/usb/host/Config.in --- uml-2.5.44-1/drivers/usb/host/Config.in 2002-08-10 21:41:23.000000000 -0400 +++ work-2.5.44-1/drivers/usb/host/Config.in 2002-10-21 10:38:01.000000000 -0400 @@ -2,9 +2,21 @@ # USB Host Controller Drivers # comment 'USB Host Controller Drivers' -dep_tristate ' EHCI HCD (USB 2.0) support' CONFIG_USB_EHCI_HCD $CONFIG_USB -dep_tristate ' OHCI HCD support' CONFIG_USB_OHCI_HCD $CONFIG_USB -dep_tristate ' UHCI HCD (most Intel and VIA) support' CONFIG_USB_UHCI_HCD_ALT $CONFIG_USB +dep_tristate ' EHCI HCD (USB 2.0) support' CONFIG_USB_EHCI_HCD $CONFIG_USB $CONFIG_PCI +dep_tristate ' OHCI HCD support' CONFIG_USB_OHCI_HCD $CONFIG_USB $CONFIG_PCI +dep_tristate ' UHCI HCD (most Intel and VIA) support' CONFIG_USB_UHCI_HCD_ALT $CONFIG_USB $CONFIG_PCI +# +# ARCH specific host controllers +# if [ "$CONFIG_ARM" = "y" ]; then +# SA1111 has a non-PCI OHCI HCD availible see above for PCI version + if [ "$CONFIG_SA1111" = "y" ]; then + dep_tristate ' OHCI HCD support' CONFIG_USB_OHCI_HCD $CONFIG_USB + fi dep_tristate ' SL811HS support' CONFIG_USB_SL811HS $CONFIG_USB fi +if [ "$CONFIG_USERMODE" = "y" ]; then + dep_tristate ' User Mode Linux Host Controller support' CONFIG_USB_UML_HCD $CONFIG_USB +# define for the user mode pci needed by usbcore + dep_tristate ' User Mode Linux fake PCI stuff' CONFIG_USB_UML_PCI $CONFIG_USB +fi diff -Nur uml-2.5.44-1/drivers/usb/host/Makefile work-2.5.44-1/drivers/usb/host/Makefile --- uml-2.5.44-1/drivers/usb/host/Makefile 2002-08-10 21:41:55.000000000 -0400 +++ work-2.5.44-1/drivers/usb/host/Makefile 2002-10-21 10:38:01.000000000 -0400 @@ -7,6 +7,21 @@ obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o obj-$(CONFIG_USB_UHCI_HCD_ALT) += uhci-hcd.o +# +# ARCH specific HCD +# + obj-$(CONFIG_USB_SL811HS) += hc_sl811.o +obj-$(CONFIG_USB_UML_HCD) += uml-hcd.o +obj-$(CONFIG_USB_UML_PCI) += uml-pci.o +export-objs += uml-pci.o + include $(TOPDIR)/Rules.make + +# this used to be possibile using macro expansion uml-hcd-objs etc. +# I was unable to figure out 2.5.41+s new method +$(obj)/uml-hcd.o: $(obj)/uml-hcd_kern.o $(obj)/uml-hcd_user.c + $(CC) $(CFLAGS_$@) $(USER_CFLAGS) -g -c -o $(obj)/uml-hcd_user.o $(obj)/uml-hcd_user.c + $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) -r -o $@ $(obj)/uml-hcd_kern.o $(obj)/uml-hcd_user.o + diff -Nur uml-2.5.44-1/drivers/usb/host/uml-hcd_kern.c work-2.5.44-1/drivers/usb/host/uml-hcd_kern.c --- uml-2.5.44-1/drivers/usb/host/uml-hcd_kern.c 1969-12-31 19:00:00.000000000 -0500 +++ work-2.5.44-1/drivers/usb/host/uml-hcd_kern.c 2002-10-21 10:38:01.000000000 -0400 @@ -0,0 +1,936 @@ +/* + * User-Mode Linux Interface driver for USB (take I). + * + * (c) 2001 Johan Verrept, Johan.Verrept@advalvas.be + * massive tinkering by James McMechan james_mcmechan@hotmail.com + * + * Fri Mar 16 06:50:05 CET 2001 : module compiled + * Fri Mar 16 21:00:00 CET 2001 : file created + * Sat Jul 27 2002 : massive tinkering + * + * Code notes: + * I have integrated a Hub simulation. This is needed to get hubs + * working properly. Because the hub driver in the host system is + * needed to detect devices, we cannot disable it. the hub driver + * This means we cannot submit an interrupt to the hub endpoint. + * When a hub driver submits the interrupt urb, we store it. + * When the timer goes off, we check the port info on the hubs. + * If we detect a change in connected devices, we complete the + * interrupt urb. + * We have a race situation here we cannot avoid. + * If a device is plugged in and our hub driver poll the status + * of the hubport before the host hub driver does it will not + * detect the device. + * WORKAROUND: if the hub driver would check the wPortStatus + * against a known value, then it could capture a missed wPortChange. + * We would have to add more code in here to make sure that this + * check is fooled in the um hub driver. + */ + +#include "linux/config.h" +#include "linux/module.h" +#include "linux/kernel.h" +#include "linux/timer.h" +#include "linux/sched.h" +#include "linux/slab.h" +#include "linux/usb.h" +#include "linux/init.h" +#include "linux/spinlock.h" +#include "linux/list.h" +#include "../core/hcd.h" + +/* USER includes */ + +#include "kern.h" +#include "kern_util.h" + +#include "uml-hcd_kern.h" + +/* This enables debug printks */ +/* #define DEBUG */ + +/* 1 ms timeout for framecounter, 5 msec device poll interval */ +#define UMUSB_TIMEOUT HZ/10 + +struct umusb_urbctx { + struct list_head list; + struct urb *urb; + struct usbdevfs_urb *uurb; + unsigned int expires; +}; + +struct umusb_hubctx { + struct list_head list; + struct urb *interrupt; +}; + +static struct device device_uml_hcd = { + name: "uml-hcd", + bus_id: "uml-hcd", +}; + +/*********** GLOBALS ************/ + +static struct umusb *umusb_hcds = NULL; +static struct timer_list umusb_poll_timer; +static atomic_t umusb_frame_counter = ATOMIC_INIT(0); +static unsigned int umusb_hub_interrupts_enabled = 0; + +static struct umusb_device *umusb_last_reported_dev = NULL; + +static LIST_HEAD(umusb_pending_urbctx); +static LIST_HEAD(umusb_active_hubctxs); +static LIST_HEAD(umusb_busses); + +/******************************************************************************* +* +* Utilities +* +*******************************************************************************/ + +long umusb_atoi(char *input) +{ + return simple_strtol(input,NULL,10); +} + +static int umusb_epnum_lookup(int epnum, struct usb_config_descriptor *cd) +{ + int i, j; + int count1 = cd->bNumInterfaces; + for (i = 0; i < count1; i++) { + struct usb_interface *in = &cd->interface[i]; + int as = in->act_altsetting; + struct usb_interface_descriptor *id = &in->altsetting[as]; + int count2 = id->bNumEndpoints; + for (j = 0; j < count2; j++) + if (epnum == id->endpoint[j].bEndpointAddress) + return i; + } + return -1; +} + +static int umusb_epnum_to_if(struct usb_device *dev, int epnum) +{ + int c; + + int count; + int result; + struct usb_config_descriptor *cd; + struct usb_device_descriptor *dd; + + cd = dev->actconfig; + if (cd != NULL) { + return umusb_epnum_lookup(epnum, cd); + } + + /* active configuration not yet set */ + dd = &dev->descriptor; + count = dd->bNumConfigurations; + for (c = 0; c < count; c++) { + cd = &dev->config[c]; + result = umusb_epnum_lookup(epnum, cd); + /* ick success if > -1 and we return here */ + if (result > -1) { + return result; + } + } + + /* ok never found a endpoint i.e. all -1 above */ + return -1; +} + +static struct umusb_device *umusb_bus_find_dev(struct list_head *bus_udev, + int devnum) +{ + struct list_head *index; + + printk(KERN_INFO "scanning for udev %d from %p\n", devnum, bus_udev); + list_for_each(index, bus_udev) { + struct umusb_device *udev = list_entry(index, + struct umusb_device, list); + printk(KERN_INFO "check device %p->devnum = %d\n",udev,udev->devnum); + if (udev->devnum == devnum) + return udev; + } + return NULL; +} + +static struct umusb_device *umusb_hub_find_device(struct umusb_device *udev, + unsigned int port) +{ + int devnum = udev->hubdev->portinfo.port[port - 1]; + return umusb_bus_find_dev(&udev->bus->devices, devnum); +} + +static struct umusb_bus *umusb_find_bus(int bus_num) +{ + struct list_head *index; + + printk(KERN_ERR "scanning for bus %d from %p\n",bus_num,&umusb_busses); + list_for_each(index, &umusb_busses) { + struct umusb_bus *bus = list_entry(index, struct umusb_bus, + list); + printk(KERN_ERR "check bus %p busnum %d\n",bus,bus->busnum); + if (bus->busnum == bus_num) + return bus; + } + return NULL; +} + +void umusb_add_bus(int bus_num) +{ + struct umusb_bus *bus; + + bus = umusb_find_bus(bus_num); + + if (bus != NULL) { + printk(KERN_INFO "add_bus bus exists %d %p\n", bus_num, bus); + goto exit; + } + + bus = (struct umusb_bus *) kmalloc(sizeof(struct umusb_bus), GFP_ATOMIC); + if (bus == NULL) { + printk(KERN_INFO "add_bus bus alloc failed %d\n", bus_num); + goto exit; + } + + memset((void *) bus, 0, sizeof(struct umusb_bus)); + printk(KERN_INFO "UMUSBk: new bus (%d) = %p\n", bus_num, bus); + bus->busnum = bus_num; + INIT_LIST_HEAD(&bus->list); + INIT_LIST_HEAD(&bus->devices); + printk(KERN_INFO "bus->devices %p->next %p ->prev %p\n",&bus->devices,bus->devices.next,bus->devices.prev); + list_add_tail(&bus->list, &umusb_busses); +exit: + printk(KERN_INFO "UMUSBk: new bus (%d) = %p\n", bus_num, bus); +} + +struct umusb_device *umusb_find_dev(int bus_num, int devnum) +{ + struct umusb_bus *bus = umusb_find_bus(bus_num); + + if (bus == NULL) { + printk(KERN_INFO "find_dev could not find proper bus %d\n", bus_num); + return NULL; + } + + return umusb_bus_find_dev(&bus->devices, devnum); +} + +void umusb_bus_add_dev(int bus_num, struct umusb_device *udev) +{ + struct umusb_bus *bus = umusb_find_bus(bus_num); + + printk(KERN_ERR "bus_add_dev bus %d at %p\n", bus_num, bus); + if (bus == NULL) { + printk(KERN_ERR "bus_add_dev could not find proper bus %d\n", bus_num); + return; + } + + udev->bus = bus; + bus->devcount++; + printk(KERN_ERR "add tail %p to bus->devices %p->next %p ->prev %p\n", udev, &bus->devices, bus->devices.next, bus->devices.prev); + list_add_tail((struct list_head *)udev, &bus->devices); + printk(KERN_ERR "new bus->devices %p->next %p ->prev %p\n", &bus->devices, bus->devices.next, bus->devices.prev); +} + +/****************************************************************************** +* +* URB processing +* +*******************************************************************************/ + + +/* + * does the completetion on a user urb on host + * unlinks the pending context for this uurb + */ +int umusb_complete_urb(struct usbdevfs_urb *uurb) +{ + struct urb *urb; + struct umusb_urbctx *urbctx = 0; + struct usb_device *dev; + struct umusb_device *udev; + int error; + struct list_head *index; + + if (uurb == NULL) + return -EINVAL; + + list_for_each(index, &umusb_pending_urbctx) { + urbctx = list_entry(index, struct umusb_urbctx, list); + if (uurb == urbctx->uurb) + break; + } + + if (urbctx != uurb->usercontext) { + printk(KERN_ERR "for some reason the context %p " + "is not the usercontext %p\n", + urbctx, uurb->usercontext); + } + + if (urbctx->uurb != uurb) { + printk(KERN_ERR "for some reason the context uurb %p " + "is not the uurb %p\n", urbctx->uurb, uurb); + } + + urbctx = (struct umusb_urbctx *) uurb->usercontext; + urb = urbctx->urb; + dev = urb->dev; + udev = (struct umusb_device *) dev->hcpriv; + + /* update urb */ + urb->status = uurb->status; + urb->actual_length = uurb->actual_length; + + printk(KERN_INFO "UMUSBk: Completing urb " + "(urbctx = %p, urb %p, uurb %p, status = %d)\n", + urbctx, urb, uurb, urb->status); + printk(KERN_INFO "UMUSBk: urb->actual_length = %d\n", + urb->actual_length); + switch (usb_pipetype(urb->pipe)) { + struct usb_ctrlrequest *cr; + __u16 port; + __u32 portbit; + __u16 *wPortChangeData; + __u16 wPortChange; + case PIPE_CONTROL: + /* humm offset by dev req but length stays the same ? */ + memcpy(urb->transfer_buffer, uurb->buffer + + sizeof(struct usb_ctrlrequest), + uurb->actual_length); + kfree(uurb->buffer); + /* + * we need to capture hub port status messages + * to rewrite wPortChange + */ + cr = (struct usb_ctrlrequest *) urb->setup_packet; + port = le16_to_cpu(cr->wIndex); + portbit = (1 << (port - 1)); + wPortChangeData = &((__u16 *) urb->transfer_buffer)[1]; + wPortChange = le16_to_cpu(*wPortChangeData); + if (udev->hubdev == NULL || + !(portbit & udev->hubdev->cstatus) || + (cr->bRequestType|cr->bRequest << 8) != + (RH_GET_STATUS | RH_OTHER | RH_CLASS)) { + break; + } + /* rewrite wPortchange bits */ + printk(KERN_INFO "UMUSBk: Detected Hub Port status " + "check on hub %p.\n", udev->hubdev); + umusb_last_reported_dev = umusb_hub_find_device(udev, + port); + if (umusb_last_reported_dev) { + /* change has not been detected yet by usb system */ + wPortChange |= 1; + *wPortChangeData = cpu_to_le16(wPortChange); + /* erase change bit */ + udev->hubdev->cstatus &= ~portbit; + printk(KERN_INFO "UMUSBk: Rewrote wPortChange" + "to %x on port %d," + "hubdev->cstatus = %lx\n", + wPortChange, port, + udev->hubdev->cstatus); + } else { + printk(KERN_INFO "UMUSBk: was rewriting " + "wPortChange with %x on port" + "%d, hubdev->cstatus = %lx but" + "got zero in hub find\n", + wPortChange, port, + udev->hubdev->cstatus); + } + break; + case PIPE_INTERRUPT: + memcpy(urb->transfer_buffer, uurb->buffer, + uurb->actual_length); + if (urb->interval == 0) + break; + if (urb->complete) + urb->complete(urb); + urb->status = -EINPROGRESS; + urbctx->expires = urb->timeout ? jiffies + urb->timeout : 0; + error = umusb_submit_urb_user(udev->fd, uurb); + usb_put_dev(dev); + return error; + default: + break; + }; + + if (urb->complete) + urb->complete(urb); + + /* nuking ctx */ + list_del_init(&urbctx->list); + kfree(urbctx); + + usb_put_dev(dev); + return 0; +} + + +/******************************************************************************* +* +* Hub Simulation +* +*******************************************************************************/ +static int umusb_hs_processurb(struct urb *urb) +{ + struct umusb_device *udev = (struct umusb_device *) urb->dev->hcpriv; + + if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { + /* FIXME: the hub interrupt is not supported atm */ + if (udev->hubdev) { + struct umusb_hubctx *hubctx = udev->hubdev->hubctx; + + printk(KERN_ERR "UMUSBk: Registering interrupt " + "urb (%p) for hubctx (%p)\n", + urb, urb->dev); + + /* check whether urb already submitted */ + if (hubctx && hubctx->interrupt) { + printk(KERN_INFO "UMUSBk: hub simulation hubctx && interrupt set\n"); + return -EILSEQ; + } + if (hubctx == NULL) { + hubctx = (struct umusb_hubctx *) kmalloc(sizeof(struct umusb_hubctx), GFP_KERNEL); + if (hubctx == NULL) { + printk(KERN_ERR "UMUSBk: hub simulation hubctx alloc failed\n"); + return -ENOMEM; + } + INIT_LIST_HEAD(&hubctx->list); + list_add(&hubctx->list, &umusb_active_hubctxs); + } + hubctx->interrupt = urb; + printk(KERN_ERR "UMUSBk: hub simulation interrupt = %p\n",hubctx->interrupt); + printk(KERN_ERR "UMUSBk: hub simulation hubctx = %p\n",hubctx); + } + printk(KERN_ERR "UMUSBk: hub simulation exit status 0\n"); + return 0; + } + if (usb_pipein(urb->pipe)) { + printk(KERN_ERR "UMUSBk: hub simulation pipein\n"); + return 1; + } + + /* simulate sets here */ + printk(KERN_ERR "UMUSBk: hub simulation exit\n"); + return 0; +} + +/******************************************************************************* +* +* Device Operations +* +*******************************************************************************/ + +/*************** Allocate Device ******************/ + +static int umusb_alloc_dev(struct usb_device *dev) +{ + printk(KERN_ERR "UMUSBk: umusb_alloc_dev (%p)->%p\n", dev, + umusb_last_reported_dev); + + /* hack. when I rewrite the wPortChange bit in the + get-status message of the parent hub, + I store this pointer. Ugly. + */ + dev->hcpriv = umusb_last_reported_dev; + + return 0; +} + +/*************** Free Device ******************/ + +static int umusb_free_dev(struct usb_device *dev) +{ + printk(KERN_ERR "UMUSBk: umusb_free_dev (%p)\n", dev); + return 0; +} + +/*************** + * Get Current Frame Number + * synchronise this with a 1 ms tick ? + ***************/ +static int umusb_get_current_frame_number(struct usb_device *dev) +{ + int ret = atomic_read(&umusb_frame_counter); + printk(KERN_ERR + "UMUSBk: umusb_get_current_frame_number returns %d\n", ret); + return ret; +} + +/*************** Submit URB ******************/ + +static int umusb_submit_urb(struct urb *urb, int mem_flags) +{ + int error = 0, send = 1, intf; + struct umusb_urbctx *urbctx; + struct usbdevfs_urb *uurb; + struct umusb_device *udev; + struct usb_ctrlrequest *cr; + int total_length; + + if (urb == NULL || urb->dev == NULL) + return -EINVAL; + + usb_get_dev(urb->dev); + + udev = (struct umusb_device *) urb->dev->hcpriv; + + /* + * check whether this is the root hub, + * this is the first device, thus the first in the list. + */ + if (udev->hubdev) { + /* return on error or success, > 0 means not handled. */ + if ((error = umusb_hs_processurb(urb)) <= 0) { + printk(KERN_ERR " hub simulation failed %d\n",error); + goto fail; + send = 0; + } + } + + total_length = sizeof(struct umusb_urbctx) + sizeof(struct usbdevfs_urb); + + urbctx = (struct umusb_urbctx *) kmalloc(total_length, mem_flags); + if (urbctx == NULL) { + printk(KERN_ERR " alloc urbctx failed\n"); + error = -ENOMEM; + goto fail; + } + + memset(urbctx, 0, total_length); + uurb = (struct usbdevfs_urb *) (&urbctx[1]); + + /* general init */ + INIT_LIST_HEAD(&urbctx->list); + urbctx->uurb = uurb; + urbctx->urb = urb; + uurb->endpoint = (usb_pipeendpoint(urb->pipe)) | (urb->pipe & 0x80L); + uurb->buffer = urb->transfer_buffer; + uurb->buffer_length = urb->transfer_buffer_length; + uurb->actual_length = urb->actual_length; + uurb->number_of_packets = urb->number_of_packets; + uurb->signr = 0; + uurb->usercontext = urbctx; + urb->hcpriv = urbctx; + urb->status = -EINPROGRESS; + + /* specific init */ + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + uurb->type = USBDEVFS_URB_TYPE_CONTROL; + cr = (struct usb_ctrlrequest *) (urb->setup_packet); + + printk(KERN_ERR + "UMUSBk: ctrl req with type = %d, request %d\n", + cr->bRequestType, cr->bRequest); + + if (cr->bRequestType == USB_TYPE_STANDARD) { + switch (cr->bRequest) { + case USB_REQ_SET_ADDRESS: + printk(KERN_ERR " UMUSBk: control sim set address\n"); + send = 0; + urb->status = 0; + + if (urb->complete) + urb->complete(urb); + + error = 0; + goto error; + + case USB_REQ_SET_CONFIGURATION: + printk(KERN_ERR " UMUSBk: control sim set configuation\n"); + /* fake setconfig on claimed drivers */ + intf = umusb_epnum_to_if(urb->dev, + usb_pipeendpoint(urb->pipe)); + if (!umusb_check_driver(udev->fd, intf)) + break; + + urb->status = 0; + if (urb->complete) + urb->complete(urb); + + error = 0; + goto error; + + default: + break; + } + } + + /* we need to prepend the setup_packet before the transfer_buffer */ + uurb->buffer_length = urb->transfer_buffer_length; + uurb->buffer_length += sizeof(struct usb_ctrlrequest); + + uurb->buffer = kmalloc(uurb->buffer_length, mem_flags); + if (uurb->buffer == NULL) { + error = -ENOMEM; + goto error; + } + memset(uurb->buffer, 0, uurb->buffer_length); + memcpy(uurb->buffer, urb->setup_packet, sizeof(struct usb_ctrlrequest)); + break; + + case PIPE_BULK: + printk(KERN_ERR "UMUSBk: PIPE_BULK\n"); + uurb->type = USBDEVFS_URB_TYPE_BULK; + break; + /* interrupt urb are simulated using BULK transfers */ + case PIPE_INTERRUPT: + printk(KERN_ERR "UMUSBk: PIPE_INTERRUPT -> BULK\n"); + uurb->type = USBDEVFS_URB_TYPE_BULK; + break; + + case PIPE_ISOCHRONOUS: + uurb->type = USBDEVFS_URB_TYPE_ISO; + + printk(KERN_ERR "UMUSBk: Warning PIPE_ISOCHRONOUS " + "not supported yet.\n"); + + error = -EINVAL; /* not supported yet */ + goto error; + + default: + error = -EINVAL; + goto error; + break; + }; + + printk(KERN_ERR "UMUSBk: Submitting urb with " + "pipe %x, endpoint = %x, buffer %p\n", + urb->pipe, uurb->endpoint, uurb->buffer); + printk(KERN_ERR "UMUSBk: buffer_length %d, actual_length = %d, " + "usercontext %p\n", + uurb->buffer_length, uurb->actual_length, + uurb->usercontext); + + urbctx->expires = urb->timeout ? jiffies + urb->timeout : 0; + + if (send) { + error = umusb_submit_urb_user(udev->fd, uurb); + if (error < 0) { + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + kfree(uurb->buffer); + default: + break; + } + goto error; + } + list_add(&urbctx->list, &umusb_pending_urbctx); + } + + return 0; +error: + kfree(urbctx); +fail: + usb_put_dev(urb->dev); + return error; +} + +/*************** Unlink URB ******************/ + +static int umusb_unlink_urb(struct urb *urb) +{ + struct umusb_urbctx *urbctx = (struct umusb_urbctx *) urb->hcpriv; + struct umusb_device *udev = (struct umusb_device *) urb->dev->hcpriv; + + if (urbctx == NULL) return -EINVAL; + struct usbdevfs_urb *uurb = urbctx->uurb; + + printk(KERN_ERR "UMUSBk: Unlinking urb urbctx = %p, urb %p, udev %p\n", + urbctx, urb, udev); + + umusb_unlink_urb_user(udev->fd, uurb); + list_del_init(&urbctx->list); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + urb->actual_length = uurb->actual_length; + memcpy(urb->transfer_buffer, uurb->buffer, uurb->actual_length); + kfree(uurb->buffer); + break; + default: + break; + }; + + kfree(urbctx); + + return 0; +} + +/*************** Device Operations ******************/ + +struct usb_operations umusb_devops = { + .allocate = umusb_alloc_dev, + .deallocate = umusb_free_dev, + .get_frame_number = umusb_get_current_frame_number, + .submit_urb = umusb_submit_urb, + .unlink_urb = umusb_unlink_urb +}; + + +/******************************************************************************* +* +* Startup +* +*******************************************************************************/ + + +static void umusb_start_usb(struct umusb *hostctx) +{ + struct usb_bus *bus; + struct usb_device *dev; + struct list_head *rdev; + + /* create bus */ + bus = usb_alloc_bus(&umusb_devops); + if (bus == NULL) { + printk(KERN_ERR "UMUSBk: bus alloc failed \n"); + goto exit; + } + + printk(KERN_ERR "UMUSBk: Created usb_bus (%p)\n", bus); + hostctx->bus = bus; + bus->hcpriv = hostctx; + bus->op = &umusb_devops; + bus->bus_name = "uml-hcd"; + + usb_register_bus(bus); + + rdev = &hostctx->realbus->devices; + if (list_empty(rdev)) { + printk(KERN_ERR "UMUSBk: no devices freeing usb_bus (%p)\n", + bus); + goto busexit; + } + + /* create device */ + dev = usb_alloc_dev(NULL, bus); + if (dev == NULL) { + printk(KERN_ERR "UMUSBk: dev alloc failed freeing bus (%p)\n", + bus); + goto busexit; + } + + printk(KERN_ERR "UMUSBk: usb root device allocated dev = %p\n", dev); + + /* hub device init */ + dev->hcpriv = rdev->next; + bus->root_hub = dev; + usb_connect(dev); + + if (usb_register_root_hub (dev, &device_uml_hcd) != 0) { + printk(KERN_ERR "UMUSBk: usb_register_root_hub failed\n"); + goto devexit; + } + printk(KERN_ERR "UMUSBk: Announcing new rootdevice " + "(dev->devnum = %d)\n", dev->devnum); + + return; +devexit: + printk(KERN_ERR " freeing device %p \n", dev); + //usb_free_dev(dev); +busexit: + printk(KERN_ERR " removing bus %p from hostctx %p\n", bus, hostctx); + //usb_deregister_bus(bus); + //usb_free_bus(bus); +exit: + return; +} + +/******************************************************************************* +* +* Interrupt routine +* +*******************************************************************************/ + +static void umusb_interrupt(unsigned long arg) +{ + int status = 0; + struct urb *urb; + struct list_head *index, *tmp; + struct umusb_device *udev; + + list_for_each_safe(index, tmp, &umusb_pending_urbctx) { + struct usbdevfs_urb *uurb; + const struct umusb_urbctx *urbctx; + urbctx = list_entry(index, struct umusb_urbctx, list); + urb = urbctx->urb; + uurb = urbctx->uurb; + udev = (struct umusb_device *)urb->dev->hcpriv; + status = umusb_interrupt_handler_user(udev->fd); + if ((status == -EAGAIN) && urbctx->expires + && (urbctx->expires < jiffies)) { + printk(KERN_ERR "UMUSBk: Timeout on urbctx " + "= %p uurb = %p, urb = %p\n", + urbctx, uurb, urb); + umusb_unlink_urb(urb); + urb->status = -ETIMEDOUT; + if (urb->complete) + urb->complete(urb); + } + + } + + if (umusb_hub_interrupts_enabled) { + unsigned long bitmap; + + list_for_each(index, &umusb_active_hubctxs) { + const struct umusb_hubctx *hubctx; + hubctx = list_entry(index, struct umusb_hubctx, list); + /* check hub status changes here */ + if (!hubctx->interrupt) + continue; + + udev = (struct umusb_device *) hubctx->interrupt->dev->hcpriv; + umusb_check_hub_port_change(udev, &bitmap); + if (bitmap) { + urb = hubctx->interrupt; + + printk(KERN_ERR "UMUSBk: Hub Interrupt on " + "hubctx %p, devnum %d, host " + "devnum %d, bitmap %lx\r\n", + hubctx, urb->dev->devnum, + udev->devnum, bitmap); + + /* + * first rescan bus to be sure we know the + * devices ourself. expensive! + */ + umusb_scan_bus(udev->bus->busnum); + + urb->status = 0; + if (urb->complete) + urb->complete(urb); + + /* reset status */ + urb->status = -EINPROGRESS; + } + } + } + umusb_poll_timer.expires = jiffies + UMUSB_TIMEOUT; + add_timer(&umusb_poll_timer); +} + +/******************************************************************************* +* +* Init and cleanup +* +*******************************************************************************/ + +static int umusb_hcd_init(void) +{ + struct list_head *index; + int i, num; + + printk(KERN_ERR "UMUSBk: UserMode HCD initialising.\n"); + + /* let's scan for hcd's */ + num = umusb_scan_hcd(); + if (num < 1) { + printk(KERN_ERR "UMUSBk: no busses found\n"); + return -ENOMEM; + } + + umusb_hcds = kmalloc(sizeof(struct umusb) * num, GFP_KERNEL); + + if (umusb_hcds == NULL) { + printk(KERN_ERR "UMUSB: kmem_cache_create for umusb_desc " + "failed (out of memory)"); + return -ENOMEM; + } + + memset((void *) umusb_hcds, 0, sizeof(struct umusb) * num); + + /* frame interrupt faker */ + init_timer(&umusb_poll_timer); + + umusb_poll_timer.expires = jiffies + UMUSB_TIMEOUT; + umusb_poll_timer.data = (unsigned long) (&umusb_poll_timer); + umusb_poll_timer.function = umusb_interrupt; + + add_timer(&umusb_poll_timer); + i = 0; + list_for_each(index, &umusb_busses) { + struct umusb_bus *bus; + + bus = list_entry(index, struct umusb_bus, list); + + if (i >= num) + break; + printk(KERN_ERR "UMUSBk: Adding bus hostctx[%d] = %p, bus = %p" + "busnum = %d\n", i, &umusb_hcds[i], bus, + bus->busnum); + umusb_hcds[i].realbus = bus; + umusb_scan_bus(bus->busnum); + umusb_start_usb(&umusb_hcds[i]); + i++; + } + + umusb_hub_interrupts_enabled = 1; + return 0; +} + + +static void umusb_hcd_cleanup(void) +{ + struct list_head *index, *tmp; + del_timer(&umusb_poll_timer); + umusb_hub_interrupts_enabled = 0; + + list_for_each_safe(index, tmp, &umusb_pending_urbctx) { + struct umusb_urbctx *urbctx = (struct umusb_urbctx *) index; + struct urb *urb = urbctx->urb; + + umusb_unlink_urb(urb); + urb->status = -ETIMEDOUT; + if (urb->complete) + urb->complete(urb); + } + INIT_LIST_HEAD(&umusb_pending_urbctx); + + if (umusb_hcds) { + struct umusb_bus *bus; + struct umusb_device *udev; + struct list_head *index2, *tmp2; + struct usb_bus *rbus; + int i; + + i = 0; + list_for_each_safe(index, tmp, &umusb_busses) { + bus = (struct umusb_bus *) index; + list_for_each_safe(index2, tmp2, &bus->devices) { + udev = (struct umusb_device *) index2; + close(udev->fd); + kfree(udev); + } + + rbus = umusb_hcds[i].bus; + printk(KERN_ERR " removing bus[%d] %p from ctx %p\n",i, rbus, &umusb_hcds[i]); + if (rbus != NULL) { + /*umusb_bus_free(bus); */ + usb_deregister_bus(rbus); + usb_free_bus(rbus); + /*kfree(bus); */ + } + i++; + } + kfree(umusb_hcds); + } + return; +} + +module_init(umusb_hcd_init); +module_exit(umusb_hcd_cleanup); +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-file-style: "linux" + * End: + */ diff -Nur uml-2.5.44-1/drivers/usb/host/uml-hcd_kern.h work-2.5.44-1/drivers/usb/host/uml-hcd_kern.h --- uml-2.5.44-1/drivers/usb/host/uml-hcd_kern.h 1969-12-31 19:00:00.000000000 -0500 +++ work-2.5.44-1/drivers/usb/host/uml-hcd_kern.h 2002-10-21 10:38:01.000000000 -0400 @@ -0,0 +1,73 @@ + +#include "uml-hcd_user.h" + + +/************ TYPES ************/ + +struct umusb { + struct usb_bus *bus; + + struct umusb_bus *realbus; +}; + +struct umusb_bus { + struct list_head list; + int busnum; + int devcount; + struct list_head devices; +}; + +/* ------------------------------------------------------------------------- + Virtual Root HUB + ------------------------------------------------------------------------- */ +/* destination of request */ +#define RH_DEVICE 0x00 +#define RH_INTERFACE 0x01 +#define RH_ENDPOINT 0x02 +#define RH_OTHER 0x03 + +#define RH_CLASS 0x20 +#define RH_VENDOR 0x40 + +/* Requests: bRequest << 8 | bmRequestType */ +#define RH_GET_STATUS 0x0080 +#define RH_CLEAR_FEATURE 0x0100 +#define RH_SET_FEATURE 0x0300 +#define RH_SET_ADDRESS 0x0500 +#define RH_GET_DESCRIPTOR 0x0680 +#define RH_SET_DESCRIPTOR 0x0700 +#define RH_GET_CONFIGURATION 0x0880 +#define RH_SET_CONFIGURATION 0x0900 +#define RH_GET_STATE 0x0280 +#define RH_GET_INTERFACE 0x0A80 +#define RH_SET_INTERFACE 0x0B00 +#define RH_SYNC_FRAME 0x0C80 +/* Our Vendor Specific Request */ +#define RH_SET_EP 0x2000 + +/* Hub port features */ +#define RH_PORT_CONNECTION 0x00 +#define RH_PORT_ENABLE 0x01 +#define RH_PORT_SUSPEND 0x02 +#define RH_PORT_OVER_CURRENT 0x03 +#define RH_PORT_RESET 0x04 +#define RH_PORT_POWER 0x08 +#define RH_PORT_LOW_SPEED 0x09 +#define RH_C_PORT_CONNECTION 0x10 +#define RH_C_PORT_ENABLE 0x11 +#define RH_C_PORT_SUSPEND 0x12 +#define RH_C_PORT_OVER_CURRENT 0x13 +#define RH_C_PORT_RESET 0x14 + +/* Hub features */ +#define RH_C_HUB_LOCAL_POWER 0x00 +#define RH_C_HUB_OVER_CURRENT 0x01 +#define RH_DEVICE_REMOTE_WAKEUP 0x00 +#define RH_ENDPOINT_STALL 0x01 + +/* Our Vendor Specific feature */ +#define RH_REMOVE_EP 0x00 + +#define RH_ACK 0x01 +#define RH_NACK 0x00 +#define RH_REQ_ERR -1 diff -Nur uml-2.5.44-1/drivers/usb/host/uml-hcd_user.c work-2.5.44-1/drivers/usb/host/uml-hcd_user.c --- uml-2.5.44-1/drivers/usb/host/uml-hcd_user.c 1969-12-31 19:00:00.000000000 -0500 +++ work-2.5.44-1/drivers/usb/host/uml-hcd_user.c 2002-10-21 10:38:01.000000000 -0400 @@ -0,0 +1,335 @@ +/* + * User Mode Linux USB HCD, Userspace part. + * + * Copyright 2001, Johan Verrept (Johan.Verrept@advalvas.be) + * massive tinkering by James McMechan james_mcmechan@hotmail.com + * + * Licensed under the GPL + * + * Credits: + * - Device sacnning code based on libusb-0.1.1, + * Johannes Erdfelt <jerdfelt@valinux.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include <strings.h> + +#include "uml-hcd_user.h" +#include "user_util.h" +#include "kern_util.h" +#include "user.h" +#include <string.h> +#include <unistd.h> + +#define UMUSB_DIRNAME "/proc/bus/usb" +#define KERN_ERR "<1>" + +/* 1 ms timeout */ + +/* consts */ +unsigned char *umusb_dirname = UMUSB_DIRNAME; + +/************ INTERFACE FUNCTIONS ***********/ + +static int umusb_is_hub(int fd) +{ + struct usbdevfs_getdriver driver; + int result; + + driver.interface = 0; + result = ioctl(fd, USBDEVFS_GETDRIVER, &driver); + if (result) { + printk(KERN_ERR "UMUSBu: umusb_is_hub failed fd = %d ret = %d" + " errno = %d \n", fd, result, errno); + return 0; + } + + printk(KERN_ERR "UMUSBu: getdriver %s\n", driver.driver); + return (strcmp(driver.driver,"hub") == 0); +} + +static void umusb_get_hub_ports(struct umusb_device * dev) +{ + struct usbdevfs_hub_portinfo *info; + int result; + struct usbdevfs_ioctl ioctrl; + + if (dev->hubdev == NULL) { + printk(KERN_ERR "UMUSBu: no device to get ports of\n"); + return; + } + + info = &dev->hubdev->portinfo; + + /* + * cache the data:only read when number of ports is 0 + */ + if (info->nports) + return; + + ioctrl.ioctl_code = USBDEVFS_HUB_PORTINFO; + ioctrl.data = info; + ioctrl.ifno = 0; + + result = ioctl(dev->fd, USBDEVFS_IOCTL, &ioctrl); + if (result < 0) { + printk(KERN_ERR "UMUSBu: get port info failed (%d).\n", errno); + memset(info, 0, sizeof(*info)); + return; + } + + /* clean status */ + memset(info->port, 0, sizeof(info->port)); + + return; +} + +int umusb_scan_bus(int bus_num) +{ + DIR *dir; + struct dirent *entry; + unsigned char path[22]; + unsigned char desc[64]; + struct umusb_device *udev; + int devnum, newdev = 0; + int fd, is_hub_flag, len, total_len; + + printk(KERN_ERR "UMUSBu: Scanning Bus (%d)...\n", bus_num); + len = snprintf(path, sizeof(path), "%s/%03d", umusb_dirname, + bus_num); + if (len >= sizeof(path)) { + printk(KERN_ERR "UMUSBu: path too long %d %s/%03d\n", + len, umusb_dirname, bus_num); + return -EINVAL; + } + + dir = opendir(path); + if (dir == NULL) + return -errno; + + while ((entry = readdir(dir)) != NULL) { + /* Skip anything starting with a . */ + if (entry->d_name[0] == '.') + continue; + + devnum = umusb_atoi(entry->d_name); + + udev = umusb_find_dev(bus_num, devnum); + if (udev) + continue; + + len = snprintf(path, sizeof(path), "%s/%03d/%03d", + umusb_dirname, bus_num, devnum); + if (len >= sizeof(path)) { + printk(KERN_ERR "UMUSBu: path too long %d " + "%s/%03d/%03d\n", len, + umusb_dirname, bus_num, devnum); + return -EINVAL; + } + fd = open(path, O_RDWR); + if (fd < 0) { + if (errno == -EPERM) + printk("<1> USB: permission denied on %s\n",path); + continue; + } + + /* determine length */ + len = read(fd, desc,sizeof(desc)); + if (len <= 0) { + printk(KERN_ERR "UMUSBu: unable to read %s",path); + close(fd); + continue; + } + + /* saving hub state so alloc can be done with one code path */ + is_hub_flag = umusb_is_hub(fd); + + total_len = len + sizeof(struct umusb_device); + + if (is_hub_flag) + total_len += sizeof(struct umusb_hub_device); + + udev = um_kmalloc(total_len); + if (udev == NULL) { + printk(KERN_ERR "UMUSBu: alloc udev failed"); + return -ENOMEM; + } + + memset(udev, 0, total_len); + + udev->devnum = devnum; + udev->fd = fd; + + udev->desc = (char *) &udev[1]; + memcpy(udev->desc,desc,len); + udev->desc_len = len; + + if (is_hub_flag) { + udev->hubdev = (struct umusb_hub_device *) &udev->desc[len]; + umusb_get_hub_ports(udev); + } + + printk(KERN_ERR "UMUSBu: New device found " + "(devnum = %d, udev = %p, fd = %d, hubctx = %p)\n", + devnum, udev, udev->fd, udev->hubdev); + + umusb_bus_add_dev(bus_num, udev); + newdev++; + } + + printk(KERN_ERR "UMUSBu: Bus scan complete, " + "bus %d has %d new\n", bus_num, newdev); + closedir(dir); + return 0; +} + +int umusb_scan_hcd(void) +{ + DIR *dir; + struct dirent *entry; + int bus_num, ret, count = 0; + + dir = opendir(umusb_dirname); + if (dir == NULL) { + printk(KERN_ERR "Sorry, cannot seem to find %s\n", + umusb_dirname); + return -EINVAL; + } + + printk(KERN_ERR "UMUSBu: Scanning system for busses (HCDs)\n"); + + /* We create a virtual UM HCD per real HCD */ + while ((entry = readdir(dir)) != NULL) { + /* Skip anything starting with a . */ + if (entry->d_name[0] == '.') + continue; + + ret = strspn(entry->d_name, "0123456789"); + if (ret != strlen(entry->d_name)) + continue; + + bus_num = umusb_atoi(entry->d_name); + + /* + * this allows rescan. unlikely, unless after an + * insmod in the parent kernel + */ + umusb_add_bus(bus_num); + count++; + } + + printk(KERN_ERR "UMUSBu: System contains %d busses\n", count); + closedir(dir); + return count; +} + + +int umusb_submit_urb_user(int fd, struct usbdevfs_urb *uurb) +{ + int ret = ioctl(fd, USBDEVFS_SUBMITURB, uurb); + if (ret < 0) { + printk(KERN_ERR "umusb_submit_urb_user failed, " + "fd = %d, errno = %d\n", fd, errno); + } + return ret; +} + +int umusb_unlink_urb_user(int fd, struct usbdevfs_urb *uurb) +{ + return ioctl(fd, USBDEVFS_DISCARDURB, uurb); +} + + +/************************************************** +** Hub Utilities +***************************************************/ +int umusb_check_hub_port_change(struct umusb_device *udev, unsigned long *bitmap) +{ + struct usbdevfs_hub_portinfo info, *current; + struct usbdevfs_ioctl ioctrl; + unsigned int ret, i, ports; + unsigned char *portmap; + + if (!udev || !udev->hubdev) + return -EINVAL; + + current = &udev->hubdev->portinfo; + ports = current->nports; + portmap = current->port; + + ioctrl.ioctl_code = USBDEVFS_HUB_PORTINFO; + ioctrl.data = &info; + ioctrl.ifno = 0; + + ret = ioctl(udev->fd, USBDEVFS_IOCTL, &ioctrl); + if (ret < 0) + return -errno; + + *bitmap = 0; + for (i = 0; i < ports; i++) + if (portmap[i] != info.port[i]) { + *bitmap |= (1 << i); + portmap[i] = info.port[i]; + } + + /* update recorded cstatus */ + udev->hubdev->cstatus |= *bitmap; + + return 0; +} + +int umusb_check_driver(int fd, int interface) +{ + struct usbdevfs_getdriver driver; + int ret; + + driver.interface = interface; + ret = ioctl(fd, USBDEVFS_GETDRIVER, &driver); + if (ret) { + printk(KERN_ERR "UMUSBk: umusb_check_driver failed (%d)\n", + ret); + /* + * we return 1 so hcd thinks this device is taken + * this is safer. + */ + return 1; + } + + return (driver.driver[0] > 0); +} + +/****************** INTERNAL FUNCTIONS *********************/ + +int umusb_interrupt_handler_user(int fd) +{ + int err; + struct usbdevfs_urb *uurb; + + err = ioctl(fd, USBDEVFS_REAPURBNDELAY, &uurb); + if (err < 0) { + if (err != -EAGAIN) + printk(KERN_ERR "UMUSBu: USBDEVFS_REAPURBNDELAY " + "failed (%d).\n", errno); + return err; + } + + return umusb_complete_urb(uurb); +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-file-style: "linux" + * End: + */ diff -Nur uml-2.5.44-1/drivers/usb/host/uml-hcd_user.h work-2.5.44-1/drivers/usb/host/uml-hcd_user.h --- uml-2.5.44-1/drivers/usb/host/uml-hcd_user.h 1969-12-31 19:00:00.000000000 -0500 +++ work-2.5.44-1/drivers/usb/host/uml-hcd_user.h 2002-10-21 10:38:01.000000000 -0400 @@ -0,0 +1,47 @@ + + +#undef UMUSB_USER_H_KERNEL + +#ifdef __KERNEL__ +#define UMUSB_USER_H_KERNEL +#undef __KERNEL__ +#endif + +#include <linux/usbdevice_fs.h> + +#ifdef UMUSB_USER_H_KERNEL +#define __KERNEL__ +#endif + + +struct umusb_hub_device { + struct umusb_hubctx *hubctx; + struct usbdevfs_hub_portinfo portinfo; + unsigned long cstatus; +}; + +struct umusb_device { + struct umusb_list { + struct umusb_list *next, *prev; + } list; + struct umusb_hub_device *hubdev; + struct umusb_bus *bus; + unsigned char *desc; + unsigned int desc_len; + int devnum; + int fd; +}; + +extern int umusb_scan_hcd(void); +extern int umusb_scan_bus(int bus_num); +extern int umusb_submit_urb_user(int fd, struct usbdevfs_urb *uurb); +extern int umusb_unlink_urb_user(int fd, struct usbdevfs_urb *uurb); +extern int umusb_interrupt_handler_user(int fd); +extern int umusb_check_driver(int fd, int interface); +extern int umusb_complete_urb(struct usbdevfs_urb *uurb); +extern int umusb_check_hub_port_change(struct umusb_device *udev, + unsigned long *bitmap); +extern long umusb_atoi(char *input); +extern void umusb_add_bus(int bus_num); +extern struct umusb_device *umusb_find_dev(int bus_num, int devnum); +extern void umusb_bus_add_dev(int bus_num, struct umusb_device *udev); diff -Nur uml-2.5.44-1/drivers/usb/host/uml-pci.c work-2.5.44-1/drivers/usb/host/uml-pci.c --- uml-2.5.44-1/drivers/usb/host/uml-pci.c 1969-12-31 19:00:00.000000000 -0500 +++ work-2.5.44-1/drivers/usb/host/uml-pci.c 2002-10-21 10:38:02.000000000 -0400 @@ -0,0 +1,545 @@ +#include <asm/io.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/module.h> + +/* + * Pool allocator ... wraps the pci_alloc_consistent page allocator, so + * small blocks are easily used by drivers for bus mastering controllers. + * This should probably be sharing the guts of the slab allocator. + */ + +struct pci_pool { /* the pool */ + struct list_head page_list; + spinlock_t lock; + size_t blocks_per_page; + size_t size; + struct pci_dev *dev; + size_t allocation; + char name [32]; + wait_queue_head_t waitq; + struct list_head pools; +}; + +struct pci_page { /* cacheable header for 'allocation' bytes */ + struct list_head page_list; + void *vaddr; + dma_addr_t dma; + unsigned in_use; + unsigned long bitmap [0]; +}; + +#define POOL_TIMEOUT_JIFFIES ((100 /* msec */ * HZ) / 1000) +#define POOL_POISON_BYTE 0xa7 + +static spinlock_t pools_lock = SPIN_LOCK_UNLOCKED; + +static ssize_t +show_pools (struct device *dev, char *buf, size_t count, loff_t off) +{ + struct pci_dev *pdev; + unsigned long flags; + unsigned temp, size; + char *next; + struct list_head *i, *j; + + if (off != 0) + return 0; + + pdev = container_of (dev, struct pci_dev, dev); + next = buf; + size = count; + + temp = snprintf (next, size, "poolinfo - 0.1\n"); + size -= temp; + next += temp; + + spin_lock_irqsave (&pools_lock, flags); + list_for_each (i, &pdev->pools) { + struct pci_pool *pool; + unsigned pages = 0, blocks = 0; + + pool = list_entry (i, struct pci_pool, pools); + + list_for_each (j, &pool->page_list) { + struct pci_page *page; + + page = list_entry (j, struct pci_page, page_list); + pages++; + blocks += page->in_use; + } + + /* per-pool info, no real statistics yet */ + temp = snprintf (next, size, "%-16s %4u %4u %4u %2u\n", + pool->name, + blocks, pages * pool->blocks_per_page, + pool->size, pages); + size -= temp; + next += temp; + } + spin_unlock_irqrestore (&pools_lock, flags); + + return count - size; +} +static DEVICE_ATTR (pools, S_IRUGO, show_pools, NULL); + +/** + * pci_pool_create - Creates a pool of pci consistent memory blocks, for dma. + * @name: name of pool, for diagnostics + * @pdev: pci device that will be doing the DMA + * @size: size of the blocks in this pool. + * @align: alignment requirement for blocks; must be a power of two + * @allocation: returned blocks won't cross this boundary (or zero) + * @mem_flags: SLAB_* flags. + * + * Returns a pci allocation pool with the requested characteristics, or + * null if one can't be created. Given one of these pools, pci_pool_alloc() + * may be used to allocate memory. Such memory will all have "consistent" + * DMA mappings, accessible by the device and its driver without using + * cache flushing primitives. The actual size of blocks allocated may be + * larger than requested because of alignment. + * + * If allocation is nonzero, objects returned from pci_pool_alloc() won't + * cross that size boundary. This is useful for devices which have + * addressing restrictions on individual DMA transfers, such as not crossing + * boundaries of 4KBytes. + */ +struct pci_pool * +pci_pool_create (const char *name, struct pci_dev *pdev, + size_t size, size_t align, size_t allocation, int mem_flags) +{ + struct pci_pool *retval; + unsigned long flags; + + if (align == 0) + align = 1; + if (size == 0) + return 0; + else if (size < align) + size = align; + else if ((size % align) != 0) { + size += align + 1; + size &= ~(align - 1); + } + + if (allocation == 0) { + if (PAGE_SIZE < size) + allocation = size; + else + allocation = PAGE_SIZE; + // FIXME: round up for less fragmentation + } else if (allocation < size) + return 0; + + if (!(retval = kmalloc (sizeof *retval, mem_flags))) + return retval; + + strncpy (retval->name, name, sizeof retval->name); + retval->name [sizeof retval->name - 1] = 0; + + retval->dev = pdev; + + if (pdev) { + spin_lock_irqsave (&pools_lock, flags); + /* note: not currently insisting "name" be unique */ + if (list_empty (&pdev->pools)) + device_create_file (&pdev->dev, &dev_attr_pools); + list_add (&retval->pools, &pdev->pools); + spin_unlock_irqrestore (&pools_lock, flags); + } else + INIT_LIST_HEAD (&retval->pools); + + INIT_LIST_HEAD (&retval->page_list); + spin_lock_init (&retval->lock); + retval->size = size; + retval->allocation = allocation; + retval->blocks_per_page = allocation / size; + init_waitqueue_head (&retval->waitq); + + return retval; +} + + +static struct pci_page * +pool_alloc_page (struct pci_pool *pool, int mem_flags) +{ + struct pci_page *page; + int mapsize; + + mapsize = pool->blocks_per_page; + mapsize = (mapsize + BITS_PER_LONG - 1) / BITS_PER_LONG; + mapsize *= sizeof (long); + + page = (struct pci_page *) kmalloc (mapsize + sizeof *page, mem_flags); + if (!page) + return 0; + page->vaddr = pci_alloc_consistent (pool->dev, + pool->allocation, + &page->dma); + if (page->vaddr) { + memset (page->bitmap, 0xff, mapsize); // bit set == free +#ifdef CONFIG_DEBUG_SLAB + memset (page->vaddr, POOL_POISON_BYTE, pool->allocation); +#endif + list_add (&page->page_list, &pool->page_list); + page->in_use = 0; + } else { + kfree (page); + page = 0; + } + return page; +} + + +static inline int +is_page_busy (int blocks, unsigned long *bitmap) +{ + while (blocks > 0) { + if (*bitmap++ != ~0UL) + return 1; + blocks -= BITS_PER_LONG; + } + return 0; +} + +static void +pool_free_page (struct pci_pool *pool, struct pci_page *page) +{ + dma_addr_t dma = page->dma; + +#ifdef CONFIG_DEBUG_SLAB + memset (page->vaddr, POOL_POISON_BYTE, pool->allocation); +#endif + pci_free_consistent (pool->dev, pool->allocation, page->vaddr, dma); + list_del (&page->page_list); + kfree (page); +} + + +/** + * pci_pool_destroy - destroys a pool of pci memory blocks. + * @pool: pci pool that will be destroyed + * + * Caller guarantees that no more memory from the pool is in use, + * and that nothing will try to use the pool after this call. + */ +void +pci_pool_destroy (struct pci_pool *pool) +{ + unsigned long flags; + + spin_lock_irqsave (&pool->lock, flags); + while (!list_empty (&pool->page_list)) { + struct pci_page *page; + page = list_entry (pool->page_list.next, + struct pci_page, page_list); + if (is_page_busy (pool->blocks_per_page, page->bitmap)) { + printk (KERN_ERR "pci_pool_destroy %s/%s, %p busy\n", + pool->dev ? pool->dev->slot_name : NULL, + pool->name, page->vaddr); + /* leak the still-in-use consistent memory */ + list_del (&page->page_list); + kfree (page); + } else + pool_free_page (pool, page); + } + + spin_lock (&pools_lock); + list_del (&pool->pools); + if (pool->dev && list_empty (&pool->dev->pools)) + device_remove_file (&pool->dev->dev, &dev_attr_pools); + spin_unlock (&pools_lock); + + spin_unlock_irqrestore (&pool->lock, flags); + kfree (pool); +} + + +/** + * pci_pool_alloc - get a block of consistent memory + * @pool: pci pool that will produce the block + * @mem_flags: SLAB_KERNEL or SLAB_ATOMIC + * @handle: pointer to dma address of block + * + * This returns the kernel virtual address of a currently unused block, + * and reports its dma address through the handle. + * If such a memory block can't be allocated, null is returned. + */ +void * +pci_pool_alloc (struct pci_pool *pool, int mem_flags, dma_addr_t *handle) +{ + unsigned long flags; + struct list_head *entry; + struct pci_page *page; + int map, block; + size_t offset; + void *retval; + +restart: + spin_lock_irqsave (&pool->lock, flags); + list_for_each (entry, &pool->page_list) { + int i; + page = list_entry (entry, struct pci_page, page_list); + /* only cachable accesses here ... */ + for (map = 0, i = 0; + i < pool->blocks_per_page; + i += BITS_PER_LONG, map++) { + if (page->bitmap [map] == 0) + continue; + block = ffz (~ page->bitmap [map]); + if ((i + block) < pool->blocks_per_page) { + clear_bit (block, &page->bitmap [map]); + offset = (BITS_PER_LONG * map) + block; + offset *= pool->size; + goto ready; + } + } + } + if (!(page = pool_alloc_page (pool, mem_flags))) { + if (mem_flags == SLAB_KERNEL) { + DECLARE_WAITQUEUE (wait, current); + + current->state = TASK_INTERRUPTIBLE; + add_wait_queue (&pool->waitq, &wait); + spin_unlock_irqrestore (&pool->lock, flags); + + schedule_timeout (POOL_TIMEOUT_JIFFIES); + + current->state = TASK_RUNNING; + remove_wait_queue (&pool->waitq, &wait); + goto restart; + } + retval = 0; + goto done; + } + + clear_bit (0, &page->bitmap [0]); + offset = 0; +ready: + page->in_use++; + retval = offset + page->vaddr; + *handle = offset + page->dma; +done: + spin_unlock_irqrestore (&pool->lock, flags); + return retval; +} + + +static struct pci_page * +pool_find_page (struct pci_pool *pool, dma_addr_t dma) +{ + unsigned long flags; + struct list_head *entry; + struct pci_page *page; + + spin_lock_irqsave (&pool->lock, flags); + list_for_each (entry, &pool->page_list) { + page = list_entry (entry, struct pci_page, page_list); + if (dma < page->dma) + continue; + if (dma < (page->dma + pool->allocation)) + goto done; + } + page = 0; +done: + spin_unlock_irqrestore (&pool->lock, flags); + return page; +} + + +/** + * pci_pool_free - put block back into pci pool + * @pool: the pci pool holding the block + * @vaddr: virtual address of block + * @dma: dma address of block + * + * Caller promises neither device nor driver will again touch this block + * unless it is first re-allocated. + */ +void +pci_pool_free (struct pci_pool *pool, void *vaddr, dma_addr_t dma) +{ + struct pci_page *page; + unsigned long flags; + int map, block; + + if ((page = pool_find_page (pool, dma)) == 0) { + printk (KERN_ERR "pci_pool_free %s/%s, %p/%lx (bad dma)\n", + pool->dev ? pool->dev->slot_name : NULL, + pool->name, vaddr, (unsigned long) dma); + return; + } + + block = dma - page->dma; + block /= pool->size; + map = block / BITS_PER_LONG; + block %= BITS_PER_LONG; + +#ifdef CONFIG_DEBUG_SLAB + if (((dma - page->dma) + (void *)page->vaddr) != vaddr) { + printk (KERN_ERR "pci_pool_free %s/%s, %p (bad vaddr)/%Lx\n", + pool->dev ? pool->dev->slot_name : NULL, + pool->name, vaddr, (unsigned long long) dma); + return; + } + if (page->bitmap [map] & (1UL << block)) { + printk (KERN_ERR "pci_pool_free %s/%s, dma %Lx already free\n", + pool->dev ? pool->dev->slot_name : NULL, + pool->name, (unsigned long long)dma); + return; + } + memset (vaddr, POOL_POISON_BYTE, pool->size); +#endif + + spin_lock_irqsave (&pool->lock, flags); + page->in_use--; + set_bit (block, &page->bitmap [map]); + if (waitqueue_active (&pool->waitq)) + wake_up (&pool->waitq); + /* + * Resist a temptation to do + * if (!is_page_busy(bpp, page->bitmap)) pool_free_page(pool, page); + * it is not interrupt safe. Better have empty pages hang around. + */ + spin_unlock_irqrestore (&pool->lock, flags); +} + +void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size, + dma_addr_t *dma_handle) +{ + void *ret; + int gfp = GFP_ATOMIC; + + if (hwdev == NULL || ((u32)hwdev->dma_mask != 0xffffffff)) + gfp |= GFP_DMA; + ret = (void *)__get_free_pages(gfp, get_order(size)); + + if (ret != NULL) { + memset(ret, 0, size); + *dma_handle = virt_to_phys(ret); + } + return ret; +} + +void pci_free_consistent(struct pci_dev *hwdev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + free_pages((unsigned long)vaddr, get_order(size)); +} + +/* Map a single buffer of the indicated size for DMA in streaming mode. + * The 32-bit bus address to use is returned. + * + * Once the device is given the dma address, the device owns this memory + * until either pci_unmap_single or pci_dma_sync_single is performed. + */ +dma_addr_t pci_map_single(struct pci_dev *hwdev, void *ptr, + size_t size, int direction) +{ + BUG_ON(direction == PCI_DMA_NONE); + /* this should have no effect on ARCH=um */ + //flush_write_buffers(); + return virt_to_phys(ptr); +} + +/* Unmap a single streaming mode DMA translation. The dma_addr and size + * must match what was provided for in a previous pci_map_single call. All + * other usages are undefined. + * + * After this call, reads by the cpu to the buffer are guarenteed to see + * whatever the device wrote there. + */ +void pci_unmap_single(struct pci_dev *hwdev, dma_addr_t dma_addr, + size_t size, int direction) +{ + BUG_ON(direction == PCI_DMA_NONE); + /* Nothing to do */ +} + +/* Make physical memory consistent for a single + * streaming mode DMA translation after a transfer. + * + * If you perform a pci_map_single() but wish to interrogate the + * buffer using the cpu, yet do not wish to teardown the PCI dma + * mapping, you must call this function before doing so. At the + * next point you give the PCI dma address back to the card, the + * device again owns the buffer. + */ +void pci_dma_sync_single(struct pci_dev *hwdev, dma_addr_t dma_handle, + size_t size, int direction) +{ + BUG_ON(direction == PCI_DMA_NONE); + /* this should have no effect on ARCH=um */ + //flush_write_buffers(); +} + +/* Map a set of buffers described by scatterlist in streaming + * mode for DMA. This is the scather-gather version of the + * above pci_map_single interface. Here the scatter gather list + * elements are each tagged with the appropriate dma address + * and length. They are obtained via sg_dma_{address,length}(SG). + * + * NOTE: An implementation may be able to use a smaller number of + * DMA address/length pairs than there are SG table elements. + * (for example via virtual mapping capabilities) + * The routine returns the number of addr/length pairs actually + * used, at most nents. + * + * Device ownership issues as mentioned above for pci_map_single are + * the same here. + */ +int pci_map_sg(struct pci_dev *hwdev, struct scatterlist *sg, + int nents, int direction) +{ + int i; + + BUG_ON(direction == PCI_DMA_NONE); + + for (i = 0; i < nents; i++ ) { + BUG_ON(!sg[i].page); + sg[i].dma_address = page_to_phys(sg[i].page) + sg[i].offset; + } + + /* this should have no effect on ARCH=um */ + //flush_write_buffers(); + return nents; +} + +/* Unmap a set of streaming mode DMA translations. + * Again, cpu read rules concerning calls here are the same as for + * pci_unmap_single() above. + */ +void pci_unmap_sg(struct pci_dev *hwdev, struct scatterlist *sg, + int nents, int direction) +{ + BUG_ON(direction == PCI_DMA_NONE); + /* Nothing to do */ +} + +/* Make physical memory consistent for a set of streaming + * mode DMA translations after a transfer. + * + * The same as pci_dma_sync_single but for a scatter-gather list, + * same rules and usage. + */ +void pci_dma_sync_sg(struct pci_dev *hwdev, + struct scatterlist *sg, + int nelems, int direction) +{ + BUG_ON(direction == PCI_DMA_NONE); + /* this should have no effect on ARCH=um */ + //flush_write_buffers(); +} + +EXPORT_SYMBOL (pci_pool_create); +EXPORT_SYMBOL (pci_pool_destroy); +EXPORT_SYMBOL (pci_pool_alloc); +EXPORT_SYMBOL (pci_pool_free); +EXPORT_SYMBOL (pci_alloc_consistent); +EXPORT_SYMBOL (pci_free_consistent); +EXPORT_SYMBOL (pci_map_single); +EXPORT_SYMBOL (pci_unmap_single); +EXPORT_SYMBOL (pci_dma_sync_single); +EXPORT_SYMBOL (pci_map_sg); +EXPORT_SYMBOL (pci_unmap_sg); +EXPORT_SYMBOL (pci_dma_sync_sg); diff -Nur uml-2.5.44-1/drivers/usb/Makefile work-2.5.44-1/drivers/usb/Makefile --- uml-2.5.44-1/drivers/usb/Makefile 2002-10-08 16:19:18.000000000 -0400 +++ work-2.5.44-1/drivers/usb/Makefile 2002-10-21 10:38:02.000000000 -0400 @@ -9,12 +9,15 @@ obj-$(CONFIG_USB_EHCI_HCD) += host/ obj-$(CONFIG_USB_OHCI_HCD) += host/ obj-$(CONFIG_USB_OHCI) += host/ -obj-$(CONFIG_USB_OHCI_SA1111) += host/ -obj-$(CONFIG_USB_SL811HS) += host/ -obj-$(CONFIG_USB_UHCI_ALT) += host/ obj-$(CONFIG_USB_UHCI_HCD_ALT) += host/ -obj-$(CONFIG_USB_UHCI_HCD) += host/ -obj-$(CONFIG_USB_UHCI) += host/ + +# +# ARCH specific host controllers +# + +obj-$(CONFIG_USB_SL811HS) += host/ +obj-$(CONFIG_USB_UML_HCD) += host/ +obj-$(CONFIG_USB_UML_PCI) += host/ obj-$(CONFIG_USB_ACM) += class/ obj-$(CONFIG_USB_AUDIO) += class/ diff -Nur uml-2.5.44-1/include/asm-um/pci.h work-2.5.44-1/include/asm-um/pci.h --- uml-2.5.44-1/include/asm-um/pci.h 2002-09-16 22:35:20.000000000 -0400 +++ work-2.5.44-1/include/asm-um/pci.h 2002-10-21 10:38:02.000000000 -0400 @@ -1,6 +1,101 @@ #ifndef __UM_PCI_H #define __UM_PCI_H +#include "arch/scatterlist.h" + +/* The PCI address space does equal the physical memory + * address space. The networking and block device layers use + * this boolean for bounce buffer decisions. + */ #define PCI_DMA_BUS_IS_PHYS (1) +#define sg_dma_address(sg) ((sg)->dma_address) +#define sg_dma_len(sg) ((sg)->length) + +/* Allocate and map kernel buffer using consistent mode DMA for a device. + * hwdev should be valid struct pci_dev pointer for PCI devices, + * NULL for PCI-like buses (ISA, EISA). + * Returns non-NULL cpu-view pointer to the buffer if successful and + * sets *dma_addrp to the pci side dma address as well, else *dma_addrp + * is undefined. + */ +extern void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size, + dma_addr_t *dma_handle); + +/* Free and unmap a consistent DMA buffer. + * cpu_addr is what was returned from pci_alloc_consistent, + * size must be the same as what as passed into pci_alloc_consistent, + * and likewise dma_addr must be the same as what *dma_addrp was set to. + * + * References to the memory and mappings associated with cpu_addr/dma_addr + * past this call are illegal. + */ +extern void pci_free_consistent(struct pci_dev *hwdev, size_t size, + void *vaddr, dma_addr_t dma_handle); + +/* Map a single buffer of the indicated size for DMA in streaming mode. + * The 32-bit bus address to use is returned. + * + * Once the device is given the dma address, the device owns this memory + * until either pci_unmap_single or pci_dma_sync_single is performed. + */ +extern dma_addr_t pci_map_single(struct pci_dev *hwdev, void *ptr, + size_t size, int direction); + +/* Unmap a single streaming mode DMA translation. The dma_addr and size + * must match what was provided for in a previous pci_map_single call. All + * other usages are undefined. + * + * After this call, reads by the cpu to the buffer are guarenteed to see + * whatever the device wrote there. + */ +extern void pci_unmap_single(struct pci_dev *hwdev, dma_addr_t dma_addr, + size_t size, int direction); + +/* Make physical memory consistent for a single + * streaming mode DMA translation after a transfer. + * + * If you perform a pci_map_single() but wish to interrogate the + * buffer using the cpu, yet do not wish to teardown the PCI dma + * mapping, you must call this function before doing so. At the + * next point you give the PCI dma address back to the card, the + * device again owns the buffer. + */ +extern void pci_dma_sync_single(struct pci_dev *hwdev, dma_addr_t dma_handle, + size_t size, int direction); + +/* Map a set of buffers described by scatterlist in streaming + * mode for DMA. This is the scather-gather version of the + * above pci_map_single interface. Here the scatter gather list + * elements are each tagged with the appropriate dma address + * and length. They are obtained via sg_dma_{address,length}(SG). + * + * NOTE: An implementation may be able to use a smaller number of + * DMA address/length pairs than there are SG table elements. + * (for example via virtual mapping capabilities) + * The routine returns the number of addr/length pairs actually + * used, at most nents. + * + * Device ownership issues as mentioned above for pci_map_single are + * the same here. + */ +extern int pci_map_sg(struct pci_dev *hwdev, struct scatterlist *sg, + int nents, int direction); + +/* Unmap a set of streaming mode DMA translations. + * Again, cpu read rules concerning calls here are the same as for + * pci_unmap_single() above. + */ +extern void pci_unmap_sg(struct pci_dev *hwdev, struct scatterlist *sg, + int nents, int direction); + +/* Make physical memory consistent for a set of streaming + * mode DMA translations after a transfer. + * + * The same as pci_dma_sync_single but for a scatter-gather list, + * same rules and usage. + */ +extern void pci_dma_sync_sg(struct pci_dev *hwdev, + struct scatterlist *sg, + int nelems, int direction); #endif