Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:pzskc383
kernel
linux-2.6.34-moorestown-still-image-gadget-driv...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File linux-2.6.34-moorestown-still-image-gadget-driver.patch of Package kernel
Index: linux-2.6.33/drivers/usb/gadget/Kconfig =================================================================== --- linux-2.6.33.orig/drivers/usb/gadget/Kconfig +++ linux-2.6.33/drivers/usb/gadget/Kconfig @@ -853,6 +853,14 @@ config USB_G_MULTI_CDC If unsure, say "y". +config USB_STILL_IMAGE + tristate "Still Image Capture Gadget" + help + The Still Image Capture Gadget acts as a USB Still Image + Capture Device. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "g_still_image". # put drivers that need isochronous transfer support (for audio # or video class gadget drivers), or specific hardware, here. Index: linux-2.6.33/drivers/usb/gadget/Makefile =================================================================== --- linux-2.6.33.orig/drivers/usb/gadget/Makefile +++ linux-2.6.33/drivers/usb/gadget/Makefile @@ -43,6 +43,7 @@ g_mass_storage-objs := mass_storage.o g_printer-objs := printer.o g_cdc-objs := cdc2.o g_multi-objs := multi.o +g_still_image-objs := still_image.o obj-$(CONFIG_USB_ZERO) += g_zero.o obj-$(CONFIG_USB_AUDIO) += g_audio.o @@ -55,4 +56,5 @@ obj-$(CONFIG_USB_G_PRINTER) += g_printer obj-$(CONFIG_USB_MIDI_GADGET) += g_midi.o obj-$(CONFIG_USB_CDC_COMPOSITE) += g_cdc.o obj-$(CONFIG_USB_G_MULTI) += g_multi.o +obj-$(CONFIG_USB_STILL_IMAGE) += g_still_image.o Index: linux-2.6.33/drivers/usb/gadget/still_image.c =================================================================== --- /dev/null +++ linux-2.6.33/drivers/usb/gadget/still_image.c @@ -0,0 +1,4464 @@ +/* + * still_image.c -- Lite USB Still Image Capture Gadget, for USB development + * Copyright (C) 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +/* + * This code is partly based on: + * File-backed USB Storage Gadget driver, Copyright (C) 2003-2008 Alan Stern + * + * + * Refer to the USB Device Class Definition for Still Image Capture Device: + * http://www.usb.org/developers/devclass_docs/usb_still_img10.zip + * + * + * Supported PIMA 15740/PTP operations: + * - GetDeviceInfo + * - OpenSession + * - CloseSession + * - GetStorageIDs + * - GetStorageInfo + * - GetNumObjects + * - GetObjectHandles + * - GetObjectInfo + * - GetObject + * - DeleteObject + * - SendObjectInfo + * - SendObject + * - CopyObject + * - MoveObject + * + * Supported object formats: + * - EXIF/JPEG, JFIF + * - PNG + * - TIFF, TIFF/IT, TIFF/EP + * - BMP + * - GIF + * - Unknown image object + * - Undefined non-image object + * + * Supported PIMA 15740/PTP events: + * - N/A + * + * Storage filesystem type: + * - Generic hierarchical + * + * + * Module options: + * folder=foldername Default NULL, name of the backing folder + * vendor=0xVVVV Default 0x8087 (Intel), USB Vendor ID + * product=0xPPPP Default 0x811e, USB Product ID + * release=0xRRRR Override the USB release number (bcdDevice) + * buflen=N Default N=16384, buffer size used (will be + * rounded down to a multiple of + * PAGE_CACHE_SIZE) + * + * Sysfs attribute file: + * folder read/write the name of the backing folder + * + */ + + +#define VERBOSE_DEBUG + +#include <linux/blkdev.h> +#include <linux/completion.h> +#include <linux/dcache.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fcntl.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/vfs.h> +#include <linux/namei.h> +#include <linux/kref.h> +#include <linux/kthread.h> +#include <linux/limits.h> +#include <linux/rwsem.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/freezer.h> +#include <linux/utsname.h> +#include <linux/sort.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include "gadget_chips.h" + +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_DESC "Still Image Gadget" +#define DRIVER_NAME "g_still_image" +#define DRIVER_VERSION "Nov 17, 2009" + + +static const char longname[] = DRIVER_DESC; +static const char shortname[] = DRIVER_NAME; + + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Xiaochen Shen <xiaochen.shen@intel.com>; " + "Hang Yuan <hang.yuan@intel.com>"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); + + +/* + * Intel Corporation donates this product ID. + * + * DO NOT REUSE THESE IDs with any other driver + * instead: allocate your own, using normal USB-IF procedures. + */ +#define DRIVER_VENDOR_ID 0x8087 +#define DRIVER_PRODUCT_ID 0x811e + + +/*-------------------------------------------------------------------------*/ + +#define MDBG(fmt, args...) \ + pr_debug(DRIVER_NAME ": " fmt, ## args) +#define MINFO(fmt, args...) \ + pr_info(DRIVER_NAME ": " fmt, ## args) + +#ifdef DEBUG +#define DBG(d, fmt, args...) \ + dev_dbg(&(d)->gadget->dev, fmt, ## args) +#else +#define DBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + + +#ifndef DEBUG +#undef VERBOSE_DEBUG +#endif /* !DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VDBG DBG +#else +#define VDBG(sti, fmt, args...) \ + do { } while (0) +#endif /* VERBOSE_DEBUG */ + +#define ERROR(d, fmt, args...) \ + dev_err(&(d)->gadget->dev, fmt, ## args) +#define WARNING(d, fmt, args...) \ + dev_warn(&(d)->gadget->dev, fmt, ## args) +#define INFO(d, fmt, args...) \ + dev_info(&(d)->gadget->dev, fmt, ## args) + + +/*-------------------------------------------------------------------------*/ + +/* encapsulate the module parameter settings */ + +static struct { + char *folder; + unsigned short vendor; + unsigned short product; + unsigned short release; + unsigned int buflen; +} mod_data = { /* default values */ + .vendor = DRIVER_VENDOR_ID, + .product = DRIVER_PRODUCT_ID, + .release = 0xffff, /* use controller chip type */ + .buflen = 16384, +}; + + +module_param_named(folder, mod_data.folder, charp, S_IRUGO); +MODULE_PARM_DESC(folder, "name of the backing folder"); + +module_param_named(vendor, mod_data.vendor, ushort, S_IRUGO); +MODULE_PARM_DESC(vendor, "USB Vendor ID"); + +module_param_named(product, mod_data.product, ushort, S_IRUGO); +MODULE_PARM_DESC(product, "USB Product ID"); + +module_param_named(release, mod_data.release, ushort, S_IRUGO); +MODULE_PARM_DESC(release, "USB release number"); + +module_param_named(buflen, mod_data.buflen, uint, S_IRUGO); +MODULE_PARM_DESC(buflen, "I/O buffer size"); + + +/*-------------------------------------------------------------------------*/ + +/* + * DESCRIPTORS ... most are static, but strings and (full) configuration + * descriptors are built on demand. Also the (static) config and interface + * descriptors are adjusted during sti_bind(). + */ +#define STRING_MANUFACTURER 1 +#define STRING_PRODUCT 2 +#define STRING_SERIAL 3 +#define STRING_CONFIG 4 +#define STRING_INTERFACE 5 + + +/* only one configuration */ +#define CONFIG_VALUE 1 + +static struct usb_device_descriptor +device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + /* the next three values can be overridden by module parameters */ + .idVendor = cpu_to_le16(DRIVER_VENDOR_ID), + .idProduct = cpu_to_le16(DRIVER_PRODUCT_ID), + .bcdDevice = cpu_to_le16(0xffff), + + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIAL, + .bNumConfigurations = 1, +}; + +static struct usb_config_descriptor +config_desc = { + .bLength = sizeof config_desc, + .bDescriptorType = USB_DT_CONFIG, + + /* wTotalLength computed by usb_gadget_config_buf() */ + .bNumInterfaces = 1, + .bConfigurationValue = CONFIG_VALUE, + .iConfiguration = STRING_CONFIG, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = CONFIG_USB_GADGET_VBUS_DRAW / 2, +}; + +static struct usb_otg_descriptor +otg_desc = { + .bLength = sizeof(otg_desc), + .bDescriptorType = USB_DT_OTG, + + .bmAttributes = USB_OTG_SRP, +}; + + +/* one interface */ +static struct usb_interface_descriptor +intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 3, /* adjusted during sti_bind() */ + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 0x01, /* Still Image Capture device */ + .bInterfaceProtocol = 0x01, /* Bulk-only protocol */ + .iInterface = STRING_INTERFACE, +}; + + +/* two full-speed endpoint descriptors: bulk-in, bulk-out */ + +static struct usb_endpoint_descriptor +fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_endpoint_descriptor +fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_endpoint_descriptor +fs_intr_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 32, /* frames -> 32 ms */ +}; + +static const struct usb_descriptor_header *fs_function[] = { + (struct usb_descriptor_header *) &otg_desc, + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &fs_bulk_in_desc, + (struct usb_descriptor_header *) &fs_bulk_out_desc, + (struct usb_descriptor_header *) &fs_intr_in_desc, + NULL, +}; + +#define FS_FUNCTION_PRE_EP_ENTRIES 2 + + +/* + * USB 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + * + * That means alternate endpoint descriptors (bigger packets) + * and a "device qualifier" ... plus more construction options + * for the config descriptor. + */ +static struct usb_qualifier_descriptor +dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + .bNumConfigurations = 1, +}; + +static struct usb_endpoint_descriptor +hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_in_desc during sti_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor +hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_out_desc during sti_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + .bInterval = 1, /* NAK every 1 uframe */ +}; + +static struct usb_endpoint_descriptor +hs_intr_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_intr_in_desc during sti_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 9, /* 2**(9-1) = 256 uframes -> 32 ms */ +}; + +static const struct usb_descriptor_header *hs_function[] = { + (struct usb_descriptor_header *) &otg_desc, + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &hs_bulk_in_desc, + (struct usb_descriptor_header *) &hs_bulk_out_desc, + (struct usb_descriptor_header *) &hs_intr_in_desc, + NULL, +}; + +#define HS_FUNCTION_PRE_EP_ENTRIES 2 + + +/* maxpacket and other transfer characteristics vary by speed. */ +static struct usb_endpoint_descriptor * +ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs, + struct usb_endpoint_descriptor *hs) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + + return fs; +} + +static char manufacturer[64]; +static char serial[13]; + +/* static strings, in UTF-8 (for simplicity we use only ASCII characters) */ +static struct usb_string strings[] = { + {STRING_MANUFACTURER, manufacturer}, + {STRING_PRODUCT, longname}, + {STRING_SERIAL, serial}, + {STRING_CONFIG, "Self-powered"}, + {STRING_INTERFACE, "Still Image"}, + {} +}; + +static struct usb_gadget_strings stringtab = { + .language = 0x0409, /* en-us */ + .strings = strings, +}; + + +/*-------------------------------------------------------------------------*/ + +/* protocol, driver and device data structures */ + +/* page size of file system */ +#define SIZE_OF_PAGE 4096 + +/* big enough to hold the biggest descriptor */ +#define EP0_BUFSIZE 256 + +#define DELAYED_STATUS (EP0_BUFSIZE + 999) + +#define OBJECTS_NUM_MAX 128 + +/* number of buffers we will use. 2 is enough for double-buffering */ +#define NUM_BUFFERS 2 + +/* PIMA 15740, operation and response datasets have at most 5 parameters */ +#define PARAM_NUM_MAX 5 + +/* PIMA 15740 generic container head length */ +#define PIMA15740_CONTAINER_LEN 12 + +/* storage id, description */ +#define STORAGE_ID 0x00010001 +#define STORAGE_DESCRIPTION "Built-in Storage" + +/* Still Image class-specific requests */ +#define STI_CANCEL_REQUEST 0x64 +#define STI_GET_EXTENDED_EVENT_DATA 0x65 +#define STI_DEVICE_RESET_REQUEST 0x66 +#define STI_GET_DEVICE_STATUS 0x67 + +#define STI_CANCEL_REQUEST_LENGTH 0x0006 +#define STI_CANCEL_REQUEST_CODE 0x4001 + +/* supported PIMA 15740 operations */ +#define PIMA15740_OP_GET_DEVICE_INFO 0x1001 +#define PIMA15740_OP_OPEN_SESSION 0x1002 +#define PIMA15740_OP_CLOSE_SESSION 0x1003 +#define PIMA15740_OP_GET_STORAGE_IDS 0x1004 +#define PIMA15740_OP_GET_STORAGE_INFO 0x1005 +#define PIMA15740_OP_GET_NUM_OBJECTS 0x1006 +#define PIMA15740_OP_GET_OBJECT_HANDLES 0x1007 +#define PIMA15740_OP_GET_OBJECT_INFO 0x1008 +#define PIMA15740_OP_GET_OBJECT 0x1009 +#define PIMA15740_OP_DELETE_OBJECT 0x100b +#define PIMA15740_OP_SEND_OBJECT_INFO 0x100c +#define PIMA15740_OP_SEND_OBJECT 0x100d +#define PIMA15740_OP_MOVE_OBJECT 0x1019 +#define PIMA15740_OP_COPY_OBJECT 0x101a + +/* PIMA 15740 responses definition */ +#define PIMA15740_RES_UNDEFINED 0x2000 +#define PIMA15740_RES_OK 0x2001 +#define PIMA15740_RES_GENERAL_ERROR 0x2002 +#define PIMA15740_RES_SESSION_NOT_OPEN 0x2003 +#define PIMA15740_RES_INVALID_TRANS_ID 0x2004 +#define PIMA15740_RES_OPERATION_NOT_SUPPORTED 0x2005 +#define PIMA15740_RES_PARAMETER_NOT_SUPPORTED 0x2006 +#define PIMA15740_RES_INCOMPLETE_TRANSFER 0x2007 +#define PIMA15740_RES_INVALID_STORAGE_ID 0x2008 +#define PIMA15740_RES_INVALID_OBJECT_HANDLE 0x2009 +#define PIMA15740_RES_DEVICE_PROP_NOT_SUPPORTED 0x200a +#define PIMA15740_RES_INVALID_OBJECT_FORMAT 0x200b +#define PIMA15740_RES_STORE_FULL 0x200c +#define PIMA15740_RES_OBJECT_WRITE_PROTECTED 0x200d +#define PIMA15740_RES_STORE_READ_ONLY 0x200e +#define PIMA15740_RES_ACCESS_DENIED 0x200f +#define PIMA15740_RES_NO_THUMBNAIL_PRESENT 0x2010 +#define PIMA15740_RES_SELF_TEST_FAILED 0x2011 +#define PIMA15740_RES_PARTIAL_DELETION 0x2012 +#define PIMA15740_RES_STORE_NOT_AVAILABLE 0x2013 +#define PIMA15740_RES_SPEC_BY_FORMAT_UNSUP 0x2014 +#define PIMA15740_RES_NO_VALID_OBJECT_INFO 0x2015 +#define PIMA15740_RES_INVALID_CODE_FORMAT 0x2016 +#define PIMA15740_RES_UNKNOWN_VENDOR_CODE 0x2017 +#define PIMA15740_RES_CAPTURE_ALREADY_TERM 0x2018 +#define PIMA15740_RES_DEVICE_BUSY 0x2019 +#define PIMA15740_RES_INVALID_PARENT_OBJECT 0x201a +#define PIMA15740_RES_INVALID_DEV_PROP_FORMAT 0x201b +#define PIMA15740_RES_INVALID_DEV_PROP_VALUE 0x201c +#define PIMA15740_RES_INVALID_PARAMETER 0x201d +#define PIMA15740_RES_SESSION_ALREADY_OPEN 0x201e +#define PIMA15740_RES_TRANSACTION_CANCELLED 0x201f +#define PIMA15740_RES_SPEC_OF_DESTINATION_UNSUP 0x2020 + +/* PIMA 15740 functional mode */ +#define PIMA15740_STANDARD_MODE 0x0000 +#define PIMA15740_SLEEP_STATE_MODE 0x0001 + +/* PIMA 15740 storage type */ +#define PIMA15740_STOR_UNDEFINED 0x0000 +#define PIMA15740_STOR_FIXED_ROM 0x0001 +#define PIMA15740_STOR_REMOVABLE_ROM 0x0002 +#define PIMA15740_STOR_FIXED_RAM 0x0003 +#define PIMA15740_STOR_REMOVABLE_RAM 0x0004 + +/* PIMA 15740 filesystem type */ +#define PIMA15740_FS_UNDEFINED 0x0000 +#define PIMA15740_FS_GENERIC_FLAT 0x0001 +#define PIMA15740_FS_HIERARCHICAL 0x0002 +#define PIMA15740_FS_DCF 0x0003 + +/* PIMA 15740 access capability */ +#define PIMA15740_ACCESS_CAP_RW 0x0000 +#define PIMA15740_ACCESS_CAP_RO_WO_DELITION 0x0001 +#define PIMA15740_ACCESS_CAP_RO_W_DELITION 0x0002 + +/* PIMA 15740 object format codes */ +#define PIMA15740_FMT_A_UNDEFINED 0x3000 +#define PIMA15740_FMT_A_ASSOCIATION 0x3001 +#define PIMA15740_FMT_I_UNDEFINED 0x3800 +#define PIMA15740_FMT_I_EXIF_JPEG 0x3801 +#define PIMA15740_FMT_I_TIFF_EP 0x3802 +#define PIMA15740_FMT_I_FLASHPIX 0x3803 +#define PIMA15740_FMT_I_BMP 0x3804 +#define PIMA15740_FMT_I_CIFF 0x3805 +#define PIMA15740_FMT_I_GIF 0x3807 +#define PIMA15740_FMT_I_JFIF 0x3808 +#define PIMA15740_FMT_I_PCD 0x3809 +#define PIMA15740_FMT_I_PICT 0x380a +#define PIMA15740_FMT_I_PNG 0x380b +#define PIMA15740_FMT_I_TIFF 0x380d +#define PIMA15740_FMT_I_TIFF_IT 0x380e +#define PIMA15740_FMT_I_JP2 0x380f +#define PIMA15740_FMT_I_JPX 0x3810 + +/* PIMA 15740 object protection status */ +#define PIMA15740_OBJECT_NO_PROTECTION 0x0000 +#define PIMA15740_OBJECT_READ_ONLY 0x0001 + +/* PIMA 15740 object association type */ +#define PIMA15740_AS_UNDEFINED 0x0000 +#define PIMA15740_AS_GENERIC_FOLDER 0x0001 + + +static const char storage_desc[] = STORAGE_DESCRIPTION; +static const char device_version[] = DRIVER_VERSION; + + +/*-------------------------------------------------------------------------*/ + +/* PIMA 15740 data structure */ + +enum pima15740_container_type { + TYPE_UNDEFINED = 0, + TYPE_COMMAND_BLOCK = 1, + TYPE_DATA_BLOCK = 2, + TYPE_RESPONSE_BLOCK = 3, + TYPE_EVENT_BLOCK = 4, +}; + +/* PIMA15740 generic container structure, little endian */ +struct pima15740_container { + __le32 container_len; + __le16 container_type; + __le16 code; + __le32 transaction_id; +} __attribute__ ((packed)); + +/* data stage of Get Extended Event Data */ +struct sti_ext_event { + u16 event_code; + u32 transaction_id; + u16 param_num; +} __attribute__ ((packed)); + +/* data stage of Get Device Status Data */ +struct sti_dev_status { + u16 wlength; + u16 code; +} __attribute__ ((packed)); + + +/* DeviceInfo Dataset */ +struct pima15740_device_info { + u16 standard_version; + u32 vendor_extension_id; + u16 vendor_extension_version; + u8 vendor_extension_desc_len; + u8 vendor_extension_desc[0]; + u16 functional_mode; + u32 operations_supported_count; + u16 operations_supported[14]; + u32 events_supported_count; + u16 events_supported[0]; + u32 device_properties_count; + u16 device_properties_supported[0]; + u32 capture_formats_count; + u16 capture_formats[0]; + u32 image_formats_count; + u16 image_formats[10]; + u8 manufacturer_len; + u8 manufacturer[sizeof(manufacturer) * 2]; + u8 model_len; + u8 model[sizeof(longname) * 2]; + u8 device_version_len; + u8 device_version[sizeof(device_version) * 2]; + u8 serial_number_len; + u8 serial_number[sizeof(serial) * 2]; +} __attribute__ ((packed)); + +static struct pima15740_device_info sti_device_info = { + .standard_version = 100, + .vendor_extension_id = 0, + .vendor_extension_version = 0, + .vendor_extension_desc_len = 0, + .functional_mode = PIMA15740_STANDARD_MODE, + .operations_supported_count = 14, + .operations_supported = { + cpu_to_le16(PIMA15740_OP_GET_DEVICE_INFO), + cpu_to_le16(PIMA15740_OP_OPEN_SESSION), + cpu_to_le16(PIMA15740_OP_CLOSE_SESSION), + cpu_to_le16(PIMA15740_OP_GET_STORAGE_IDS), + cpu_to_le16(PIMA15740_OP_GET_STORAGE_INFO), + cpu_to_le16(PIMA15740_OP_GET_NUM_OBJECTS), + cpu_to_le16(PIMA15740_OP_GET_OBJECT_HANDLES), + cpu_to_le16(PIMA15740_OP_GET_OBJECT_INFO), + cpu_to_le16(PIMA15740_OP_GET_OBJECT), + cpu_to_le16(PIMA15740_OP_DELETE_OBJECT), + cpu_to_le16(PIMA15740_OP_SEND_OBJECT_INFO), + cpu_to_le16(PIMA15740_OP_SEND_OBJECT), + cpu_to_le16(PIMA15740_OP_COPY_OBJECT), + cpu_to_le16(PIMA15740_OP_MOVE_OBJECT) + }, + .events_supported_count = 0, + .device_properties_count = 0, + .capture_formats_count = 0, + .image_formats_count = 10, + .image_formats = { + cpu_to_le16(PIMA15740_FMT_I_EXIF_JPEG), + cpu_to_le16(PIMA15740_FMT_I_JFIF), + cpu_to_le16(PIMA15740_FMT_I_PNG), + cpu_to_le16(PIMA15740_FMT_I_TIFF), + cpu_to_le16(PIMA15740_FMT_I_TIFF_EP), + cpu_to_le16(PIMA15740_FMT_I_TIFF_IT), + cpu_to_le16(PIMA15740_FMT_I_BMP), + cpu_to_le16(PIMA15740_FMT_I_GIF), + cpu_to_le16(PIMA15740_FMT_I_UNDEFINED), + cpu_to_le16(PIMA15740_FMT_A_UNDEFINED) + }, + /* others will be filled in sti_bind() */ +}; + + +/* StorageInfo Dataset */ +struct pima15740_storage_info { + u16 storage_type; + u16 filesystem_type; + u16 access_capability; + u64 max_capacity; + u64 free_space_in_bytes; + u32 free_space_in_images; + u8 storage_desc_len; + u8 storage_desc[sizeof(storage_desc) * 2]; + u8 volume_label_len; + u8 volume_label[0]; +} __attribute__ ((packed)); + +static struct pima15740_storage_info sti_storage_info = { + .storage_type = cpu_to_le16(PIMA15740_STOR_FIXED_RAM), + .filesystem_type = cpu_to_le16(PIMA15740_FS_HIERARCHICAL), + .access_capability = cpu_to_le16(PIMA15740_ACCESS_CAP_RW), + .storage_desc_len = sizeof(storage_desc), + .volume_label_len = 0, + /* others will be filled later */ +}; + + +/* ObjectInfo Dataset */ +struct pima15740_object_info { + u32 storage_id; + u16 object_format; + u16 protection_status; + u32 object_compressed_size; + u16 thumb_format; + u32 thumb_compressed_size; + u32 thumb_pix_width; + u32 thumb_pix_height; + u32 image_pix_width; + u32 image_pix_height; + u32 image_bit_depth; + u32 parent_object; + u16 association_type; + u32 association_desc; + u32 sequence_number; + /* filename, capture date, modification date, keywords */ + u8 obj_strings[]; /* size will be fixed later */ +} __attribute__ ((packed)); + +/* object list element with object info data */ +struct sti_object { + struct list_head list; + u32 obj_handle; + u32 parent_object; + u32 storage_id; + int is_dir; + int send_valid; + size_t obj_info_size; + char filename[NAME_MAX]; + char full_path[PATH_MAX]; + struct pima15740_object_info obj_info; +}; + + +/*-------------------------------------------------------------------------*/ + +/* device data structure */ + +enum sti_buffer_state { + BUF_STATE_EMPTY = 0, + BUF_STATE_FULL, + BUF_STATE_BUSY +}; + +struct sti_buffhd { + void *buf; + enum sti_buffer_state state; + struct sti_buffhd *next; + unsigned int bulk_out_intended_length; + struct usb_request *inreq; + int inreq_busy; + struct usb_request *outreq; + int outreq_busy; +}; + +enum sti_state { + STI_STATE_COMMAND_PHASE = -10, /* this one isn't used anywhere */ + STI_STATE_DATA_PHASE, + STI_STATE_STATUS_PHASE, + + STI_STATE_IDLE = 0, + STI_STATE_ABORT_BULK_OUT, + STI_STATE_RESET, + STI_STATE_INTERFACE_CHANGE, + STI_STATE_CONFIG_CHANGE, + STI_STATE_DISCONNECT, + STI_STATE_EXIT, + STI_STATE_TERMINATED +}; + +enum data_direction { + DATA_DIR_UNKNOWN = 0, + DATA_DIR_FROM_HOST, + DATA_DIR_TO_HOST, + DATA_DIR_NONE +}; + +struct sti_dev { + /* lock protects: device, req, endpoints states */ + spinlock_t lock; + + /* filesem protects: backing folder in use */ + struct rw_semaphore filesem; + + struct usb_gadget *gadget; + + /* reference counting: wait until released */ + struct kref ref; + + /* handy copy of gadget->ep0 */ + struct usb_ep *ep0; + + /* for control responses */ + struct usb_request *ep0req; + unsigned int ep0_req_tag; + const char *ep0req_name; + + /* for interrupt responses */ + struct usb_request *intreq; + int intreq_busy; + struct sti_buffhd *intr_buffhd; + + /* for exception handling */ + enum sti_state state; + unsigned int exception_req_tag; + + unsigned int bulk_out_maxpacket; + u8 config, new_config; + + unsigned int running:1; + unsigned int bulk_in_enabled:1; + unsigned int bulk_out_enabled:1; + unsigned int intr_in_enabled:1; + unsigned int registered:1; + unsigned int session_open:1; + + unsigned long atomic_bitflags; +#define REGISTERED 0 +#define CLEAR_BULK_HALTS 1 +#define SUSPENDED 2 + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; + struct usb_ep *intr_in; + + struct sti_buffhd *next_buffhd_to_fill; + struct sti_buffhd *next_buffhd_to_drain; + struct sti_buffhd buffhds[NUM_BUFFERS]; + + int thread_wakeup_needed; + struct completion thread_notifier; + struct task_struct *thread_task; + + __le32 container_len; + __le16 container_type; + __le16 code; + __le32 transaction_id; + + __le16 response_code; + + u32 ops_params[PARAM_NUM_MAX]; + u32 session_id; + u32 storage_id; + u32 object_num; + u32 sub_object_num; + + char root_path[PATH_MAX]; + struct file *root_filp; + struct list_head obj_list; + struct list_head tmp_obj_list; + + struct sti_ext_event ext_event_data; + struct sti_dev_status status_data; + + struct device dev; +}; + + +/*-------------------------------------------------------------------------*/ + +#define backing_folder_is_open(sti) ((sti)->root_filp != NULL) + + +typedef void (*sti_routine_t)(struct sti_dev *); + +static int exception_in_progress(struct sti_dev *sti) +{ + return (sti->state > STI_STATE_IDLE); +} + +/* make bulk-out requests be divisible by the maxpacket size */ +static void set_bulk_out_req_length(struct sti_dev *sti, + struct sti_buffhd *bh, unsigned int length) +{ + unsigned int rem; + VDBG(sti, "---> %s()\n", __func__); + + bh->bulk_out_intended_length = length; + rem = length % sti->bulk_out_maxpacket; + if (rem > 0) + length += sti->bulk_out_maxpacket - rem; + bh->outreq->length = length; + + VDBG(sti, "<--- %s()\n", __func__); +} + + +/* global variables */ +static struct sti_dev *the_sti; +static struct usb_gadget_driver sti_driver; + +static void close_backing_folder(struct sti_dev *sti); + + +/*-------------------------------------------------------------------------*/ + +#ifdef VERBOSE_DEBUG + +static void dump_msg(struct sti_dev *sti, const char *label, + const u8 *buf, unsigned int length) +{ + if (length < 512) { + DBG(sti, "%s, length %u:\n", label, length); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, + 16, 1, buf, length, 0); + } +} + +static void dump_cb(struct sti_dev *sti) +{ + print_hex_dump(KERN_DEBUG, "PIMA15740 Command Block: ", + DUMP_PREFIX_NONE, 16, 1, &sti->container_len, + PIMA15740_CONTAINER_LEN, 0); +} + +static void dump_device_info(struct sti_dev *sti) +{ + int i; + + VDBG(sti, "DeviceInfo Dataset:\n"); + VDBG(sti, "\tstandard_version: %u\n", + sti_device_info.standard_version); + VDBG(sti, "\tvendor_extension_id: %u\n", + sti_device_info.vendor_extension_id); + VDBG(sti, "\tvendor_extension_version: %u\n", + sti_device_info.vendor_extension_version); + VDBG(sti, "\tvendor_extension_desc_len: %u\n", + sti_device_info.vendor_extension_desc_len); + VDBG(sti, "\tfunctional_mode: 0x%04x\n", + sti_device_info.functional_mode); + VDBG(sti, "\toperations_supported_count: %u\n", + sti_device_info.operations_supported_count); + VDBG(sti, "\toperations_supported:\n"); + for (i = 0; i < sti_device_info.operations_supported_count; i++) + VDBG(sti, "\t\t0x%04x\n", + sti_device_info.operations_supported[i]); + VDBG(sti, "\tevents_supported_count: %u\n", + sti_device_info.events_supported_count); + VDBG(sti, "\tdevice_properties_count: %u\n", + sti_device_info.device_properties_count); + VDBG(sti, "\tcapture_formats_count: %u\n", + sti_device_info.capture_formats_count); + VDBG(sti, "\timage_formats_count: %u\n", + sti_device_info.image_formats_count); + VDBG(sti, "\tmanufacturer_len: %u\n", + sti_device_info.manufacturer_len); + VDBG(sti, "\tmanufacturer: %s\n", manufacturer); + VDBG(sti, "\tmodel_len: %u\n", + sti_device_info.model_len); + VDBG(sti, "\tmodel: %s\n", longname); + VDBG(sti, "\tdevice_version_len: %u\n", + sti_device_info.device_version_len); + VDBG(sti, "\tdevice_version: %s\n", device_version); + VDBG(sti, "\tserial_number_len: %u\n", + sti_device_info.serial_number_len); + VDBG(sti, "\tserial_number: %s\n", serial); +} + +static void dump_storage_info(struct sti_dev *sti) +{ + VDBG(sti, "StorageInfo Dataset:\n"); + VDBG(sti, "\tstorage_type: 0x%04x\n", sti_storage_info.storage_type); + VDBG(sti, "\tfilesystem_type: 0x%04x\n", + sti_storage_info.filesystem_type); + VDBG(sti, "\taccess_capability: 0x%04x\n", + sti_storage_info.access_capability); + VDBG(sti, "\tmax_capacity: %llu\n", sti_storage_info.max_capacity); + VDBG(sti, "\tfree_space_in_bytes: %llu\n", + sti_storage_info.free_space_in_bytes); + VDBG(sti, "\tfree_space_in_images: %u\n", + sti_storage_info.free_space_in_images); + VDBG(sti, "\tstorage_desc_len: %u\n", + sti_storage_info.storage_desc_len); + VDBG(sti, "\tstorage_desc: %s\n", storage_desc); + VDBG(sti, "\tvolume_label_len: %u\n", + sti_storage_info.volume_label_len); +} + +static void dump_object_info(struct sti_dev *sti, struct sti_object *obj) +{ + u8 filename_len; + + VDBG(sti, "ObjectInfo Dataset:\n"); + VDBG(sti, "\tstorage_id: 0x%08x\n", obj->obj_info.storage_id); + VDBG(sti, "\tobject_format: 0x%04x\n", obj->obj_info.object_format); + VDBG(sti, "\tprotection_status: 0x%04x\n", + obj->obj_info.protection_status); + VDBG(sti, "\tobject_compressed_size: %u\n", + obj->obj_info.object_compressed_size); + VDBG(sti, "\tthumb_format: %u\n", obj->obj_info.thumb_format); + VDBG(sti, "\tthumb_compressed_size: %u\n", + obj->obj_info.thumb_compressed_size); + VDBG(sti, "\tthumb_pix_width: %u\n", + obj->obj_info.thumb_pix_width); + VDBG(sti, "\tthumb_pix_height: %u\n", + obj->obj_info.thumb_pix_height); + VDBG(sti, "\timage_pix_width: %u\n", + obj->obj_info.image_pix_width); + VDBG(sti, "\timage_pix_height: %u\n", + obj->obj_info.image_pix_height); + VDBG(sti, "\timage_bit_depth: %u\n", + obj->obj_info.image_bit_depth); + VDBG(sti, "\tparent_object: 0x%08x\n", + obj->obj_info.parent_object); + VDBG(sti, "\tassociation_type: 0x%04x\n", + obj->obj_info.association_type); + VDBG(sti, "\tassociation_desc: 0x%08x\n", + obj->obj_info.association_desc); + VDBG(sti, "\tsequence_number: 0x%08x\n", + obj->obj_info.sequence_number); + VDBG(sti, "\tfilename_len: %u\n", obj->obj_info.obj_strings[0]); + filename_len = obj->obj_info.obj_strings[0]; + VDBG(sti, "\tfilename: %s\n", obj->filename); + VDBG(sti, "\tcapture_date_len: %u\n", + obj->obj_info.obj_strings[filename_len * 2 + 1]); + VDBG(sti, "\tmodification_date_len: %u\n", + obj->obj_info.obj_strings[filename_len * 2 + 2]); + VDBG(sti, "\tkeywords_len: %u\n", + obj->obj_info.obj_strings[filename_len * 2 + 3]); +} + +#else + +static void dump_msg(struct sti_dev *sti, const char *label, + const u8 *buf, unsigned int length) +{} + +static void dump_cb(struct sti_dev *sti) +{} + +static void dump_device_info(struct sti_dev *sti) +{} + +static void dump_storage_info(struct sti_dev *sti) +{} + +static void dump_object_info(struct sti_dev *sti, struct sti_object *obj) +{} + +#endif /* VERBOSE_DEBUG */ + + +/*-------------------------------------------------------------------------*/ + + + +/* + * Config descriptors must agree with the code that sets configurations + * and with code managing interfaces and their altsettings. They must + * also handle different speeds and other-speed requests. + */ +static int populate_config_buf(struct usb_gadget *gadget, + u8 *buf, u8 type, unsigned index) +{ + enum usb_device_speed speed = gadget->speed; + int len; + const struct usb_descriptor_header **function; + + if (index > 0) + return -EINVAL; + + if (gadget_is_dualspeed(gadget) && type == USB_DT_OTHER_SPEED_CONFIG) + speed = (USB_SPEED_FULL + USB_SPEED_HIGH) - speed; + if (gadget_is_dualspeed(gadget) && speed == USB_SPEED_HIGH) + function = hs_function; + else + function = fs_function; + + /* for now, don't advertise srp-only devices */ + if (!gadget_is_otg(gadget)) + function++; + + len = usb_gadget_config_buf(&config_desc, buf, EP0_BUFSIZE, function); + ((struct usb_config_descriptor *) buf)->bDescriptorType = type; + + return len; +} + + +/*-------------------------------------------------------------------------*/ + +/* these routines may be called in process context or in_irq */ + +/* caller must hold sti->lock */ +static void wakeup_thread(struct sti_dev *sti) +{ + VDBG(sti, "---> %s()\n", __func__); + + /* tell the main thread that something has happened */ + sti->thread_wakeup_needed = 1; + if (sti->thread_task) + wake_up_process(sti->thread_task); + + VDBG(sti, "<--- %s()\n", __func__); +} + + +static void raise_exception(struct sti_dev *sti, enum sti_state new_state) +{ + unsigned long flags; + VDBG(sti, "---> %s()\n", __func__); + + /* + * Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. + */ + spin_lock_irqsave(&sti->lock, flags); + if (sti->state <= new_state) { + sti->exception_req_tag = sti->ep0_req_tag; + sti->state = new_state; + if (sti->thread_task) + send_sig_info(SIGUSR1, SEND_SIG_FORCED, + sti->thread_task); + } + spin_unlock_irqrestore(&sti->lock, flags); + + VDBG(sti, "<--- %s()\n", __func__); +} + + +/*-------------------------------------------------------------------------*/ + +/* + * The disconnect callback and ep0 routines. These always run in_irq, + * except that ep0_queue() is called in the main thread to acknowledge + * completion of various requests: set config, set interface, and + * Bulk-only device reset. + */ + +static void sti_disconnect(struct usb_gadget *gadget) +{ + struct sti_dev *sti = get_gadget_data(gadget); + VDBG(sti, "---> %s()\n", __func__); + + DBG(sti, "disconnect or port reset\n"); + raise_exception(sti, STI_STATE_DISCONNECT); + + VDBG(sti, "<--- %s()\n", __func__); +} + +static int ep0_queue(struct sti_dev *sti) +{ + int rc; + VDBG(sti, "---> %s()\n", __func__); + + rc = usb_ep_queue(sti->ep0, sti->ep0req, GFP_ATOMIC); + if (rc != 0 && rc != -ESHUTDOWN) { + /* we can't do much more than wait for a reset */ + WARNING(sti, "error in submission: %s --> %d\n", + sti->ep0->name, rc); + } + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + +static void ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sti_dev *sti = ep->driver_data; + VDBG(sti, "---> %s()\n", __func__); + + if (req->actual > 0) + dump_msg(sti, sti->ep0req_name, req->buf, req->actual); + + if (req->status || req->actual != req->length) + VDBG(sti, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + + /* request was cancelled */ + if (req->status == -ECONNRESET) + usb_ep_fifo_flush(ep); + + if (req->status == 0 && req->context) + ((sti_routine_t) (req->context))(sti); + + VDBG(sti, "<--- %s()\n", __func__); +} + + +/*-------------------------------------------------------------------------*/ + +/* endpoint completion handlers, always run in_irq */ + +static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sti_dev *sti = ep->driver_data; + struct sti_buffhd *bh = req->context; + VDBG(sti, "---> %s()\n", __func__); + + if (req->status || req->actual != req->length) + VDBG(sti, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + /* request was cancelled */ + if (req->status == -ECONNRESET) + usb_ep_fifo_flush(ep); + + /* hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&sti->lock); + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(sti); + spin_unlock(&sti->lock); + + VDBG(sti, "<--- %s()\n", __func__); +} + +static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sti_dev *sti = ep->driver_data; + struct sti_buffhd *bh = req->context; + VDBG(sti, "---> %s()\n", __func__); + + dump_msg(sti, "bulk-out", req->buf, req->actual); + if (req->status || req->actual != bh->bulk_out_intended_length) + VDBG(sti, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, + bh->bulk_out_intended_length); + + /* request was cancelled */ + if (req->status == -ECONNRESET) + usb_ep_fifo_flush(ep); + + /* hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&sti->lock); + bh->outreq_busy = 0; + bh->state = BUF_STATE_FULL; + wakeup_thread(sti); + spin_unlock(&sti->lock); + + VDBG(sti, "<--- %s()\n", __func__); +} + + +/*-------------------------------------------------------------------------*/ + +static int sti_set_halt(struct sti_dev *sti, struct usb_ep *ep) +{ + const char *name; + VDBG(sti, "---> %s()\n", __func__); + + if (ep == sti->bulk_in) + name = "bulk-in"; + else if (ep == sti->bulk_out) + name = "bulk-out"; + else + name = ep->name; + + DBG(sti, "%s set halt\n", name); + VDBG(sti, "<--- %s()\n", __func__); + + return usb_ep_set_halt(ep); +} + +static int halt_bulk_in_endpoint(struct sti_dev *sti) +{ + int rc; + VDBG(sti, "---> %s()\n", __func__); + + rc = sti_set_halt(sti, sti->bulk_in); + if (rc == -EAGAIN) + VDBG(sti, "delayed bulk-in endpoint halt\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(sti, "usb_ep_set_halt -> %d\n", rc); + rc = 0; + break; + } + + /* wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + + rc = usb_ep_set_halt(sti->bulk_in); + } + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static void received_cancel_request(struct sti_dev *sti) +{ + struct usb_request *req = sti->ep0req; + u16 cancel_code; + u32 trans_id; + VDBG(sti, "---> %s()\n", __func__); + + /* error in command transfer */ + if (req->status || req->length != req->actual) { + /* wait for reset */ + sti_set_halt(sti, sti->ep0); + return; + } + + VDBG(sti, "receive cancel request\n"); + + if (!req->buf) + return; + + spin_lock(&sti->lock); + cancel_code = get_unaligned_le16(req->buf); + if (cancel_code != cpu_to_le16(STI_CANCEL_REQUEST_CODE)) { + VDBG(sti, "invalid cancel_code: 0x%04x\n", cancel_code); + goto out; + } + + trans_id = get_unaligned_le32(req->buf + 2); + if (trans_id != sti->transaction_id) { + VDBG(sti, "invalid trans_id:0x%04x\n", trans_id); + goto out; + } + + /* stall bulk endpoints */ + sti_set_halt(sti, sti->bulk_out); + halt_bulk_in_endpoint(sti); + + sti->response_code = PIMA15740_RES_TRANSACTION_CANCELLED; +out: + wakeup_thread(sti); + spin_unlock(&sti->lock); + + VDBG(sti, "<--- %s()\n", __func__); +} + + +/* ep0 class-specific request handlers, always run in_irq */ +static int class_setup_req(struct sti_dev *sti, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_request *req = sti->ep0req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->config) + return value; + + /* handle class-specific requests */ + switch (ctrl->bRequest) { + + case STI_CANCEL_REQUEST: + if (ctrl->bRequestType != (USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0 || w_length != 6) { + value = -EDOM; + break; + } + + DBG(sti, "cancel request\n"); + + value = w_length; + sti->ep0req->context = received_cancel_request; + break; + + case STI_GET_EXTENDED_EVENT_DATA: + /* asynchronous events by interrupt endpoint */ + if (ctrl->bRequestType != (USB_DIR_IN | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0) { + value = -EDOM; + break; + } + + DBG(sti, "get extended event data\n"); + + sti->ext_event_data.event_code = PIMA15740_RES_OK; + sti->ext_event_data.transaction_id = sti->transaction_id; + sti->ext_event_data.param_num = 0; + + value = min_t(unsigned, w_length, + sizeof(struct sti_ext_event)); + memcpy(req->buf, &sti->ext_event_data, value); + break; + + case STI_DEVICE_RESET_REQUEST: + if (ctrl->bRequestType != (USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0 || w_length != 0) { + value = -EDOM; + break; + } + + /* Raise an exception to stop the current operation + * and reinitialize our state. */ + DBG(sti, "device reset request\n"); + + sti->response_code = PIMA15740_RES_OK; + sti->session_open = 0; + + raise_exception(sti, STI_STATE_RESET); + value = DELAYED_STATUS; + break; + + case STI_GET_DEVICE_STATUS: + if (ctrl->bRequestType != (USB_DIR_IN | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0) { + value = -EDOM; + break; + } + + DBG(sti, "get device status\n"); + sti->status_data.wlength = 4; + sti->status_data.code = cpu_to_le16(PIMA15740_RES_OK); + + value = min_t(unsigned, w_length, + sizeof(struct sti_dev_status)); + memcpy(req->buf, &sti->status_data, value); + break; + + default: + DBG(sti, "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + le16_to_cpu(ctrl->wValue), w_index, w_length); + break; + } + + VDBG(sti, "<--- %s()\n", __func__); + return value; +} + + +/*-------------------------------------------------------------------------*/ + +/* ep0 standard request handlers, always run in_irq */ + +static int standard_setup_req(struct sti_dev *sti, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_request *req = sti->ep0req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + VDBG(sti, "---> %s()\n", __func__); + + /* usually this just stores reply data in the pre-allocated ep0 buffer, + * but config change events will also reconfigure hardware */ + switch (ctrl->bRequest) { + + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + VDBG(sti, "get device descriptor\n"); + value = sizeof device_desc; + memcpy(req->buf, &device_desc, value); + break; + case USB_DT_DEVICE_QUALIFIER: + VDBG(sti, "get device qualifier\n"); + if (!gadget_is_dualspeed(sti->gadget)) + break; + value = sizeof dev_qualifier; + memcpy(req->buf, &dev_qualifier, value); + break; + + case USB_DT_OTHER_SPEED_CONFIG: + VDBG(sti, "get other-speed config descriptor\n"); + if (!gadget_is_dualspeed(sti->gadget)) + break; + goto get_config; + case USB_DT_CONFIG: + VDBG(sti, "get configuration descriptor\n"); +get_config: + value = populate_config_buf(sti->gadget, + req->buf, + w_value >> 8, + w_value & 0xff); + break; + + case USB_DT_STRING: + VDBG(sti, "get string descriptor\n"); + + /* wIndex == language code */ + value = usb_gadget_get_string(&stringtab, + w_value & 0xff, req->buf); + break; + } + break; + + /* one config, two speeds */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + VDBG(sti, "set configuration\n"); + if (w_value == CONFIG_VALUE || w_value == 0) { + sti->new_config = w_value; + + /* Raise an exception to wipe out previous transaction + * state (queued bufs, etc) and set the new config. */ + raise_exception(sti, STI_STATE_CONFIG_CHANGE); + value = DELAYED_STATUS; + } + break; + + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + VDBG(sti, "get configuration\n"); + *(u8 *) req->buf = sti->config; + value = 1; + break; + + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_INTERFACE)) + break; + if (sti->config && w_index == 0) { + + /* Raise an exception to wipe out previous transaction + * state (queued bufs, etc) and install the new + * interface altsetting. */ + raise_exception(sti, STI_STATE_INTERFACE_CHANGE); + value = DELAYED_STATUS; + } + break; + + case USB_REQ_GET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_INTERFACE)) + break; + if (!sti->config) + break; + if (w_index != 0) { + value = -EDOM; + break; + } + VDBG(sti, "get interface\n"); + *(u8 *) req->buf = 0; + value = 1; + break; + + default: + VDBG(sti, "unknown control req %02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, le16_to_cpu(ctrl->wLength)); + } + + VDBG(sti, "<--- %s()\n", __func__); + return value; +} + +static int sti_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + struct sti_dev *sti = get_gadget_data(gadget); + int rc; + int w_length = le16_to_cpu(ctrl->wLength); + VDBG(sti, "---> %s()\n", __func__); + + /* record arrival of a new request */ + ++sti->ep0_req_tag; + sti->ep0req->context = NULL; + sti->ep0req->length = 0; + dump_msg(sti, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl)); + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) + rc = class_setup_req(sti, ctrl); + else + rc = standard_setup_req(sti, ctrl); + + /* respond with data/status or defer until later */ + if (rc >= 0 && rc != DELAYED_STATUS) { + rc = min(rc, w_length); + sti->ep0req->length = rc; + sti->ep0req->zero = rc < w_length; + sti->ep0req_name = (ctrl->bRequestType & USB_DIR_IN ? + "ep0-in" : "ep0-out"); + rc = ep0_queue(sti); + } + + VDBG(sti, "<--- %s()\n", __func__); + /* device either stalls (rc < 0) or reports success */ + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +/* all the following routines run in process context */ + +/* use this for bulk or interrupt transfers, not ep0 */ +static void start_transfer(struct sti_dev *sti, struct usb_ep *ep, + struct usb_request *req, int *pbusy, + enum sti_buffer_state *state) +{ + int rc; + VDBG(sti, "---> %s()\n", __func__); + + if (ep == sti->bulk_in) + dump_msg(sti, "bulk-in", req->buf, req->length); + else if (ep == sti->intr_in) + dump_msg(sti, "intr-in", req->buf, req->length); + + spin_lock_irq(&sti->lock); + *pbusy = 1; + *state = BUF_STATE_BUSY; + spin_unlock_irq(&sti->lock); + + rc = usb_ep_queue(ep, req, GFP_KERNEL); + VDBG(sti, "start_transfer, rc: %d\n", rc); + if (rc != 0) { + *pbusy = 0; + *state = BUF_STATE_EMPTY; + if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && + req->length == 0)) + WARNING(sti, "error in submission: %s --> %d\n", + ep->name, rc); + } + + VDBG(sti, "<--- %s()\n", __func__); +} + + +static int sleep_thread(struct sti_dev *sti) +{ + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + /* wait until a signal arrives or we are woken up */ + for (;;) { + try_to_freeze(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + rc = -EINTR; + break; + } + if (sti->thread_wakeup_needed) + break; + + schedule(); + } + + __set_current_state(TASK_RUNNING); + sti->thread_wakeup_needed = 0; + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int fill_data_container(struct sti_buffhd *bh, + struct sti_dev *sti, unsigned int size) +{ + struct pima15740_container *rb; + VDBG(sti, "---> %s()\n", __func__); + + rb = bh->buf; + + rb->container_len = size; + rb->container_type = TYPE_DATA_BLOCK; + rb->code = sti->code; + rb->transaction_id = sti->transaction_id; + + bh->inreq->zero = 0; + + VDBG(sti, "<--- %s()\n", __func__); + return 0; +} + + +static int send_response(struct sti_dev *sti, unsigned int code) +{ + struct sti_buffhd *bh; + struct pima15740_container *rb; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + /* wait for the next buffer to become available */ + bh = sti->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(sti); + if (rc) + return rc; + } + + rb = bh->buf; + + rb->container_len = PIMA15740_CONTAINER_LEN; + rb->container_type = TYPE_RESPONSE_BLOCK; + rb->code = code; + rb->transaction_id = sti->transaction_id; + + bh->inreq->length = PIMA15740_CONTAINER_LEN; + bh->state = BUF_STATE_FULL; + bh->inreq->zero = 0; + + start_transfer(sti, sti->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + + sti->next_buffhd_to_fill = bh->next; + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int send_params_response(struct sti_dev *sti, unsigned int code, + u32 p1, u32 p2, u32 p3, unsigned p_num) +{ + struct sti_buffhd *bh; + struct pima15740_container *rb; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + /* wait for the next buffer to become available */ + bh = sti->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(sti); + if (rc) + return rc; + } + + rb = bh->buf; + + rb->container_len = PIMA15740_CONTAINER_LEN + p_num * 4; + rb->container_type = TYPE_RESPONSE_BLOCK; + rb->code = code; + rb->transaction_id = sti->transaction_id; + + switch (p_num) { + case 3: + memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN, &p1, 4); + memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN + 4, &p2, 4); + memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN + 8, &p3, 4); + break; + case 2: + memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN, &p1, 4); + memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN + 4, &p2, 4); + break; + case 1: + memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN, &p1, 4); + break; + default: + break; + } + + bh->inreq->length = PIMA15740_CONTAINER_LEN + p_num * 4; + bh->state = BUF_STATE_FULL; + bh->inreq->zero = 0; + + start_transfer(sti, sti->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + + sti->next_buffhd_to_fill = bh->next; + + VDBG(sti, "<--- %s()\n", __func__); + return rc; + +} + +/* ISO-8859-1 to UTF-16LE */ +static unsigned short str_to_uni16(const char *src, char *dest) +{ + unsigned int i; + + for (i = 0; i < strlen(src); i++) { + dest[i * 2] = src[i]; + dest[i * 2 + 1] = '\0'; + } + + /* null-terminated string */ + dest[i * 2] = dest[i * 2 + 1] = '\0'; + + return (i + 1) * 2; +} + +/* UTF-16LE to ISO-8859-1 */ +static void uni16_to_str(const char *src, char *dest, unsigned short len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + dest[i] = src[i * 2]; +} + + +static int do_get_device_info(struct sti_dev *sti, struct sti_buffhd *bh) +{ + size_t size; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + /* dump DeviceInfo Dataset */ + dump_device_info(sti); + + size = sizeof sti_device_info; + fill_data_container(bh, sti, PIMA15740_CONTAINER_LEN + size); + + memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &sti_device_info, size); + + bh->inreq->length = PIMA15740_CONTAINER_LEN + size; + bh->state = BUF_STATE_FULL; + start_transfer(sti, sti->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + sti->next_buffhd_to_fill = bh->next; + + /* send response */ + rc = send_response(sti, PIMA15740_RES_OK); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int filldir_all(void *__buf, const char *name, int len, + loff_t pos, u64 ino, unsigned int d_type) +{ + struct sti_dev *sti = __buf; + struct sti_object *obj; + char *ext; + u8 filename_len; + char filename_utf16le[NAME_MAX * 2]; + size_t obj_size; + u16 object_format = PIMA15740_FMT_A_UNDEFINED; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + VDBG(sti, "name: %s, len: %d, pos: %lu, ino: %llu, d_type: %u\n", + name, len, (unsigned long)pos, ino, d_type); + + /* ignore "." and ".." directories */ + if (!strcmp(name, ".") || !strcmp(name, "..")) + goto out; + + if (d_type != DT_DIR && d_type != DT_REG) + goto out; + + /* filename strings length */ + filename_len = len + 1; + VDBG(sti, "filename_len: %u\n", filename_len); + + /* sti_object size */ + obj_size = sizeof(*obj) + 2 * filename_len + 4; + VDBG(sti, "obj_size: %u\n", obj_size); + obj = kzalloc(obj_size, GFP_KERNEL); + if (!obj) { + rc = -ENOMEM; + goto out; + } + + /* fill part of sti_object info */ + obj->storage_id = STORAGE_ID; + obj->send_valid = 0; + + /* ObjectInfo Dataset size */ + obj->obj_info_size = sizeof(struct pima15740_object_info) + + 2 * filename_len + 4; + VDBG(sti, "obj_info_size: %u\n", obj->obj_info_size); + + /* filename */ + memset(obj->filename, 0, sizeof(obj->filename)); + strncpy(obj->filename, name, len); + + /* fill ObjectInfo Dataset */ + obj->obj_info.storage_id = cpu_to_le32(STORAGE_ID); + + if (d_type == DT_DIR) { /* association */ + object_format = PIMA15740_FMT_A_ASSOCIATION; + obj->obj_info.association_type = + cpu_to_le16(PIMA15740_AS_GENERIC_FOLDER); + obj->is_dir = 1; + } else if (d_type == DT_REG) { /* regular file */ + ext = strrchr(obj->filename, '.'); + if (ext) { + /* image object */ + if (!strcasecmp(ext, ".jpg") || + !strcasecmp(ext, ".jpeg") || + !strcasecmp(ext, ".jpe")) + object_format = PIMA15740_FMT_I_EXIF_JPEG; + else if (!strcasecmp(ext, ".jfif")) + object_format = PIMA15740_FMT_I_JFIF; + else if (!strcasecmp(ext, ".tif") || + !strcasecmp(ext, ".tiff")) + object_format = PIMA15740_FMT_I_TIFF; + else if (!strcasecmp(ext, ".png")) + object_format = PIMA15740_FMT_I_PNG; + else if (!strcasecmp(ext, ".bmp")) + object_format = PIMA15740_FMT_I_BMP; + else if (!strcasecmp(ext, ".gif")) + object_format = PIMA15740_FMT_I_GIF; + else /* undefined non-image object */ + object_format = PIMA15740_FMT_A_UNDEFINED; + } else /* file without extension */ + object_format = PIMA15740_FMT_A_UNDEFINED; + obj->obj_info.association_type = + cpu_to_le16(PIMA15740_AS_UNDEFINED); + obj->is_dir = 0; + } + obj->obj_info.object_format = cpu_to_le16(object_format); + + /* protection_status, object_compressed_size will be filled later */ + obj->obj_info.thumb_format = cpu_to_le16(0); + obj->obj_info.thumb_compressed_size = cpu_to_le32(0); + obj->obj_info.thumb_pix_width = cpu_to_le32(0); + obj->obj_info.thumb_pix_height = cpu_to_le32(0); + obj->obj_info.image_pix_width = cpu_to_le32(0); + obj->obj_info.image_pix_height = cpu_to_le32(0); + obj->obj_info.image_bit_depth = cpu_to_le32(0); + + obj->obj_info.association_desc = cpu_to_le32(0); + obj->obj_info.sequence_number = cpu_to_le32(0); + + /* filename_utf16le: UTF-16LE unicode string */ + obj->obj_info.obj_strings[0] = filename_len; + memset(filename_utf16le, 0, sizeof(filename_utf16le)); + str_to_uni16(obj->filename, filename_utf16le); + memcpy(obj->obj_info.obj_strings + 1, filename_utf16le, + filename_len * 2); + + /* capture date */ + obj->obj_info.obj_strings[filename_len * 2 + 1] = 0; + + /* modification date */ + obj->obj_info.obj_strings[filename_len * 2 + 2] = 0; + + /* keywords */ + obj->obj_info.obj_strings[filename_len * 2 + 3] = 0; + + /* increase object number */ + sti->sub_object_num++; + + /* add to temp object list */ + list_add_tail(&obj->list, &sti->tmp_obj_list); +out: + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +/* alphabetic sort function */ +static int alnumsort(const void *a, const void *b) +{ + const struct sti_object *oa = *(const struct sti_object **)a; + const struct sti_object *ob = *(const struct sti_object **)b; + return strcmp(oa->filename, ob->filename); +} + + +/* descend through the hierarchical folder recursively */ +static int list_objects(struct sti_dev *sti, const char *folder_name, + struct sti_object *folder_obj) +{ + struct file *filp; + struct dentry *dentry; + struct sti_object *obj = NULL; + struct sti_object *tmp_obj; + struct sti_object **pobj; + struct kstat stat; + u32 parent_object; + int i, rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + /* root directory */ + if (!strcmp(folder_name, sti->root_path)) { + filp = sti->root_filp; + parent_object = 0; + VDBG(sti, "root directory\n"); + } else { /* subdirectory */ + filp = filp_open(folder_name, O_RDONLY | O_DIRECTORY, 0); + if (IS_ERR(filp)) { + ERROR(sti, "unable to open folder: %s\n", + folder_name); + return PTR_ERR(filp); + } + VDBG(sti, "folder_name: %s\n", folder_name); + parent_object = folder_obj->obj_handle; + } + dentry = filp->f_dentry; + + sti->sub_object_num = 0; + filp->f_pos = 0; + rc = vfs_readdir(filp, filldir_all, sti); + if (rc) + ERROR(sti, "vfs_readdir %s error: %d\n", + folder_name, rc); + VDBG(sti, "%d objects in folder %s\n", + sti->sub_object_num, folder_name); + + /* no file in the directory */ + if (!sti->sub_object_num) + goto out; + + /* pre-allocated objects array */ + pobj = kzalloc(OBJECTS_NUM_MAX * sizeof(struct sti_object *), + GFP_KERNEL); + if (!pobj) { + rc = -ENOMEM; + goto out; + } + + i = 0; + list_for_each_entry_safe(obj, tmp_obj, &sti->tmp_obj_list, list) { + pobj[i] = obj; + /* remove from temp object list */ + list_del_init(&obj->list); + i++; + } + VDBG(sti, "i = %d\n", i); + pobj[i] = NULL; + + /* sort the objects array */ + sort(pobj, sti->sub_object_num, sizeof(struct sti_object *), + alnumsort, NULL); + + while (*pobj) { + /* increase total object number */ + sti->object_num++; + + /* fill object handle */ + (*pobj)->obj_handle = sti->object_num; + + /* fill parent object */ + (*pobj)->parent_object = cpu_to_le32(parent_object); + (*pobj)->obj_info.parent_object = cpu_to_le32(parent_object); + + /* object full path */ + memset((*pobj)->full_path, 0, sizeof((*pobj)->full_path)); + snprintf((*pobj)->full_path, sizeof((*pobj)->full_path), + "%s/%s", folder_name, (*pobj)->filename); + + VDBG(sti, "full_path: %s, obj_handle: 0x%08x, " + "parent_object: 0x%08x\n", + (*pobj)->full_path, (*pobj)->obj_handle, + parent_object); + + /* get file statistics info */ + rc = vfs_stat((char __user *)(*pobj)->full_path, &stat); + if (rc) { + ERROR(sti, "vfs_stat error: %d\n", rc); + goto out; + } + + /* fill remained ObjectInfo Dataset */ + if (stat.mode & S_IWUSR) + (*pobj)->obj_info.protection_status = + cpu_to_le16(PIMA15740_OBJECT_NO_PROTECTION); + else + (*pobj)->obj_info.protection_status = + cpu_to_le16(PIMA15740_OBJECT_READ_ONLY); + + (*pobj)->obj_info.object_compressed_size = + cpu_to_le32((u32)stat.size); + + /* add to object list */ + list_add_tail(&(*pobj)->list, &sti->obj_list); + + if ((*pobj)->is_dir) + list_objects(sti, (*pobj)->full_path, *pobj); + + pobj++; + } + + /* free pre-allocated objects array */ + kfree(pobj); +out: + if (strcmp(folder_name, sti->root_path)) + filp_close(filp, current->files); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_open_session(struct sti_dev *sti) +{ + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_ALREADY_OPEN; + goto out; + } + + sti->session_id = sti->ops_params[0]; + VDBG(sti, "session_id: 0x%08x\n", sti->session_id); + if (sti->session_id) { + sti->response_code = PIMA15740_RES_OK; + sti->session_open = 1; + } else { + sti->response_code = PIMA15740_RES_INVALID_PARAMETER; + sti->session_open = 0; + goto out; + } + + /* reset total object number */ + sti->object_num = 0; + + /* enumerate all objects */ + rc = list_objects(sti, sti->root_path, NULL); + if (rc) + sti->response_code = PIMA15740_RES_DEVICE_BUSY; +out: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_close_session(struct sti_dev *sti) +{ + struct sti_object *obj, *tmp_obj; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (sti->session_open) { + sti->response_code = PIMA15740_RES_OK; + sti->session_open = 0; + } else { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out; + } + + spin_lock_irq(&sti->lock); + + /* release object list */ + list_for_each_entry_safe(obj, tmp_obj, &sti->obj_list, list) { + list_del_init(&obj->list); + kfree(obj); + } + + spin_unlock_irq(&sti->lock); + + DBG(sti, "release object list\n"); +out: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_get_storage_ids(struct sti_dev *sti, struct sti_buffhd *bh) +{ + size_t size; + u32 i; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out; + } + + sti->storage_id = cpu_to_le32(STORAGE_ID); + DBG(sti, "storage_id: 0x%08x\n", sti->storage_id); + + /* 4 bytes array number and 4 bytes storage id */ + size = 8; + fill_data_container(bh, sti, PIMA15740_CONTAINER_LEN + size); + + /* support one storage id */ + i = 1; + memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &i, 4); + memcpy(bh->buf + PIMA15740_CONTAINER_LEN + 4, &sti->storage_id, 4); + + bh->inreq->length = PIMA15740_CONTAINER_LEN + size; + bh->state = BUF_STATE_FULL; + start_transfer(sti, sti->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + sti->next_buffhd_to_fill = bh->next; + + sti->response_code = PIMA15740_RES_OK; +out: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_get_storage_info(struct sti_dev *sti, struct sti_buffhd *bh) +{ + size_t size; + u32 storage_id; + u64 sbytes_max, sbytes_free; + struct kstatfs sbuf; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out; + } + + /* storage id */ + storage_id = sti->ops_params[0]; + if (storage_id != sti->storage_id) { + WARNING(sti, "invalid storage id: 0x%08x\n", storage_id); + sti->response_code = PIMA15740_RES_INVALID_STORAGE_ID; + goto out; + } + + /* get filesystem statistics info */ + rc = vfs_statfs(sti->root_filp->f_dentry, &sbuf); + if (rc) { + sti->response_code = PIMA15740_RES_ACCESS_DENIED; + goto out; + } + + /* fill remained items in StorageInfo Dataset */ + sbytes_max = (u64) sbuf.f_bsize * sbuf.f_blocks; + sbytes_free = (u64) sbuf.f_bsize * sbuf.f_bfree; + sti_storage_info.max_capacity = cpu_to_le64(sbytes_max); + sti_storage_info.free_space_in_bytes = cpu_to_le64(sbytes_free); + sti_storage_info.free_space_in_images = cpu_to_le32((u32)~0); + str_to_uni16(storage_desc, sti_storage_info.storage_desc); + + /* dump StorageInfo Dataset */ + dump_storage_info(sti); + + memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &sti_storage_info, + sizeof(sti_storage_info)); + + size = PIMA15740_CONTAINER_LEN + sizeof(sti_storage_info); + fill_data_container(bh, sti, size); + + bh->inreq->length = size; + bh->state = BUF_STATE_FULL; + start_transfer(sti, sti->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + sti->next_buffhd_to_fill = bh->next; + + sti->response_code = PIMA15740_RES_OK; +out: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_get_num_objects(struct sti_dev *sti, struct sti_buffhd *bh) +{ + int i; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out; + } + + for (i = 0; i < PARAM_NUM_MAX; i++) + VDBG(sti, "parameter[%u]: 0x%08x\n", + i + 1, sti->ops_params[i]); + + if (!backing_folder_is_open(sti)) { + ERROR(sti, "backing folder is not open\n"); + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + goto out; + } + + DBG(sti, "total object number: %u\n", sti->object_num); + + sti->response_code = PIMA15740_RES_OK; +out: + /* send response */ + rc = send_params_response(sti, sti->response_code, + sti->object_num, 0, 0, + 1); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_get_object_handles(struct sti_dev *sti, struct sti_buffhd *bh) +{ + int i; + size_t size; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out; + } + + for (i = 0; i < PARAM_NUM_MAX; i++) + VDBG(sti, "parameter[%u]: 0x%08x\n", + i + 1, sti->ops_params[i]); + + if (!backing_folder_is_open(sti)) { + ERROR(sti, "backing folder is not open\n"); + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + goto out; + } + + /* 4 bytes array number plus object handles size */ + size = 4 + sti->object_num * 4; + VDBG(sti, "total object number: %u, payload size: %u\n", + sti->object_num, size); + fill_data_container(bh, sti, PIMA15740_CONTAINER_LEN + size); + + /* fill object handles array */ + memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &sti->object_num, 4); + for (i = 1; i <= sti->object_num; i++) + memcpy(bh->buf + PIMA15740_CONTAINER_LEN + i * 4, &i, 4); + + bh->inreq->length = PIMA15740_CONTAINER_LEN + size; + bh->state = BUF_STATE_FULL; + start_transfer(sti, sti->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + sti->next_buffhd_to_fill = bh->next; + + sti->response_code = PIMA15740_RES_OK; +out: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_get_object_info(struct sti_dev *sti, struct sti_buffhd *bh) +{ + size_t size = 0; + u32 obj_handle; + struct sti_object *obj; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out; + } + + obj_handle = sti->ops_params[0]; + if (obj_handle == 0 || obj_handle > sti->object_num) { + WARNING(sti, "invalid object handle: 0x%08x\n", obj_handle); + sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE; + goto out; + } + + spin_lock_irq(&sti->lock); + + /* find the object */ + list_for_each_entry(obj, &sti->obj_list, list) { + if (obj->obj_handle == obj_handle) + break; + } + + spin_unlock_irq(&sti->lock); + + memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &obj->obj_info, + obj->obj_info_size); + size = PIMA15740_CONTAINER_LEN + obj->obj_info_size; + fill_data_container(bh, sti, size); + + bh->inreq->length = size; + bh->state = BUF_STATE_FULL; + start_transfer(sti, sti->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + sti->next_buffhd_to_fill = bh->next; + + DBG(sti, "get object info: %s\n", obj->full_path); + VDBG(sti, "obj_handle: 0x%08x\n", obj->obj_handle); + + /* dump ObjectInfo Dataset */ + dump_object_info(sti, obj); + + sti->response_code = PIMA15740_RES_OK; +out: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_get_object(struct sti_dev *sti, struct sti_buffhd *bh) +{ + u32 obj_handle; + loff_t file_size, file_offset, file_offset_tmp; + unsigned int amount_left, amount; + ssize_t nread; + struct sti_object *obj; + struct file *filp = NULL; + struct inode *inode = NULL; + char __user *buf; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out1; + } + + obj_handle = sti->ops_params[0]; + if (obj_handle == 0 || obj_handle > sti->object_num) { + WARNING(sti, "invalid object handle: 0x%08x\n", obj_handle); + sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE; + goto out1; + } + + spin_lock_irq(&sti->lock); + + /* find the object */ + list_for_each_entry(obj, &sti->obj_list, list) { + if (obj->obj_handle == obj_handle) + break; + } + + spin_unlock_irq(&sti->lock); + + /* open object file */ + filp = filp_open(obj->full_path, O_RDONLY | O_LARGEFILE, 0); + if (IS_ERR(filp)) { + ERROR(sti, "unable to open file: %s. Err = %d\n", + obj->full_path, (int) PTR_ERR(filp)); + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + goto out1; + } + + /* figure out the size and read the remaining amount */ + inode = filp->f_dentry->d_inode; + file_size = i_size_read(inode->i_mapping->host); + VDBG(sti, "object file size: %llu\n", (unsigned long long) file_size); + if (unlikely(file_size == 0)) { + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + goto out2; + } + + DBG(sti, "get object: %s\n", obj->full_path); + + file_offset = 0; + amount_left = file_size; + + while (amount_left > 0) { + bh = sti->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(sti); + if (rc) { + filp_close(filp, current->files); + return rc; + } + } + + /* don't read more than the buffer size */ + if (file_offset == 0) { + fill_data_container(bh, sti, + file_size + PIMA15740_CONTAINER_LEN); + buf = (char __user *) bh->buf + + PIMA15740_CONTAINER_LEN; + amount = min((unsigned int) amount_left, + mod_data.buflen - PIMA15740_CONTAINER_LEN); + } else { + buf = (char __user *) bh->buf; + amount = min((unsigned int) amount_left, + mod_data.buflen); + } + + /* no more left to read */ + if (amount == 0) + break; + + /* perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(filp, buf, amount, &file_offset_tmp); + VDBG(sti, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + + if (signal_pending(current)) { + filp_close(filp, current->files); + return -EINTR; + } + + if (nread < 0) { + WARNING(sti, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + WARNING(sti, "partial file read: %d/%u\n", + (int) nread, amount); + /* round down to a block */ + nread -= (nread & 511); + } + + /* + * PIMA 15740 generic container head resides in + * first data block payload + */ + if (file_offset == 0) + bh->inreq->length = nread + PIMA15740_CONTAINER_LEN; + else + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + bh->inreq->zero = 0; + + file_offset += nread; + amount_left -= nread; + + /* send this buffer and go read some more */ + start_transfer(sti, sti->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + sti->next_buffhd_to_fill = bh->next; + } + + sti->response_code = PIMA15740_RES_OK; +out2: + filp_close(filp, current->files); +out1: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_delete_object(struct sti_dev *sti, struct sti_buffhd *bh) +{ + u32 obj_handle; + struct sti_object *obj, *tmp_obj; + struct nameidata nd; + int i; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out; + } + + for (i = 0; i < PARAM_NUM_MAX; i++) + VDBG(sti, "parameter[%u]: 0x%08x\n", + i + 1, sti->ops_params[i]); + + obj_handle = sti->ops_params[0]; + if (obj_handle == 0 || obj_handle > sti->object_num) { + WARNING(sti, "invalid object handle: 0x%08x\n", obj_handle); + sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE; + goto out; + } + + spin_lock_irq(&sti->lock); + + /* find the object */ + list_for_each_entry_safe(obj, tmp_obj, &sti->obj_list, list) { + if (obj->obj_handle == obj_handle) { + list_del_init(&obj->list); + kfree(obj); + break; + } + } + + spin_unlock_irq(&sti->lock); + + /* lookup the object file */ + rc = path_lookup(obj->full_path, 0, &nd); + if (rc) { + ERROR(sti, "invalid object file path: %s\n", obj->full_path); + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + goto out; + } + + /* unlink the file */ + rc = vfs_unlink(nd.path.dentry->d_parent->d_inode, nd.path.dentry); + if (rc) { + ERROR(sti, "can't delete object\n"); + sti->response_code = PIMA15740_RES_DEVICE_BUSY; + goto out; + } + + DBG(sti, "delete object: %s\n", obj->full_path); + + sti->response_code = PIMA15740_RES_OK; +out: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_send_object_info(struct sti_dev *sti, struct sti_buffhd *bh) +{ + u8 filename_len; + u32 storage_id; + u32 parent_object = 0xffffffff; + unsigned int offset; + struct sti_object *obj, *parent_obj; + size_t obj_size; + int i; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out2; + } + + for (i = 0; i < PARAM_NUM_MAX; i++) + VDBG(sti, "parameter[%u]: 0x%08x\n", + i + 1, sti->ops_params[i]); + + /* destination storage id */ + storage_id = sti->ops_params[0]; + if (storage_id != STORAGE_ID) { + WARNING(sti, "invalid storage id: 0x%08x\n", storage_id); + sti->response_code = PIMA15740_RES_INVALID_STORAGE_ID; + goto out2; + } + + /* parent object handle where object should be placed */ + parent_object = sti->ops_params[1]; + + /* if root directory, parent object is 0xffffffff */ + if (parent_object == 0 || (parent_object > sti->object_num + && parent_object != 0xffffffff)) { + WARNING(sti, "invalid parent handle: 0x%08x\n", + parent_object); + sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT; + goto out2; + } + + /* queue a request to read ObjectInfo Dataset */ + set_bulk_out_req_length(sti, bh, 512); + bh->outreq->short_not_ok = 1; + start_transfer(sti, sti->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + + /* wait for the ObjectInfo Dataset to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(sti); + if (rc) + goto out1; + } + + /* filename strings length */ + offset = offsetof(struct pima15740_object_info, obj_strings[0]); + filename_len = *(u8 *)(bh->outreq->buf + PIMA15740_CONTAINER_LEN + + offset); + VDBG(sti, "filename_len: %u\n", filename_len); + + /* sti_object size */ + obj_size = sizeof(*obj) + 2 * filename_len + 4; + VDBG(sti, "obj_size: %u\n", obj_size); + obj = kzalloc(obj_size, GFP_KERNEL); + if (!obj) { + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + goto out2; + } + + spin_lock_irq(&sti->lock); + + /* increase total object number */ + sti->object_num++; + + /* fill sti_object info */ + obj->obj_handle = sti->object_num; + VDBG(sti, "obj_handle: 0x%08x\n", obj->obj_handle); + + if (parent_object == 0xffffffff) + obj->parent_object = 0; + else + obj->parent_object = parent_object; + VDBG(sti, "parent_object: 0x%08x\n", obj->parent_object); + + obj->storage_id = storage_id; + + /* mark object ready to send */ + obj->send_valid = 1; + + /* ObjectInfo Dataset size */ + obj->obj_info_size = sizeof(struct pima15740_object_info) + + 2 * filename_len + 4; + VDBG(sti, "obj_info_size: %u\n", obj->obj_info_size); + + /* filename */ + offset = offsetof(struct pima15740_object_info, obj_strings[1]); + uni16_to_str(bh->outreq->buf + PIMA15740_CONTAINER_LEN + offset, + obj->filename, filename_len); + + /* object full path */ + memset(obj->full_path, 0, sizeof(obj->full_path)); + if (parent_object == 0xffffffff) { + snprintf(obj->full_path, sizeof(obj->full_path), "%s/%s", + sti->root_path, obj->filename); + } else { + /* find the parent object */ + list_for_each_entry(parent_obj, &sti->obj_list, list) { + if (parent_obj->obj_handle == parent_object) + break; + } + snprintf(obj->full_path, sizeof(obj->full_path), "%s/%s", + parent_obj->full_path, obj->filename); + } + VDBG(sti, "full_path: %s\n", obj->full_path); + + /* fetch ObjectInfo Dataset from buffer */ + memcpy(&obj->obj_info, bh->outreq->buf + PIMA15740_CONTAINER_LEN, + obj->obj_info_size); + + /* root directory, modify parent object */ + if (parent_object == 0xffffffff) + obj->obj_info.parent_object = cpu_to_le32(0); + + /* capture date */ + obj->obj_info.obj_strings[filename_len * 2 + 1] = 0; + + /* modification date */ + obj->obj_info.obj_strings[filename_len * 2 + 2] = 0; + + /* keywords */ + obj->obj_info.obj_strings[filename_len * 2 + 3] = 0; + + bh->state = BUF_STATE_EMPTY; + + /* add to object list */ + list_add_tail(&obj->list, &sti->obj_list); + + spin_unlock_irq(&sti->lock); + + DBG(sti, "send object info: %s\n", obj->filename); + + /* dump ObjectInfo Dataset */ + dump_object_info(sti, obj); +out2: + /* send response */ + rc = send_params_response(sti, PIMA15740_RES_OK, + sti->storage_id, parent_object, sti->object_num, + 3); +out1: + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_send_object(struct sti_dev *sti, struct sti_buffhd *bh) +{ + int rc = -EINVAL; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + loff_t file_size, file_offset, file_offset_tmp, + usb_offset; + unsigned int amount; + ssize_t nwritten; + struct sti_object *obj; + struct file *filp = NULL; + char __user *buf; + VDBG(sti, "---> %s()\n", __func__); + + spin_lock_irq(&sti->lock); + + /* find the object */ + list_for_each_entry(obj, &sti->obj_list, list) { + if (obj->send_valid) + break; + } + + /* mark object already sent */ + obj->send_valid = 0; + + spin_unlock_irq(&sti->lock); + + /* open object file */ + filp = filp_open(obj->full_path, O_CREAT | O_RDWR | O_LARGEFILE, 0666); + if (IS_ERR(filp)) { + ERROR(sti, "unable to open file: %s. Err = %d\n", + obj->full_path, (int) PTR_ERR(filp)); + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + goto out1; + } + + file_size = obj->obj_info.object_compressed_size; + VDBG(sti, "object file size: %llu\n", + (unsigned long long) file_size); + if (unlikely(file_size == 0)) { + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + goto out2; + } + + DBG(sti, "send object: %s\n", obj->full_path); + + /* carry out the file writes */ + get_some_more = 1; + file_offset = usb_offset = 0; + + amount_left_to_req = file_size + PIMA15740_CONTAINER_LEN; + amount_left_to_write = file_size; + VDBG(sti, "in total: amount_left_to_req: %u\n", + amount_left_to_req); + VDBG(sti, "in total: amount_left_to_write: %u\n", + amount_left_to_write); + + while (amount_left_to_write > 0) { + bh = sti->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + amount = min(amount_left_to_req, mod_data.buflen); + amount = min((loff_t) amount, file_size + + PIMA15740_CONTAINER_LEN - usb_offset); + VDBG(sti, "usb amount: %u\n", amount); + + /* no left data request to transfer */ + if (amount == 0) { + get_some_more = 0; + continue; + } + + /* get the next buffer */ + usb_offset += amount; + amount_left_to_req -= amount; + + if (amount_left_to_req == 0) + get_some_more = 0; + + /* amount is always divisible by bulk-out + maxpacket size */ + bh->outreq->length = bh->bulk_out_intended_length = + amount; + bh->outreq->short_not_ok = 1; + start_transfer(sti, sti->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + sti->next_buffhd_to_fill = bh->next; + continue; + } + + /* write the received data to the backing folder */ + bh = sti->next_buffhd_to_drain; + + /* host stopped early */ + if (bh->state == BUF_STATE_EMPTY && !get_some_more) { + WARNING(sti, "host stops early, bh->state: %d\n", + bh->state); + sti->response_code = PIMA15740_RES_INCOMPLETE_TRANSFER; + goto out2; + } + + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + sti->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* something go wrong with the transfer */ + if (bh->outreq->status != 0) { + sti->response_code = + PIMA15740_RES_INCOMPLETE_TRANSFER; + goto out2; + } + + /* + * PIMA 15740 generic container head resides in + * first data block payload + */ + if (file_offset == 0) { + buf = (char __user *) bh->buf + + PIMA15740_CONTAINER_LEN; + amount = bh->outreq->actual - + PIMA15740_CONTAINER_LEN; + } else { + buf = (char __user *) bh->buf; + amount = bh->outreq->actual; + } + amount = min((loff_t) amount, + file_size - file_offset); + + /* across page boundary, recalculate the length */ + if (amount == 0) { + INFO(sti, "extra bulk out zlp packets\n"); + usb_offset -= bh->outreq->length; + amount_left_to_req += bh->outreq->length; + continue; + } + + /* perform the write */ + file_offset_tmp = file_offset; + nwritten = vfs_write(filp, (char __user *) buf, + amount, &file_offset_tmp); + VDBG(sti, "file write %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nwritten); + + if (signal_pending(current)) { + filp_close(filp, current->files); + return -EINTR; + } + + if (nwritten < 0) { + VDBG(sti, "error in file write: %d\n", + (int) nwritten); + nwritten = 0; + } else if (nwritten < amount) { + VDBG(sti, "partial file write: %d/%u\n", + (int) nwritten, amount); + /* round down to a block */ + nwritten -= (nwritten & 511); + } + + file_offset += nwritten; + amount_left_to_write -= nwritten; + + VDBG(sti, "file_offset: %llu, " + "amount_left_to_write: %u\n", + (unsigned long long) file_offset, + amount_left_to_write); + + /* error occurred */ + if (nwritten < amount) { + sti->response_code = + PIMA15740_RES_INCOMPLETE_TRANSFER; + goto out2; + } + continue; + } + + /* wait for something to happen */ + rc = sleep_thread(sti); + if (rc) { + filp_close(filp, current->files); + return rc; + } + } + + /* fsync object file */ + vfs_fsync(filp, filp->f_path.dentry, 1); + + sti->response_code = PIMA15740_RES_OK; +out2: + filp_close(filp, current->files); +out1: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_copy_object(struct sti_dev *sti, struct sti_buffhd *bh) +{ + int rc = 0, i; + size_t size = 0; + unsigned int old_obj_handle, new_obj_parent_handle; + unsigned int new_storage_id, amount, amount_left; + struct sti_object *old_obj = NULL, *new_obj_parent = NULL; + struct sti_object *new_obj, *tmp_obj; + char *new_obj_fname; + struct file *old_fp, *new_fp; + struct inode *inode = NULL; + char __user *buf; + loff_t file_size, file_offset, file_offset_tmp; + ssize_t nread, nwritten; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out1; + } + + old_obj_handle = sti->ops_params[0]; + new_storage_id = sti->ops_params[1]; + new_obj_parent_handle = sti->ops_params[2]; + + if ((old_obj_handle == 0) || (old_obj_handle > sti->object_num)) { + WARNING(sti, "invalid object handle: %u\n", old_obj_handle); + sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE; + goto out1; + } + + if (new_storage_id != sti->storage_id) { + WARNING(sti, "invalid storage id: %u\n", new_storage_id); + sti->response_code = PIMA15740_RES_INVALID_STORAGE_ID; + goto out1; + } + + if (new_obj_parent_handle == 0 || (new_obj_parent_handle > + sti->object_num && + new_obj_parent_handle != 0xffffffff)) { + WARNING(sti, "invalid parent object handle: %u\n", + new_obj_parent_handle); + sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT; + goto out1; + } + + /* find the old object to be copied */ + i = 0; + list_for_each_entry(tmp_obj, &sti->obj_list, list) { + if (tmp_obj->obj_handle == old_obj_handle) { + i++; + old_obj = tmp_obj; + } + + if (tmp_obj->obj_handle == new_obj_parent_handle) { + i++; + new_obj_parent = tmp_obj; + } + + if (i == 2) + break; + } + + if (i != 2 || !old_obj || !new_obj_parent) { + WARNING(sti, "invalid objects %u or %u\n", + old_obj_handle, new_obj_parent_handle); + sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT; + goto out1; + } + + size = strlen(new_obj_parent->full_path) + + strlen(old_obj->filename) + 2; + new_obj_fname = kzalloc(size, GFP_KERNEL); + if (!new_obj_fname) { + sti->response_code = PIMA15740_RES_DEVICE_BUSY; + rc = -EINVAL; + goto out1; + } + strncpy(new_obj_fname, new_obj_parent->full_path, size); + strncat(new_obj_fname, "/", size); + strncat(new_obj_fname, old_obj->filename, size); + + VDBG(sti, "copy new object %s\n", new_obj_fname); + + old_fp = filp_open(old_obj->full_path, O_RDONLY | O_LARGEFILE, 0); + if (IS_ERR(old_fp)) { + ERROR(sti, "unable to open file: %s. Err = %d\n", + old_obj->full_path, (int) PTR_ERR(old_fp)); + sti->response_code = PIMA15740_RES_DEVICE_BUSY; + rc = -EINVAL; + goto out2; + } + + new_fp = filp_open(new_obj_fname, O_CREAT | O_RDWR | O_LARGEFILE, 0666); + if (IS_ERR(new_fp)) { + ERROR(sti, "unable to create file: %s. Err = %d\n", + new_obj_fname, (int) PTR_ERR(new_fp)); + sti->response_code = PIMA15740_RES_DEVICE_BUSY; + rc = -EINVAL; + goto out3; + } + + buf = kzalloc(SIZE_OF_PAGE, GFP_KERNEL); + if (!buf) { + sti->response_code = PIMA15740_RES_OPERATION_NOT_SUPPORTED; + rc = -EINVAL; + goto out4; + } + + inode = old_fp->f_dentry->d_inode; + file_size = i_size_read(inode->i_mapping->host); + VDBG(sti, "object file size: %llu\n", (unsigned long long) file_size); + + if (unlikely(file_size == 0)) { + sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE; + rc = -EIO; + goto out5; + } + + file_offset = 0; + amount_left = file_size; + + while (amount_left > 0) { + amount = min(amount_left, (unsigned int) SIZE_OF_PAGE); + if (amount == 0) + break; + + file_offset_tmp = file_offset; + nread = vfs_read(old_fp, buf, amount, &file_offset_tmp); + + if (signal_pending(current)) { + rc = -EINTR; + goto out5; + } + + if (nread < 0) { + DBG(sti, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + DBG(sti, "partial file read: %d/%u\n", + (int) nread, amount); + /* round down to a block */ + nread -= (nread & 511); + } + + amount = min(amount, (unsigned int) nread); + file_offset_tmp = file_offset; + nwritten = vfs_write(new_fp, buf, amount, &file_offset_tmp); + + if (signal_pending(current)) { + rc = -EINTR; + goto out5; + } + + if (nwritten < 0) { + VDBG(sti, "error in file write: %d\n", + (int) nwritten); + nwritten = 0; + } else if (nwritten < amount) { + VDBG(sti, "partial file write: %d/%u\n", + (int) nwritten, amount); + /* round down to a block */ + nwritten -= (nwritten & 511); + } + + amount = min(amount, (unsigned int) nwritten); + file_offset += amount; + amount_left -= amount; + } + + sti->object_num++; + + size = sizeof(*old_obj); + new_obj = kzalloc(size, GFP_KERNEL); + if (!new_obj) { + rc = -ENOMEM; + goto out5; + } + + /* change obj_handle */ + new_obj->obj_handle = sti->object_num; + + /* change parent object */ + if (new_obj_parent_handle == 0xffffffff) + new_obj->parent_object = 0; + else + new_obj->parent_object = new_obj_parent_handle; + + new_obj->storage_id = old_obj->storage_id; + new_obj->is_dir = old_obj->is_dir; + new_obj->send_valid = old_obj->send_valid; + new_obj->obj_info_size = old_obj->obj_info_size; + strncpy(new_obj->filename, old_obj->filename, + sizeof(new_obj->filename)); + + /* change full path name */ + strncpy(new_obj->full_path, new_obj_fname, sizeof(new_obj->full_path)); + + /* copy object_info */ + memcpy(&new_obj->obj_info, &old_obj->obj_info, old_obj->obj_info_size); + + /* fill parent_object in object_info */ + new_obj->obj_info.parent_object = new_obj->parent_object; + + list_add_tail(&new_obj->list, &sti->obj_list); + + sti->response_code = PIMA15740_RES_OK; +out5: + kfree(buf); +out4: + filp_close(new_fp, current->files); +out3: + filp_close(old_fp, current->files); +out2: + kfree(new_obj_fname); +out1: + /* send response */ + rc = send_params_response(sti, sti->response_code, + sti->object_num, 0, 0, + 1); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +static int do_move_object(struct sti_dev *sti, struct sti_buffhd *bh) +{ + int i, rc = 0; + size_t size = 0; + unsigned int old_obj_handle, new_obj_parent_handle; + unsigned int new_storage_id; + char *new_obj_fname; + struct file *old_fp, *new_fp; + struct inode *old_dir, *new_dir; + struct dentry *old_dentry, *new_dentry; + struct sti_object *old_obj = NULL; + struct sti_object *new_obj = NULL; + struct sti_object *new_obj_parent = NULL; + struct sti_object *tmp_obj = NULL; + VDBG(sti, "---> %s()\n", __func__); + + if (!sti->session_open) { + sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN; + goto out1; + } + + old_obj_handle = sti->ops_params[0]; + new_storage_id = sti->ops_params[1]; + new_obj_parent_handle = sti->ops_params[2]; + + if ((old_obj_handle == 0) || (old_obj_handle > sti->object_num)) { + WARNING(sti, "invalid object handle: %u\n", old_obj_handle); + sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE; + goto out1; + } + if (new_storage_id != sti->storage_id) { + WARNING(sti, "invalid storage id: %u\n", new_storage_id); + sti->response_code = PIMA15740_RES_INVALID_STORAGE_ID; + goto out1; + } + if (new_obj_parent_handle == 0 || (new_obj_parent_handle > + sti->object_num && + new_obj_parent_handle != 0xffffffff)) { + WARNING(sti, "invalid parent object handle: %u\n", + new_obj_parent_handle); + sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT; + goto out1; + } + + /* find the old object to be copied */ + i = 0; + list_for_each_entry(tmp_obj, &sti->obj_list, list) { + if (tmp_obj->obj_handle == old_obj_handle) { + i++; + old_obj = tmp_obj; + } + + if (tmp_obj->obj_handle == new_obj_parent_handle) { + i++; + new_obj_parent = tmp_obj; + } + + if (i == 2) + break; + } + + if (i != 2 || !old_obj || !new_obj_parent) { + WARNING(sti, "invalid objects %u or %u\n", + old_obj_handle, new_obj_parent_handle); + sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT; + goto out1; + } + + size = strlen(new_obj_parent->full_path) + + strlen(old_obj->filename) + 2; + new_obj_fname = kzalloc(size, GFP_KERNEL); + if (!new_obj_fname) { + sti->response_code = PIMA15740_RES_DEVICE_BUSY; + rc = -EINVAL; + goto out1; + } + strncpy(new_obj_fname, new_obj_parent->full_path, size); + strncat(new_obj_fname, "/", size); + strncat(new_obj_fname, old_obj->filename, size); + + VDBG(sti, "move to new object %s\n", new_obj_fname); + + old_fp = filp_open(old_obj->full_path, O_RDONLY | O_LARGEFILE, 0); + if (IS_ERR(old_fp)) { + ERROR(sti, "unable to open file: %s. Err = %d\n", + old_obj->full_path, (int) PTR_ERR(old_fp)); + sti->response_code = PIMA15740_RES_DEVICE_BUSY; + rc = -EINVAL; + goto out2; + } + + new_fp = filp_open(new_obj_fname, O_CREAT | O_RDWR | O_LARGEFILE, 0666); + if (IS_ERR(new_fp)) { + ERROR(sti, "unable to create file: %s. Err = %d\n", + new_obj_fname, (int) PTR_ERR(new_fp)); + sti->response_code = PIMA15740_RES_DEVICE_BUSY; + rc = -EINVAL; + goto out3; + } + + old_dir = old_fp->f_dentry->d_parent->d_inode; + new_dir = new_fp->f_dentry->d_parent->d_inode; + old_dentry = old_fp->f_dentry; + new_dentry = new_fp->f_dentry; + + rc = vfs_rename(old_dir, old_dentry, new_dir, new_dentry); + + if (rc) { + sti->response_code = PIMA15740_RES_OPERATION_NOT_SUPPORTED; + goto out4; + } else + sti->response_code = PIMA15740_RES_OK; + + size = sizeof(*old_obj); + new_obj = kzalloc(size, GFP_KERNEL); + if (!new_obj) { + rc = -ENOMEM; + goto out4; + } + + /* change parent object */ + if (new_obj_parent_handle == 0xffffffff) + new_obj->parent_object = 0; + else + new_obj->parent_object = new_obj_parent_handle; + + new_obj->obj_handle = old_obj->obj_handle; + new_obj->storage_id = old_obj->storage_id; + new_obj->is_dir = old_obj->is_dir; + new_obj->send_valid = old_obj->send_valid; + new_obj->obj_info_size = old_obj->obj_info_size; + strncpy(new_obj->filename, old_obj->filename, + sizeof(new_obj->filename)); + + /* change full path name */ + strncpy(new_obj->full_path, new_obj_fname, sizeof(new_obj->full_path)); + + /* copy object_info */ + memcpy(&new_obj->obj_info, &old_obj->obj_info, old_obj->obj_info_size); + + /* fill parent_object in object_info */ + new_obj->obj_info.parent_object = new_obj->parent_object; + + list_add_tail(&new_obj->list, &sti->obj_list); + list_del_init(&old_obj->list); + kfree(old_obj); +out4: + filp_close(new_fp, current->files); +out3: + filp_close(old_fp, current->files); +out2: + kfree(new_obj_fname); +out1: + /* send response */ + rc = send_response(sti, sti->response_code); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +/* TODO: PIMA 15740 Event handling via interrupt endpoint */ +static int send_status(struct sti_dev *sti) +{ + VDBG(sti, "---> %s()\n", __func__); + VDBG(sti, "<--- %s()\n", __func__); + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* handle supported PIMA 15740 operations */ +static int do_still_image_command(struct sti_dev *sti) +{ + struct sti_buffhd *bh; + int rc = -EINVAL; + int reply = -EINVAL; + VDBG(sti, "---> %s()\n", __func__); + + dump_cb(sti); + + if (!backing_folder_is_open(sti)) { + ERROR(sti, "backing folder is not open\n"); + return rc; + } + + /* wait for the next buffer to become available for data or status */ + bh = sti->next_buffhd_to_drain = sti->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(sti); + if (rc) + return rc; + } + + down_read(&sti->filesem); + switch (sti->code) { + + case PIMA15740_OP_GET_DEVICE_INFO: + DBG(sti, "PIMA15740 OPS: get device info\n"); + reply = do_get_device_info(sti, bh); + break; + + case PIMA15740_OP_OPEN_SESSION: + DBG(sti, "PIMA15740 OPS: open session\n"); + reply = do_open_session(sti); + break; + + case PIMA15740_OP_CLOSE_SESSION: + DBG(sti, "PIMA15740 OPS: close session\n"); + reply = do_close_session(sti); + break; + + case PIMA15740_OP_GET_STORAGE_IDS: + DBG(sti, "PIMA15740 OPS: get storage ids\n"); + reply = do_get_storage_ids(sti, bh); + break; + + case PIMA15740_OP_GET_STORAGE_INFO: + DBG(sti, "PIMA15740 OPS: get storage info\n"); + reply = do_get_storage_info(sti, bh); + break; + + case PIMA15740_OP_GET_NUM_OBJECTS: + DBG(sti, "PIMA15740 OPS: get num objects\n"); + reply = do_get_num_objects(sti, bh); + break; + + case PIMA15740_OP_GET_OBJECT_HANDLES: + DBG(sti, "PIMA15740 OPS: get object handles\n"); + reply = do_get_object_handles(sti, bh); + break; + + case PIMA15740_OP_GET_OBJECT_INFO: + DBG(sti, "PIMA15740 OPS: get object info\n"); + reply = do_get_object_info(sti, bh); + break; + + case PIMA15740_OP_GET_OBJECT: + DBG(sti, "PIMA15740 OPS: get object\n"); + reply = do_get_object(sti, bh); + break; + + case PIMA15740_OP_DELETE_OBJECT: + DBG(sti, "PIMA15740 OPS: delete object\n"); + reply = do_delete_object(sti, bh); + break; + + case PIMA15740_OP_SEND_OBJECT_INFO: + DBG(sti, "PIMA15740 OPS: send object info\n"); + reply = do_send_object_info(sti, bh); + break; + + case PIMA15740_OP_SEND_OBJECT: + DBG(sti, "PIMA15740 OPS: send object\n"); + reply = do_send_object(sti, bh); + break; + + case PIMA15740_OP_COPY_OBJECT: + DBG(sti, "PIMA15740 OPS: copy object\n"); + reply = do_copy_object(sti, bh); + break; + + case PIMA15740_OP_MOVE_OBJECT: + DBG(sti, "PIMA15740 OPS: move object\n"); + reply = do_move_object(sti, bh); + break; + + default: + WARNING(sti, "unknown PIMA15740 OPS 0x%04x\n", sti->code); + break; + } + up_read(&sti->filesem); + + if (reply == -EINTR || signal_pending(current)) + rc = -EINTR; + + if (reply == -EINVAL) + rc = 0; + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +/* received PIMA 15740 Command Blocks */ +static int received_cb(struct sti_dev *sti, struct sti_buffhd *bh) +{ + struct usb_request *req = bh->outreq; + struct pima15740_container *cb = req->buf; + unsigned short n; + VDBG(sti, "---> %s()\n", __func__); + + /* this is not a real packet */ + if (req->status) + return -EINVAL; + + /* save the command for later */ + sti->container_len = cb->container_len; + sti->container_type = cb->container_type; + sti->code = cb->code; + sti->transaction_id = cb->transaction_id; + + /* get Command Block Parameters 1..N */ + n = sti->container_len - PIMA15740_CONTAINER_LEN; + if (n != 0) + memcpy(sti->ops_params, cb + 1, n); + + VDBG(sti, "Command Block: len=%u, type=0x%04x, " + "code=0x%04x, trans_id=0x%08x\n", + sti->container_len, sti->container_type, + sti->code, sti->transaction_id); + + VDBG(sti, "<--- %s()\n", __func__); + return 0; +} + + +static int get_next_command(struct sti_dev *sti) +{ + struct sti_buffhd *bh; + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + /* wait for the next buffer to become available */ + bh = sti->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(sti); + if (rc) + return rc; + } + + /* queue a request to read a Bulk-only Command Block */ + set_bulk_out_req_length(sti, bh, 512); + bh->outreq->short_not_ok = 1; + start_transfer(sti, sti->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + + /* we will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. */ + + /* wait for the Command Block to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(sti); + if (rc) + return rc; + } + smp_rmb(); + rc = received_cb(sti, bh); + bh->state = BUF_STATE_EMPTY; + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int enable_endpoint(struct sti_dev *sti, struct usb_ep *ep, + const struct usb_endpoint_descriptor *d) +{ + int rc; + VDBG(sti, "---> %s()\n", __func__); + + ep->driver_data = sti; + rc = usb_ep_enable(ep, d); + if (rc) + ERROR(sti, "can't enable %s, result %d\n", ep->name, rc); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + +static int alloc_request(struct sti_dev *sti, struct usb_ep *ep, + struct usb_request **preq) +{ + VDBG(sti, "---> %s()\n", __func__); + + *preq = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (*preq) + return 0; + + ERROR(sti, "can't allocate request for %s\n", ep->name); + + VDBG(sti, "<--- %s()\n", __func__); + return -ENOMEM; +} + +/* + * Reset interface setting and re-init endpoint state (toggle etc). + * Call with altsetting < 0 to disable the interface. The only other + * available altsetting is 0, which enables the interface. + */ +static int do_set_interface(struct sti_dev *sti, int altsetting) +{ + int rc = 0; + int i; + const struct usb_endpoint_descriptor *d; + VDBG(sti, "---> %s()\n", __func__); + + if (sti->running) + DBG(sti, "reset interface\n"); + +reset: + /* deallocate the requests */ + for (i = 0; i < NUM_BUFFERS; ++i) { + struct sti_buffhd *bh = &sti->buffhds[i]; + + if (bh->inreq) { + usb_ep_free_request(sti->bulk_in, bh->inreq); + bh->inreq = NULL; + } + if (bh->outreq) { + usb_ep_free_request(sti->bulk_out, bh->outreq); + bh->outreq = NULL; + } + } + if (sti->intreq) { + usb_ep_free_request(sti->intr_in, sti->intreq); + sti->intreq = NULL; + } + + /* disable the endpoints */ + if (sti->bulk_in_enabled) { + usb_ep_disable(sti->bulk_in); + sti->bulk_in_enabled = 0; + } + if (sti->bulk_out_enabled) { + usb_ep_disable(sti->bulk_out); + sti->bulk_out_enabled = 0; + } + if (sti->intr_in_enabled) { + usb_ep_disable(sti->intr_in); + sti->intr_in_enabled = 0; + } + + sti->running = 0; + if (altsetting < 0 || rc != 0) + return rc; + + DBG(sti, "set interface %d\n", altsetting); + + /* enable the endpoints */ + d = ep_desc(sti->gadget, &fs_bulk_in_desc, &hs_bulk_in_desc); + rc = enable_endpoint(sti, sti->bulk_in, d); + if (rc) + goto reset; + sti->bulk_in_enabled = 1; + + d = ep_desc(sti->gadget, &fs_bulk_out_desc, &hs_bulk_out_desc); + rc = enable_endpoint(sti, sti->bulk_out, d); + if (rc) + goto reset; + sti->bulk_out_enabled = 1; + sti->bulk_out_maxpacket = le16_to_cpu(d->wMaxPacketSize); + clear_bit(CLEAR_BULK_HALTS, &sti->atomic_bitflags); + + d = ep_desc(sti->gadget, &fs_intr_in_desc, &hs_intr_in_desc); + rc = enable_endpoint(sti, sti->intr_in, d); + if (rc) + goto reset; + sti->intr_in_enabled = 1; + + /* allocate the requests */ + for (i = 0; i < NUM_BUFFERS; ++i) { + struct sti_buffhd *bh = &sti->buffhds[i]; + + rc = alloc_request(sti, sti->bulk_in, &bh->inreq); + if (rc) + goto reset; + + rc = alloc_request(sti, sti->bulk_out, &bh->outreq); + if (rc) + goto reset; + + bh->inreq->buf = bh->outreq->buf = bh->buf; + bh->inreq->context = bh->outreq->context = bh; + bh->inreq->complete = bulk_in_complete; + bh->outreq->complete = bulk_out_complete; + } + + rc = alloc_request(sti, sti->intr_in, &sti->intreq); + if (rc) + goto reset; + + sti->running = 1; + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +/* + * Change our operational configuration. This code must agree with the code + * that returns config descriptors, and with interface altsetting code. + * + * It's also responsible for power management interactions. Some + * configurations might not work with our current power sources. + * For now we just assume the gadget is always self-powered. + */ +static int do_set_config(struct sti_dev *sti, u8 new_config) +{ + int rc = 0; + VDBG(sti, "---> %s()\n", __func__); + + /* disable the single interface */ + if (sti->config != 0) { + DBG(sti, "reset config\n"); + sti->config = 0; + rc = do_set_interface(sti, -1); + } + + /* enable the interface */ + if (new_config != 0) { + sti->config = new_config; + rc = do_set_interface(sti, 0); + if (rc) + sti->config = 0; /* reset on errors */ + else { + char *speed; + + switch (sti->gadget->speed) { + case USB_SPEED_LOW: + speed = "low"; + break; + case USB_SPEED_FULL: + speed = "full"; + break; + case USB_SPEED_HIGH: + speed = "high"; + break; + default: + speed = "?"; + break; + } + INFO(sti, "%s speed config #%d\n", + speed, sti->config); + } + } + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct sti_dev *sti) +{ + siginfo_t info; + int sig; + int i; + int num_active; + struct sti_buffhd *bh; + enum sti_state old_state; + u8 new_config; + unsigned int exception_req_tag; + int rc; + + VDBG(sti, "---> %s()\n", __func__); + + /* Clear the existing signals. Anything but SIGUSR1 is converted + * into a high-priority EXIT exception. */ + for (;;) { + sig = dequeue_signal_lock(current, ¤t->blocked, &info); + if (!sig) + break; + + if (sig != SIGUSR1) { + if (sti->state < STI_STATE_EXIT) + DBG(sti, "main thread exiting on signal\n"); + raise_exception(sti, STI_STATE_EXIT); + } + } + + /* cancel all the pending transfers */ + if (sti->intreq_busy) + usb_ep_dequeue(sti->intr_in, sti->intreq); + + for (i = 0; i < NUM_BUFFERS; ++i) { + bh = &sti->buffhds[i]; + if (bh->inreq_busy) + usb_ep_dequeue(sti->bulk_in, bh->inreq); + if (bh->outreq_busy) + usb_ep_dequeue(sti->bulk_out, bh->outreq); + } + + /* wait until everything is idle */ + for (;;) { + num_active = sti->intreq_busy; + for (i = 0; i < NUM_BUFFERS; ++i) { + bh = &sti->buffhds[i]; + num_active += bh->inreq_busy + bh->outreq_busy; + } + + if (num_active == 0) + break; + + if (sleep_thread(sti)) + return; + } + + /* clear out the controller's fifos */ + if (sti->bulk_in_enabled) + usb_ep_fifo_flush(sti->bulk_in); + if (sti->bulk_out_enabled) + usb_ep_fifo_flush(sti->bulk_out); + if (sti->intr_in_enabled) + usb_ep_fifo_flush(sti->intr_in); + + /* + * Reset the I/O buffer states and pointers, the device + * state, and the exception. Then invoke the handler. + */ + spin_lock_irq(&sti->lock); + + for (i = 0; i < NUM_BUFFERS; ++i) { + bh = &sti->buffhds[i]; + bh->state = BUF_STATE_EMPTY; + } + sti->next_buffhd_to_fill = sti->next_buffhd_to_drain = + &sti->buffhds[0]; + + exception_req_tag = sti->exception_req_tag; + new_config = sti->new_config; + old_state = sti->state; + + if (old_state == STI_STATE_ABORT_BULK_OUT) + sti->state = STI_STATE_STATUS_PHASE; + else + sti->state = STI_STATE_IDLE; + spin_unlock_irq(&sti->lock); + + /* carry out any extra actions required for the exception */ + switch (old_state) { + default: + break; + + case STI_STATE_ABORT_BULK_OUT: + send_status(sti); + spin_lock_irq(&sti->lock); + if (sti->state == STI_STATE_STATUS_PHASE) + sti->state = STI_STATE_IDLE; + spin_unlock_irq(&sti->lock); + break; + + case STI_STATE_RESET: + /* in case we were forced against our will to halt a + * bulk endpoint, clear the halt now */ + if (test_and_clear_bit(CLEAR_BULK_HALTS, + &sti->atomic_bitflags)) { + usb_ep_clear_halt(sti->bulk_in); + usb_ep_clear_halt(sti->bulk_out); + } + + if (sti->ep0_req_tag == exception_req_tag) + /* complete the status stage */ + ep0_queue(sti); + break; + + case STI_STATE_INTERFACE_CHANGE: + rc = do_set_interface(sti, 0); + if (sti->ep0_req_tag != exception_req_tag) + break; + if (rc != 0) /* STALL on errors */ + sti_set_halt(sti, sti->ep0); + else /* complete the status stage */ + ep0_queue(sti); + break; + + case STI_STATE_CONFIG_CHANGE: + rc = do_set_config(sti, new_config); + if (sti->ep0_req_tag != exception_req_tag) + break; + if (rc != 0) /* STALL on errors */ + sti_set_halt(sti, sti->ep0); + else /* complete the status stage */ + ep0_queue(sti); + break; + + case STI_STATE_DISCONNECT: + do_set_config(sti, 0); /* unconfigured state */ + break; + + case STI_STATE_EXIT: + case STI_STATE_TERMINATED: + do_set_config(sti, 0); /* free resources */ + spin_lock_irq(&sti->lock); + sti->state = STI_STATE_TERMINATED; /* stop the thread */ + spin_unlock_irq(&sti->lock); + break; + } + + VDBG(sti, "<--- %s()\n", __func__); +} + + +/*-------------------------------------------------------------------------*/ + +static int sti_main_thread(void *sti_) +{ + struct sti_dev *sti = sti_; + VDBG(sti, "---> %s()\n", __func__); + + /* + * allow the thread to be killed by a signal, but set the signal mask + * to block everything but INT, TERM, KILL, and USR1 + */ + allow_signal(SIGINT); + allow_signal(SIGTERM); + allow_signal(SIGKILL); + allow_signal(SIGUSR1); + + /* allow the thread to be frozen */ + set_freezable(); + + /* + * arrange for userspace references to be interpreted as kernel + * pointers. That way we can pass a kernel pointer to a routine + * that expects a __user pointer and it will work okay. + */ + set_fs(get_ds()); + + /* the main loop */ + while (sti->state != STI_STATE_TERMINATED) { + if (exception_in_progress(sti) || signal_pending(current)) { + handle_exception(sti); + continue; + } + + if (!sti->running) { + sleep_thread(sti); + continue; + } + + if (get_next_command(sti)) + continue; + + spin_lock_irq(&sti->lock); + if (!exception_in_progress(sti)) + sti->state = STI_STATE_DATA_PHASE; + spin_unlock_irq(&sti->lock); + + if (do_still_image_command(sti)) + continue; + + spin_lock_irq(&sti->lock); + if (!exception_in_progress(sti)) + sti->state = STI_STATE_STATUS_PHASE; + spin_unlock_irq(&sti->lock); + + if (send_status(sti)) + continue; + + spin_lock_irq(&sti->lock); + if (!exception_in_progress(sti)) + sti->state = STI_STATE_IDLE; + spin_unlock_irq(&sti->lock); + } + + spin_lock_irq(&sti->lock); + sti->thread_task = NULL; + spin_unlock_irq(&sti->lock); + + /* in case we are exiting because of a signal, unregister the + * gadget driver */ + if (test_and_clear_bit(REGISTERED, &sti->atomic_bitflags)) + usb_gadget_unregister_driver(&sti_driver); + + /* let the unbind and cleanup routines know the thread has exited */ + complete_and_exit(&sti->thread_notifier, 0); + + VDBG(sti, "<--- %s()\n", __func__); +} + + +/*-------------------------------------------------------------------------*/ + +static int open_backing_folder(struct sti_dev *sti, const char *folder_name) +{ + struct file *filp = NULL; + int rc = -EINVAL; + struct inode *inode = NULL; + size_t len; + VDBG(sti, "---> %s()\n", __func__); + + /* remove the trailing path sign */ + len = strlen(folder_name); + if (len > 1 && folder_name[len-1] == '/') + ((char *) folder_name)[len-1] = 0; + + memset(sti->root_path, 0, sizeof(sti->root_path)); + strncpy(sti->root_path, folder_name, sizeof(sti->root_path)); + + filp = filp_open(sti->root_path, O_RDONLY | O_DIRECTORY, 0); + if (IS_ERR(filp)) { + ERROR(sti, "unable to open backing folder: %s\n", + sti->root_path); + return PTR_ERR(filp); + } + + if (filp->f_path.dentry) + inode = filp->f_dentry->d_inode; + + if (!inode || !S_ISDIR(inode->i_mode)) { + ERROR(sti, "%s is not a directory\n", sti->root_path); + goto out; + } + + get_file(filp); + + sti->root_filp = filp; + + INFO(sti, "open backing folder: %s\n", folder_name); + rc = 0; +out: + filp_close(filp, current->files); + + VDBG(sti, "<--- %s()\n", __func__); + return rc; +} + +static void close_backing_folder(struct sti_dev *sti) +{ + VDBG(sti, "---> %s()\n", __func__); + + if (sti->root_filp) { + INFO(sti, "close backing folder\n"); + fput(sti->root_filp); + sti->root_filp = NULL; + } + + VDBG(sti, "<--- %s()\n", __func__); +} + + +/*-------------------------------------------------------------------------*/ + +/* sysfs attribute files */ +static ssize_t show_folder(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sti_dev *sti = dev_get_drvdata(dev); + char *p; + ssize_t rc; + + down_read(&sti->filesem); + if (backing_folder_is_open(sti)) { + /* get the complete pathname */ + p = d_path(&sti->root_filp->f_path, buf, PAGE_SIZE - 1); + if (IS_ERR(p)) + rc = PTR_ERR(p); + else { + rc = strlen(p); + memmove(buf, p, rc); + + /* add a newline */ + buf[rc] = '\n'; + buf[++rc] = 0; + } + } else { /* no file */ + *buf = 0; + rc = 0; + } + up_read(&sti->filesem); + + return rc; +} + + +static ssize_t store_folder(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sti_dev *sti = dev_get_drvdata(dev); + int rc = 0; + + /* remove a trailing newline */ + if (count > 0 && buf[count-1] == '\n') + ((char *) buf)[count-1] = 0; + + /* eject current medium */ + down_write(&sti->filesem); + if (backing_folder_is_open(sti)) + close_backing_folder(sti); + + /* load new medium */ + if (count > 0 && buf[0]) + rc = open_backing_folder(sti, buf); + + up_write(&sti->filesem); + + return (rc < 0 ? rc : count); +} + +/* the write permissions and store_xxx pointers are set in sti_bind() */ +static DEVICE_ATTR(folder, 0444, show_folder, NULL); + + +/*-------------------------------------------------------------------------*/ + +static void sti_release(struct kref *ref) +{ + struct sti_dev *sti = container_of(ref, struct sti_dev, ref); + kfree(sti); +} + +static void gadget_release(struct device *dev) +{ + struct sti_dev *sti = dev_get_drvdata(dev); + VDBG(sti, "---> %s()\n", __func__); + VDBG(sti, "<--- %s()\n", __func__); + + kref_put(&sti->ref, sti_release); +} + + +static void /* __init_or_exit */ sti_unbind(struct usb_gadget *gadget) +{ + struct sti_dev *sti = get_gadget_data(gadget); + int i; + struct usb_request *req = sti->ep0req; + VDBG(sti, "---> %s()\n", __func__); + + DBG(sti, "unbind\n"); + clear_bit(REGISTERED, &sti->atomic_bitflags); + + /* unregister the sysfs attribute files */ + if (sti->registered) { + device_remove_file(&sti->dev, &dev_attr_folder); + close_backing_folder(sti); + device_unregister(&sti->dev); + sti->registered = 0; + } + + /* if the thread isn't already dead, tell it to exit now */ + if (sti->state != STI_STATE_TERMINATED) { + raise_exception(sti, STI_STATE_EXIT); + wait_for_completion(&sti->thread_notifier); + + /* the cleanup routine waits for this completion also */ + complete(&sti->thread_notifier); + } + + /* free the data buffers */ + for (i = 0; i < NUM_BUFFERS; ++i) + kfree(sti->buffhds[i].buf); + + /* free the request and buffer for endpoint 0 */ + if (req) { + kfree(req->buf); + usb_ep_free_request(sti->ep0, req); + } + + set_gadget_data(gadget, NULL); + + VDBG(sti, "<--- %s()\n", __func__); +} + + +static int __init check_parameters(struct sti_dev *sti) +{ + int gcnum; + VDBG(sti, "---> %s()\n", __func__); + + /* parameter wasn't set */ + if (mod_data.release == 0xffff) { + gcnum = usb_gadget_controller_number(sti->gadget); + if (gcnum >= 0) + mod_data.release = 0x0300 + gcnum; + else { + WARNING(sti, "controller '%s' not recognized\n", + sti->gadget->name); + mod_data.release = 0x0399; + } + } + + mod_data.buflen &= PAGE_CACHE_MASK; + if (mod_data.buflen <= 0) { + ERROR(sti, "invalid buflen\n"); + return -ETOOSMALL; + } + + VDBG(sti, "<--- %s()\n", __func__); + return 0; +} + + +static int __init sti_bind(struct usb_gadget *gadget) +{ + struct sti_dev *sti = the_sti; + int rc; + int i; + struct usb_ep *ep; + struct usb_request *req; + + sti->gadget = gadget; + set_gadget_data(gadget, sti); + sti->ep0 = gadget->ep0; + sti->ep0->driver_data = sti; + + rc = check_parameters(sti); + if (rc) + goto out; + + /* enable store_xxx attributes */ + dev_attr_folder.attr.mode = 0644; + dev_attr_folder.store = store_folder; + + sti->dev.release = gadget_release; + sti->dev.parent = &gadget->dev; + sti->dev.driver = &sti_driver.driver; + dev_set_drvdata(&sti->dev, sti); + dev_set_name(&sti->dev, "%s", sti_driver.driver.name); + + rc = device_register(&sti->dev); + if (rc) { + INFO(sti, "failed to register sti: %d\n", rc); + goto out; + } + + rc = device_create_file(&sti->dev, &dev_attr_folder); + if (rc) { + device_unregister(&sti->dev); + goto out; + } + + sti->registered = 1; + kref_get(&sti->ref); + + /* initialize object list */ + INIT_LIST_HEAD(&sti->obj_list); + INIT_LIST_HEAD(&sti->tmp_obj_list); + + if (mod_data.folder && *mod_data.folder) + rc = open_backing_folder(sti, mod_data.folder); + if (rc) + goto out; + + /* find all the endpoints we will use */ + usb_ep_autoconfig_reset(gadget); + ep = usb_ep_autoconfig(gadget, &fs_bulk_in_desc); + if (!ep) + goto autoconf_fail; + + /* claim bulk-in endpoint */ + ep->driver_data = sti; + sti->bulk_in = ep; + + ep = usb_ep_autoconfig(gadget, &fs_bulk_out_desc); + if (!ep) + goto autoconf_fail; + + /* claim bulk-out endpoint */ + ep->driver_data = sti; + sti->bulk_out = ep; + + ep = usb_ep_autoconfig(gadget, &fs_intr_in_desc); + if (!ep) + goto autoconf_fail; + + /* claim intr-in endpoint */ + ep->driver_data = sti; + sti->intr_in = ep; + + /* fix up the descriptors */ + device_desc.bMaxPacketSize0 = sti->ep0->maxpacket; + device_desc.idVendor = cpu_to_le16(mod_data.vendor); + device_desc.idProduct = cpu_to_le16(mod_data.product); + device_desc.bcdDevice = cpu_to_le16(mod_data.release); + + fs_function[3 + FS_FUNCTION_PRE_EP_ENTRIES] = NULL; + + if (gadget_is_dualspeed(gadget)) { + hs_function[3 + HS_FUNCTION_PRE_EP_ENTRIES] = NULL; + + /* assume ep0 uses the same maxpacket value for both speeds */ + dev_qualifier.bMaxPacketSize0 = sti->ep0->maxpacket; + + /* assume endpoint addresses are the same for both speeds */ + hs_bulk_in_desc.bEndpointAddress = + fs_bulk_in_desc.bEndpointAddress; + hs_bulk_out_desc.bEndpointAddress = + fs_bulk_out_desc.bEndpointAddress; + hs_intr_in_desc.bEndpointAddress = + fs_intr_in_desc.bEndpointAddress; + } + + if (gadget_is_otg(gadget)) + otg_desc.bmAttributes |= USB_OTG_HNP; + + rc = -ENOMEM; + + /* allocate the request and buffer for endpoint 0 */ + sti->ep0req = req = usb_ep_alloc_request(sti->ep0, GFP_KERNEL); + if (!req) + goto autoconf_fail; + + req->buf = kmalloc(EP0_BUFSIZE, GFP_KERNEL); + if (!req->buf) + goto autoconf_fail; + + req->complete = ep0_complete; + + /* allocate the data buffers */ + for (i = 0; i < NUM_BUFFERS; ++i) { + struct sti_buffhd *bh = &sti->buffhds[i]; + + /* + * Allocate for the bulk-in endpoint. We assume that + * the buffer will also work with the bulk-out (and + * interrupt-in) endpoint. + */ + bh->buf = kmalloc(mod_data.buflen, GFP_KERNEL); + if (!bh->buf) + goto autoconf_fail; + + bh->next = bh + 1; + } + sti->buffhds[NUM_BUFFERS - 1].next = &sti->buffhds[0]; + + /* this should reflect the actual gadget power source */ + usb_gadget_set_selfpowered(gadget); + + snprintf(manufacturer, sizeof manufacturer, "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + gadget->name); + + DBG(sti, "manufacturer: %s\n", manufacturer); + + /* + * on a real device, serial[] would be loaded from permanent + * storage. We just encode it from the driver version string. + */ + for (i = 0; i < sizeof(serial) - 2; i += 2) { + unsigned char c = DRIVER_VERSION[i / 2]; + + if (!c) + break; + + snprintf(&serial[i], sizeof(&serial[i]), "%02X", c); + } + + /* fill remained device info */ + sti_device_info.manufacturer_len = sizeof(manufacturer); + str_to_uni16(manufacturer, sti_device_info.manufacturer); + + sti_device_info.model_len = sizeof(longname); + str_to_uni16(longname, sti_device_info.model); + + sti_device_info.device_version_len = sizeof(device_version); + str_to_uni16(device_version, sti_device_info.device_version); + + sti_device_info.serial_number_len = sizeof(serial); + str_to_uni16(serial, sti_device_info.serial_number); + + /* create main kernel thread */ + sti->thread_task = kthread_create(sti_main_thread, sti, + "still-image-gadget"); + + if (IS_ERR(sti->thread_task)) { + rc = PTR_ERR(sti->thread_task); + goto autoconf_fail; + } + + INFO(sti, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + INFO(sti, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n", + mod_data.vendor, mod_data.product, mod_data.release); + INFO(sti, "I/O thread pid: %d, buflen=%u\n", + task_pid_nr(sti->thread_task), mod_data.buflen); + + set_bit(REGISTERED, &sti->atomic_bitflags); + + /* tell the thread to start working */ + wake_up_process(sti->thread_task); + + DBG(sti, "bind\n"); + return 0; + +autoconf_fail: + ERROR(sti, "unable to autoconfigure all endpoints\n"); + rc = -ENOTSUPP; +out: + /* the thread is dead */ + sti->state = STI_STATE_TERMINATED; + + sti_unbind(gadget); + complete(&sti->thread_notifier); + + VDBG(sti, "<---> %s()\n", __func__); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static void sti_suspend(struct usb_gadget *gadget) +{ + struct sti_dev *sti = get_gadget_data(gadget); + + DBG(sti, "suspend\n"); + set_bit(SUSPENDED, &sti->atomic_bitflags); +} + + +static void sti_resume(struct usb_gadget *gadget) +{ + struct sti_dev *sti = get_gadget_data(gadget); + + DBG(sti, "resume\n"); + clear_bit(SUSPENDED, &sti->atomic_bitflags); +} + + +/*-------------------------------------------------------------------------*/ + +static struct usb_gadget_driver sti_driver = { +#ifdef CONFIG_USB_GADGET_DUALSPEED + .speed = USB_SPEED_HIGH, +#else + .speed = USB_SPEED_FULL, +#endif + .function = (char *) longname, + .bind = sti_bind, + .unbind = sti_unbind, + .disconnect = sti_disconnect, + .setup = sti_setup, + .suspend = sti_suspend, + .resume = sti_resume, + + .driver = { + .name = (char *) shortname, + .owner = THIS_MODULE, + /* .release = ... */ + /* .suspend = ... */ + /* .resume = ... */ + }, +}; + + +static int __init sti_alloc(void) +{ + struct sti_dev *sti; + + sti = kzalloc(sizeof *sti, GFP_KERNEL); + if (!sti) + return -ENOMEM; + + spin_lock_init(&sti->lock); + init_rwsem(&sti->filesem); + kref_init(&sti->ref); + init_completion(&sti->thread_notifier); + + the_sti = sti; + + return 0; +} + + +static int __init sti_init(void) +{ + int rc; + struct sti_dev *sti; + + rc = sti_alloc(); + if (rc) + return rc; + + sti = the_sti; + + rc = usb_gadget_register_driver(&sti_driver); + if (rc) + kref_put(&sti->ref, sti_release); + + return rc; +} +module_init(sti_init); + + +static void __exit sti_cleanup(void) +{ + struct sti_dev *sti = the_sti; + + /* unregister the driver if the thread hasn't already done */ + if (test_and_clear_bit(REGISTERED, &sti->atomic_bitflags)) + usb_gadget_unregister_driver(&sti_driver); + + /* wait for the thread to finish up */ + wait_for_completion(&sti->thread_notifier); + + kref_put(&sti->ref, sti_release); +} +module_exit(sti_cleanup);
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor