root/drivers/usb/storage/sierra_ms.c
// 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 = sysfs_emit(buf, "Forced Mass Storage\n");
        } else {
                swocInfo = kmalloc_obj(struct swoc_info);
                if (!swocInfo) {
                        sysfs_emit(buf, "Error\n");
                        return -ENOMEM;
                }
                result = sierra_get_swoc_info(udev, swocInfo);
                if (result < 0) {
                        dev_dbg(dev, "SWIMS: failed SWoC query\n");
                        kfree(swocInfo);
                        sysfs_emit(buf, "Error\n");
                        return -EIO;
                }
                debug_swoc(dev, swocInfo);
                result = sysfs_emit(buf,
                                    "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_obj(struct swoc_info);
                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);
}