/* * Copyright (C) 2020 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include #include #include #include #include #include #include #include "usbblk.h" /** @file * * USB mass storage driver * */ static void usbblk_stop ( struct usbblk_device *usbblk, int rc ); /** List of USB block devices */ static LIST_HEAD ( usbblk_devices ); /****************************************************************************** * * Endpoint management * ****************************************************************************** */ /** * Open endpoints * * @v usbblk USB block device * @ret rc Return status code */ static int usbblk_open ( struct usbblk_device *usbblk ) { struct usb_device *usb = usbblk->func->usb; unsigned int interface = usbblk->func->interface[0]; int rc; /* Sanity checks */ assert ( ! usbblk->in.open ); assert ( ! usbblk->out.open ); /* Issue reset */ if ( ( rc = usb_control ( usb, USBBLK_RESET, 0, interface, NULL, 0 ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s could not issue reset: %s\n", usbblk->func->name, strerror ( rc ) ); goto err_reset; } /* Open bulk OUT endpoint */ if ( ( rc = usb_endpoint_open ( &usbblk->out ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s could not open bulk OUT: %s\n", usbblk->func->name, strerror ( rc ) ); goto err_open_out; } /* Clear any bulk OUT halt condition */ if ( ( rc = usb_endpoint_clear_halt ( &usbblk->out ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s could not reset bulk OUT: %s\n", usbblk->func->name, strerror ( rc ) ); goto err_clear_out; } /* Open bulk IN endpoint */ if ( ( rc = usb_endpoint_open ( &usbblk->in ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s could not open bulk IN: %s\n", usbblk->func->name, strerror ( rc ) ); goto err_open_in; } /* Clear any bulk IN halt condition */ if ( ( rc = usb_endpoint_clear_halt ( &usbblk->in ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s could not reset bulk IN: %s\n", usbblk->func->name, strerror ( rc ) ); goto err_clear_in; } return 0; err_clear_in: usb_endpoint_close ( &usbblk->in ); err_open_in: err_clear_out: usb_endpoint_close ( &usbblk->out ); err_open_out: err_reset: return rc; } /** * Close endpoints * * @v usbblk USB block device */ static void usbblk_close ( struct usbblk_device *usbblk ) { /* Close bulk OUT endpoint */ if ( usbblk->out.open ) usb_endpoint_close ( &usbblk->out ); /* Close bulk IN endpoint */ if ( usbblk->in.open ) usb_endpoint_close ( &usbblk->in ); } /****************************************************************************** * * Bulk OUT endpoint * ****************************************************************************** */ /** * Issue bulk OUT command * * @v usbblk USB block device * @ret rc Return status code */ static int usbblk_out_command ( struct usbblk_device *usbblk ) { struct usbblk_command *cmd = &usbblk->cmd; struct usbblk_command_wrapper *wrapper; struct io_buffer *iobuf; int rc; /* Sanity checks */ assert ( cmd->tag ); assert ( ! ( cmd->scsi.data_in_len && cmd->scsi.data_out_len ) ); /* Allocate I/O buffer */ iobuf = alloc_iob ( sizeof ( *wrapper ) ); if ( ! iobuf ) { rc = -ENOMEM; goto err_alloc; } /* Populate command */ wrapper = iob_put ( iobuf, sizeof ( *wrapper ) ); memset ( wrapper, 0, sizeof ( *wrapper ) ); wrapper->signature = cpu_to_le32 ( USBBLK_COMMAND_SIGNATURE ); wrapper->tag = cmd->tag; /* non-endian */ if ( cmd->scsi.data_out_len ) { wrapper->len = cpu_to_le32 ( cmd->scsi.data_out_len ); } else { wrapper->len = cpu_to_le32 ( cmd->scsi.data_in_len ); wrapper->flags = USB_DIR_IN; } wrapper->lun = ntohs ( cmd->scsi.lun.u16[0] ); wrapper->cblen = sizeof ( wrapper->cb ); memcpy ( wrapper->cb, &cmd->scsi.cdb, sizeof ( wrapper->cb ) ); /* Issue command */ if ( ( rc = usb_stream ( &usbblk->out, iobuf, 0 ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s bulk OUT could not issue command: " "%s\n", usbblk->func->name, strerror ( rc ) ); goto err_stream; } return 0; err_stream: free_iob ( iobuf ); err_alloc: return rc; } /** * Send bulk OUT data block * * @v usbblk USB block device * @ret rc Return status code */ static int usbblk_out_data ( struct usbblk_device *usbblk ) { struct usbblk_command *cmd = &usbblk->cmd; struct io_buffer *iobuf; size_t len; int rc; /* Calculate length */ assert ( cmd->tag ); assert ( cmd->scsi.data_out != UNULL ); assert ( cmd->offset < cmd->scsi.data_out_len ); len = ( cmd->scsi.data_out_len - cmd->offset ); if ( len > USBBLK_MAX_LEN ) len = USBBLK_MAX_LEN; assert ( ( len % usbblk->out.mtu ) == 0 ); /* Allocate I/O buffer */ iobuf = alloc_iob ( len ); if ( ! iobuf ) { rc = -ENOMEM; goto err_alloc; } /* Populate I/O buffer */ copy_from_user ( iob_put ( iobuf, len ), cmd->scsi.data_out, cmd->offset, len ); /* Send data */ if ( ( rc = usb_stream ( &usbblk->out, iobuf, 0 ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s bulk OUT could not send data: %s\n", usbblk->func->name, strerror ( rc ) ); goto err_stream; } /* Consume data */ cmd->offset += len; return 0; err_stream: free_iob ( iobuf ); err_alloc: return rc; } /** * Refill bulk OUT endpoint * * @v usbblk USB block device * @ret rc Return status code */ static int usbblk_out_refill ( struct usbblk_device *usbblk ) { struct usbblk_command *cmd = &usbblk->cmd; int rc; /* Sanity checks */ assert ( cmd->tag ); /* Refill endpoint */ while ( ( cmd->offset < cmd->scsi.data_out_len ) && ( usbblk->out.fill < USBBLK_MAX_FILL ) ) { if ( ( rc = usbblk_out_data ( usbblk ) ) != 0 ) return rc; } return 0; } /** * Complete bulk OUT transfer * * @v ep USB endpoint * @v iobuf I/O buffer * @v rc Completion status code */ static void usbblk_out_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, int rc ) { struct usbblk_device *usbblk = container_of ( ep, struct usbblk_device, out ); struct usbblk_command *cmd = &usbblk->cmd; /* Ignore cancellations after closing endpoint */ if ( ! ep->open ) goto drop; /* Sanity check */ assert ( cmd->tag ); /* Check for failures */ if ( rc != 0 ) { DBGC ( usbblk, "USBBLK %s bulk OUT failed: %s\n", usbblk->func->name, strerror ( rc ) ); goto err; } /* Trigger refill process, if applicable */ if ( cmd->offset < cmd->scsi.data_out_len ) process_add ( &usbblk->process ); drop: /* Free I/O buffer */ free_iob ( iobuf ); return; err: free_iob ( iobuf ); usbblk_stop ( usbblk, rc ); } /** Bulk OUT endpoint operations */ static struct usb_endpoint_driver_operations usbblk_out_operations = { .complete = usbblk_out_complete, }; /****************************************************************************** * * Bulk IN endpoint * ****************************************************************************** */ /** * Handle bulk IN data block * * @v usbblk USB block device * @v data Data block * @v len Length of data * @ret rc Return status code */ static int usbblk_in_data ( struct usbblk_device *usbblk, const void *data, size_t len ) { struct usbblk_command *cmd = &usbblk->cmd; /* Sanity checks */ assert ( cmd->tag ); assert ( cmd->scsi.data_in != UNULL ); assert ( cmd->offset <= cmd->scsi.data_in_len ); assert ( len <= ( cmd->scsi.data_in_len - cmd->offset ) ); /* Store data */ copy_to_user ( cmd->scsi.data_in, cmd->offset, data, len ); cmd->offset += len; return 0; } /** * Handle bulk IN status * * @v usbblk USB block device * @v data Status data * @v len Length of status data * @ret rc Return status code */ static int usbblk_in_status ( struct usbblk_device *usbblk, const void *data, size_t len ) { struct usbblk_command *cmd = &usbblk->cmd; const struct usbblk_status_wrapper *stat; /* Sanity checks */ assert ( cmd->tag ); /* Validate length */ if ( len < sizeof ( *stat ) ) { DBGC ( usbblk, "USBBLK %s bulk IN malformed status:\n", usbblk->func->name ); DBGC_HDA ( usbblk, 0, data, len ); return -EIO; } stat = data; /* Validate signature */ if ( stat->signature != cpu_to_le32 ( USBBLK_STATUS_SIGNATURE ) ) { DBGC ( usbblk, "USBBLK %s bulk IN invalid signature %08x:\n", usbblk->func->name, le32_to_cpu ( stat->signature ) ); DBGC_HDA ( usbblk, 0, stat, sizeof ( *stat ) ); return -EIO; } /* Validate tag */ if ( stat->tag != cmd->tag ) { DBGC ( usbblk, "USBBLK %s bulk IN tag mismatch (got %08x, " "expected %08x):\n", usbblk->func->name, stat->tag, cmd->tag ); DBGC_HDA ( usbblk, 0, stat, sizeof ( *stat ) ); return -EIO; } /* Check status */ if ( stat->status ) { DBGC ( usbblk, "USBBLK %s bulk IN status %02x:\n", usbblk->func->name, stat->status ); DBGC_HDA ( usbblk, 0, stat, sizeof ( *stat ) ); return -EIO; } /* Check for residual data */ if ( stat->residue ) { DBGC ( usbblk, "USBBLK %s bulk IN residue %#x:\n", usbblk->func->name, le32_to_cpu ( stat->residue ) ); return -EIO; } /* Mark command as complete */ usbblk_stop ( usbblk, 0 ); return 0; } /** * Refill bulk IN endpoint * * @v usbblk USB block device * @ret rc Return status code */ static int usbblk_in_refill ( struct usbblk_device *usbblk ) { struct usbblk_command *cmd = &usbblk->cmd; struct usbblk_status_wrapper *stat; size_t remaining; unsigned int max; int rc; /* Sanity checks */ assert ( cmd->tag ); /* Calculate maximum required refill */ remaining = sizeof ( *stat ); if ( cmd->scsi.data_in_len ) { assert ( cmd->offset <= cmd->scsi.data_in_len ); remaining += ( cmd->scsi.data_in_len - cmd->offset ); } max = ( ( remaining + USBBLK_MAX_LEN - 1 ) / USBBLK_MAX_LEN ); /* Refill bulk IN endpoint */ if ( ( rc = usb_refill_limit ( &usbblk->in, max ) ) != 0 ) return rc; return 0; } /** * Complete bulk IN transfer * * @v ep USB endpoint * @v iobuf I/O buffer * @v rc Completion status code */ static void usbblk_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, int rc ) { struct usbblk_device *usbblk = container_of ( ep, struct usbblk_device, in ); struct usbblk_command *cmd = &usbblk->cmd; size_t remaining; size_t len; /* Ignore cancellations after closing endpoint */ if ( ! ep->open ) goto drop; /* Sanity check */ assert ( cmd->tag ); /* Handle errors */ if ( rc != 0 ) { DBGC ( usbblk, "USBBLK %s bulk IN failed: %s\n", usbblk->func->name, strerror ( rc ) ); goto err; } /* Trigger refill process */ process_add ( &usbblk->process ); /* Handle data portion, if any */ if ( cmd->scsi.data_in_len ) { assert ( cmd->offset <= cmd->scsi.data_in_len ); remaining = ( cmd->scsi.data_in_len - cmd->offset ); len = iob_len ( iobuf ); if ( len > remaining ) len = remaining; if ( len ) { if ( ( rc = usbblk_in_data ( usbblk, iobuf->data, len ) ) != 0 ) goto err; iob_pull ( iobuf, len ); } } /* Handle status portion, if any */ len = iob_len ( iobuf ); if ( len ) { if ( ( rc = usbblk_in_status ( usbblk, iobuf->data, len ) ) != 0 ) goto err; } drop: /* Free I/O buffer */ free_iob ( iobuf ); return; err: free_iob ( iobuf ); usbblk_stop ( usbblk, rc ); } /** Bulk IN endpoint operations */ static struct usb_endpoint_driver_operations usbblk_in_operations = { .complete = usbblk_in_complete, }; /****************************************************************************** * * Refill process * ****************************************************************************** */ /** * Refill endpoints * * @v usbblk USB block device */ static void usbblk_step ( struct usbblk_device *usbblk ) { /* Refill bulk OUT endpoint */ usbblk_out_refill ( usbblk ); /* Refill bulk IN endpoint */ usbblk_in_refill ( usbblk ); } /** Refill process descriptor */ static struct process_descriptor usbblk_process_desc = PROC_DESC ( struct usbblk_device, process, usbblk_step ); /****************************************************************************** * * SCSI command management * ****************************************************************************** */ /** Next command tag */ static uint16_t usbblk_tag; /** * Stop SCSI command * * @v usbblk USB block device * @v rc Reason for stop */ static void usbblk_stop ( struct usbblk_device *usbblk, int rc ) { /* Stop process */ process_del ( &usbblk->process ); /* Reset command */ memset ( &usbblk->cmd, 0, sizeof ( usbblk->cmd ) ); /* Close endpoints if an error occurred */ if ( rc != 0 ) { DBGC ( usbblk, "USBBLK %s closing for error recovery\n", usbblk->func->name ); usbblk_close ( usbblk ); } /* Terminate command */ intf_restart ( &usbblk->data, rc ); } /** * Start new SCSI command * * @v usbblk USB block device * @v scsicmd SCSI command * @ret rc Return status code */ static int usbblk_start ( struct usbblk_device *usbblk, struct scsi_cmd *scsicmd ) { struct usbblk_command *cmd = &usbblk->cmd; int rc; /* Fail if command is in progress */ if ( cmd->tag ) { rc = -EBUSY; DBGC ( usbblk, "USBBLK %s cannot support multiple commands\n", usbblk->func->name ); goto err_busy; } /* Refuse bidirectional commands */ if ( scsicmd->data_in_len && scsicmd->data_out_len ) { rc = -EOPNOTSUPP; DBGC ( usbblk, "USBBLK %s cannot support bidirectional " "commands\n", usbblk->func->name ); goto err_bidirectional; } /* Sanity checks */ assert ( ! process_running ( &usbblk->process ) ); assert ( cmd->offset == 0 ); /* Initialise command */ memcpy ( &cmd->scsi, scsicmd, sizeof ( cmd->scsi ) ); cmd->tag = ( USBBLK_TAG_MAGIC | ++usbblk_tag ); /* Issue bulk OUT command */ if ( ( rc = usbblk_out_command ( usbblk ) ) != 0 ) goto err_command; /* Start refill process */ process_add ( &usbblk->process ); return 0; err_command: memset ( &usbblk->cmd, 0, sizeof ( usbblk->cmd ) ); err_bidirectional: err_busy: return rc; } /****************************************************************************** * * SCSI interfaces * ****************************************************************************** */ /** SCSI data interface operations */ static struct interface_operation usbblk_data_operations[] = { INTF_OP ( intf_close, struct usbblk_device *, usbblk_stop ), }; /** SCSI data interface descriptor */ static struct interface_descriptor usbblk_data_desc = INTF_DESC ( struct usbblk_device, data, usbblk_data_operations ); /** * Check SCSI command flow-control window * * @v usbblk USB block device * @ret len Length of window */ static size_t usbblk_scsi_window ( struct usbblk_device *usbblk ) { struct usbblk_command *cmd = &usbblk->cmd; /* Allow a single command if no command is currently in progress */ return ( cmd->tag ? 0 : 1 ); } /** * Issue SCSI command * * @v usbblk USB block device * @v data SCSI data interface * @v scsicmd SCSI command * @ret tag Command tag, or negative error */ static int usbblk_scsi_command ( struct usbblk_device *usbblk, struct interface *data, struct scsi_cmd *scsicmd ) { struct usbblk_command *cmd = &usbblk->cmd; int rc; /* (Re)open endpoints if needed */ if ( ( ! usbblk->in.open ) && ( ( rc = usbblk_open ( usbblk ) ) != 0 ) ) goto err_open; /* Start new command */ if ( ( rc = usbblk_start ( usbblk, scsicmd ) ) != 0 ) goto err_start; /* Attach to parent interface and return */ intf_plug_plug ( &usbblk->data, data ); return cmd->tag; usbblk_stop ( usbblk, rc ); err_start: usbblk_close ( usbblk ); err_open: return rc; } /** * Close SCSI interface * * @v usbblk USB block device * @v rc Reason for close */ static void usbblk_scsi_close ( struct usbblk_device *usbblk, int rc ) { /* Restart interfaces */ intfs_restart ( rc, &usbblk->scsi, &usbblk->data, NULL ); /* Stop any in-progress command */ usbblk_stop ( usbblk, rc ); /* Close endpoints */ usbblk_close ( usbblk ); /* Flag as closed */ usbblk->opened = 0; } /** * Describe as an EFI device path * * @v usbblk USB block device * @ret path EFI device path, or NULL on error */ static EFI_DEVICE_PATH_PROTOCOL * usbblk_efi_describe ( struct usbblk_device *usbblk ) { return efi_usb_path ( usbblk->func ); } /** SCSI command interface operations */ static struct interface_operation usbblk_scsi_operations[] = { INTF_OP ( scsi_command, struct usbblk_device *, usbblk_scsi_command ), INTF_OP ( xfer_window, struct usbblk_device *, usbblk_scsi_window ), INTF_OP ( intf_close, struct usbblk_device *, usbblk_scsi_close ), EFI_INTF_OP ( efi_describe, struct usbblk_device *, usbblk_efi_describe ), }; /** SCSI command interface descriptor */ static struct interface_descriptor usbblk_scsi_desc = INTF_DESC ( struct usbblk_device, scsi, usbblk_scsi_operations ); /****************************************************************************** * * SAN device interface * ****************************************************************************** */ /** * Find USB block device * * @v name USB block device name * @ret usbblk USB block device, or NULL */ static struct usbblk_device * usbblk_find ( const char *name ) { struct usbblk_device *usbblk; /* Look for matching device */ list_for_each_entry ( usbblk, &usbblk_devices, list ) { if ( strcmp ( usbblk->func->name, name ) == 0 ) return usbblk; } return NULL; } /** * Open USB block device URI * * @v parent Parent interface * @v uri URI * @ret rc Return status code */ static int usbblk_open_uri ( struct interface *parent, struct uri *uri ) { static struct scsi_lun lun; struct usbblk_device *usbblk; int rc; /* Sanity check */ if ( ! uri->opaque ) return -EINVAL; /* Find matching device */ usbblk = usbblk_find ( uri->opaque ); if ( ! usbblk ) return -ENOENT; /* Fail if device is already open */ if ( usbblk->opened ) return -EBUSY; /* Open SCSI device */ if ( ( rc = scsi_open ( parent, &usbblk->scsi, &lun ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s could not open SCSI device: %s\n", usbblk->func->name, strerror ( rc ) ); return rc; } /* Mark as opened */ usbblk->opened = 1; return 0; } /** USB block device URI opener */ struct uri_opener usbblk_uri_opener __uri_opener = { .scheme = "usb", .open = usbblk_open_uri, }; /****************************************************************************** * * USB interface * ****************************************************************************** */ /** * Probe device * * @v func USB function * @v config Configuration descriptor * @ret rc Return status code */ static int usbblk_probe ( struct usb_function *func, struct usb_configuration_descriptor *config ) { struct usb_device *usb = func->usb; struct usbblk_device *usbblk; struct usb_interface_descriptor *desc; int rc; /* Allocate and initialise structure */ usbblk = zalloc ( sizeof ( *usbblk ) ); if ( ! usbblk ) { rc = -ENOMEM; goto err_alloc; } usbblk->func = func; usb_endpoint_init ( &usbblk->out, usb, &usbblk_out_operations ); usb_endpoint_init ( &usbblk->in, usb, &usbblk_in_operations ); usb_refill_init ( &usbblk->in, 0, USBBLK_MAX_LEN, USBBLK_MAX_FILL ); intf_init ( &usbblk->scsi, &usbblk_scsi_desc, &usbblk->refcnt ); intf_init ( &usbblk->data, &usbblk_data_desc, &usbblk->refcnt ); process_init_stopped ( &usbblk->process, &usbblk_process_desc, &usbblk->refcnt ); /* Locate interface descriptor */ desc = usb_interface_descriptor ( config, func->interface[0], 0 ); if ( ! desc ) { DBGC ( usbblk, "USBBLK %s missing interface descriptor\n", usbblk->func->name ); rc = -ENOENT; goto err_desc; } /* Describe endpoints */ if ( ( rc = usb_endpoint_described ( &usbblk->out, config, desc, USB_BULK_OUT, 0 ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s could not describe bulk OUT: %s\n", usbblk->func->name, strerror ( rc ) ); goto err_out; } if ( ( rc = usb_endpoint_described ( &usbblk->in, config, desc, USB_BULK_IN, 0 ) ) != 0 ) { DBGC ( usbblk, "USBBLK %s could not describe bulk IN: %s\n", usbblk->func->name, strerror ( rc ) ); goto err_in; } /* Add to list of devices */ list_add_tail ( &usbblk->list, &usbblk_devices ); usb_func_set_drvdata ( func, usbblk ); return 0; err_in: err_out: err_desc: ref_put ( &usbblk->refcnt ); err_alloc: return rc; } /** * Remove device * * @v func USB function */ static void usbblk_remove ( struct usb_function *func ) { struct usbblk_device *usbblk = usb_func_get_drvdata ( func ); /* Remove from list of devices */ list_del ( &usbblk->list ); /* Close all interfaces */ usbblk_scsi_close ( usbblk, -ENODEV ); /* Shut down interfaces */ intfs_shutdown ( -ENODEV, &usbblk->scsi, &usbblk->data, NULL ); /* Drop reference */ ref_put ( &usbblk->refcnt ); } /** Mass storage class device IDs */ static struct usb_device_id usbblk_ids[] = { { .name = "usbblk", .vendor = USB_ANY_ID, .product = USB_ANY_ID, }, }; /** Mass storage driver */ struct usb_driver usbblk_driver __usb_driver = { .ids = usbblk_ids, .id_count = ( sizeof ( usbblk_ids ) / sizeof ( usbblk_ids[0] ) ), .class = USB_CLASS_ID ( USB_CLASS_MSC, USB_SUBCLASS_MSC_SCSI, USB_PROTOCOL_MSC_BULK ), .score = USB_SCORE_NORMAL, .probe = usbblk_probe, .remove = usbblk_remove, };