]>
Commit | Line | Data |
---|---|---|
d64399b5 | 1 | /* usbms.c - USB Mass Storage Support. */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
4 | * Copyright (C) 2008 Free Software Foundation, Inc. | |
5 | * | |
6 | * GRUB is free software: you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 3 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * GRUB is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <grub/dl.h> | |
21 | #include <grub/mm.h> | |
22 | #include <grub/usb.h> | |
23 | #include <grub/scsi.h> | |
24 | #include <grub/scsicmd.h> | |
25 | #include <grub/misc.h> | |
26 | ||
27 | #define GRUB_USBMS_DIRECTION_BIT 7 | |
28 | ||
29 | /* The USB Mass Storage Command Block Wrapper. */ | |
30 | struct grub_usbms_cbw | |
31 | { | |
32 | grub_uint32_t signature; | |
33 | grub_uint32_t tag; | |
34 | grub_uint32_t transfer_length; | |
35 | grub_uint8_t flags; | |
36 | grub_uint8_t lun; | |
37 | grub_uint8_t length; | |
38 | grub_uint8_t cbwcb[16]; | |
39 | } __attribute__ ((packed)); | |
40 | ||
41 | struct grub_usbms_csw | |
42 | { | |
43 | grub_uint32_t signature; | |
44 | grub_uint32_t tag; | |
45 | grub_uint32_t residue; | |
46 | grub_uint8_t status; | |
47 | } __attribute__ ((packed)); | |
48 | ||
49 | struct grub_usbms_dev | |
50 | { | |
51 | struct grub_usb_device *dev; | |
52 | ||
53 | int luns; | |
54 | ||
55 | int interface; | |
56 | struct grub_usb_desc_endp *in; | |
57 | struct grub_usb_desc_endp *out; | |
58 | ||
59 | int in_maxsz; | |
60 | int out_maxsz; | |
61 | ||
62 | struct grub_usbms_dev *next; | |
63 | }; | |
64 | typedef struct grub_usbms_dev *grub_usbms_dev_t; | |
65 | ||
66 | static grub_usbms_dev_t grub_usbms_dev_list; | |
67 | ||
68 | static int devcnt; | |
69 | ||
70 | static grub_err_t | |
71 | grub_usbms_reset (grub_usb_device_t dev, int interface) | |
72 | { | |
73 | return grub_usb_control_msg (dev, 0x21, 255, 0, interface, 0, 0); | |
74 | } | |
75 | ||
76 | static void | |
77 | grub_usbms_finddevs (void) | |
78 | { | |
79 | auto int usb_iterate (grub_usb_device_t dev); | |
80 | ||
81 | int usb_iterate (grub_usb_device_t usbdev) | |
82 | { | |
83 | grub_usb_err_t err; | |
84 | struct grub_usb_desc_device *descdev = &usbdev->descdev; | |
85 | int i; | |
86 | ||
778ff324 AN |
87 | if (descdev->class != 0 || descdev->subclass || descdev->protocol != 0 |
88 | || descdev->configcnt == 0) | |
d64399b5 | 89 | return 0; |
90 | ||
91 | /* XXX: Just check configuration 0 for now. */ | |
92 | for (i = 0; i < usbdev->config[0].descconf->numif; i++) | |
93 | { | |
94 | struct grub_usbms_dev *usbms; | |
95 | struct grub_usb_desc_if *interf; | |
96 | int j; | |
778ff324 AN |
97 | grub_uint8_t luns = 0; |
98 | ||
99 | grub_dprintf ("usbms", "alive\n"); | |
d64399b5 | 100 | |
101 | interf = usbdev->config[0].interf[i].descif; | |
102 | ||
103 | /* If this is not a USB Mass Storage device with a supported | |
104 | protocol, just skip it. */ | |
778ff324 AN |
105 | grub_dprintf ("usbms", "iterate: interf=%d, class=%d, subclass=%d, protocol=%d\n", |
106 | i, interf->class, interf->subclass, interf->protocol); | |
107 | ||
d64399b5 | 108 | if (interf->class != GRUB_USB_CLASS_MASS_STORAGE |
778ff324 AN |
109 | || ( interf->subclass != GRUB_USBMS_SUBCLASS_BULK && |
110 | /* Experimental support of RBC, MMC-2, UFI, SFF-8070i devices */ | |
111 | interf->subclass != GRUB_USBMS_SUBCLASS_RBC && | |
112 | interf->subclass != GRUB_USBMS_SUBCLASS_MMC2 && | |
113 | interf->subclass != GRUB_USBMS_SUBCLASS_UFI && | |
114 | interf->subclass != GRUB_USBMS_SUBCLASS_SFF8070 ) | |
d64399b5 | 115 | || interf->protocol != GRUB_USBMS_PROTOCOL_BULK) |
116 | { | |
117 | continue; | |
118 | } | |
119 | ||
778ff324 AN |
120 | grub_dprintf ("usbms", "alive\n"); |
121 | ||
d64399b5 | 122 | devcnt++; |
eab58da2 | 123 | usbms = grub_zalloc (sizeof (struct grub_usbms_dev)); |
d64399b5 | 124 | if (! usbms) |
125 | return 1; | |
126 | ||
127 | usbms->dev = usbdev; | |
128 | usbms->interface = i; | |
d64399b5 | 129 | |
778ff324 AN |
130 | grub_dprintf ("usbms", "alive\n"); |
131 | ||
d64399b5 | 132 | /* Iterate over all endpoints of this interface, at least a |
133 | IN and OUT bulk endpoint are required. */ | |
134 | for (j = 0; j < interf->endpointcnt; j++) | |
135 | { | |
136 | struct grub_usb_desc_endp *endp; | |
137 | endp = &usbdev->config[0].interf[i].descendp[j]; | |
138 | ||
139 | if ((endp->endp_addr & 128) && (endp->attrib & 3) == 2) | |
140 | { | |
141 | /* Bulk IN endpoint. */ | |
142 | usbms->in = endp; | |
778ff324 AN |
143 | /* Clear Halt is not possible yet! */ |
144 | /* grub_usb_clear_halt (usbdev, endp->endp_addr); */ | |
d64399b5 | 145 | usbms->in_maxsz = endp->maxpacket; |
146 | } | |
147 | else if (!(endp->endp_addr & 128) && (endp->attrib & 3) == 2) | |
148 | { | |
149 | /* Bulk OUT endpoint. */ | |
150 | usbms->out = endp; | |
778ff324 AN |
151 | /* Clear Halt is not possible yet! */ |
152 | /* grub_usb_clear_halt (usbdev, endp->endp_addr); */ | |
d64399b5 | 153 | usbms->out_maxsz = endp->maxpacket; |
154 | } | |
155 | } | |
156 | ||
157 | if (!usbms->in || !usbms->out) | |
158 | { | |
159 | grub_free (usbms); | |
160 | return 0; | |
161 | } | |
162 | ||
778ff324 AN |
163 | grub_dprintf ("usbms", "alive\n"); |
164 | ||
165 | /* XXX: Activate the first configuration. */ | |
166 | grub_usb_set_configuration (usbdev, 1); | |
167 | ||
d64399b5 | 168 | /* Query the amount of LUNs. */ |
169 | err = grub_usb_control_msg (usbdev, 0xA1, 254, | |
170 | 0, i, 1, (char *) &luns); | |
778ff324 | 171 | |
d64399b5 | 172 | if (err) |
173 | { | |
174 | /* In case of a stall, clear the stall. */ | |
175 | if (err == GRUB_USB_ERR_STALL) | |
176 | { | |
778ff324 AN |
177 | grub_usb_clear_halt (usbdev, usbms->in->endp_addr); |
178 | grub_usb_clear_halt (usbdev, usbms->out->endp_addr); | |
d64399b5 | 179 | } |
d64399b5 | 180 | /* Just set the amount of LUNs to one. */ |
181 | grub_errno = GRUB_ERR_NONE; | |
182 | usbms->luns = 1; | |
183 | } | |
184 | else | |
778ff324 AN |
185 | /* luns = 0 means one LUN with ID 0 present ! */ |
186 | /* We get from device not number of LUNs but highest | |
187 | * LUN number. LUNs are numbered from 0, | |
188 | * i.e. number of LUNs is luns+1 ! */ | |
189 | usbms->luns = luns + 1; | |
d64399b5 | 190 | |
778ff324 | 191 | grub_dprintf ("usbms", "alive\n"); |
d64399b5 | 192 | |
193 | usbms->next = grub_usbms_dev_list; | |
194 | grub_usbms_dev_list = usbms; | |
195 | ||
778ff324 AN |
196 | #if 0 /* All this part should be probably deleted. |
197 | * This make trouble on some devices if they are not in | |
198 | * Phase Error state - and there they should be not in such state... | |
199 | * Bulk only mass storage reset procedure should be used only | |
200 | * on place and in time when it is really necessary. */ | |
201 | /* Reset recovery procedure */ | |
4241d2b1 | 202 | /* Bulk-Only Mass Storage Reset, after the reset commands |
d64399b5 | 203 | will be accepted. */ |
204 | grub_usbms_reset (usbdev, i); | |
778ff324 AN |
205 | grub_usb_clear_halt (usbdev, usbms->in->endp_addr); |
206 | grub_usb_clear_halt (usbdev, usbms->out->endp_addr); | |
207 | #endif | |
d64399b5 | 208 | |
209 | return 0; | |
210 | } | |
211 | ||
778ff324 | 212 | grub_dprintf ("usbms", "alive\n"); |
d64399b5 | 213 | return 0; |
214 | } | |
778ff324 | 215 | grub_dprintf ("usbms", "alive\n"); |
d64399b5 | 216 | |
217 | grub_usb_iterate (usb_iterate); | |
778ff324 AN |
218 | grub_dprintf ("usbms", "alive\n"); |
219 | ||
d64399b5 | 220 | } |
221 | ||
222 | \f | |
223 | ||
224 | static int | |
225 | grub_usbms_iterate (int (*hook) (const char *name, int luns)) | |
226 | { | |
227 | grub_usbms_dev_t p; | |
228 | int cnt = 0; | |
229 | ||
230 | for (p = grub_usbms_dev_list; p; p = p->next) | |
231 | { | |
8b442f3f | 232 | char *devname; |
61eb45ee | 233 | devname = grub_xasprintf ("usb%d", cnt); |
d64399b5 | 234 | |
235 | if (hook (devname, p->luns)) | |
8b442f3f VS |
236 | { |
237 | grub_free (devname); | |
238 | return 1; | |
239 | } | |
240 | grub_free (devname); | |
d64399b5 | 241 | cnt++; |
242 | } | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
247 | static grub_err_t | |
4241d2b1 | 248 | grub_usbms_transfer (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd, |
249 | grub_size_t size, char *buf, int read_write) | |
d64399b5 | 250 | { |
251 | struct grub_usbms_cbw cbw; | |
252 | grub_usbms_dev_t dev = (grub_usbms_dev_t) scsi->data; | |
253 | struct grub_usbms_csw status; | |
254 | static grub_uint32_t tag = 0; | |
e3b27c39 | 255 | grub_usb_err_t err = GRUB_USB_ERR_NONE; |
7d4873c2 | 256 | grub_usb_err_t errCSW = GRUB_USB_ERR_NONE; |
ee293aee | 257 | int retrycnt = 3 + 1; |
778ff324 | 258 | grub_size_t i; |
d64399b5 | 259 | |
260 | retry: | |
ee293aee | 261 | retrycnt--; |
d64399b5 | 262 | if (retrycnt == 0) |
ee293aee | 263 | return grub_error (GRUB_ERR_IO, "USB Mass Storage stalled"); |
d64399b5 | 264 | |
265 | /* Setup the request. */ | |
266 | grub_memset (&cbw, 0, sizeof (cbw)); | |
267 | cbw.signature = grub_cpu_to_le32 (0x43425355); | |
268 | cbw.tag = tag++; | |
269 | cbw.transfer_length = grub_cpu_to_le32 (size); | |
270 | cbw.flags = (!read_write) << GRUB_USBMS_DIRECTION_BIT; | |
778ff324 | 271 | cbw.lun = scsi->lun; /* In USB MS CBW are LUN bits on another place than in SCSI CDB, both should be set correctly. */ |
d64399b5 | 272 | cbw.length = cmdsize; |
273 | grub_memcpy (cbw.cbwcb, cmd, cmdsize); | |
778ff324 AN |
274 | |
275 | /* Debug print of CBW content. */ | |
276 | grub_dprintf ("usb", "CBW: sign=0x%08x tag=0x%08x len=0x%08x\n", | |
277 | cbw.signature, cbw.tag, cbw.transfer_length); | |
278 | grub_dprintf ("usb", "CBW: flags=0x%02x lun=0x%02x CB_len=0x%02x\n", | |
279 | cbw.flags, cbw.lun, cbw.length); | |
280 | grub_dprintf ("usb", "CBW: cmd:\n %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", | |
281 | cbw.cbwcb[ 0], cbw.cbwcb[ 1], cbw.cbwcb[ 2], cbw.cbwcb[ 3], | |
282 | cbw.cbwcb[ 4], cbw.cbwcb[ 5], cbw.cbwcb[ 6], cbw.cbwcb[ 7], | |
283 | cbw.cbwcb[ 8], cbw.cbwcb[ 9], cbw.cbwcb[10], cbw.cbwcb[11], | |
284 | cbw.cbwcb[12], cbw.cbwcb[13], cbw.cbwcb[14], cbw.cbwcb[15]); | |
285 | ||
286 | /* Write the request. | |
287 | * XXX: Error recovery is maybe still not fully correct. */ | |
288 | err = grub_usb_bulk_write (dev->dev, dev->out->endp_addr, | |
d64399b5 | 289 | sizeof (cbw), (char *) &cbw); |
290 | if (err) | |
291 | { | |
292 | if (err == GRUB_USB_ERR_STALL) | |
293 | { | |
294 | grub_usb_clear_halt (dev->dev, dev->out->endp_addr); | |
7d4873c2 | 295 | goto CheckCSW; |
d64399b5 | 296 | } |
ac70fa32 | 297 | return grub_error (GRUB_ERR_IO, "USB Mass Storage request failed"); |
d64399b5 | 298 | } |
299 | ||
778ff324 AN |
300 | /* Read/write the data, (maybe) according to specification. */ |
301 | if (size && (read_write == 0)) | |
d64399b5 | 302 | { |
778ff324 AN |
303 | err = grub_usb_bulk_read (dev->dev, dev->in->endp_addr, size, buf); |
304 | grub_dprintf ("usb", "read: %d %d\n", err, GRUB_USB_ERR_STALL); | |
7d4873c2 AN |
305 | if (err) |
306 | { | |
307 | if (err == GRUB_USB_ERR_STALL) | |
308 | grub_usb_clear_halt (dev->dev, dev->in->endp_addr); | |
309 | goto CheckCSW; | |
310 | } | |
778ff324 AN |
311 | /* Debug print of received data. */ |
312 | grub_dprintf ("usb", "buf:\n"); | |
313 | if (size <= 64) | |
314 | for (i=0; i<size; i++) | |
315 | grub_dprintf ("usb", "0x%02x: 0x%02x\n", i, buf[i]); | |
316 | else | |
317 | grub_dprintf ("usb", "Too much data for debug print...\n"); | |
d64399b5 | 318 | } |
778ff324 | 319 | else if (size) |
d64399b5 | 320 | { |
778ff324 | 321 | err = grub_usb_bulk_write (dev->dev, dev->out->endp_addr, size, buf); |
d64399b5 | 322 | grub_dprintf ("usb", "write: %d %d\n", err, GRUB_USB_ERR_STALL); |
778ff324 | 323 | grub_dprintf ("usb", "buf:\n"); |
7d4873c2 AN |
324 | if (err) |
325 | { | |
326 | if (err == GRUB_USB_ERR_STALL) | |
327 | grub_usb_clear_halt (dev->dev, dev->out->endp_addr); | |
328 | goto CheckCSW; | |
329 | } | |
778ff324 AN |
330 | /* Debug print of sent data. */ |
331 | if (size <= 256) | |
332 | for (i=0; i<size; i++) | |
333 | grub_dprintf ("usb", "0x%02x: 0x%02x\n", i, buf[i]); | |
334 | else | |
335 | grub_dprintf ("usb", "Too much data for debug print...\n"); | |
d64399b5 | 336 | } |
337 | ||
778ff324 AN |
338 | /* Read the status - (maybe) according to specification. */ |
339 | CheckCSW: | |
7d4873c2 | 340 | errCSW = grub_usb_bulk_read (dev->dev, dev->in->endp_addr, |
778ff324 | 341 | sizeof (status), (char *) &status); |
7d4873c2 | 342 | if (errCSW) |
d64399b5 | 343 | { |
778ff324 | 344 | grub_usb_clear_halt (dev->dev, dev->in->endp_addr); |
7d4873c2 | 345 | errCSW = grub_usb_bulk_read (dev->dev, dev->in->endp_addr, |
778ff324 | 346 | sizeof (status), (char *) &status); |
7d4873c2 | 347 | if (errCSW) |
778ff324 | 348 | { /* Bulk-only reset device. */ |
7d4873c2 | 349 | grub_dprintf ("usb", "Bulk-only reset device - errCSW\n"); |
778ff324 AN |
350 | grub_usbms_reset (dev->dev, dev->interface); |
351 | grub_usb_clear_halt (dev->dev, dev->in->endp_addr); | |
352 | grub_usb_clear_halt (dev->dev, dev->out->endp_addr); | |
d64399b5 | 353 | goto retry; |
778ff324 | 354 | } |
d64399b5 | 355 | } |
356 | ||
778ff324 AN |
357 | /* Debug print of CSW content. */ |
358 | grub_dprintf ("usb", "CSW: sign=0x%08x tag=0x%08x resid=0x%08x\n", | |
359 | status.signature, status.tag, status.residue); | |
360 | grub_dprintf ("usb", "CSW: status=0x%02x\n", status.status); | |
361 | ||
7d4873c2 AN |
362 | /* If phase error or not valid signature, do bulk-only reset device. */ |
363 | if ((status.status == 2) || | |
364 | (status.signature != grub_cpu_to_le32(0x53425355))) | |
365 | { /* Bulk-only reset device. */ | |
366 | grub_dprintf ("usb", "Bulk-only reset device - bad status\n"); | |
d64399b5 | 367 | grub_usbms_reset (dev->dev, dev->interface); |
368 | grub_usb_clear_halt (dev->dev, dev->in->endp_addr); | |
369 | grub_usb_clear_halt (dev->dev, dev->out->endp_addr); | |
370 | ||
ee293aee | 371 | goto retry; |
d64399b5 | 372 | } |
373 | ||
7d4873c2 AN |
374 | /* If "command failed" status or data transfer failed -> error */ |
375 | if ((status.status || err) && !read_write) | |
d64399b5 | 376 | return grub_error (GRUB_ERR_READ_ERROR, |
377 | "error communication with USB Mass Storage device"); | |
7d4873c2 AN |
378 | else if ((status.status || err) && read_write) |
379 | return grub_error (GRUB_ERR_WRITE_ERROR, | |
380 | "error communication with USB Mass Storage device"); | |
d64399b5 | 381 | |
382 | return GRUB_ERR_NONE; | |
383 | } | |
384 | ||
385 | ||
386 | static grub_err_t | |
387 | grub_usbms_read (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd, | |
388 | grub_size_t size, char *buf) | |
389 | { | |
4241d2b1 | 390 | return grub_usbms_transfer (scsi, cmdsize, cmd, size, buf, 0); |
d64399b5 | 391 | } |
392 | ||
393 | static grub_err_t | |
394 | grub_usbms_write (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd, | |
395 | grub_size_t size, char *buf) | |
396 | { | |
4241d2b1 | 397 | return grub_usbms_transfer (scsi, cmdsize, cmd, size, buf, 1); |
d64399b5 | 398 | } |
399 | ||
400 | static grub_err_t | |
401 | grub_usbms_open (const char *name, struct grub_scsi *scsi) | |
402 | { | |
403 | grub_usbms_dev_t p; | |
404 | int devnum; | |
405 | int i = 0; | |
406 | ||
407 | if (grub_strncmp (name, "usb", 3)) | |
408 | return grub_error (GRUB_ERR_UNKNOWN_DEVICE, | |
409 | "not a USB Mass Storage device"); | |
410 | ||
411 | devnum = grub_strtoul (name + 3, NULL, 10); | |
412 | for (p = grub_usbms_dev_list; p; p = p->next) | |
413 | { | |
414 | /* Check if this is the devnumth device. */ | |
415 | if (devnum == i) | |
416 | { | |
417 | scsi->data = p; | |
418 | scsi->name = grub_strdup (name); | |
419 | scsi->luns = p->luns; | |
420 | if (! scsi->name) | |
421 | return grub_errno; | |
422 | ||
423 | return GRUB_ERR_NONE; | |
424 | } | |
425 | ||
426 | i++; | |
427 | } | |
428 | ||
429 | return grub_error (GRUB_ERR_UNKNOWN_DEVICE, | |
430 | "not a USB Mass Storage device"); | |
431 | } | |
432 | ||
433 | static void | |
434 | grub_usbms_close (struct grub_scsi *scsi) | |
435 | { | |
436 | grub_free (scsi->name); | |
437 | } | |
438 | ||
439 | static struct grub_scsi_dev grub_usbms_dev = | |
440 | { | |
441 | .name = "usb", | |
442 | .iterate = grub_usbms_iterate, | |
443 | .open = grub_usbms_open, | |
444 | .close = grub_usbms_close, | |
445 | .read = grub_usbms_read, | |
446 | .write = grub_usbms_write | |
b39f9d20 | 447 | }; |
d64399b5 | 448 | |
449 | GRUB_MOD_INIT(usbms) | |
450 | { | |
451 | grub_usbms_finddevs (); | |
452 | grub_scsi_dev_register (&grub_usbms_dev); | |
453 | } | |
454 | ||
455 | GRUB_MOD_FINI(usbms) | |
456 | { | |
457 | grub_scsi_dev_unregister (&grub_usbms_dev); | |
458 | } |