1*b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 232fe5e39SKevin Lloyd #include <scsi/scsi.h> 332fe5e39SKevin Lloyd #include <scsi/scsi_host.h> 432fe5e39SKevin Lloyd #include <scsi/scsi_cmnd.h> 532fe5e39SKevin Lloyd #include <scsi/scsi_device.h> 632fe5e39SKevin Lloyd #include <linux/usb.h> 76eb0de82SPaul Gortmaker #include <linux/module.h> 85a0e3ad6STejun Heo #include <linux/slab.h> 932fe5e39SKevin Lloyd 1032fe5e39SKevin Lloyd #include "usb.h" 1132fe5e39SKevin Lloyd #include "transport.h" 1232fe5e39SKevin Lloyd #include "protocol.h" 1332fe5e39SKevin Lloyd #include "scsiglue.h" 1432fe5e39SKevin Lloyd #include "sierra_ms.h" 1532fe5e39SKevin Lloyd #include "debug.h" 1632fe5e39SKevin Lloyd 1732fe5e39SKevin Lloyd #define SWIMS_USB_REQUEST_SetSwocMode 0x0B 1832fe5e39SKevin Lloyd #define SWIMS_USB_REQUEST_GetSwocInfo 0x0A 1932fe5e39SKevin Lloyd #define SWIMS_USB_INDEX_SetMode 0x0000 2032fe5e39SKevin Lloyd #define SWIMS_SET_MODE_Modem 0x0001 2132fe5e39SKevin Lloyd 2232fe5e39SKevin Lloyd #define TRU_NORMAL 0x01 2332fe5e39SKevin Lloyd #define TRU_FORCE_MS 0x02 2432fe5e39SKevin Lloyd #define TRU_FORCE_MODEM 0x03 2532fe5e39SKevin Lloyd 2632fe5e39SKevin Lloyd static unsigned int swi_tru_install = 1; 2732fe5e39SKevin Lloyd module_param(swi_tru_install, uint, S_IRUGO | S_IWUSR); 2832fe5e39SKevin Lloyd MODULE_PARM_DESC(swi_tru_install, "TRU-Install mode (1=Full Logic (def)," 2932fe5e39SKevin Lloyd " 2=Force CD-Rom, 3=Force Modem)"); 3032fe5e39SKevin Lloyd 3132fe5e39SKevin Lloyd struct swoc_info { 3232fe5e39SKevin Lloyd __u8 rev; 3332fe5e39SKevin Lloyd __u8 reserved[8]; 3432fe5e39SKevin Lloyd __u16 LinuxSKU; 3532fe5e39SKevin Lloyd __u16 LinuxVer; 3632fe5e39SKevin Lloyd __u8 reserved2[47]; 3732fe5e39SKevin Lloyd } __attribute__((__packed__)); 3832fe5e39SKevin Lloyd 3932fe5e39SKevin Lloyd static bool containsFullLinuxPackage(struct swoc_info *swocInfo) 4032fe5e39SKevin Lloyd { 4132fe5e39SKevin Lloyd if ((swocInfo->LinuxSKU >= 0x2100 && swocInfo->LinuxSKU <= 0x2FFF) || 4232fe5e39SKevin Lloyd (swocInfo->LinuxSKU >= 0x7100 && swocInfo->LinuxSKU <= 0x7FFF)) 4332fe5e39SKevin Lloyd return true; 4432fe5e39SKevin Lloyd else 4532fe5e39SKevin Lloyd return false; 4632fe5e39SKevin Lloyd } 4732fe5e39SKevin Lloyd 4832fe5e39SKevin Lloyd static int sierra_set_ms_mode(struct usb_device *udev, __u16 eSWocMode) 4932fe5e39SKevin Lloyd { 5032fe5e39SKevin Lloyd int result; 51191648d0SJoe Perches dev_dbg(&udev->dev, "SWIMS: %s", "DEVICE MODE SWITCH\n"); 5232fe5e39SKevin Lloyd result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 5332fe5e39SKevin Lloyd SWIMS_USB_REQUEST_SetSwocMode, /* __u8 request */ 5432fe5e39SKevin Lloyd USB_TYPE_VENDOR | USB_DIR_OUT, /* __u8 request type */ 5532fe5e39SKevin Lloyd eSWocMode, /* __u16 value */ 5632fe5e39SKevin Lloyd 0x0000, /* __u16 index */ 5732fe5e39SKevin Lloyd NULL, /* void *data */ 5832fe5e39SKevin Lloyd 0, /* __u16 size */ 5932fe5e39SKevin Lloyd USB_CTRL_SET_TIMEOUT); /* int timeout */ 6032fe5e39SKevin Lloyd return result; 6132fe5e39SKevin Lloyd } 6232fe5e39SKevin Lloyd 6332fe5e39SKevin Lloyd 6432fe5e39SKevin Lloyd static int sierra_get_swoc_info(struct usb_device *udev, 6532fe5e39SKevin Lloyd struct swoc_info *swocInfo) 6632fe5e39SKevin Lloyd { 6732fe5e39SKevin Lloyd int result; 6832fe5e39SKevin Lloyd 69191648d0SJoe Perches dev_dbg(&udev->dev, "SWIMS: Attempting to get TRU-Install info\n"); 7032fe5e39SKevin Lloyd 7132fe5e39SKevin Lloyd result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 7232fe5e39SKevin Lloyd SWIMS_USB_REQUEST_GetSwocInfo, /* __u8 request */ 7332fe5e39SKevin Lloyd USB_TYPE_VENDOR | USB_DIR_IN, /* __u8 request type */ 7432fe5e39SKevin Lloyd 0, /* __u16 value */ 7532fe5e39SKevin Lloyd 0, /* __u16 index */ 7632fe5e39SKevin Lloyd (void *) swocInfo, /* void *data */ 7732fe5e39SKevin Lloyd sizeof(struct swoc_info), /* __u16 size */ 7832fe5e39SKevin Lloyd USB_CTRL_SET_TIMEOUT); /* int timeout */ 7932fe5e39SKevin Lloyd 8032fe5e39SKevin Lloyd swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); 8132fe5e39SKevin Lloyd swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); 8232fe5e39SKevin Lloyd return result; 8332fe5e39SKevin Lloyd } 8432fe5e39SKevin Lloyd 85191648d0SJoe Perches static void debug_swoc(const struct device *dev, struct swoc_info *swocInfo) 8632fe5e39SKevin Lloyd { 87191648d0SJoe Perches dev_dbg(dev, "SWIMS: SWoC Rev: %02d\n", swocInfo->rev); 88191648d0SJoe Perches dev_dbg(dev, "SWIMS: Linux SKU: %04X\n", swocInfo->LinuxSKU); 89191648d0SJoe Perches dev_dbg(dev, "SWIMS: Linux Version: %04X\n", swocInfo->LinuxVer); 9032fe5e39SKevin Lloyd } 9132fe5e39SKevin Lloyd 9232fe5e39SKevin Lloyd 9332fe5e39SKevin Lloyd static ssize_t show_truinst(struct device *dev, struct device_attribute *attr, 9432fe5e39SKevin Lloyd char *buf) 9532fe5e39SKevin Lloyd { 9632fe5e39SKevin Lloyd struct swoc_info *swocInfo; 9732fe5e39SKevin Lloyd struct usb_interface *intf = to_usb_interface(dev); 9832fe5e39SKevin Lloyd struct usb_device *udev = interface_to_usbdev(intf); 9932fe5e39SKevin Lloyd int result; 10032fe5e39SKevin Lloyd if (swi_tru_install == TRU_FORCE_MS) { 10132fe5e39SKevin Lloyd result = snprintf(buf, PAGE_SIZE, "Forced Mass Storage\n"); 10232fe5e39SKevin Lloyd } else { 10332fe5e39SKevin Lloyd swocInfo = kmalloc(sizeof(struct swoc_info), GFP_KERNEL); 10432fe5e39SKevin Lloyd if (!swocInfo) { 10532fe5e39SKevin Lloyd snprintf(buf, PAGE_SIZE, "Error\n"); 10632fe5e39SKevin Lloyd return -ENOMEM; 10732fe5e39SKevin Lloyd } 10832fe5e39SKevin Lloyd result = sierra_get_swoc_info(udev, swocInfo); 10932fe5e39SKevin Lloyd if (result < 0) { 110191648d0SJoe Perches dev_dbg(dev, "SWIMS: failed SWoC query\n"); 11132fe5e39SKevin Lloyd kfree(swocInfo); 11232fe5e39SKevin Lloyd snprintf(buf, PAGE_SIZE, "Error\n"); 11332fe5e39SKevin Lloyd return -EIO; 11432fe5e39SKevin Lloyd } 115191648d0SJoe Perches debug_swoc(dev, swocInfo); 11632fe5e39SKevin Lloyd result = snprintf(buf, PAGE_SIZE, 11732fe5e39SKevin Lloyd "REV=%02d SKU=%04X VER=%04X\n", 11832fe5e39SKevin Lloyd swocInfo->rev, 11932fe5e39SKevin Lloyd swocInfo->LinuxSKU, 12032fe5e39SKevin Lloyd swocInfo->LinuxVer); 12132fe5e39SKevin Lloyd kfree(swocInfo); 12232fe5e39SKevin Lloyd } 12332fe5e39SKevin Lloyd return result; 12432fe5e39SKevin Lloyd } 125d9624e75SGreg Kroah-Hartman static DEVICE_ATTR(truinst, S_IRUGO, show_truinst, NULL); 12632fe5e39SKevin Lloyd 12732fe5e39SKevin Lloyd int sierra_ms_init(struct us_data *us) 12832fe5e39SKevin Lloyd { 12932fe5e39SKevin Lloyd int result, retries; 13032fe5e39SKevin Lloyd struct swoc_info *swocInfo; 13132fe5e39SKevin Lloyd struct usb_device *udev; 13232fe5e39SKevin Lloyd struct Scsi_Host *sh; 13332fe5e39SKevin Lloyd 13432fe5e39SKevin Lloyd retries = 3; 13532fe5e39SKevin Lloyd result = 0; 13632fe5e39SKevin Lloyd udev = us->pusb_dev; 13732fe5e39SKevin Lloyd 13832fe5e39SKevin Lloyd sh = us_to_host(us); 1390220a3f0SAlan Cox scsi_get_host_dev(sh); 14032fe5e39SKevin Lloyd 14132fe5e39SKevin Lloyd /* Force Modem mode */ 14232fe5e39SKevin Lloyd if (swi_tru_install == TRU_FORCE_MODEM) { 143191648d0SJoe Perches usb_stor_dbg(us, "SWIMS: Forcing Modem Mode\n"); 14432fe5e39SKevin Lloyd result = sierra_set_ms_mode(udev, SWIMS_SET_MODE_Modem); 14532fe5e39SKevin Lloyd if (result < 0) 146191648d0SJoe Perches usb_stor_dbg(us, "SWIMS: Failed to switch to modem mode\n"); 14732fe5e39SKevin Lloyd return -EIO; 14832fe5e39SKevin Lloyd } 14932fe5e39SKevin Lloyd /* Force Mass Storage mode (keep CD-Rom) */ 15032fe5e39SKevin Lloyd else if (swi_tru_install == TRU_FORCE_MS) { 151191648d0SJoe Perches usb_stor_dbg(us, "SWIMS: Forcing Mass Storage Mode\n"); 15232fe5e39SKevin Lloyd goto complete; 15332fe5e39SKevin Lloyd } 15432fe5e39SKevin Lloyd /* Normal TRU-Install Logic */ 15532fe5e39SKevin Lloyd else { 156191648d0SJoe Perches usb_stor_dbg(us, "SWIMS: Normal SWoC Logic\n"); 15732fe5e39SKevin Lloyd 15832fe5e39SKevin Lloyd swocInfo = kmalloc(sizeof(struct swoc_info), 15932fe5e39SKevin Lloyd GFP_KERNEL); 160191648d0SJoe Perches if (!swocInfo) 16132fe5e39SKevin Lloyd return -ENOMEM; 16232fe5e39SKevin Lloyd 16332fe5e39SKevin Lloyd retries = 3; 16432fe5e39SKevin Lloyd do { 16532fe5e39SKevin Lloyd retries--; 16632fe5e39SKevin Lloyd result = sierra_get_swoc_info(udev, swocInfo); 16732fe5e39SKevin Lloyd if (result < 0) { 168191648d0SJoe Perches usb_stor_dbg(us, "SWIMS: Failed SWoC query\n"); 16932fe5e39SKevin Lloyd schedule_timeout_uninterruptible(2*HZ); 17032fe5e39SKevin Lloyd } 17132fe5e39SKevin Lloyd } while (retries && result < 0); 17232fe5e39SKevin Lloyd 17332fe5e39SKevin Lloyd if (result < 0) { 174191648d0SJoe Perches usb_stor_dbg(us, "SWIMS: Completely failed SWoC query\n"); 17532fe5e39SKevin Lloyd kfree(swocInfo); 17632fe5e39SKevin Lloyd return -EIO; 17732fe5e39SKevin Lloyd } 17832fe5e39SKevin Lloyd 179191648d0SJoe Perches debug_swoc(&us->pusb_dev->dev, swocInfo); 18032fe5e39SKevin Lloyd 181f0183a33SFelipe Balbi /* 182f0183a33SFelipe Balbi * If there is not Linux software on the TRU-Install device 18332fe5e39SKevin Lloyd * then switch to modem mode 18432fe5e39SKevin Lloyd */ 18532fe5e39SKevin Lloyd if (!containsFullLinuxPackage(swocInfo)) { 186191648d0SJoe Perches usb_stor_dbg(us, "SWIMS: Switching to Modem Mode\n"); 18732fe5e39SKevin Lloyd result = sierra_set_ms_mode(udev, 18832fe5e39SKevin Lloyd SWIMS_SET_MODE_Modem); 18932fe5e39SKevin Lloyd if (result < 0) 190191648d0SJoe Perches usb_stor_dbg(us, "SWIMS: Failed to switch modem\n"); 19132fe5e39SKevin Lloyd kfree(swocInfo); 19232fe5e39SKevin Lloyd return -EIO; 19332fe5e39SKevin Lloyd } 19432fe5e39SKevin Lloyd kfree(swocInfo); 19532fe5e39SKevin Lloyd } 19632fe5e39SKevin Lloyd complete: 19732fe5e39SKevin Lloyd result = device_create_file(&us->pusb_intf->dev, &dev_attr_truinst); 19832fe5e39SKevin Lloyd 199be475d90SAlan Stern return 0; 20032fe5e39SKevin Lloyd } 20132fe5e39SKevin Lloyd 202