| // Code for handling usb attached scsi devices. |
| // |
| // only usb 2.0 for now. |
| // |
| // once we have xhci driver with usb 3.0 support this must |
| // be updated to use usb3 streams so booting from usb3 |
| // devices actually works. |
| // |
| // Authors: |
| // Gerd Hoffmann <kraxel@redhat.com> |
| // |
| // based on usb-msc.c which is written by: |
| // Kevin O'Connor <kevin@koconnor.net> |
| // |
| // This file may be distributed under the terms of the GNU LGPLv3 license. |
| |
| #include "biosvar.h" // GET_GLOBALFLAT |
| #include "block.h" // DTYPE_USB |
| #include "blockcmd.h" // cdb_read |
| #include "config.h" // CONFIG_USB_UAS |
| #include "malloc.h" // free |
| #include "output.h" // dprintf |
| #include "std/disk.h" // DISK_RET_SUCCESS |
| #include "string.h" // memset |
| #include "usb.h" // struct usb_s |
| #include "usb-uas.h" // usb_uas_init |
| #include "util.h" // bootprio_find_usb |
| |
| #define UAS_UI_COMMAND 0x01 |
| #define UAS_UI_SENSE 0x03 |
| #define UAS_UI_RESPONSE 0x04 |
| #define UAS_UI_TASK_MGMT 0x05 |
| #define UAS_UI_READ_READY 0x06 |
| #define UAS_UI_WRITE_READY 0x07 |
| |
| #define UAS_PIPE_ID_COMMAND 0x01 |
| #define UAS_PIPE_ID_STATUS 0x02 |
| #define UAS_PIPE_ID_DATA_IN 0x03 |
| #define UAS_PIPE_ID_DATA_OUT 0x04 |
| |
| typedef struct { |
| u8 id; |
| u8 reserved; |
| u16 tag; |
| } PACKED uas_ui_header; |
| |
| typedef struct { |
| u8 prio_taskattr; /* 6:3 priority, 2:0 task attribute */ |
| u8 reserved_1; |
| u8 add_cdb_length; /* 7:2 additional adb length (dwords) */ |
| u8 reserved_2; |
| u8 lun[8]; |
| u8 cdb[16]; |
| u8 add_cdb[]; |
| } PACKED uas_ui_command; |
| |
| typedef struct { |
| u16 status_qualifier; |
| u8 status; |
| u8 reserved[7]; |
| u16 sense_length; |
| u8 sense_data[18]; |
| } PACKED uas_ui_sense; |
| |
| typedef struct { |
| u16 add_response_info; |
| u8 response_code; |
| } PACKED uas_ui_response; |
| |
| typedef struct { |
| u8 function; |
| u8 reserved; |
| u16 task_tag; |
| u8 lun[8]; |
| } PACKED uas_ui_task_mgmt; |
| |
| typedef struct { |
| uas_ui_header hdr; |
| union { |
| uas_ui_command command; |
| uas_ui_sense sense; |
| uas_ui_task_mgmt task; |
| uas_ui_response response; |
| }; |
| } PACKED uas_ui; |
| |
| struct uasdrive_s { |
| struct drive_s drive; |
| struct usbdevice_s *usbdev; |
| struct usb_pipe *command, *status, *data_in, *data_out; |
| u32 lun; |
| }; |
| |
| int |
| uas_process_op(struct disk_op_s *op) |
| { |
| if (!CONFIG_USB_UAS) |
| return DISK_RET_EBADTRACK; |
| |
| struct uasdrive_s *drive_gf = container_of( |
| op->drive_fl, struct uasdrive_s, drive); |
| |
| uas_ui ui; |
| memset(&ui, 0, sizeof(ui)); |
| ui.hdr.id = UAS_UI_COMMAND; |
| ui.hdr.tag = 0xdead; |
| ui.command.lun[1] = GET_GLOBALFLAT(drive_gf->lun); |
| int blocksize = scsi_fill_cmd(op, ui.command.cdb, sizeof(ui.command.cdb)); |
| if (blocksize < 0) |
| return default_process_op(op); |
| int ret = usb_send_bulk(GET_GLOBALFLAT(drive_gf->command), |
| USB_DIR_OUT, MAKE_FLATPTR(GET_SEG(SS), &ui), |
| sizeof(ui.hdr) + sizeof(ui.command)); |
| if (ret) { |
| dprintf(1, "uas: command send fail"); |
| goto fail; |
| } |
| |
| memset(&ui, 0xff, sizeof(ui)); |
| ret = usb_send_bulk(GET_GLOBALFLAT(drive_gf->status), |
| USB_DIR_IN, MAKE_FLATPTR(GET_SEG(SS), &ui), sizeof(ui)); |
| if (ret) { |
| dprintf(1, "uas: status recv fail"); |
| goto fail; |
| } |
| |
| switch (ui.hdr.id) { |
| case UAS_UI_SENSE: |
| goto have_sense; |
| case UAS_UI_READ_READY: |
| ret = usb_send_bulk(GET_GLOBALFLAT(drive_gf->data_in), |
| USB_DIR_IN, op->buf_fl, op->count * blocksize); |
| if (ret) { |
| dprintf(1, "uas: data read fail"); |
| goto fail; |
| } |
| break; |
| case UAS_UI_WRITE_READY: |
| ret = usb_send_bulk(GET_GLOBALFLAT(drive_gf->data_out), |
| USB_DIR_OUT, op->buf_fl, op->count * blocksize); |
| if (ret) { |
| dprintf(1, "uas: data write fail"); |
| goto fail; |
| } |
| break; |
| default: |
| dprintf(1, "uas: unknown status ui id %d", ui.hdr.id); |
| goto fail; |
| } |
| |
| memset(&ui, 0xff, sizeof(ui)); |
| ret = usb_send_bulk(GET_GLOBALFLAT(drive_gf->status), |
| USB_DIR_IN, MAKE_FLATPTR(GET_SEG(SS), &ui), sizeof(ui)); |
| if (ret) { |
| dprintf(1, "uas: status recv fail"); |
| goto fail; |
| } |
| if (ui.hdr.id != UAS_UI_SENSE) { |
| dprintf(1, "uas: expected sense ui, got ui id %d", ui.hdr.id); |
| goto fail; |
| } |
| |
| have_sense: |
| if (ui.sense.status == 0) { |
| return DISK_RET_SUCCESS; |
| } |
| |
| fail: |
| return DISK_RET_EBADTRACK; |
| } |
| |
| static void |
| uas_init_lun(struct uasdrive_s *drive, struct usbdevice_s *usbdev, |
| struct usb_pipe *command, struct usb_pipe *status, |
| struct usb_pipe *data_in, struct usb_pipe *data_out, |
| u32 lun) |
| { |
| memset(drive, 0, sizeof(*drive)); |
| if (usb_32bit_pipe(data_in)) |
| drive->drive.type = DTYPE_UAS_32; |
| else |
| drive->drive.type = DTYPE_UAS; |
| drive->usbdev = usbdev; |
| drive->command = command; |
| drive->status = status; |
| drive->data_in = data_in; |
| drive->data_out = data_out; |
| drive->lun = lun; |
| } |
| |
| static int |
| uas_add_lun(u32 lun, struct drive_s *tmpl_drv) |
| { |
| struct uasdrive_s *tmpl_lun = |
| container_of(tmpl_drv, struct uasdrive_s, drive); |
| struct uasdrive_s *drive = malloc_fseg(sizeof(*drive)); |
| if (!drive) { |
| warn_noalloc(); |
| return -1; |
| } |
| uas_init_lun(drive, tmpl_lun->usbdev, |
| tmpl_lun->command, tmpl_lun->status, |
| tmpl_lun->data_in, tmpl_lun->data_out, |
| lun); |
| |
| int prio = bootprio_find_usb(drive->usbdev, drive->lun); |
| int ret = scsi_drive_setup(&drive->drive, "USB UAS", prio, 0, lun); |
| if (ret) { |
| free(drive); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int |
| usb_uas_setup(struct usbdevice_s *usbdev) |
| { |
| if (!CONFIG_USB_UAS) |
| return -1; |
| |
| // Verify right kind of device |
| struct usb_interface_descriptor *iface = usbdev->iface; |
| if (iface->bInterfaceSubClass != US_SC_SCSI || |
| iface->bInterfaceProtocol != US_PR_UAS) { |
| dprintf(1, "Unsupported UAS device (subclass=%02x proto=%02x)\n" |
| , iface->bInterfaceSubClass, iface->bInterfaceProtocol); |
| return -1; |
| } |
| |
| /* find & allocate pipes */ |
| struct usb_endpoint_descriptor *ep = NULL; |
| struct usb_pipe *command = NULL; |
| struct usb_pipe *status = NULL; |
| struct usb_pipe *data_in = NULL; |
| struct usb_pipe *data_out = NULL; |
| u8 *desc = (u8*)iface; |
| while (desc) { |
| desc += desc[0]; |
| switch (desc[1]) { |
| case USB_DT_ENDPOINT: |
| ep = (void*)desc; |
| break; |
| case USB_DT_ENDPOINT_COMPANION: |
| /* No support (yet) for usb3 streams */ |
| dprintf(1, "Superspeed UAS devices not supported (yet)\n"); |
| goto fail; |
| case 0x24: |
| switch (desc[2]) { |
| case UAS_PIPE_ID_COMMAND: |
| command = usb_alloc_pipe(usbdev, ep); |
| break; |
| case UAS_PIPE_ID_STATUS: |
| status = usb_alloc_pipe(usbdev, ep); |
| break; |
| case UAS_PIPE_ID_DATA_IN: |
| data_in = usb_alloc_pipe(usbdev, ep); |
| break; |
| case UAS_PIPE_ID_DATA_OUT: |
| data_out = usb_alloc_pipe(usbdev, ep); |
| break; |
| default: |
| goto fail; |
| } |
| break; |
| default: |
| desc = NULL; |
| break; |
| } |
| } |
| if (!command || !status || !data_in || !data_out) |
| goto fail; |
| |
| struct uasdrive_s lun0; |
| uas_init_lun(&lun0, usbdev, command, status, data_in, data_out, 0); |
| int ret = scsi_rep_luns_scan(&lun0.drive, uas_add_lun); |
| if (ret <= 0) { |
| dprintf(1, "Unable to configure UAS drive.\n"); |
| goto fail; |
| } |
| |
| return 0; |
| |
| fail: |
| usb_free_pipe(usbdev, command); |
| usb_free_pipe(usbdev, status); |
| usb_free_pipe(usbdev, data_in); |
| usb_free_pipe(usbdev, data_out); |
| return -1; |
| } |