diff options
Diffstat (limited to 'drivers/usb/storage/sierra_ms.c')
-rw-r--r-- | drivers/usb/storage/sierra_ms.c | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c new file mode 100644 index 000000000..0774ba22f --- /dev/null +++ b/drivers/usb/storage/sierra_ms.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <linux/usb.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "usb.h" +#include "transport.h" +#include "protocol.h" +#include "scsiglue.h" +#include "sierra_ms.h" +#include "debug.h" + +#define SWIMS_USB_REQUEST_SetSwocMode 0x0B +#define SWIMS_USB_REQUEST_GetSwocInfo 0x0A +#define SWIMS_USB_INDEX_SetMode 0x0000 +#define SWIMS_SET_MODE_Modem 0x0001 + +#define TRU_NORMAL 0x01 +#define TRU_FORCE_MS 0x02 +#define TRU_FORCE_MODEM 0x03 + +static unsigned int swi_tru_install = 1; +module_param(swi_tru_install, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(swi_tru_install, "TRU-Install mode (1=Full Logic (def)," + " 2=Force CD-Rom, 3=Force Modem)"); + +struct swoc_info { + __u8 rev; + __u8 reserved[8]; + __u16 LinuxSKU; + __u16 LinuxVer; + __u8 reserved2[47]; +} __attribute__((__packed__)); + +static bool containsFullLinuxPackage(struct swoc_info *swocInfo) +{ + if ((swocInfo->LinuxSKU >= 0x2100 && swocInfo->LinuxSKU <= 0x2FFF) || + (swocInfo->LinuxSKU >= 0x7100 && swocInfo->LinuxSKU <= 0x7FFF)) + return true; + else + return false; +} + +static int sierra_set_ms_mode(struct usb_device *udev, __u16 eSWocMode) +{ + int result; + dev_dbg(&udev->dev, "SWIMS: %s", "DEVICE MODE SWITCH\n"); + result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SWIMS_USB_REQUEST_SetSwocMode, /* __u8 request */ + USB_TYPE_VENDOR | USB_DIR_OUT, /* __u8 request type */ + eSWocMode, /* __u16 value */ + 0x0000, /* __u16 index */ + NULL, /* void *data */ + 0, /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + return result; +} + + +static int sierra_get_swoc_info(struct usb_device *udev, + struct swoc_info *swocInfo) +{ + int result; + + dev_dbg(&udev->dev, "SWIMS: Attempting to get TRU-Install info\n"); + + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + SWIMS_USB_REQUEST_GetSwocInfo, /* __u8 request */ + USB_TYPE_VENDOR | USB_DIR_IN, /* __u8 request type */ + 0, /* __u16 value */ + 0, /* __u16 index */ + (void *) swocInfo, /* void *data */ + sizeof(struct swoc_info), /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); + swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); + return result; +} + +static void debug_swoc(const struct device *dev, struct swoc_info *swocInfo) +{ + dev_dbg(dev, "SWIMS: SWoC Rev: %02d\n", swocInfo->rev); + dev_dbg(dev, "SWIMS: Linux SKU: %04X\n", swocInfo->LinuxSKU); + dev_dbg(dev, "SWIMS: Linux Version: %04X\n", swocInfo->LinuxVer); +} + + +static ssize_t truinst_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct swoc_info *swocInfo; + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + int result; + if (swi_tru_install == TRU_FORCE_MS) { + result = snprintf(buf, PAGE_SIZE, "Forced Mass Storage\n"); + } else { + swocInfo = kmalloc(sizeof(struct swoc_info), GFP_KERNEL); + if (!swocInfo) { + snprintf(buf, PAGE_SIZE, "Error\n"); + return -ENOMEM; + } + result = sierra_get_swoc_info(udev, swocInfo); + if (result < 0) { + dev_dbg(dev, "SWIMS: failed SWoC query\n"); + kfree(swocInfo); + snprintf(buf, PAGE_SIZE, "Error\n"); + return -EIO; + } + debug_swoc(dev, swocInfo); + result = snprintf(buf, PAGE_SIZE, + "REV=%02d SKU=%04X VER=%04X\n", + swocInfo->rev, + swocInfo->LinuxSKU, + swocInfo->LinuxVer); + kfree(swocInfo); + } + return result; +} +static DEVICE_ATTR_RO(truinst); + +int sierra_ms_init(struct us_data *us) +{ + int result, retries; + struct swoc_info *swocInfo; + struct usb_device *udev; + + udev = us->pusb_dev; + + /* Force Modem mode */ + if (swi_tru_install == TRU_FORCE_MODEM) { + usb_stor_dbg(us, "SWIMS: Forcing Modem Mode\n"); + result = sierra_set_ms_mode(udev, SWIMS_SET_MODE_Modem); + if (result < 0) + usb_stor_dbg(us, "SWIMS: Failed to switch to modem mode\n"); + return -EIO; + } + /* Force Mass Storage mode (keep CD-Rom) */ + else if (swi_tru_install == TRU_FORCE_MS) { + usb_stor_dbg(us, "SWIMS: Forcing Mass Storage Mode\n"); + goto complete; + } + /* Normal TRU-Install Logic */ + else { + usb_stor_dbg(us, "SWIMS: Normal SWoC Logic\n"); + + swocInfo = kmalloc(sizeof(struct swoc_info), + GFP_KERNEL); + if (!swocInfo) + return -ENOMEM; + + retries = 3; + do { + retries--; + result = sierra_get_swoc_info(udev, swocInfo); + if (result < 0) { + usb_stor_dbg(us, "SWIMS: Failed SWoC query\n"); + schedule_timeout_uninterruptible(2*HZ); + } + } while (retries && result < 0); + + if (result < 0) { + usb_stor_dbg(us, "SWIMS: Completely failed SWoC query\n"); + kfree(swocInfo); + return -EIO; + } + + debug_swoc(&us->pusb_dev->dev, swocInfo); + + /* + * If there is not Linux software on the TRU-Install device + * then switch to modem mode + */ + if (!containsFullLinuxPackage(swocInfo)) { + usb_stor_dbg(us, "SWIMS: Switching to Modem Mode\n"); + result = sierra_set_ms_mode(udev, + SWIMS_SET_MODE_Modem); + if (result < 0) + usb_stor_dbg(us, "SWIMS: Failed to switch modem\n"); + kfree(swocInfo); + return -EIO; + } + kfree(swocInfo); + } +complete: + return device_create_file(&us->pusb_intf->dev, &dev_attr_truinst); +} + |