18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci#include <scsi/scsi.h> 38c2ecf20Sopenharmony_ci#include <scsi/scsi_host.h> 48c2ecf20Sopenharmony_ci#include <scsi/scsi_cmnd.h> 58c2ecf20Sopenharmony_ci#include <scsi/scsi_device.h> 68c2ecf20Sopenharmony_ci#include <linux/usb.h> 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci#include <linux/slab.h> 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include "usb.h" 118c2ecf20Sopenharmony_ci#include "transport.h" 128c2ecf20Sopenharmony_ci#include "protocol.h" 138c2ecf20Sopenharmony_ci#include "scsiglue.h" 148c2ecf20Sopenharmony_ci#include "sierra_ms.h" 158c2ecf20Sopenharmony_ci#include "debug.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define SWIMS_USB_REQUEST_SetSwocMode 0x0B 188c2ecf20Sopenharmony_ci#define SWIMS_USB_REQUEST_GetSwocInfo 0x0A 198c2ecf20Sopenharmony_ci#define SWIMS_USB_INDEX_SetMode 0x0000 208c2ecf20Sopenharmony_ci#define SWIMS_SET_MODE_Modem 0x0001 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define TRU_NORMAL 0x01 238c2ecf20Sopenharmony_ci#define TRU_FORCE_MS 0x02 248c2ecf20Sopenharmony_ci#define TRU_FORCE_MODEM 0x03 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic unsigned int swi_tru_install = 1; 278c2ecf20Sopenharmony_cimodule_param(swi_tru_install, uint, S_IRUGO | S_IWUSR); 288c2ecf20Sopenharmony_ciMODULE_PARM_DESC(swi_tru_install, "TRU-Install mode (1=Full Logic (def)," 298c2ecf20Sopenharmony_ci " 2=Force CD-Rom, 3=Force Modem)"); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistruct swoc_info { 328c2ecf20Sopenharmony_ci __u8 rev; 338c2ecf20Sopenharmony_ci __u8 reserved[8]; 348c2ecf20Sopenharmony_ci __u16 LinuxSKU; 358c2ecf20Sopenharmony_ci __u16 LinuxVer; 368c2ecf20Sopenharmony_ci __u8 reserved2[47]; 378c2ecf20Sopenharmony_ci} __attribute__((__packed__)); 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic bool containsFullLinuxPackage(struct swoc_info *swocInfo) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci if ((swocInfo->LinuxSKU >= 0x2100 && swocInfo->LinuxSKU <= 0x2FFF) || 428c2ecf20Sopenharmony_ci (swocInfo->LinuxSKU >= 0x7100 && swocInfo->LinuxSKU <= 0x7FFF)) 438c2ecf20Sopenharmony_ci return true; 448c2ecf20Sopenharmony_ci else 458c2ecf20Sopenharmony_ci return false; 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic int sierra_set_ms_mode(struct usb_device *udev, __u16 eSWocMode) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci int result; 518c2ecf20Sopenharmony_ci dev_dbg(&udev->dev, "SWIMS: %s", "DEVICE MODE SWITCH\n"); 528c2ecf20Sopenharmony_ci result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 538c2ecf20Sopenharmony_ci SWIMS_USB_REQUEST_SetSwocMode, /* __u8 request */ 548c2ecf20Sopenharmony_ci USB_TYPE_VENDOR | USB_DIR_OUT, /* __u8 request type */ 558c2ecf20Sopenharmony_ci eSWocMode, /* __u16 value */ 568c2ecf20Sopenharmony_ci 0x0000, /* __u16 index */ 578c2ecf20Sopenharmony_ci NULL, /* void *data */ 588c2ecf20Sopenharmony_ci 0, /* __u16 size */ 598c2ecf20Sopenharmony_ci USB_CTRL_SET_TIMEOUT); /* int timeout */ 608c2ecf20Sopenharmony_ci return result; 618c2ecf20Sopenharmony_ci} 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic int sierra_get_swoc_info(struct usb_device *udev, 658c2ecf20Sopenharmony_ci struct swoc_info *swocInfo) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci int result; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci dev_dbg(&udev->dev, "SWIMS: Attempting to get TRU-Install info\n"); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 728c2ecf20Sopenharmony_ci SWIMS_USB_REQUEST_GetSwocInfo, /* __u8 request */ 738c2ecf20Sopenharmony_ci USB_TYPE_VENDOR | USB_DIR_IN, /* __u8 request type */ 748c2ecf20Sopenharmony_ci 0, /* __u16 value */ 758c2ecf20Sopenharmony_ci 0, /* __u16 index */ 768c2ecf20Sopenharmony_ci (void *) swocInfo, /* void *data */ 778c2ecf20Sopenharmony_ci sizeof(struct swoc_info), /* __u16 size */ 788c2ecf20Sopenharmony_ci USB_CTRL_SET_TIMEOUT); /* int timeout */ 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); 818c2ecf20Sopenharmony_ci swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); 828c2ecf20Sopenharmony_ci return result; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic void debug_swoc(const struct device *dev, struct swoc_info *swocInfo) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci dev_dbg(dev, "SWIMS: SWoC Rev: %02d\n", swocInfo->rev); 888c2ecf20Sopenharmony_ci dev_dbg(dev, "SWIMS: Linux SKU: %04X\n", swocInfo->LinuxSKU); 898c2ecf20Sopenharmony_ci dev_dbg(dev, "SWIMS: Linux Version: %04X\n", swocInfo->LinuxVer); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic ssize_t truinst_show(struct device *dev, struct device_attribute *attr, 948c2ecf20Sopenharmony_ci char *buf) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct swoc_info *swocInfo; 978c2ecf20Sopenharmony_ci struct usb_interface *intf = to_usb_interface(dev); 988c2ecf20Sopenharmony_ci struct usb_device *udev = interface_to_usbdev(intf); 998c2ecf20Sopenharmony_ci int result; 1008c2ecf20Sopenharmony_ci if (swi_tru_install == TRU_FORCE_MS) { 1018c2ecf20Sopenharmony_ci result = snprintf(buf, PAGE_SIZE, "Forced Mass Storage\n"); 1028c2ecf20Sopenharmony_ci } else { 1038c2ecf20Sopenharmony_ci swocInfo = kmalloc(sizeof(struct swoc_info), GFP_KERNEL); 1048c2ecf20Sopenharmony_ci if (!swocInfo) { 1058c2ecf20Sopenharmony_ci snprintf(buf, PAGE_SIZE, "Error\n"); 1068c2ecf20Sopenharmony_ci return -ENOMEM; 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci result = sierra_get_swoc_info(udev, swocInfo); 1098c2ecf20Sopenharmony_ci if (result < 0) { 1108c2ecf20Sopenharmony_ci dev_dbg(dev, "SWIMS: failed SWoC query\n"); 1118c2ecf20Sopenharmony_ci kfree(swocInfo); 1128c2ecf20Sopenharmony_ci snprintf(buf, PAGE_SIZE, "Error\n"); 1138c2ecf20Sopenharmony_ci return -EIO; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci debug_swoc(dev, swocInfo); 1168c2ecf20Sopenharmony_ci result = snprintf(buf, PAGE_SIZE, 1178c2ecf20Sopenharmony_ci "REV=%02d SKU=%04X VER=%04X\n", 1188c2ecf20Sopenharmony_ci swocInfo->rev, 1198c2ecf20Sopenharmony_ci swocInfo->LinuxSKU, 1208c2ecf20Sopenharmony_ci swocInfo->LinuxVer); 1218c2ecf20Sopenharmony_ci kfree(swocInfo); 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci return result; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(truinst); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ciint sierra_ms_init(struct us_data *us) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci int result, retries; 1308c2ecf20Sopenharmony_ci struct swoc_info *swocInfo; 1318c2ecf20Sopenharmony_ci struct usb_device *udev; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci retries = 3; 1348c2ecf20Sopenharmony_ci result = 0; 1358c2ecf20Sopenharmony_ci udev = us->pusb_dev; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci /* Force Modem mode */ 1388c2ecf20Sopenharmony_ci if (swi_tru_install == TRU_FORCE_MODEM) { 1398c2ecf20Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Forcing Modem Mode\n"); 1408c2ecf20Sopenharmony_ci result = sierra_set_ms_mode(udev, SWIMS_SET_MODE_Modem); 1418c2ecf20Sopenharmony_ci if (result < 0) 1428c2ecf20Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Failed to switch to modem mode\n"); 1438c2ecf20Sopenharmony_ci return -EIO; 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci /* Force Mass Storage mode (keep CD-Rom) */ 1468c2ecf20Sopenharmony_ci else if (swi_tru_install == TRU_FORCE_MS) { 1478c2ecf20Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Forcing Mass Storage Mode\n"); 1488c2ecf20Sopenharmony_ci goto complete; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci /* Normal TRU-Install Logic */ 1518c2ecf20Sopenharmony_ci else { 1528c2ecf20Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Normal SWoC Logic\n"); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci swocInfo = kmalloc(sizeof(struct swoc_info), 1558c2ecf20Sopenharmony_ci GFP_KERNEL); 1568c2ecf20Sopenharmony_ci if (!swocInfo) 1578c2ecf20Sopenharmony_ci return -ENOMEM; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci retries = 3; 1608c2ecf20Sopenharmony_ci do { 1618c2ecf20Sopenharmony_ci retries--; 1628c2ecf20Sopenharmony_ci result = sierra_get_swoc_info(udev, swocInfo); 1638c2ecf20Sopenharmony_ci if (result < 0) { 1648c2ecf20Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Failed SWoC query\n"); 1658c2ecf20Sopenharmony_ci schedule_timeout_uninterruptible(2*HZ); 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci } while (retries && result < 0); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci if (result < 0) { 1708c2ecf20Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Completely failed SWoC query\n"); 1718c2ecf20Sopenharmony_ci kfree(swocInfo); 1728c2ecf20Sopenharmony_ci return -EIO; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci debug_swoc(&us->pusb_dev->dev, swocInfo); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci /* 1788c2ecf20Sopenharmony_ci * If there is not Linux software on the TRU-Install device 1798c2ecf20Sopenharmony_ci * then switch to modem mode 1808c2ecf20Sopenharmony_ci */ 1818c2ecf20Sopenharmony_ci if (!containsFullLinuxPackage(swocInfo)) { 1828c2ecf20Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Switching to Modem Mode\n"); 1838c2ecf20Sopenharmony_ci result = sierra_set_ms_mode(udev, 1848c2ecf20Sopenharmony_ci SWIMS_SET_MODE_Modem); 1858c2ecf20Sopenharmony_ci if (result < 0) 1868c2ecf20Sopenharmony_ci usb_stor_dbg(us, "SWIMS: Failed to switch modem\n"); 1878c2ecf20Sopenharmony_ci kfree(swocInfo); 1888c2ecf20Sopenharmony_ci return -EIO; 1898c2ecf20Sopenharmony_ci } 1908c2ecf20Sopenharmony_ci kfree(swocInfo); 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_cicomplete: 1938c2ecf20Sopenharmony_ci return device_create_file(&us->pusb_intf->dev, &dev_attr_truinst); 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 196