[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [RFC][PATCH 3/4] PVUSB: backend driver
This patch adds the PVUSB backendend driver. Signed-off-by: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbback/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbback/Makefile Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,4 @@ +obj-$(CONFIG_XEN_USB_BACKEND) := usbbk.o + +usbbk-y := usbstub.o xenbus.o interface.o usbback.o + diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbback/interface.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbback/interface.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,208 @@ +/* + * interface.c + * + * Xen USB backend interface management. + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * or, + * + * When distributed separately from the Linux kernel or incorporated into + * other software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "usbback.h" + +static LIST_HEAD(usbif_list); +static DEFINE_SPINLOCK(usbif_list_lock); + +usbif_t *find_usbif(int dom_id, int dev_id) +{ + usbif_t *usbif; + int found = 0; + unsigned long flags; + + spin_lock_irqsave(&usbif_list_lock, flags); + list_for_each_entry(usbif, &usbif_list, usbif_list) { + if (usbif->domid == dom_id + && usbif->handle == dev_id) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&usbif_list_lock, flags); + + if (found) + return usbif; + + return NULL; +} + +usbif_t *usbif_alloc(domid_t domid, unsigned int handle) +{ + usbif_t *usbif; + unsigned long flags; + int i; + + usbif = kzalloc(sizeof(usbif_t), GFP_KERNEL); + if (!usbif) + return NULL; + + usbif->domid = domid; + usbif->handle = handle; + spin_lock_init(&usbif->ring_lock); + atomic_set(&usbif->refcnt, 0); + init_waitqueue_head(&usbif->wq); + init_waitqueue_head(&usbif->waiting_to_free); + spin_lock_init(&usbif->plug_lock); + INIT_LIST_HEAD(&usbif->plugged_devices); + spin_lock_init(&usbif->addr_lock); + for (i = 0; i < USB_DEV_ADDR_SIZE; i++) { + usbif->addr_table[i] = NULL; + } + + spin_lock_irqsave(&usbif_list_lock, flags); + list_add(&usbif->usbif_list, &usbif_list); + spin_unlock_irqrestore(&usbif_list_lock, flags); + + return usbif; +} + +static int map_frontend_page(usbif_t *usbif, unsigned long shared_page) +{ + struct gnttab_map_grant_ref op; + + gnttab_set_map_op(&op, (unsigned long)usbif->ring_area->addr, + GNTMAP_host_map, shared_page, usbif->domid); + + if (HYPERVISOR_grant_table_op(GNTTABOP_map_grant_ref, &op, 1)) + BUG(); + + if (op.status) { + printk(KERN_ERR "grant table operation failure\n"); + return op.status; + } + + usbif->shmem_ref = shared_page; + usbif->shmem_handle = op.handle; + + return 0; +} + +static void unmap_frontend_page(usbif_t *usbif) +{ + struct gnttab_unmap_grant_ref op; + + gnttab_set_unmap_op(&op, (unsigned long)usbif->ring_area->addr, + GNTMAP_host_map, usbif->shmem_handle); + + if (HYPERVISOR_grant_table_op(GNTTABOP_unmap_grant_ref, &op, 1)) + BUG(); +} + +int usbif_map(usbif_t *usbif, unsigned long shared_page, unsigned int evtchn) +{ + int err; + usbif_sring_t *sring; + + if (usbif->irq) + return 0; + + if ((usbif->ring_area = alloc_vm_area(PAGE_SIZE)) == NULL) + return -ENOMEM; + + err = map_frontend_page(usbif, shared_page); + if (err) { + free_vm_area(usbif->ring_area); + return err; + } + + sring = (usbif_sring_t *) usbif->ring_area->addr; + BACK_RING_INIT(&usbif->ring, sring, PAGE_SIZE); + + err = bind_interdomain_evtchn_to_irqhandler( + usbif->domid, evtchn, usbbk_be_int, 0, "usbif-backend", usbif); + if (err < 0) + { + unmap_frontend_page(usbif); + free_vm_area(usbif->ring_area); + usbif->ring.sring = NULL; + return err; + } + usbif->irq = err; + + return 0; +} + +void usbif_disconnect(usbif_t *usbif) +{ + struct usbstub *stub, *tmp; + unsigned long flags; + + if (usbif->xenusbd) { + kthread_stop(usbif->xenusbd); + usbif->xenusbd = NULL; + } + + spin_lock_irqsave(&usbif->plug_lock, flags); + list_for_each_entry_safe(stub, tmp, &usbif->plugged_devices, plugged_list) { + usbbk_unlink_urbs(stub); + detach_device_without_lock(usbif, stub); + } + spin_unlock_irqrestore(&usbif->plug_lock, flags); + + wait_event(usbif->waiting_to_free, atomic_read(&usbif->refcnt) == 0); + + if (usbif->irq) { + unbind_from_irqhandler(usbif->irq, usbif); + usbif->irq = 0; + } + + if (usbif->ring.sring) { + unmap_frontend_page(usbif); + free_vm_area(usbif->ring_area); + usbif->ring.sring = NULL; + } +} + +void usbif_free(usbif_t *usbif) +{ + unsigned long flags; + + spin_lock_irqsave(&usbif_list_lock, flags); + list_del(&usbif->usbif_list); + spin_unlock_irqrestore(&usbif_list_lock, flags); + kfree(usbif); +} diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbback/usbback.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbback/usbback.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,1075 @@ +/* + * usbback.c + * + * Xen USB backend driver + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * or, + * + * When distributed separately from the Linux kernel or incorporated into + * other software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <linux/mm.h> +#include <xen/balloon.h> +#include "usbback.h" + +#if 0 +#include "../../usb/core/hub.h" +#endif + +int usbif_reqs = USBIF_BACK_MAX_PENDING_REQS; +module_param_named(reqs, usbif_reqs, int, 0); +MODULE_PARM_DESC(reqs, "Number of usbback requests to allocate"); + +struct pending_req_segment { + uint16_t offset; + uint16_t length; +}; + +typedef struct { + usbif_t *usbif; + + uint16_t id; /* request id */ + + struct usbstub *stub; + struct list_head urb_list; + + /* urb */ + struct urb *urb; + void *buffer; + dma_addr_t transfer_dma; + struct usb_ctrlrequest *setup; + dma_addr_t setup_dma; + + /* request segments */ + uint16_t nr_buffer_segs; /* number of urb->transfer_buffer segments */ + uint16_t nr_extra_segs; /* number of iso_frame_desc segments (ISO) */ + struct pending_req_segment *seg; + + struct list_head free_list; +} pending_req_t; + +static pending_req_t *pending_reqs; +static struct list_head pending_free; +static DEFINE_SPINLOCK(pending_free_lock); +static DECLARE_WAIT_QUEUE_HEAD(pending_free_wq); + +#define USBBACK_INVALID_HANDLE (~0) + +static struct page **pending_pages; +static grant_handle_t *pending_grant_handles; + +static inline int vaddr_pagenr(pending_req_t *req, int seg) +{ + return (req - pending_reqs) * USBIF_MAX_SEGMENTS_PER_REQUEST + seg; +} + +static inline unsigned long vaddr(pending_req_t *req, int seg) +{ + unsigned long pfn = page_to_pfn(pending_pages[vaddr_pagenr(req, seg)]); + return (unsigned long)pfn_to_kaddr(pfn); +} + +#define pending_handle(_req, _seg) \ + (pending_grant_handles[vaddr_pagenr(_req, _seg)]) + +static pending_req_t* alloc_req(void) +{ + pending_req_t *req = NULL; + unsigned long flags; + + spin_lock_irqsave(&pending_free_lock, flags); + if (!list_empty(&pending_free)) { + req = list_entry(pending_free.next, pending_req_t, free_list); + list_del(&req->free_list); + } + spin_unlock_irqrestore(&pending_free_lock, flags); + return req; +} + +static void free_req(pending_req_t *req) +{ + unsigned long flags; + int was_empty; + + spin_lock_irqsave(&pending_free_lock, flags); + was_empty = list_empty(&pending_free); + list_add(&req->free_list, &pending_free); + spin_unlock_irqrestore(&pending_free_lock, flags); + if (was_empty) + wake_up(&pending_free_wq); +} + +static inline void add_req_to_submitting_list(struct usbstub *stub, pending_req_t *pending_req) +{ + unsigned long flags; + + spin_lock_irqsave(&stub->submitting_lock, flags); + list_add_tail(&pending_req->urb_list, &stub->submitting_list); + spin_unlock_irqrestore(&stub->submitting_lock, flags); +} + +static inline void remove_req_from_submitting_list(struct usbstub *stub, pending_req_t *pending_req) +{ + unsigned long flags; + + spin_lock_irqsave(&stub->submitting_lock, flags); + list_del_init(&pending_req->urb_list); + spin_unlock_irqrestore(&stub->submitting_lock, flags); +} + +void usbbk_unlink_urbs(struct usbstub *stub) +{ + pending_req_t *req, *tmp; + unsigned long flags; + + spin_lock_irqsave(&stub->submitting_lock, flags); + list_for_each_entry_safe(req, tmp, &stub->submitting_list, urb_list) { + usb_unlink_urb(req->urb); + } + spin_unlock_irqrestore(&stub->submitting_lock, flags); +} + +static void fast_flush_area(pending_req_t *pending_req) +{ + struct gnttab_unmap_grant_ref unmap[USBIF_MAX_SEGMENTS_PER_REQUEST]; + unsigned int i, nr_segs, invcount = 0; + grant_handle_t handle; + int ret; + + nr_segs = pending_req->nr_buffer_segs + pending_req->nr_extra_segs; + + if (nr_segs) { + for (i = 0; i < nr_segs; i++) { + handle = pending_handle(pending_req, i); + if (handle == USBBACK_INVALID_HANDLE) + continue; + gnttab_set_unmap_op(&unmap[invcount], vaddr(pending_req, i), + GNTMAP_host_map, handle); + pending_handle(pending_req, i) = USBBACK_INVALID_HANDLE; + invcount++; + } + + ret = HYPERVISOR_grant_table_op( + GNTTABOP_unmap_grant_ref, unmap, invcount); + BUG_ON(ret); + + kfree(pending_req->seg); + } + + return; +} + +static void copy_buff_to_pages(void *buff, pending_req_t *pending_req, + int start, int nr_pages) +{ + unsigned long copied = 0; + int i; + + for (i = start; i < start + nr_pages; i++) { + memcpy((void *) vaddr(pending_req, i) + pending_req->seg[i].offset, + buff + copied, + pending_req->seg[i].length); + copied += pending_req->seg[i].length; + } +} + +static void copy_pages_to_buff(void *buff, pending_req_t *pending_req, + int start, int nr_pages) +{ + unsigned long copied = 0; + int i; + + for (i = start; i < start + nr_pages; i++) { + memcpy(buff + copied, + (void *) vaddr(pending_req, i) + pending_req->seg[i].offset, + pending_req->seg[i].length); + copied += pending_req->seg[i].length; + } +} + +static int usbbk_alloc_urb(usbif_request_t *req, pending_req_t *pending_req) +{ + int ret; + + if (usb_pipeisoc(req->pipe)) + pending_req->urb = usb_alloc_urb(req->u.isoc.number_of_packets, GFP_KERNEL); + else + pending_req->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pending_req->urb) { + printk(KERN_ERR "usbback: can't alloc urb\n"); + ret = -ENOMEM; + goto fail; + } + + if (req->buffer_length) { + pending_req->buffer = usb_buffer_alloc(pending_req->stub->udev, + req->buffer_length, GFP_KERNEL, + &pending_req->transfer_dma); + if (!pending_req->buffer) { + printk(KERN_ERR "usbback: can't alloc urb buffer\n"); + ret = -ENOMEM; + goto fail_free_urb; + } + } + + if (usb_pipecontrol(req->pipe)) { + pending_req->setup = usb_buffer_alloc(pending_req->stub->udev, + sizeof(struct usb_ctrlrequest), GFP_KERNEL, + &pending_req->setup_dma); + if (!pending_req->setup) { + printk(KERN_ERR "usbback: can't alloc usb_ctrlrequest\n"); + ret = -ENOMEM; + goto fail_free_buffer; + } + } + + return 0; + +fail_free_buffer: + if (req->buffer_length) + usb_buffer_free(pending_req->stub->udev, req->buffer_length, + pending_req->buffer, pending_req->transfer_dma); +fail_free_urb: + usb_free_urb(pending_req->urb); +fail: + return ret; +} + +static void usbbk_free_urb(struct urb *urb) +{ + if (usb_pipecontrol(urb->pipe)) + usb_buffer_free(urb->dev, sizeof(struct usb_ctrlrequest), + urb->setup_packet, urb->setup_dma); + if (urb->transfer_buffer_length) + usb_buffer_free(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + barrier(); + usb_free_urb(urb); +} + +static void usbbk_notify_work(usbif_t *usbif) +{ + usbif->waiting_reqs = 1; + wake_up(&usbif->wq); +} + +irqreturn_t usbbk_be_int(int irq, void *dev_id, struct pt_regs *regs) +{ + usbbk_notify_work(dev_id); + return IRQ_HANDLED; +} + +static void usbbk_do_response(pending_req_t *pending_req, int32_t status, + int32_t actual_length, int32_t error_count, uint16_t start_frame) +{ + usbif_t *usbif = pending_req->usbif; + usbif_response_t *ring_res; + unsigned long flags; + int notify; + + spin_lock_irqsave(&usbif->ring_lock, flags); + ring_res = RING_GET_RESPONSE(&usbif->ring, usbif->ring.rsp_prod_pvt); + ring_res->id = pending_req->id; + ring_res->status = status; + ring_res->actual_length = actual_length; + ring_res->error_count = error_count; + ring_res->start_frame = start_frame; + usbif->ring.rsp_prod_pvt++; + barrier(); + RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&usbif->ring, notify); + spin_unlock_irqrestore(&usbif->ring_lock, flags); + + if (notify) + notify_remote_via_irq(usbif->irq); +} + +static void usbbk_urb_complete(struct urb *urb, struct pt_regs *regs) +{ + pending_req_t *pending_req = (pending_req_t *)urb->context; + + if (usb_pipein(urb->pipe) && urb->status == 0 && urb->actual_length > 0) + copy_buff_to_pages(pending_req->buffer, pending_req, + 0, pending_req->nr_buffer_segs); + + if (usb_pipeisoc(urb->pipe)) + copy_buff_to_pages(&urb->iso_frame_desc[0], pending_req, + pending_req->nr_buffer_segs, pending_req->nr_extra_segs); + + barrier(); + + fast_flush_area(pending_req); + + usbbk_do_response(pending_req, urb->status, urb->actual_length, + urb->error_count, urb->start_frame); + + remove_req_from_submitting_list(pending_req->stub, pending_req); + + barrier(); + usbbk_free_urb(urb); + usbif_put(pending_req->usbif); + free_req(pending_req); +} + +static int usbbk_gnttab_map(usbif_t *usbif, + usbif_request_t *req, pending_req_t *pending_req) +{ + int i, ret; + unsigned int nr_segs; + uint32_t flags; + struct gnttab_map_grant_ref map[USBIF_MAX_SEGMENTS_PER_REQUEST]; + + nr_segs = pending_req->nr_buffer_segs + pending_req->nr_extra_segs; + + if (nr_segs > USBIF_MAX_SEGMENTS_PER_REQUEST) { + printk(KERN_ERR "Bad number of segments in request\n"); + ret = -EINVAL; + goto fail; + } + + if (nr_segs) { + pending_req->seg = kmalloc(sizeof(struct pending_req_segment) + * nr_segs, GFP_KERNEL); + if (!pending_req->seg) { + ret = -ENOMEM; + goto fail; + } + + if (pending_req->nr_buffer_segs) { + flags = GNTMAP_host_map; + if (usb_pipeout(req->pipe)) + flags |= GNTMAP_readonly; + for (i = 0; i < pending_req->nr_buffer_segs; i++) + gnttab_set_map_op(&map[i], vaddr( + pending_req, i), flags, + req->seg[i].gref, + usbif->domid); + } + + if (pending_req->nr_extra_segs) { + flags = GNTMAP_host_map; + for (i = req->nr_buffer_segs; i < nr_segs; i++) + gnttab_set_map_op(&map[i], vaddr( + pending_req, i), flags, + req->seg[i].gref, + usbif->domid); + } + + ret = HYPERVISOR_grant_table_op(GNTTABOP_map_grant_ref, + map, nr_segs); + BUG_ON(ret); + + for (i = 0; i < nr_segs; i++) { + if (unlikely(map[i].status != 0)) { + printk(KERN_ERR "usbback: invalid buffer -- could not remap it\n"); + map[i].handle = USBBACK_INVALID_HANDLE; + ret |= 1; + } + + pending_handle(pending_req, i) = map[i].handle; + + if (ret) + continue; + + set_phys_to_machine(__pa(vaddr( + pending_req, i)) >> PAGE_SHIFT, + FOREIGN_FRAME(map[i].dev_bus_addr >> PAGE_SHIFT)); + + pending_req->seg[i].offset = req->seg[i].offset; + pending_req->seg[i].length = req->seg[i].length; + + barrier(); + + if (pending_req->seg[i].offset >= PAGE_SIZE || + pending_req->seg[i].length > PAGE_SIZE || + pending_req->seg[i].offset + pending_req->seg[i].length > PAGE_SIZE) + ret |= 1; + } + + if (ret) + goto fail_flush; + } + + return 0; + +fail_flush: + fast_flush_area(pending_req); + ret = -ENOMEM; + +fail: + return ret; +} + +static void usbbk_init_urb(usbif_request_t *req, pending_req_t *pending_req) +{ + unsigned int pipe; + struct usb_device *udev = pending_req->stub->udev; + struct urb *urb = pending_req->urb; + + switch (usb_pipetype(req->pipe)) { + case PIPE_ISOCHRONOUS: + if (usb_pipein(req->pipe)) + pipe = usb_rcvisocpipe(udev, usb_pipeendpoint(req->pipe)); + else + pipe = usb_sndisocpipe(udev, usb_pipeendpoint(req->pipe)); + + urb->dev = udev; + urb->pipe = pipe; + urb->transfer_flags = req->transfer_flags; + urb->transfer_flags |= URB_ISO_ASAP; + urb->transfer_buffer = pending_req->buffer; + urb->transfer_buffer_length = req->buffer_length; + urb->complete = usbbk_urb_complete; + urb->context = pending_req; + urb->interval = req->u.isoc.interval; + urb->start_frame = req->u.isoc.start_frame; + urb->number_of_packets = req->u.isoc.number_of_packets; + + break; + case PIPE_INTERRUPT: + if (usb_pipein(req->pipe)) + pipe = usb_rcvintpipe(udev, usb_pipeendpoint(req->pipe)); + else + pipe = usb_sndintpipe(udev, usb_pipeendpoint(req->pipe)); + + usb_fill_int_urb(urb, udev, pipe, + pending_req->buffer, req->buffer_length, + usbbk_urb_complete, + pending_req, req->u.intr.interval); + urb->transfer_flags = req->transfer_flags; + + break; + case PIPE_CONTROL: + if (usb_pipein(req->pipe)) + pipe = usb_rcvctrlpipe(udev, 0); + else + pipe = usb_sndctrlpipe(udev, 0); + + usb_fill_control_urb(urb, udev, pipe, + (unsigned char *) pending_req->setup, + pending_req->buffer, req->buffer_length, + usbbk_urb_complete, pending_req); + memcpy(pending_req->setup, req->u.ctrl, 8); + urb->setup_dma = pending_req->setup_dma; + urb->transfer_flags = req->transfer_flags; + urb->transfer_flags |= URB_NO_SETUP_DMA_MAP; + + break; + case PIPE_BULK: + if (usb_pipein(req->pipe)) + pipe = usb_rcvbulkpipe(udev, usb_pipeendpoint(req->pipe)); + else + pipe = usb_sndbulkpipe(udev, usb_pipeendpoint(req->pipe)); + + usb_fill_bulk_urb(urb, udev, pipe, + pending_req->buffer, req->buffer_length, + usbbk_urb_complete, pending_req); + urb->transfer_flags = req->transfer_flags; + + break; + default: + break; + } + + if (req->buffer_length) { + urb->transfer_dma = pending_req->transfer_dma; + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } +} + +struct set_interface_request { + pending_req_t *pending_req; + int interface; + int alternate; + struct work_struct work; +}; + +static void usbbk_set_interface_work(void *data) +{ + struct set_interface_request *req = (struct set_interface_request *) data; + pending_req_t *pending_req = req->pending_req; + struct usb_device *udev = req->pending_req->stub->udev; + + int ret; + + usb_lock_device(udev); + ret = usb_set_interface(udev, req->interface, req->alternate); + usb_unlock_device(udev); + usb_put_dev(udev); + + usbbk_do_response(pending_req, ret, 0, 0, 0); + usbif_put(pending_req->usbif); + free_req(pending_req); + kfree(req); +} + +static int usbbk_set_interface(pending_req_t *pending_req, int interface, int alternate) +{ + struct set_interface_request *req; + struct usb_device *udev = pending_req->stub->udev; + + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + req->pending_req = pending_req; + req->interface = interface; + req->alternate = alternate; + INIT_WORK(&req->work, usbbk_set_interface_work, req); + usb_get_dev(udev); + schedule_work(&req->work); + return 0; +} + +struct clear_halt_request { + pending_req_t *pending_req; + int pipe; + struct work_struct work; +}; + +static void usbbk_clear_halt_work(void *data) +{ + struct clear_halt_request *req = (struct clear_halt_request *) data; + pending_req_t *pending_req = req->pending_req; + struct usb_device *udev = req->pending_req->stub->udev; + int ret; + + usb_lock_device(udev); + ret = usb_clear_halt(req->pending_req->stub->udev, req->pipe); + usb_unlock_device(udev); + usb_put_dev(udev); + + usbbk_do_response(pending_req, ret, 0, 0, 0); + usbif_put(pending_req->usbif); + free_req(pending_req); + kfree(req); +} + +static int usbbk_clear_halt(pending_req_t *pending_req, int pipe) +{ + struct clear_halt_request *req; + struct usb_device *udev = pending_req->stub->udev; + + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + req->pending_req = pending_req; + req->pipe = pipe; + INIT_WORK(&req->work, usbbk_clear_halt_work, req); + + usb_get_dev(udev); + schedule_work(&req->work); + return 0; +} + +#if 0 +struct port_reset_request { + pending_req_t *pending_req; + struct work_struct work; +}; + +static void usbbk_port_reset_work(void *data) +{ + struct port_reset_request *req = (struct port_reset_request *) data; + pending_req_t *pending_req = req->pending_req; + struct usb_device *udev = pending_req->stub->udev; + int ret, ret_lock; + + ret = ret_lock = usb_lock_device_for_reset(udev, NULL); + if (ret_lock >= 0) { + ret = usb_reset_device(udev); + if (ret_lock) + usb_unlock_device(udev); + } + usb_put_dev(udev); + + usbbk_do_response(pending_req, ret, 0, 0, 0); + usbif_put(pending_req->usbif); + free_req(pending_req); + kfree(req); +} + +static int usbbk_port_reset(pending_req_t *pending_req) +{ + struct port_reset_request *req; + struct usb_device *udev = pending_req->stub->udev; + + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->pending_req = pending_req; + INIT_WORK(&req->work, usbbk_port_reset_work, req); + + usb_get_dev(udev); + schedule_work(&req->work); + return 0; +} +#endif + +static void usbbk_set_address(usbif_t *usbif, struct usbstub *stub, int cur_addr, int new_addr) +{ + unsigned long flags; + + spin_lock_irqsave(&usbif->addr_lock, flags); + if (cur_addr) + usbif->addr_table[cur_addr] = NULL; + if (new_addr) + usbif->addr_table[new_addr] = stub; + stub->addr = new_addr; + spin_unlock_irqrestore(&usbif->addr_lock, flags); +} + +struct usbstub *find_attached_device(usbif_t *usbif, int portnum) +{ + struct usbstub *stub; + int found = 0; + unsigned long flags; + + spin_lock_irqsave(&usbif->plug_lock, flags); + list_for_each_entry(stub, &usbif->plugged_devices, plugged_list) { + if (stub->id->portnum == portnum) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&usbif->plug_lock, flags); + + if (found) + return stub; + + return NULL; +} + +static int check_and_submit_special_ctrlreq(usbif_t *usbif, usbif_request_t *req, pending_req_t *pending_req) +{ + int devnum; + struct usbstub *stub = NULL; + struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *) req->u.ctrl; + int ret; + int done = 0; + + devnum = usb_pipedevice(req->pipe); + + /* + * When the device is first connected or reseted, USB device has no address. + * In this initial state, following requests are send to device address (#0), + * + * 1. GET_DESCRIPTOR (with Descriptor Type is "DEVICE") is send, + * and OS knows what device is connected to. + * + * 2. SET_ADDRESS is send, and then, device has its address. + * + * In the next step, SET_CONFIGURATION is send to addressed device, and then, + * the device is finally ready to use. + */ + if (unlikely(devnum == 0)) { + stub = find_attached_device(usbif, usbif_pipeportnum(req->pipe)); + if (unlikely(!stub)) { + ret = -ENODEV; + goto fail_response; + } + + switch (ctrl->bRequest) { + case USB_REQ_GET_DESCRIPTOR: + /* + * GET_DESCRIPTOR request to device #0. + * through to normal urb transfer. + */ + pending_req->stub = stub; + return 0; + break; + case USB_REQ_SET_ADDRESS: + /* + * SET_ADDRESS request to device #0. + * add attached device to addr_table. + */ + { + __u16 addr = le16_to_cpu(ctrl->wValue); + usbbk_set_address(usbif, stub, 0, addr); + } + ret = 0; + goto fail_response; + break; + default: + ret = -EINVAL; + goto fail_response; + } + } else { + if (unlikely(!usbif->addr_table[devnum])) { + ret = -ENODEV; + goto fail_response; + } + pending_req->stub = usbif->addr_table[devnum]; + } + + /* + * Check special request + */ + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + /* + * SET_ADDRESS request to addressed device. + * change addr or remove from addr_table. + */ + { + __u16 addr = le16_to_cpu(ctrl->wValue); + usbbk_set_address(usbif, stub, devnum, addr); + } + ret = 0; + goto fail_response; + break; +#if 0 + case USB_REQ_SET_CONFIGURATION: + /* + * linux 2.6.27 or later version only! + */ + if (ctrl->RequestType == USB_RECIP_DEVICE) { + __u16 config = le16_to_cpu(ctrl->wValue); + usb_driver_set_configuration(pending_req->stub->udev, config); + done = 1; + } + break; +#endif + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType == USB_RECIP_INTERFACE) { + __u16 alt = le16_to_cpu(ctrl->wValue); + __u16 intf = le16_to_cpu(ctrl->wIndex); + usbbk_set_interface(pending_req, intf, alt); + done = 1; + } + break; + case USB_REQ_CLEAR_FEATURE: + if (ctrl->bRequestType == USB_RECIP_ENDPOINT + && ctrl->wValue == USB_ENDPOINT_HALT) { + int pipe; + int ep = le16_to_cpu(ctrl->wIndex) & 0x0f; + int dir = le16_to_cpu(ctrl->wIndex) + & USB_DIR_IN; + if (dir) + pipe = usb_rcvctrlpipe(pending_req->stub->udev, ep); + else + pipe = usb_sndctrlpipe(pending_req->stub->udev, ep); + usbbk_clear_halt(pending_req, pipe); + done = 1; + } + break; +#if 0 /* not tested yet */ + case USB_REQ_SET_FEATURE: + if (ctrl->bRequestType == USB_RT_PORT) { + __u16 feat = le16_to_cpu(ctrl->wValue); + if (feat == USB_PORT_FEAT_RESET) { + usbbk_port_reset(pending_req); + done = 1; + } + } + break; +#endif + default: + break; + } + + return done; + +fail_response: + usbbk_do_response(pending_req, ret, 0, 0, 0); + usbif_put(usbif); + free_req(pending_req); + return 1; +} + +static void dispatch_request_to_pending_reqs(usbif_t *usbif, + usbif_request_t *req, + pending_req_t *pending_req) +{ + int ret; + + pending_req->id = req->id; + pending_req->usbif = usbif; + + barrier(); + + /* + * TODO: + * receive unlink request and cancel the urb in backend + */ +#if 0 + if (unlikely(usb_pipeunlink(req->pipe))) { + + } +#endif + + usbif_get(usbif); + + if (usb_pipecontrol(req->pipe)) { + if (check_and_submit_special_ctrlreq(usbif, req, pending_req)) + return; + } else { + int devnum = usb_pipedevice(req->pipe); + if (unlikely(!usbif->addr_table[devnum])) { + ret = -ENODEV; + goto fail_response; + } + pending_req->stub = usbif->addr_table[devnum]; + } + + barrier(); + + ret = usbbk_alloc_urb(req, pending_req); + if (ret) { + ret = -ESHUTDOWN; + goto fail_response; + } + + add_req_to_submitting_list(pending_req->stub, pending_req); + + barrier(); + + usbbk_init_urb(req, pending_req); + + barrier(); + + pending_req->nr_buffer_segs = req->nr_buffer_segs; + if (usb_pipeisoc(req->pipe)) + pending_req->nr_extra_segs = req->u.isoc.nr_frame_desc_segs; + else + pending_req->nr_extra_segs = 0; + + barrier(); + + ret = usbbk_gnttab_map(usbif, req, pending_req); + if (ret) { + printk(KERN_ERR "usbback: invalid buffer\n"); + ret = -ESHUTDOWN; + goto fail_free_urb; + } + + barrier(); + + if (usb_pipeout(req->pipe) && req->buffer_length) + copy_pages_to_buff(pending_req->buffer, + pending_req, + 0, + pending_req->nr_buffer_segs); + if (usb_pipeisoc(req->pipe)) { + copy_pages_to_buff(&pending_req->urb->iso_frame_desc[0], + pending_req, + pending_req->nr_buffer_segs, + pending_req->nr_extra_segs); + } + + barrier(); + + ret = usb_submit_urb(pending_req->urb, GFP_KERNEL); + if (ret) { + printk(KERN_ERR "usbback: failed submitting urb, error %d\n", ret); + ret = -ESHUTDOWN; + goto fail_flush_area; + } + return; + +fail_flush_area: + fast_flush_area(pending_req); +fail_free_urb: + remove_req_from_submitting_list(pending_req->stub, pending_req); + barrier(); + usbbk_free_urb(pending_req->urb); +fail_response: + usbbk_do_response(pending_req, ret, 0, 0, 0); + usbif_put(usbif); + free_req(pending_req); +} + +static int usbbk_start_submit_urb(usbif_t *usbif) +{ + usbif_back_ring_t *usb_ring = &usbif->ring; + usbif_request_t *ring_req; + pending_req_t *pending_req; + RING_IDX rc, rp; + int more_to_do = 0; + + rc = usb_ring->req_cons; + rp = usb_ring->sring->req_prod; + rmb(); + + while (rc != rp) { + if (RING_REQUEST_CONS_OVERFLOW(usb_ring, rc)) { + printk(KERN_WARNING "RING_REQUEST_CONS_OVERFLOW\n"); + break; + } + + pending_req = alloc_req(); + if (NULL == pending_req) { + more_to_do = 1; + break; + } + + ring_req = RING_GET_REQUEST(usb_ring, rc); + usb_ring->req_cons = ++rc; + + dispatch_request_to_pending_reqs(usbif, ring_req, + pending_req); + } + + RING_FINAL_CHECK_FOR_REQUESTS(&usbif->ring, more_to_do); + + cond_resched(); + + return more_to_do; +} + +int usbbk_schedule(void *arg) +{ + usbif_t *usbif = (usbif_t *)arg; + + usbif_get(usbif); + + while(!kthread_should_stop()) { + wait_event_interruptible( + usbif->wq, + usbif->waiting_reqs || kthread_should_stop()); + wait_event_interruptible( + pending_free_wq, + !list_empty(&pending_free) || kthread_should_stop()); + usbif->waiting_reqs = 0; + smp_mb(); + + if (usbbk_start_submit_urb(usbif)) + usbif->waiting_reqs = 1; + } + + usbif->xenusbd = NULL; + usbif_put(usbif); + + return 0; +} + +/* + * attach the grabbed device to usbif. + */ +void usbbk_plug_device(usbif_t *usbif, struct usbstub *stub) +{ + unsigned long flags; + + spin_lock_irqsave(&usbif->plug_lock, flags); + list_add(&stub->plugged_list, &usbif->plugged_devices); + spin_unlock_irqrestore(&usbif->plug_lock, flags); + stub->plugged = 1; + stub->usbif = usbif; +} + +/* + * detach the grabbed device from usbif. + */ +void usbbk_unplug_device(usbif_t *usbif, struct usbstub *stub) +{ + unsigned long flags; + + if (stub->addr) + usbbk_set_address(usbif, stub, stub->addr, 0); + spin_lock_irqsave(&usbif->plug_lock, flags); + list_del(&stub->plugged_list); + spin_unlock_irqrestore(&usbif->plug_lock, flags); + stub->plugged = 0; + stub->usbif = NULL; +} + +void detach_device_without_lock(usbif_t *usbif, struct usbstub *stub) +{ + if (stub->addr) + usbbk_set_address(usbif, stub, stub->addr, 0); + list_del(&stub->plugged_list); + stub->plugged = 0; + stub->usbif = NULL; +} + +static int __init usbback_init(void) +{ + int i, mmap_pages; + + if (!is_running_on_xen()) + return -ENODEV; + + if (usbstub_init()) + return -ENODEV; + + mmap_pages = usbif_reqs * USBIF_MAX_SEGMENTS_PER_REQUEST; + pending_reqs = kmalloc(sizeof(pending_reqs[0]) * + usbif_reqs, GFP_KERNEL); + pending_grant_handles = kmalloc(sizeof(pending_grant_handles[0]) * + mmap_pages, GFP_KERNEL); + pending_pages = alloc_empty_pages_and_pagevec(mmap_pages); + + if (!pending_reqs || !pending_grant_handles || !pending_pages) + goto out_of_memory; + + for (i = 0; i < mmap_pages; i++) + pending_grant_handles[i] = USBBACK_INVALID_HANDLE; + + memset(pending_reqs, 0, sizeof(pending_reqs)); + INIT_LIST_HEAD(&pending_free); + + for (i = 0; i < usbif_reqs; i++) { + list_add_tail(&pending_reqs[i].free_list, &pending_free); + } + + usbback_xenbus_init(); + + return 0; + + out_of_memory: + kfree(pending_reqs); + kfree(pending_grant_handles); + free_empty_pages_and_pagevec(pending_pages, mmap_pages); + printk("%s: out of memory\n", __FUNCTION__); + return -ENOMEM; +} + +static void __exit usbback_exit(void) +{ + usbback_xenbus_exit(); + usbstub_exit(); + kfree(pending_reqs); + kfree(pending_grant_handles); + free_empty_pages_and_pagevec(pending_pages, usbif_reqs * USBIF_MAX_SEGMENTS_PER_REQUEST); +} + +module_init(usbback_init); +module_exit(usbback_exit); + +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("Xen USB backend driver (usbback)"); +MODULE_LICENSE("Dual BSD/GPL"); diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbback/usbback.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbback/usbback.h Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,158 @@ +/* + * usbback.h + * + * This file is part of Xen USB backend driver. + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * or, + * + * When distributed separately from the Linux kernel or incorporated into + * other software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef __XEN_USBBACK_H__ +#define __XEN_USBBACK_H__ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/vmalloc.h> +#include <linux/kthread.h> +#include <linux/wait.h> +#include <linux/list.h> +#include <linux/kref.h> +#include <xen/evtchn.h> +#include <xen/gnttab.h> +#include <xen/driver_util.h> +#include <xen/interface/xen.h> +#include <xen/interface/io/usbif.h> + +struct usbstub; + +#define USB_DEV_ADDR_SIZE 128 + +typedef struct usbif_st { + domid_t domid; + unsigned int handle; + struct xenbus_device *xbdev; + struct list_head usbif_list; + + unsigned int irq; + + usbif_back_ring_t ring; + struct vm_struct *ring_area; + + spinlock_t ring_lock; + atomic_t refcnt; + grant_handle_t shmem_handle; + grant_ref_t shmem_ref; + + /* device address lookup table */ + spinlock_t addr_lock; + struct usbstub *addr_table[USB_DEV_ADDR_SIZE]; + + /* plugged device list */ + unsigned plaggable:1; + spinlock_t plug_lock; + struct list_head plugged_devices; + + /* request schedule */ + struct task_struct *xenusbd; + unsigned int waiting_reqs; + wait_queue_head_t waiting_to_free; + wait_queue_head_t wq; + +} usbif_t; + +struct usbstub_id +{ + struct list_head id_list; + + char bus_id[BUS_ID_SIZE]; + int dom_id; + int dev_id; + int portnum; +}; + +struct usbstub +{ + struct usbstub_id *id; + struct usb_device *udev; + struct usb_interface *interface; + usbif_t *usbif; + + struct list_head grabbed_list; + + unsigned plugged:1; + struct list_head plugged_list; + + int addr; + + spinlock_t submitting_lock; + struct list_head submitting_list; +}; + +usbif_t *usbif_alloc(domid_t domid, unsigned int handle); +void usbif_disconnect(usbif_t *usbif); +void usbif_free(usbif_t *usbif); +int usbif_map(usbif_t *usbif, unsigned long shared_page, unsigned int evtchn); + +#define usbif_get(_b) (atomic_inc(&(_b)->refcnt)) +#define usbif_put(_b) \ + do { \ + if (atomic_dec_and_test(&(_b)->refcnt)) \ + wake_up(&(_b)->waiting_to_free); \ + } while (0) + +void usbback_xenbus_init(void); +void usbback_xenbus_exit(void); + +irqreturn_t usbbk_be_int(int irq, void *dev_id, struct pt_regs *regs); +int usbbk_schedule(void *arg); +struct usbstub *find_attached_device(usbif_t *usbif, int port); +struct usbstub *find_grabbed_device(int dom_id, int dev_id, int port); +usbif_t *find_usbif(int dom_id, int dev_id); +void usbback_reconfigure(usbif_t *usbif); +void usbbk_plug_device(usbif_t *usbif, struct usbstub *stub); +void usbbk_unplug_device(usbif_t *usbif, struct usbstub *stub); +void detach_device_without_lock(usbif_t *usbif, struct usbstub *stub); +void usbbk_unlink_urbs(struct usbstub *stub); + +int usbstub_init(void); +void usbstub_exit(void); + +#endif /* __XEN_USBBACK_H__ */ diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbback/usbstub.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbback/usbstub.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,447 @@ +/* + * usbstub.c + * + * USB stub driver - grabbing and managing USB devices. + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * or, + * + * When distributed separately from the Linux kernel or incorporated into + * other software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "usbback.h" + +static LIST_HEAD(usbstub_ids); +static DEFINE_SPINLOCK(usbstub_ids_lock); +static LIST_HEAD(grabbed_devices); +static DEFINE_SPINLOCK(grabbed_devices_lock); + +struct usbstub *find_grabbed_device(int dom_id, int dev_id, int portnum) +{ + struct usbstub *stub; + int found = 0; + unsigned long flags; + + spin_lock_irqsave(&grabbed_devices_lock, flags); + list_for_each_entry(stub, &grabbed_devices, grabbed_list) { + if (stub->id->dom_id == dom_id + && stub->id->dev_id == dev_id + && stub->id->portnum == portnum) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&grabbed_devices_lock, flags); + + if (found) + return stub; + + return NULL; +} + +static struct usbstub *usbstub_alloc(struct usb_interface *interface, + struct usbstub_id *stub_id) +{ + struct usbstub *stub; + + stub = kzalloc(sizeof(*stub), GFP_KERNEL); + if (!stub) { + printk(KERN_ERR "no memory for alloc usbstub\n"); + return NULL; + } + + stub->udev = usb_get_dev(interface_to_usbdev(interface)); + stub->interface = interface; + stub->id = stub_id; + spin_lock_init(&stub->submitting_lock); + INIT_LIST_HEAD(&stub->submitting_list); + + return stub; +} + +static int usbstub_free(struct usbstub *stub) +{ + if (!stub) + return -EINVAL; + + usb_put_dev(stub->udev); + stub->interface = NULL; + stub->udev = NULL; + stub->id = NULL; + kfree(stub); + + return 0; +} + +static int usbstub_match_one(struct usb_interface *interface, + struct usbstub_id *stub_id) +{ + char *udev_busid = interface->dev.parent->bus_id; + + if (!(strncmp(stub_id->bus_id, udev_busid, BUS_ID_SIZE))) { + return 1; + } + + return 0; +} + +static struct usbstub_id *usbstub_match(struct usb_interface *interface) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usbstub_id *stub_id; + unsigned long flags; + int found = 0; + + /* hub currently not supported, so skip. */ + if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) + return NULL; + + spin_lock_irqsave(&usbstub_ids_lock, flags); + list_for_each_entry(stub_id, &usbstub_ids, id_list) { + if (usbstub_match_one(interface, stub_id)) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&usbstub_ids_lock, flags); + + if (found) + return stub_id; + + return NULL; +} + +static void add_to_grabbed_devices(struct usbstub *stub) +{ + unsigned long flags; + + spin_lock_irqsave(&grabbed_devices_lock, flags); + list_add(&stub->grabbed_list, &grabbed_devices); + spin_unlock_irqrestore(&grabbed_devices_lock, flags); +} + +static void remove_from_grabbed_devices(struct usbstub *stub) +{ + unsigned long flags; + + spin_lock_irqsave(&grabbed_devices_lock, flags); + list_del(&stub->grabbed_list); + spin_unlock_irqrestore(&grabbed_devices_lock, flags); +} + +static int usbstub_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usbstub_id *stub_id = NULL; + struct usbstub *stub = NULL; + usbif_t *usbif = NULL; + int retval = 0; + + if ((stub_id = usbstub_match(interface))) { + stub = usbstub_alloc(interface, stub_id); + if (!stub) + return -ENOMEM; + + usb_set_intfdata(interface, stub); + add_to_grabbed_devices(stub); + usbif = find_usbif(stub_id->dom_id, stub_id->dev_id); + if (usbif) { + usbbk_plug_device(usbif, stub); + usbback_reconfigure(usbif); + } + + } else + retval = -ENODEV; + + return retval; +} + +static void usbstub_disconnect(struct usb_interface *interface) +{ + struct usbstub *stub + = (struct usbstub *) usb_get_intfdata(interface); + + usb_set_intfdata(interface, NULL); + + if (!stub) + return; + + if (stub->usbif) { + usbback_reconfigure(stub->usbif); + usbbk_unplug_device(stub->usbif, stub); + } + + usbbk_unlink_urbs(stub); + + remove_from_grabbed_devices(stub); + + usbstub_free(stub); + + return; +} + +static inline int str_to_vport(const char *buf, + char *phys_bus, + int *dom_id, + int *dev_id, + int *port) +{ + char *p; + int len; + int err; + + /* no physical bus */ + if (!(p = strchr(buf, ':'))) + return -EINVAL; + + len = p - buf; + + /* bad physical bus */ + if (len + 1 > BUS_ID_SIZE) + return -EINVAL; + + strlcpy(phys_bus, buf, len + 1); + err = sscanf(p + 1, "%d:%d:%d", dom_id, dev_id, port); + if (err == 3) + return 0; + else + return -EINVAL; +} + +static int usbstub_id_add(const char *bus_id, + const int dom_id, + const int dev_id, + const int portnum) +{ + struct usbstub_id *stub_id; + unsigned long flags; + + stub_id = kzalloc(sizeof(*stub_id), GFP_KERNEL); + if (!stub_id) + return -ENOMEM; + + stub_id->dom_id = dom_id; + stub_id->dev_id = dev_id; + stub_id->portnum = portnum; + + strncpy(stub_id->bus_id, bus_id, BUS_ID_SIZE); + + spin_lock_irqsave(&usbstub_ids_lock, flags); + list_add(&stub_id->id_list, &usbstub_ids); + spin_unlock_irqrestore(&usbstub_ids_lock, flags); + + return 0; +} + +static int usbstub_id_remove(const char *phys_bus, + const int dom_id, + const int dev_id, + const int portnum) +{ + struct usbstub_id *stub_id, *tmp; + int err = -ENOENT; + unsigned long flags; + + spin_lock_irqsave(&usbstub_ids_lock, flags); + list_for_each_entry_safe(stub_id, tmp, &usbstub_ids, id_list) { + if (stub_id->dom_id == dom_id + && stub_id->dev_id == dev_id + && stub_id->portnum == portnum) { + list_del(&stub_id->id_list); + kfree(stub_id); + + err = 0; + } + } + spin_unlock_irqrestore(&usbstub_ids_lock, flags); + + return err; +} + +static ssize_t usbstub_vport_add(struct device_driver *driver, + const char *buf, size_t count) +{ + int err = 0; + + char bus_id[BUS_ID_SIZE]; + int dom_id; + int dev_id; + int portnum; + + err = str_to_vport(buf, &bus_id[0], &dom_id, &dev_id, &portnum); + if (err) + goto out; + + err = usbstub_id_add(&bus_id[0], dom_id, dev_id, portnum); + +out: + if (!err) + err = count; + return err; +} + +DRIVER_ATTR(new_vport, S_IWUSR, NULL, usbstub_vport_add); + +static ssize_t usbstub_vport_remove(struct device_driver *driver, + const char *buf, size_t count) +{ + int err = 0; + + char bus_id[BUS_ID_SIZE]; + int dom_id; + int dev_id; + int portnum; + + err = str_to_vport(buf, &bus_id[0], &dom_id, &dev_id, &portnum); + if (err) + goto out; + + err = usbstub_id_remove(&bus_id[0], dom_id, dev_id, portnum); + +out: + if (!err) + err = count; + return err; +} + +DRIVER_ATTR(remove_vport, S_IWUSR, NULL, usbstub_vport_remove); + +static ssize_t usbstub_vport_show(struct device_driver *driver, + char *buf) +{ + struct usbstub_id *stub_id; + size_t count = 0; + unsigned long flags; + + spin_lock_irqsave(&usbstub_ids_lock, flags); + list_for_each_entry(stub_id, &usbstub_ids, id_list) { + if (count >= PAGE_SIZE) + break; + count += scnprintf((char *)buf + count, PAGE_SIZE - count, + "%s:%d:%d:%d\n", + &stub_id->bus_id[0], + stub_id->dom_id, + stub_id->dev_id, + stub_id->portnum); + } + spin_unlock_irqrestore(&usbstub_ids_lock, flags); + + return count; +} + +DRIVER_ATTR(vports, S_IRUSR, usbstub_vport_show, NULL); + +static ssize_t usbstub_devices_show(struct device_driver *driver, + char *buf) +{ + struct usbstub *stub; + size_t count = 0; + unsigned long flags; + + spin_lock_irqsave(&grabbed_devices_lock, flags); + list_for_each_entry(stub, &grabbed_devices, grabbed_list) { + if (count >= PAGE_SIZE) + break; + + count += scnprintf((char *)buf + count, PAGE_SIZE - count, + "%u-%s:%u.%u\n", + stub->udev->bus->busnum, + stub->udev->devpath, + stub->udev->config->desc.bConfigurationValue, + stub->interface->cur_altsetting->desc.bInterfaceNumber); + + } + spin_unlock_irqrestore(&grabbed_devices_lock, flags); + + return count; +} + +DRIVER_ATTR(grabbed_devices, S_IRUSR, usbstub_devices_show, NULL); + +/* table of devices that matches any usbdevice */ +static struct usb_device_id usbstub_table[] = { + { .driver_info = 1 }, /* wildcard, see usb_match_id() */ + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usbstub_table); + +static struct usb_driver usbback_usb_driver = { + .name = "usbback", + .probe = usbstub_probe, + .disconnect = usbstub_disconnect, + .id_table = usbstub_table, +}; + +int __init usbstub_init(void) +{ + int err; + + err = usb_register(&usbback_usb_driver); + if (err < 0) + goto out; + if (!err) + err = driver_create_file(&usbback_usb_driver.driver, + &driver_attr_new_vport); + if (!err) + err = driver_create_file(&usbback_usb_driver.driver, + &driver_attr_remove_vport); + if (!err) + err = driver_create_file(&usbback_usb_driver.driver, + &driver_attr_vports); + if (!err) + err = driver_create_file(&usbback_usb_driver.driver, + &driver_attr_grabbed_devices); + if (err) + usbstub_exit(); + +out: + return err; +} + +void __exit usbstub_exit(void) +{ + driver_remove_file(&usbback_usb_driver.driver, + &driver_attr_new_vport); + driver_remove_file(&usbback_usb_driver.driver, + &driver_attr_remove_vport); + driver_remove_file(&usbback_usb_driver.driver, + &driver_attr_vports); + driver_remove_file(&usbback_usb_driver.driver, + &driver_attr_grabbed_devices); + + usb_deregister(&usbback_usb_driver); +} diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbback/xenbus.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbback/xenbus.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,269 @@ +/* + * xenbus.c + * + * Xenbus interface for USB backend driver. + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * or, + * + * When distributed separately from the Linux kernel or incorporated into + * other software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <xen/xenbus.h> +#include "usbback.h" + +static int start_xenusbd(usbif_t *usbif) +{ + int err = 0; + char name[TASK_COMM_LEN]; + + snprintf(name, TASK_COMM_LEN, "usbback.%d.%d", usbif->domid, usbif->handle); + usbif->xenusbd = kthread_run(usbbk_schedule, usbif, name); + if (IS_ERR(usbif->xenusbd)) { + err = PTR_ERR(usbif->xenusbd); + usbif->xenusbd = NULL; + xenbus_dev_error(usbif->xbdev, err, "start xenusbd"); + } + return err; +} + +static int usbback_remove(struct xenbus_device *dev) +{ + usbif_t *usbif = dev->dev.driver_data; + + if (usbif) { + usbif_disconnect(usbif); + usbif_free(usbif);; + } + dev->dev.driver_data = NULL; + + return 0; +} + +static int usbback_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + usbif_t *usbif; + unsigned int handle; + int err; + + if (usb_disabled()) + return -ENODEV; + + handle = simple_strtoul(strrchr(dev->otherend,'/')+1, NULL, 0); + usbif = usbif_alloc(dev->otherend_id, handle); + if (!usbif) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating backend interface"); + return -ENOMEM; + } + usbif->xbdev = dev; + dev->dev.driver_data = usbif; + + err = xenbus_switch_state(dev, XenbusStateInitWait); + if (err) + goto fail; + + return 0; + +fail: + usbback_remove(dev); + return err; +} + +static int connect_ring(usbif_t *usbif) +{ + struct xenbus_device *dev = usbif->xbdev; + unsigned long ring_ref; + unsigned int evtchn; + int err; + + err = xenbus_gather(XBT_NIL, dev->otherend, + "ring-ref", "%lu", &ring_ref, + "event-channel", "%u", &evtchn, NULL); + if (err) { + xenbus_dev_fatal(dev, err, + "reading %s/ring-ref and event-channel", + dev->otherend); + return err; + } + + printk("usbback: ring-ref %ld, event-channel %d\n", + ring_ref, evtchn); + + err = usbif_map(usbif, ring_ref, evtchn); + if (err) { + xenbus_dev_fatal(dev, err, "mapping ring-ref %lu port %u", + ring_ref, evtchn); + return err; + } + + return 0; +} + +void usbback_do_hotplug(usbif_t *usbif) +{ + struct xenbus_transaction xbt; + struct xenbus_device *dev = usbif->xbdev; + struct usbstub *stub = NULL; + int err; + char port_str[8]; + int i; + int num_ports; + int state; + +again: + err = xenbus_transaction_start(&xbt); + if (err) { + xenbus_dev_fatal(dev, err, "starting transaction"); + return; + } + + err = xenbus_scanf(xbt, dev->nodename, + "num-ports", "%d", &num_ports); + + for (i = 1; i <= num_ports; i++) { + stub = find_attached_device(usbif, i); + if (stub) + state = stub->udev->speed; + else + state = 0; + sprintf(port_str, "port-%d", i); + err = xenbus_printf(xbt, dev->nodename, port_str, "%d", state); + if (err) { + xenbus_dev_fatal(dev, err, "writing port-%d state", i); + goto abort; + } + } + + err = xenbus_transaction_end(xbt, 0); + if (err == -EAGAIN) + goto again; + if (err) + xenbus_dev_fatal(dev, err, "completing transaction"); + + return; + +abort: + xenbus_transaction_end(xbt, 1); +} + +void usbback_reconfigure(usbif_t *usbif) +{ + struct xenbus_device *dev = usbif->xbdev; + + if (dev->state == XenbusStateConnected) + xenbus_switch_state(dev, XenbusStateReconfiguring); +} + +void frontend_changed(struct xenbus_device *dev, + enum xenbus_state frontend_state) +{ + usbif_t *usbif = dev->dev.driver_data; + int err; + + switch (frontend_state) { + case XenbusStateInitialising: + if (dev->state == XenbusStateClosed) { + printk("%s: %s: prepare for reconnect\n", + __FUNCTION__, dev->nodename); + xenbus_switch_state(dev, XenbusStateInitWait); + } + break; + + case XenbusStateInitialised: + err = connect_ring(usbif); + if (err) + break; + start_xenusbd(usbif); + usbback_do_hotplug(usbif); + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateConnected: + if (dev->state == XenbusStateConnected) + break; + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateClosing: + usbif_disconnect(usbif); + xenbus_switch_state(dev, XenbusStateClosing); + break; + + case XenbusStateClosed: + xenbus_switch_state(dev, XenbusStateClosed); + break; + + case XenbusStateReconfiguring: + usbback_do_hotplug(usbif); + xenbus_switch_state(dev, XenbusStateReconfigured); + break; + + case XenbusStateUnknown: + device_unregister(&dev->dev); + break; + + default: + xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend", + frontend_state); + break; + } +} + +static const struct xenbus_device_id usbback_ids[] = { + { "vusb" }, + { "" }, +}; + +static struct xenbus_driver usbback_driver = { + .name = "vusb", + .owner = THIS_MODULE, + .ids = usbback_ids, + .probe = usbback_probe, + .otherend_changed = frontend_changed, + .remove = usbback_remove, +}; + +void usbback_xenbus_init(void) +{ + xenbus_register_backend(&usbback_driver); +} + +void usbback_xenbus_exit(void) +{ + xenbus_unregister_driver(&usbback_driver); +} _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxxxxxxxx http://lists.xensource.com/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |