diff options
author | Craig W. Nadler <craig@nadler.us> | 2007-11-11 15:00:15 -0800 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-02-01 14:34:49 -0800 |
commit | 25a010c8c1a5f0cc2e2794adf969e2df2ad1f0b6 (patch) | |
tree | 1d2e477e44a2a8122696711bfd2b9aad35a265bf | |
parent | 676d3aa16f66d94bf5654781b77d1e070c8b0514 (diff) |
USB: add Printer Gadget Driver
G_PRINTER: Adds a USB printer gadget driver for use in printer firmware.
This adds a USB printer gadget driver for use in printer firmware.
The printer gadget channels data between the USB host and a userspace
program driving the print engine. The user space program reads and
writes the device file /dev/g_printer to receive or send printer data.
It can use ioctl calls to the device file to get or set printer status.
Signed-off-by: Craig W. Nadler <craig@nadler.us>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | Documentation/usb/gadget_printer.txt | 510 | ||||
-rw-r--r-- | drivers/usb/gadget/Kconfig | 14 | ||||
-rw-r--r-- | drivers/usb/gadget/Makefile | 3 | ||||
-rw-r--r-- | drivers/usb/gadget/printer.c | 1592 | ||||
-rw-r--r-- | include/linux/usb/Kbuild | 1 | ||||
-rw-r--r-- | include/linux/usb/g_printer.h | 31 | ||||
-rw-r--r-- | include/linux/usb/gadgetfs.h | 5 |
7 files changed, 2156 insertions, 0 deletions
diff --git a/Documentation/usb/gadget_printer.txt b/Documentation/usb/gadget_printer.txt new file mode 100644 index 00000000000..ad995bf0db4 --- /dev/null +++ b/Documentation/usb/gadget_printer.txt @@ -0,0 +1,510 @@ + + Linux USB Printer Gadget Driver + 06/04/2007 + + Copyright (C) 2007 Craig W. Nadler <craig@nadler.us> + + + +GENERAL +======= + +This driver may be used if you are writing printer firmware using Linux as +the embedded OS. This driver has nothing to do with using a printer with +your Linux host system. + +You will need a USB device controller and a Linux driver for it that accepts +a gadget / "device class" driver using the Linux USB Gadget API. After the +USB device controller driver is loaded then load the printer gadget driver. +This will present a printer interface to the USB Host that your USB Device +port is connected to. + +This driver is structured for printer firmware that runs in user mode. The +user mode printer firmware will read and write data from the kernel mode +printer gadget driver using a device file. The printer returns a printer status +byte when the USB HOST sends a device request to get the printer status. The +user space firmware can read or write this status byte using a device file +/dev/g_printer . Both blocking and non-blocking read/write calls are supported. + + + + +HOWTO USE THIS DRIVER +===================== + +To load the USB device controller driver and the printer gadget driver. The +following example uses the Netchip 2280 USB device controller driver: + +modprobe net2280 +modprobe g_printer + + +The follow command line parameter can be used when loading the printer gadget +(ex: modprobe g_printer idVendor=0x0525 idProduct=0xa4a8 ): + +idVendor - This is the Vendor ID used in the device descriptor. The default is + the Netchip vendor id 0x0525. YOU MUST CHANGE TO YOUR OWN VENDOR ID + BEFORE RELEASING A PRODUCT. If you plan to release a product and don't + already have a Vendor ID please see www.usb.org for details on how to + get one. + +idProduct - This is the Product ID used in the device descriptor. The default + is 0xa4a8, you should change this to an ID that's not used by any of + your other USB products if you have any. It would be a good idea to + start numbering your products starting with say 0x0001. + +bcdDevice - This is the version number of your product. It would be a good idea + to put your firmware version here. + +iManufacturer - A string containing the name of the Vendor. + +iProduct - A string containing the Product Name. + +iSerialNum - A string containing the Serial Number. This should be changed for + each unit of your product. + +iPNPstring - The PNP ID string used for this printer. You will want to set + either on the command line or hard code the PNP ID string used for + your printer product. + +qlen - The number of 8k buffers to use per endpoint. The default is 10, you + should tune this for your product. You may also want to tune the + size of each buffer for your product. + + + + +USING THE EXAMPLE CODE +====================== + +This example code talks to stdout, instead of a print engine. + +To compile the test code below: + +1) save it to a file called prn_example.c +2) compile the code with the follow command: + gcc prn_example.c -o prn_example + + + +To read printer data from the host to stdout: + + # prn_example -read_data + + +To write printer data from a file (data_file) to the host: + + # cat data_file | prn_example -write_data + + +To get the current printer status for the gadget driver: + + # prn_example -get_status + + Printer status is: + Printer is NOT Selected + Paper is Out + Printer OK + + +To set printer to Selected/On-line: + + # prn_example -selected + + +To set printer to Not Selected/Off-line: + + # prn_example -not_selected + + +To set paper status to paper out: + + # prn_example -paper_out + + +To set paper status to paper loaded: + + # prn_example -paper_loaded + + +To set error status to printer OK: + + # prn_example -no_error + + +To set error status to ERROR: + + # prn_example -error + + + + +EXAMPLE CODE +============ + + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <linux/poll.h> +#include <sys/ioctl.h> +#include <linux/usb/g_printer.h> + +#define PRINTER_FILE "/dev/g_printer" +#define BUF_SIZE 512 + + +/* + * 'usage()' - Show program usage. + */ + +static void +usage(const char *option) /* I - Option string or NULL */ +{ + if (option) { + fprintf(stderr,"prn_example: Unknown option \"%s\"!\n", + option); + } + + fputs("\n", stderr); + fputs("Usage: prn_example -[options]\n", stderr); + fputs("Options:\n", stderr); + fputs("\n", stderr); + fputs("-get_status Get the current printer status.\n", stderr); + fputs("-selected Set the selected status to selected.\n", stderr); + fputs("-not_selected Set the selected status to NOT selected.\n", + stderr); + fputs("-error Set the error status to error.\n", stderr); + fputs("-no_error Set the error status to NO error.\n", stderr); + fputs("-paper_out Set the paper status to paper out.\n", stderr); + fputs("-paper_loaded Set the paper status to paper loaded.\n", + stderr); + fputs("-read_data Read printer data from driver.\n", stderr); + fputs("-write_data Write printer sata to driver.\n", stderr); + fputs("-NB_read_data (Non-Blocking) Read printer data from driver.\n", + stderr); + fputs("\n\n", stderr); + + exit(1); +} + + +static int +read_printer_data() +{ + struct pollfd fd[1]; + + /* Open device file for printer gadget. */ + fd[0].fd = open(PRINTER_FILE, O_RDWR); + if (fd[0].fd < 0) { + printf("Error %d opening %s\n", fd[0].fd, PRINTER_FILE); + close(fd[0].fd); + return(-1); + } + + fd[0].events = POLLIN | POLLRDNORM; + + while (1) { + static char buf[BUF_SIZE]; + int bytes_read; + int retval; + + /* Wait for up to 1 second for data. */ + retval = poll(fd, 1, 1000); + + if (retval && (fd[0].revents & POLLRDNORM)) { + + /* Read data from printer gadget driver. */ + bytes_read = read(fd[0].fd, buf, BUF_SIZE); + + if (bytes_read < 0) { + printf("Error %d reading from %s\n", + fd[0].fd, PRINTER_FILE); + close(fd[0].fd); + return(-1); + } else if (bytes_read > 0) { + /* Write data to standard OUTPUT (stdout). */ + fwrite(buf, 1, bytes_read, stdout); + fflush(stdout); + } + + } + + } + + /* Close the device file. */ + close(fd[0].fd); + + return 0; +} + + +static int +write_printer_data() +{ + struct pollfd fd[1]; + + /* Open device file for printer gadget. */ + fd[0].fd = open (PRINTER_FILE, O_RDWR); + if (fd[0].fd < 0) { + printf("Error %d opening %s\n", fd[0].fd, PRINTER_FILE); + close(fd[0].fd); + return(-1); + } + + fd[0].events = POLLOUT | POLLWRNORM; + + while (1) { + int retval; + static char buf[BUF_SIZE]; + /* Read data from standard INPUT (stdin). */ + int bytes_read = fread(buf, 1, BUF_SIZE, stdin); + + if (!bytes_read) { + break; + } + + while (bytes_read) { + + /* Wait for up to 1 second to sent data. */ + retval = poll(fd, 1, 1000); + + /* Write data to printer gadget driver. */ + if (retval && (fd[0].revents & POLLWRNORM)) { + retval = write(fd[0].fd, buf, bytes_read); + if (retval < 0) { + printf("Error %d writing to %s\n", + fd[0].fd, + PRINTER_FILE); + close(fd[0].fd); + return(-1); + } else { + bytes_read -= retval; + } + + } + + } + + } + + /* Wait until the data has been sent. */ + fsync(fd[0].fd); + + /* Close the device file. */ + close(fd[0].fd); + + return 0; +} + + +static int +read_NB_printer_data() +{ + int fd; + static char buf[BUF_SIZE]; + int bytes_read; + + /* Open device file for printer gadget. */ + fd = open(PRINTER_FILE, O_RDWR|O_NONBLOCK); + if (fd < 0) { + printf("Error %d opening %s\n", fd, PRINTER_FILE); + close(fd); + return(-1); + } + + while (1) { + /* Read data from printer gadget driver. */ + bytes_read = read(fd, buf, BUF_SIZE); + if (bytes_read <= 0) { + break; + } + + /* Write data to standard OUTPUT (stdout). */ + fwrite(buf, 1, bytes_read, stdout); + fflush(stdout); + } + + /* Close the device file. */ + close(fd); + + return 0; +} + + +static int +get_printer_status() +{ + int retval; + int fd; + + /* Open device file for printer gadget. */ + fd = open(PRINTER_FILE, O_RDWR); + if (fd < 0) { + printf("Error %d opening %s\n", fd, PRINTER_FILE); + close(fd); + return(-1); + } + + /* Make the IOCTL call. */ + retval = ioctl(fd, GADGET_GET_PRINTER_STATUS); + if (retval < 0) { + fprintf(stderr, "ERROR: Failed to set printer status\n"); + return(-1); + } + + /* Close the device file. */ + close(fd); + + return(retval); +} + + +static int +set_printer_status(unsigned char buf, int clear_printer_status_bit) +{ + int retval; + int fd; + + retval = get_printer_status(); + if (retval < 0) { + fprintf(stderr, "ERROR: Failed to get printer status\n"); + return(-1); + } + + /* Open device file for printer gadget. */ + fd = open(PRINTER_FILE, O_RDWR); + + if (fd < 0) { + printf("Error %d opening %s\n", fd, PRINTER_FILE); + close(fd); + return(-1); + } + + if (clear_printer_status_bit) { + retval &= ~buf; + } else { + retval |= buf; + } + + /* Make the IOCTL call. */ + if (ioctl(fd, GADGET_SET_PRINTER_STATUS, (unsigned char)retval)) { + fprintf(stderr, "ERROR: Failed to set printer status\n"); + return(-1); + } + + /* Close the device file. */ + close(fd); + + return 0; +} + + +static int +display_printer_status() +{ + char printer_status; + + printer_status = get_printer_status(); + if (printer_status < 0) { + fprintf(stderr, "ERROR: Failed to get printer status\n"); + return(-1); + } + + printf("Printer status is:\n"); + if (printer_status & PRINTER_SELECTED) { + printf(" Printer is Selected\n"); + } else { + printf(" Printer is NOT Selected\n"); + } + if (printer_status & PRINTER_PAPER_EMPTY) { + printf(" Paper is Out\n"); + } else { + printf(" Paper is Loaded\n"); + } + if (printer_status & PRINTER_NOT_ERROR) { + printf(" Printer OK\n"); + } else { + printf(" Printer ERROR\n"); + } + + return(0); +} + + +int +main(int argc, char *argv[]) +{ + int i; /* Looping var */ + int retval = 0; + + /* No Args */ + if (argc == 1) { + usage(0); + exit(0); + } + + for (i = 1; i < argc && !retval; i ++) { + + if (argv[i][0] != '-') { + continue; + } + + if (!strcmp(argv[i], "-get_status")) { + if (display_printer_status()) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-paper_loaded")) { + if (set_printer_status(PRINTER_PAPER_EMPTY, 1)) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-paper_out")) { + if (set_printer_status(PRINTER_PAPER_EMPTY, 0)) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-selected")) { + if (set_printer_status(PRINTER_SELECTED, 0)) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-not_selected")) { + if (set_printer_status(PRINTER_SELECTED, 1)) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-error")) { + if (set_printer_status(PRINTER_NOT_ERROR, 1)) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-no_error")) { + if (set_printer_status(PRINTER_NOT_ERROR, 0)) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-read_data")) { + if (read_printer_data()) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-write_data")) { + if (write_printer_data()) { + retval = 1; + } + + } else if (!strcmp(argv[i], "-NB_read_data")) { + if (read_NB_printer_data()) { + retval = 1; + } + + } else { + usage(argv[i]); + retval = 1; + } + } + + exit(retval); +} diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index e706882a8e6..8a88f24270b 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -548,6 +548,20 @@ config USB_MIDI_GADGET Say "y" to link the driver statically, or "m" to build a dynamically linked module called "g_midi". +config USB_G_PRINTER + tristate "Printer Gadget" + help + The Printer Gadget channels data between the USB host and a + userspace program driving the print engine. The user space + program reads and writes the device file /dev/g_printer to + receive or send printer data. It can use ioctl calls to + the device file to get or set printer status. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_printer". + + For more information, see Documentation/usb/gadget_printer.txt + which includes sample code for accessing the device file. # put drivers that need isochronous transfer support (for audio # or video class gadget drivers), or specific hardware, here. diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 904e57bf611..c3aab80b6c7 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -28,6 +28,8 @@ g_midi-objs := gmidi.o usbstring.o config.o epautoconf.o gadgetfs-objs := inode.o g_file_storage-objs := file_storage.o usbstring.o config.o \ epautoconf.o +g_printer-objs := printer.o usbstring.o config.o \ + epautoconf.o ifeq ($(CONFIG_USB_ETH_RNDIS),y) g_ether-objs += rndis.o @@ -38,5 +40,6 @@ obj-$(CONFIG_USB_ETH) += g_ether.o obj-$(CONFIG_USB_GADGETFS) += gadgetfs.o obj-$(CONFIG_USB_FILE_STORAGE) += g_file_storage.o obj-$(CONFIG_USB_G_SERIAL) += g_serial.o +obj-$(CONFIG_USB_G_PRINTER) += g_printer.o obj-$(CONFIG_USB_MIDI_GADGET) += g_midi.o diff --git a/drivers/usb/gadget/printer.c b/drivers/usb/gadget/printer.c new file mode 100644 index 00000000000..9fdabc8fcac --- /dev/null +++ b/drivers/usb/gadget/printer.c @@ -0,0 +1,1592 @@ +/* + * printer.c -- Printer gadget driver + * + * Copyright (C) 2003-2005 David Brownell + * Copyright (C) 2006 Craig W. Nadler + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/utsname.h> +#include <linux/device.h> +#include <linux/moduleparam.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/types.h> +#include <linux/ctype.h> +#include <linux/cdev.h> + +#include <asm/byteorder.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <asm/system.h> +#include <linux/uaccess.h> +#include <asm/unaligned.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/g_printer.h> + +#include "gadget_chips.h" + +#define DRIVER_DESC "Printer Gadget" +#define DRIVER_VERSION "2007 OCT 06" + +static const char shortname [] = "printer"; +static const char driver_desc [] = DRIVER_DESC; + +static dev_t g_printer_devno; + +static struct class *usb_gadget_class; + +/*-------------------------------------------------------------------------*/ + +struct printer_dev { + spinlock_t lock; /* lock this structure */ + /* lock buffer lists during read/write calls */ + spinlock_t lock_printer_io; + struct usb_gadget *gadget; + struct usb_request *req; /* for control responses */ + u8 config; + s8 interface; + struct usb_ep *in_ep, *out_ep; + const struct usb_endpoint_descriptor + *in, *out; + struct list_head rx_reqs; /* List of free RX structs */ + struct list_head rx_reqs_active; /* List of Active RX xfers */ + struct list_head rx_buffers; /* List of completed xfers */ + /* wait until there is data to be read. */ + wait_queue_head_t rx_wait; + struct list_head tx_reqs; /* List of free TX structs */ + struct list_head tx_reqs_active; /* List of Active TX xfers */ + /* Wait until there are write buffers available to use. */ + wait_queue_head_t tx_wait; + /* Wait until all write buffers have been sent. */ + wait_queue_head_t tx_flush_wait; + struct usb_request *current_rx_req; + size_t current_rx_bytes; + u8 *current_rx_buf; + u8 printer_status; + u8 reset_printer; + struct class_device *printer_class_dev; + struct cdev printer_cdev; + struct device *pdev; + u8 printer_cdev_open; + wait_queue_head_t wait; +}; + +static struct printer_dev usb_printer_gadget; + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. + */ +#define PRINTER_VENDOR_NUM 0x0525 /* NetChip */ +#define PRINTER_PRODUCT_NUM 0xa4a8 /* Linux-USB Printer Gadget */ + +/* Some systems will want different product identifers published in the + * device descriptor, either numbers or strings or both. These string + * parameters are in UTF-8 (superset of ASCII's 7 bit characters). + */ + +static ushort __initdata idVendor; +module_param(idVendor, ushort, S_IRUGO); +MODULE_PARM_DESC(idVendor, "USB Vendor ID"); + +static ushort __initdata idProduct; +module_param(idProduct, ushort, S_IRUGO); +MODULE_PARM_DESC(idProduct, "USB Product ID"); + +static ushort __initdata bcdDevice; +module_param(bcdDevice, ushort, S_IRUGO); +MODULE_PARM_DESC(bcdDevice, "USB Device version (BCD)"); + +static char *__initdata iManufacturer; +module_param(iManufacturer, charp, S_IRUGO); +MODULE_PARM_DESC(iManufacturer, "USB Manufacturer string"); + +static char *__initdata iProduct; +module_param(iProduct, charp, S_IRUGO); +MODULE_PARM_DESC(iProduct, "USB Product string"); + +static char *__initdata iSerialNum; +module_param(iSerialNum, charp, S_IRUGO); +MODULE_PARM_DESC(iSerialNum, "1"); + +static char *__initdata iPNPstring; +module_param(iPNPstring, charp, S_IRUGO); +MODULE_PARM_DESC(iPNPstring, "MFG:linux;MDL:g_printer;CLS:PRINTER;SN:1;"); + +/* Number of requests to allocate per endpoint, not used for ep0. */ +static unsigned qlen = 10; +module_param(qlen, uint, S_IRUGO|S_IWUSR); + +#define QLEN qlen + +#ifdef CONFIG_USB_GADGET_DUALSPEED +#define DEVSPEED USB_SPEED_HIGH +#else /* full speed (low speed doesn't do bulk) */ +#define DEVSPEED USB_SPEED_FULL +#endif + +/*-------------------------------------------------------------------------*/ + +#define xprintk(d, level, fmt, args...) \ + printk(level "%s: " fmt, DRIVER_DESC, ## args) + +#ifdef DEBUG +#define DBG(dev, fmt, args...) \ + xprintk(dev, KERN_DEBUG, fmt, ## args) +#else +#define DBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE +#define VDBG(dev, fmt, args...) \ + xprintk(dev, KERN_DEBUG, fmt, ## args) +#else +#define VDBG(dev, fmt, args...) \ + do { } while (0) +#endif /* VERBOSE */ + +#define ERROR(dev, fmt, args...) \ + xprintk(dev, KERN_ERR, fmt, ## args) +#define WARN(dev, fmt, args...) \ + xprintk(dev, KERN_WARNING, fmt, ## args) +#define INFO(dev, fmt, args...) \ + xprintk(dev, KERN_INFO, fmt, ## args) + +/*-------------------------------------------------------------------------*/ + +/* USB DRIVER HOOKUP (to the hardware driver, below us), mostly + * ep0 implementation: descriptors, config management, setup(). + * also optional class-specific notification interrupt transfer. + */ + +/* + * DESCRIPTORS ... most are static, but strings and (full) configuration + * descriptors are built on demand. + */ + +#define STRING_MANUFACTURER 1 +#define STRING_PRODUCT 2 +#define STRING_SERIALNUM 3 + +/* holds our biggest descriptor */ +#define USB_DESC_BUFSIZE 256 +#define USB_BUFSIZE 8192 + +/* This device advertises one configuration. */ +#define DEV_CONFIG_VALUE 1 +#define PRINTER_INTERFACE 0 + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(PRINTER_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16(PRINTER_PRODUCT_NUM), + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIALNUM, + .bNumConfigurations = 1 +}; + +static struct usb_otg_descriptor otg_desc = { + .bLength = sizeof otg_desc, + .bDescriptorType = USB_DT_OTG, + .bmAttributes = USB_OTG_SRP +}; + +static struct usb_config_descriptor config_desc = { + .bLength = sizeof config_desc, + .bDescriptorType = USB_DT_CONFIG, + + /* compute wTotalLength on the fly */ + .bNumInterfaces = 1, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = 0, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 1 /* Self-Powered */ +}; + +static struct usb_interface_descriptor intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = PRINTER_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_PRINTER, + .bInterfaceSubClass = 1, /* Printer Sub-Class */ + .bInterfaceProtocol = 2, /* Bi-Directional */ + .iInterface = 0 +}; + +static struct usb_endpoint_descriptor fs_ep_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK +}; + +static struct usb_endpoint_descriptor fs_ep_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK +}; + +static const struct usb_descriptor_header *fs_printer_function [11] = { + (struct usb_descriptor_header *) &otg_desc, + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &fs_ep_in_desc, + (struct usb_descriptor_header *) &fs_ep_out_desc, + NULL +}; + +#ifdef CONFIG_USB_GADGET_DUALSPEED + +/* + * usb 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + */ + +static struct usb_endpoint_descriptor hs_ep_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512) +}; + +static struct usb_endpoint_descriptor hs_ep_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512) +}; + +static struct usb_qualifier_descriptor dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PRINTER, + .bNumConfigurations = 1 +}; + +static const struct usb_descriptor_header *hs_printer_function [11] = { + (struct usb_descriptor_header *) &otg_desc, + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &hs_ep_in_desc, + (struct usb_descriptor_header *) &hs_ep_out_desc, + NULL +}; + +/* maxpacket and other transfer characteristics vary by speed. */ +#define ep_desc(g, hs, fs) (((g)->speed == USB_SPEED_HIGH)?(hs):(fs)) + +#else + +/* if there's no high speed support, maxpacket doesn't change. */ +#define ep_desc(g, hs, fs) (((void)(g)), (fs)) + +#endif /* !CONFIG_USB_GADGET_DUALSPEED */ + +/*-------------------------------------------------------------------------*/ + +/* descriptors that are built on-demand */ + +static char manufacturer [50]; +static char product_desc [40] = DRIVER_DESC; +static char serial_num [40] = "1"; +static char pnp_string [1024] = + "XXMFG:linux;MDL:g_printer;CLS:PRINTER;SN:1;"; + +/* static strings, in UTF-8 */ +static struct usb_string strings [] = { + { STRING_MANUFACTURER, manufacturer, }, + { STRING_PRODUCT, product_desc, }, + { STRING_SERIALNUM, serial_num, }, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab = { + .language = 0x0409, /* en-us */ + .strings = strings, +}; + +/*-------------------------------------------------------------------------*/ + +static struct usb_request * +printer_req_alloc(struct usb_ep *ep, unsigned len, gfp_t gfp_flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, gfp_flags); + + if (req != NULL) { + req->length = len; + req->buf = kmalloc(len, gfp_flags); + if (req->buf == NULL) { + usb_ep_free_request(ep, req); + return NULL; + } + } + + return req; +} + +static void +printer_req_free(struct usb_ep *ep, struct usb_request *req) +{ + if (ep != NULL && req != NULL) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +/*-------------------------------------------------------------------------*/ + +static void rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct printer_dev *dev = ep->driver_data; + int status = req->status; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + list_del_init(&req->list); /* Remode from Active List */ + + switch (status) { + + /* normal completion */ + case 0: + list_add_tail(&req->list, &dev->rx_buffers); + wake_up_interruptible(&dev->rx_wait); + DBG(dev, "G_Printer : rx length %d\n", req->actual); + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + VDBG(dev, "rx shutdown, code %d\n", status); + list_add(&req->list, &dev->rx_reqs); + break; + + /* for hardware automagic (such as pxa) */ + case -ECONNABORTED: /* endpoint reset */ + DBG(dev, "rx %s reset\n", ep->name); + list_add(&req->list, &dev->rx_reqs); + break; + + /* data overrun */ + case -EOVERFLOW: + /* FALLTHROUGH */ + + default: + DBG(dev, "rx status %d\n", status); + list_add(&req->list, &dev->rx_reqs); + break; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct printer_dev *dev = ep->driver_data; + + switch (req->status) { + default: + VDBG(dev, "tx err %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + break; + case 0: + break; + } + + spin_lock(&dev->lock); + /* Take the request struct off the active list and put it on the + * free list. + */ + list_del_init(&req->list); + list_add(&req->list, &dev->tx_reqs); + wake_up_interruptible(&dev->tx_wait); + if (likely(list_empty(&dev->tx_reqs_active))) + wake_up_interruptible(&dev->tx_flush_wait); + + spin_unlock(&dev->lock); +} + +/*-------------------------------------------------------------------------*/ + +static int +printer_open(struct inode *inode, struct file *fd) +{ + struct printer_dev *dev; + unsigned long flags; + int ret = -EBUSY; + + dev = container_of(inode->i_cdev, struct printer_dev, printer_cdev); + + spin_lock_irqsave(&dev->lock, flags); + + if (!dev->printer_cdev_open) { + dev->printer_cdev_open = 1; + fd->private_data = dev; + ret = 0; + /* Change the printer status to show that it's on-line. */ + dev->printer_status |= PRINTER_SELECTED; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + DBG(dev, "printer_open returned %x\n", ret); + + return ret; +} + +static int +printer_close(struct inode *inode, struct file *fd) +{ + struct printer_dev *dev = fd->private_data; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->printer_cdev_open = 0; + fd->private_data = NULL; + /* Change printer status to show that the printer is off-line. */ + dev->printer_status &= ~PRINTER_SELECTED; + spin_unlock_irqrestore(&dev->lock, flags); + + DBG(dev, "printer_close\n"); + + return 0; +} + +static ssize_t +printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) +{ + struct printer_dev *dev = fd->private_data; + unsigned long flags; + size_t size; + size_t bytes_copied; + struct usb_request *req; + /* This is a pointer to the current USB rx request. */ + struct usb_request *current_rx_req; + /* This is the number of bytes in the current rx buffer. */ + size_t current_rx_bytes; + /* This is a pointer to the current rx buffer. */ + u8 *current_rx_buf; + + if (len == 0) + return -EINVAL; + + DBG(dev, "printer_read trying to read %d bytes\n", (int)len); + + spin_lock(&dev->lock_printer_io); + spin_lock_irqsave(&dev->lock, flags); + + /* We will use this flag later to check if a printer reset happened + * after we turn interrupts back on. + */ + dev->reset_printer = 0; + + while (likely(!list_empty(&dev->rx_reqs))) { + int error; + + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del_init(&req->list); + + /* The USB Host sends us whatever amount of data it wants to + * so we always set the length field to the full USB_BUFSIZE. + * If the amount of data is more than the read() caller asked + * for it will be stored in the request buffer until it is + * asked for by read(). + */ + req->length = USB_BUFSIZE; + req->complete = rx_complete; + + error = usb_ep_queue(dev->out_ep, req, GFP_ATOMIC); + if (error) { + DBG(dev, "rx submit --> %d\n", error); + list_add(&req->list, &dev->rx_reqs); + break; + } else { + list_add(&req->list, &dev->rx_reqs_active); + } + } + + bytes_copied = 0; + current_rx_req = dev->current_rx_req; + current_rx_bytes = dev->current_rx_bytes; + current_rx_buf = dev->current_rx_buf; + dev->current_rx_req = NULL; + dev->current_rx_bytes = 0; + dev->current_rx_buf = NULL; + + /* Check if there is any data in the read buffers. Please note that + * current_rx_bytes is the number of bytes in the current rx buffer. + * If it is zero then check if there are any other rx_buffers that + * are on the completed list. We are only out of data if all rx + * buffers are empty. + */ + if ((current_rx_bytes == 0) && + (likely(list_empty(&dev->rx_buffers)))) { + /* Turn interrupts back on before sleeping. */ + spin_u |