1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <scsi/scsi.h> |
3 | #include <scsi/scsi_host.h> |
4 | #include <scsi/scsi_cmnd.h> |
5 | #include <scsi/scsi_device.h> |
6 | #include <linux/usb.h> |
7 | #include <linux/module.h> |
8 | #include <linux/slab.h> |
9 | |
10 | #include "usb.h" |
11 | #include "transport.h" |
12 | #include "protocol.h" |
13 | #include "scsiglue.h" |
14 | #include "sierra_ms.h" |
15 | #include "debug.h" |
16 | |
17 | #define SWIMS_USB_REQUEST_SetSwocMode 0x0B |
18 | #define SWIMS_USB_REQUEST_GetSwocInfo 0x0A |
19 | #define SWIMS_USB_INDEX_SetMode 0x0000 |
20 | #define SWIMS_SET_MODE_Modem 0x0001 |
21 | |
22 | #define TRU_NORMAL 0x01 |
23 | #define TRU_FORCE_MS 0x02 |
24 | #define TRU_FORCE_MODEM 0x03 |
25 | |
26 | static unsigned int swi_tru_install = 1; |
27 | module_param(swi_tru_install, uint, S_IRUGO | S_IWUSR); |
28 | MODULE_PARM_DESC(swi_tru_install, "TRU-Install mode (1=Full Logic (def)," |
29 | " 2=Force CD-Rom, 3=Force Modem)" ); |
30 | |
31 | struct swoc_info { |
32 | __u8 rev; |
33 | __u8 reserved[8]; |
34 | __u16 LinuxSKU; |
35 | __u16 LinuxVer; |
36 | __u8 reserved2[47]; |
37 | } __attribute__((__packed__)); |
38 | |
39 | static bool containsFullLinuxPackage(struct swoc_info *swocInfo) |
40 | { |
41 | if ((swocInfo->LinuxSKU >= 0x2100 && swocInfo->LinuxSKU <= 0x2FFF) || |
42 | (swocInfo->LinuxSKU >= 0x7100 && swocInfo->LinuxSKU <= 0x7FFF)) |
43 | return true; |
44 | else |
45 | return false; |
46 | } |
47 | |
48 | static int sierra_set_ms_mode(struct usb_device *udev, __u16 eSWocMode) |
49 | { |
50 | int result; |
51 | dev_dbg(&udev->dev, "SWIMS: %s" , "DEVICE MODE SWITCH\n" ); |
52 | result = usb_control_msg(dev: udev, usb_sndctrlpipe(udev, 0), |
53 | SWIMS_USB_REQUEST_SetSwocMode, /* __u8 request */ |
54 | USB_TYPE_VENDOR | USB_DIR_OUT, /* __u8 request type */ |
55 | value: eSWocMode, /* __u16 value */ |
56 | index: 0x0000, /* __u16 index */ |
57 | NULL, /* void *data */ |
58 | size: 0, /* __u16 size */ |
59 | USB_CTRL_SET_TIMEOUT); /* int timeout */ |
60 | return result; |
61 | } |
62 | |
63 | |
64 | static int sierra_get_swoc_info(struct usb_device *udev, |
65 | struct swoc_info *swocInfo) |
66 | { |
67 | int result; |
68 | |
69 | dev_dbg(&udev->dev, "SWIMS: Attempting to get TRU-Install info\n" ); |
70 | |
71 | result = usb_control_msg(dev: udev, usb_rcvctrlpipe(udev, 0), |
72 | SWIMS_USB_REQUEST_GetSwocInfo, /* __u8 request */ |
73 | USB_TYPE_VENDOR | USB_DIR_IN, /* __u8 request type */ |
74 | value: 0, /* __u16 value */ |
75 | index: 0, /* __u16 index */ |
76 | data: (void *) swocInfo, /* void *data */ |
77 | size: sizeof(struct swoc_info), /* __u16 size */ |
78 | USB_CTRL_SET_TIMEOUT); /* int timeout */ |
79 | |
80 | swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); |
81 | swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); |
82 | return result; |
83 | } |
84 | |
85 | static void debug_swoc(const struct device *dev, struct swoc_info *swocInfo) |
86 | { |
87 | dev_dbg(dev, "SWIMS: SWoC Rev: %02d\n" , swocInfo->rev); |
88 | dev_dbg(dev, "SWIMS: Linux SKU: %04X\n" , swocInfo->LinuxSKU); |
89 | dev_dbg(dev, "SWIMS: Linux Version: %04X\n" , swocInfo->LinuxVer); |
90 | } |
91 | |
92 | |
93 | static ssize_t truinst_show(struct device *dev, struct device_attribute *attr, |
94 | char *buf) |
95 | { |
96 | struct swoc_info *swocInfo; |
97 | struct usb_interface *intf = to_usb_interface(dev); |
98 | struct usb_device *udev = interface_to_usbdev(intf); |
99 | int result; |
100 | if (swi_tru_install == TRU_FORCE_MS) { |
101 | result = snprintf(buf, PAGE_SIZE, fmt: "Forced Mass Storage\n" ); |
102 | } else { |
103 | swocInfo = kmalloc(size: sizeof(struct swoc_info), GFP_KERNEL); |
104 | if (!swocInfo) { |
105 | snprintf(buf, PAGE_SIZE, fmt: "Error\n" ); |
106 | return -ENOMEM; |
107 | } |
108 | result = sierra_get_swoc_info(udev, swocInfo); |
109 | if (result < 0) { |
110 | dev_dbg(dev, "SWIMS: failed SWoC query\n" ); |
111 | kfree(objp: swocInfo); |
112 | snprintf(buf, PAGE_SIZE, fmt: "Error\n" ); |
113 | return -EIO; |
114 | } |
115 | debug_swoc(dev, swocInfo); |
116 | result = snprintf(buf, PAGE_SIZE, |
117 | fmt: "REV=%02d SKU=%04X VER=%04X\n" , |
118 | swocInfo->rev, |
119 | swocInfo->LinuxSKU, |
120 | swocInfo->LinuxVer); |
121 | kfree(objp: swocInfo); |
122 | } |
123 | return result; |
124 | } |
125 | static DEVICE_ATTR_RO(truinst); |
126 | |
127 | int sierra_ms_init(struct us_data *us) |
128 | { |
129 | int result, retries; |
130 | struct swoc_info *swocInfo; |
131 | struct usb_device *udev; |
132 | |
133 | udev = us->pusb_dev; |
134 | |
135 | /* Force Modem mode */ |
136 | if (swi_tru_install == TRU_FORCE_MODEM) { |
137 | usb_stor_dbg(us, fmt: "SWIMS: Forcing Modem Mode\n" ); |
138 | result = sierra_set_ms_mode(udev, SWIMS_SET_MODE_Modem); |
139 | if (result < 0) |
140 | usb_stor_dbg(us, fmt: "SWIMS: Failed to switch to modem mode\n" ); |
141 | return -EIO; |
142 | } |
143 | /* Force Mass Storage mode (keep CD-Rom) */ |
144 | else if (swi_tru_install == TRU_FORCE_MS) { |
145 | usb_stor_dbg(us, fmt: "SWIMS: Forcing Mass Storage Mode\n" ); |
146 | goto complete; |
147 | } |
148 | /* Normal TRU-Install Logic */ |
149 | else { |
150 | usb_stor_dbg(us, fmt: "SWIMS: Normal SWoC Logic\n" ); |
151 | |
152 | swocInfo = kmalloc(size: sizeof(struct swoc_info), |
153 | GFP_KERNEL); |
154 | if (!swocInfo) |
155 | return -ENOMEM; |
156 | |
157 | retries = 3; |
158 | do { |
159 | retries--; |
160 | result = sierra_get_swoc_info(udev, swocInfo); |
161 | if (result < 0) { |
162 | usb_stor_dbg(us, fmt: "SWIMS: Failed SWoC query\n" ); |
163 | schedule_timeout_uninterruptible(timeout: 2*HZ); |
164 | } |
165 | } while (retries && result < 0); |
166 | |
167 | if (result < 0) { |
168 | usb_stor_dbg(us, fmt: "SWIMS: Completely failed SWoC query\n" ); |
169 | kfree(objp: swocInfo); |
170 | return -EIO; |
171 | } |
172 | |
173 | debug_swoc(dev: &us->pusb_dev->dev, swocInfo); |
174 | |
175 | /* |
176 | * If there is not Linux software on the TRU-Install device |
177 | * then switch to modem mode |
178 | */ |
179 | if (!containsFullLinuxPackage(swocInfo)) { |
180 | usb_stor_dbg(us, fmt: "SWIMS: Switching to Modem Mode\n" ); |
181 | result = sierra_set_ms_mode(udev, |
182 | SWIMS_SET_MODE_Modem); |
183 | if (result < 0) |
184 | usb_stor_dbg(us, fmt: "SWIMS: Failed to switch modem\n" ); |
185 | kfree(objp: swocInfo); |
186 | return -EIO; |
187 | } |
188 | kfree(objp: swocInfo); |
189 | } |
190 | complete: |
191 | return device_create_file(device: &us->pusb_intf->dev, entry: &dev_attr_truinst); |
192 | } |
193 | |
194 | |