/* usbtrans.c - USB Transfers and Transactions. */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2008 Free Software Foundation, Inc. * * VAS_EBOOT 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 3 of the License, or * (at your option) any later version. * * VAS_EBOOT 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 VAS_EBOOT. If not, see . */ #include #include #include #include #include #include #include #include static inline unsigned int VasEBoot_usb_bulk_maxpacket (VasEBoot_usb_device_t dev, struct VasEBoot_usb_desc_endp *endpoint) { /* Use the maximum packet size given in the endpoint descriptor. */ if (dev->initialized && endpoint && (unsigned int) endpoint->maxpacket) return endpoint->maxpacket; return 64; } static VasEBoot_usb_err_t VasEBoot_usb_execute_and_wait_transfer (VasEBoot_usb_device_t dev, VasEBoot_usb_transfer_t transfer, int timeout, VasEBoot_size_t *actual) { VasEBoot_usb_err_t err; VasEBoot_uint64_t endtime; err = dev->controller.dev->setup_transfer (&dev->controller, transfer); if (err) return err; /* endtime moved behind setup transfer to prevent false timeouts * while debugging... */ endtime = VasEBoot_get_time_ms () + timeout; while (1) { err = dev->controller.dev->check_transfer (&dev->controller, transfer, actual); if (!err) return VAS_EBOOT_USB_ERR_NONE; if (err != VAS_EBOOT_USB_ERR_WAIT) return err; if (VasEBoot_get_time_ms () > endtime) { err = dev->controller.dev->cancel_transfer (&dev->controller, transfer); if (err) return err; return VAS_EBOOT_USB_ERR_TIMEOUT; } VasEBoot_cpu_idle (); } } VasEBoot_usb_err_t VasEBoot_usb_control_msg (VasEBoot_usb_device_t dev, VasEBoot_uint8_t reqtype, VasEBoot_uint8_t request, VasEBoot_uint16_t value, VasEBoot_uint16_t index, VasEBoot_size_t size0, char *data_in) { int i; VasEBoot_usb_transfer_t transfer; int datablocks; volatile struct VasEBoot_usb_packet_setup *setupdata; VasEBoot_uint32_t setupdata_addr; VasEBoot_usb_err_t err; unsigned int max; struct VasEBoot_pci_dma_chunk *data_chunk, *setupdata_chunk; volatile char *data; VasEBoot_uint32_t data_addr; VasEBoot_size_t size = size0; VasEBoot_size_t actual; /* FIXME: avoid allocation any kind of buffer in a first place. */ data_chunk = VasEBoot_memalign_dma32 (128, size ? : 16); if (!data_chunk) return VAS_EBOOT_USB_ERR_INTERNAL; data = VasEBoot_dma_get_virt (data_chunk); data_addr = VasEBoot_dma_get_phys (data_chunk); VasEBoot_memcpy ((char *) data, data_in, size); VasEBoot_arch_sync_dma_caches (data, size); VasEBoot_dprintf ("usb", "control: reqtype=0x%02x req=0x%02x val=0x%02x idx=0x%02x size=%lu\n", reqtype, request, value, index, (unsigned long)size); /* Create a transfer. */ transfer = VasEBoot_malloc (sizeof (*transfer)); if (! transfer) { VasEBoot_dma_free (data_chunk); return VAS_EBOOT_USB_ERR_INTERNAL; } setupdata_chunk = VasEBoot_memalign_dma32 (32, sizeof (*setupdata)); if (! setupdata_chunk) { VasEBoot_free (transfer); VasEBoot_dma_free (data_chunk); return VAS_EBOOT_USB_ERR_INTERNAL; } setupdata = VasEBoot_dma_get_virt (setupdata_chunk); setupdata_addr = VasEBoot_dma_get_phys (setupdata_chunk); /* Determine the maximum packet size. */ if (dev->descdev.maxsize0) max = dev->descdev.maxsize0; else max = 64; VasEBoot_dprintf ("usb", "control: transfer = %p, dev = %p\n", transfer, dev); datablocks = (size + max - 1) / max; /* XXX: Discriminate between different types of control messages. */ transfer->transcnt = datablocks + 2; transfer->size = size; /* XXX ? */ transfer->endpoint = 0; transfer->devaddr = dev->addr; transfer->type = VAS_EBOOT_USB_TRANSACTION_TYPE_CONTROL; transfer->max = max; transfer->dev = dev; /* Allocate an array of transfer data structures. */ transfer->transactions = VasEBoot_malloc (transfer->transcnt * sizeof (struct VasEBoot_usb_transfer)); if (! transfer->transactions) { VasEBoot_free (transfer); VasEBoot_dma_free (setupdata_chunk); VasEBoot_dma_free (data_chunk); return VAS_EBOOT_USB_ERR_INTERNAL; } /* Build a Setup packet. XXX: Endianness. */ setupdata->reqtype = reqtype; setupdata->request = request; setupdata->value = value; setupdata->index = index; setupdata->length = size; VasEBoot_arch_sync_dma_caches (setupdata, sizeof (*setupdata)); transfer->transactions[0].size = sizeof (*setupdata); transfer->transactions[0].pid = VAS_EBOOT_USB_TRANSFER_TYPE_SETUP; transfer->transactions[0].data = setupdata_addr; transfer->transactions[0].toggle = 0; /* Now the data... XXX: Is this the right way to transfer control transfers? */ for (i = 0; i < datablocks; i++) { VasEBoot_usb_transaction_t tr = &transfer->transactions[i + 1]; tr->size = (size > max) ? max : size; /* Use the right most bit as the data toggle. Simple and effective. */ tr->toggle = !(i & 1); if (reqtype & 128) tr->pid = VAS_EBOOT_USB_TRANSFER_TYPE_IN; else tr->pid = VAS_EBOOT_USB_TRANSFER_TYPE_OUT; tr->data = data_addr + i * max; tr->preceding = i * max; size -= max; } /* End with an empty OUT transaction. */ transfer->transactions[datablocks + 1].size = 0; transfer->transactions[datablocks + 1].data = 0; if ((reqtype & 128) && datablocks) transfer->transactions[datablocks + 1].pid = VAS_EBOOT_USB_TRANSFER_TYPE_OUT; else transfer->transactions[datablocks + 1].pid = VAS_EBOOT_USB_TRANSFER_TYPE_IN; transfer->transactions[datablocks + 1].toggle = 1; err = VasEBoot_usb_execute_and_wait_transfer (dev, transfer, 1000, &actual); VasEBoot_dprintf ("usb", "control: err=%d\n", err); VasEBoot_free (transfer->transactions); VasEBoot_free (transfer); VasEBoot_dma_free (setupdata_chunk); VasEBoot_arch_sync_dma_caches (data, size0); VasEBoot_memcpy (data_in, (char *) data, size0); VasEBoot_dma_free (data_chunk); return err; } static VasEBoot_usb_transfer_t VasEBoot_usb_bulk_setup_readwrite (VasEBoot_usb_device_t dev, struct VasEBoot_usb_desc_endp *endpoint, VasEBoot_size_t size0, char *data_in, VasEBoot_transfer_type_t type) { int i; VasEBoot_usb_transfer_t transfer; int datablocks; unsigned int max; volatile char *data; VasEBoot_uint32_t data_addr; struct VasEBoot_pci_dma_chunk *data_chunk; VasEBoot_size_t size = size0; int toggle = dev->toggle[endpoint->endp_addr]; VasEBoot_dprintf ("usb", "bulk: size=0x%02lx type=%d\n", (unsigned long) size, type); /* FIXME: avoid allocation any kind of buffer in a first place. */ data_chunk = VasEBoot_memalign_dma32 (128, size); if (!data_chunk) return NULL; data = VasEBoot_dma_get_virt (data_chunk); data_addr = VasEBoot_dma_get_phys (data_chunk); if (type == VAS_EBOOT_USB_TRANSFER_TYPE_OUT) { VasEBoot_memcpy ((char *) data, data_in, size); VasEBoot_arch_sync_dma_caches (data, size); } /* Create a transfer. */ transfer = VasEBoot_malloc (sizeof (struct VasEBoot_usb_transfer)); if (! transfer) { VasEBoot_dma_free (data_chunk); return NULL; } max = VasEBoot_usb_bulk_maxpacket (dev, endpoint); datablocks = ((size + max - 1) / max); transfer->transcnt = datablocks; transfer->size = size - 1; transfer->endpoint = endpoint->endp_addr; transfer->devaddr = dev->addr; transfer->type = VAS_EBOOT_USB_TRANSACTION_TYPE_BULK; transfer->dir = type; transfer->max = max; transfer->dev = dev; transfer->last_trans = -1; /* Reset index of last processed transaction (TD) */ transfer->data_chunk = data_chunk; transfer->data = data_in; /* Allocate an array of transfer data structures. */ transfer->transactions = VasEBoot_malloc (transfer->transcnt * sizeof (struct VasEBoot_usb_transfer)); if (! transfer->transactions) { VasEBoot_free (transfer); VasEBoot_dma_free (data_chunk); return NULL; } /* Set up all transfers. */ for (i = 0; i < datablocks; i++) { VasEBoot_usb_transaction_t tr = &transfer->transactions[i]; tr->size = (size > max) ? max : size; /* XXX: Use the right most bit as the data toggle. Simple and effective. */ tr->toggle = toggle; toggle = toggle ? 0 : 1; tr->pid = type; tr->data = data_addr + i * max; tr->preceding = i * max; size -= tr->size; } return transfer; } static void VasEBoot_usb_bulk_finish_readwrite (VasEBoot_usb_transfer_t transfer) { VasEBoot_usb_device_t dev = transfer->dev; int toggle = dev->toggle[transfer->endpoint]; /* We must remember proper toggle value even if some transactions * were not processed - correct value should be inversion of last * processed transaction (TD). */ if (transfer->last_trans >= 0) toggle = transfer->transactions[transfer->last_trans].toggle ? 0 : 1; else toggle = dev->toggle[transfer->endpoint]; /* Nothing done, take original */ VasEBoot_dprintf ("usb", "bulk: toggle=%d\n", toggle); dev->toggle[transfer->endpoint] = toggle; if (transfer->dir == VAS_EBOOT_USB_TRANSFER_TYPE_IN) { VasEBoot_arch_sync_dma_caches (VasEBoot_dma_get_virt (transfer->data_chunk), transfer->size + 1); VasEBoot_memcpy (transfer->data, (void *) VasEBoot_dma_get_virt (transfer->data_chunk), transfer->size + 1); } VasEBoot_free (transfer->transactions); VasEBoot_dma_free (transfer->data_chunk); VasEBoot_free (transfer); } static VasEBoot_usb_err_t VasEBoot_usb_bulk_readwrite (VasEBoot_usb_device_t dev, struct VasEBoot_usb_desc_endp *endpoint, VasEBoot_size_t size0, char *data_in, VasEBoot_transfer_type_t type, int timeout, VasEBoot_size_t *actual) { VasEBoot_usb_err_t err; VasEBoot_usb_transfer_t transfer; transfer = VasEBoot_usb_bulk_setup_readwrite (dev, endpoint, size0, data_in, type); if (!transfer) return VAS_EBOOT_USB_ERR_INTERNAL; err = VasEBoot_usb_execute_and_wait_transfer (dev, transfer, timeout, actual); VasEBoot_usb_bulk_finish_readwrite (transfer); return err; } static VasEBoot_usb_err_t VasEBoot_usb_bulk_readwrite_packetize (VasEBoot_usb_device_t dev, struct VasEBoot_usb_desc_endp *endpoint, VasEBoot_transfer_type_t type, VasEBoot_size_t size, char *data) { VasEBoot_size_t actual, transferred; VasEBoot_usb_err_t err = VAS_EBOOT_USB_ERR_NONE; VasEBoot_size_t current_size, position; VasEBoot_size_t max_bulk_transfer_len = MAX_USB_TRANSFER_LEN; VasEBoot_size_t max; if (dev->controller.dev->max_bulk_tds) { max = VasEBoot_usb_bulk_maxpacket (dev, endpoint); /* Calculate max. possible length of bulk transfer */ max_bulk_transfer_len = dev->controller.dev->max_bulk_tds * max; } for (position = 0, transferred = 0; position < size; position += max_bulk_transfer_len) { current_size = size - position; if (current_size >= max_bulk_transfer_len) current_size = max_bulk_transfer_len; err = VasEBoot_usb_bulk_readwrite (dev, endpoint, current_size, &data[position], type, 1000, &actual); transferred += actual; if (err || (current_size != actual)) break; } if (!err && transferred != size) err = VAS_EBOOT_USB_ERR_DATA; return err; } VasEBoot_usb_err_t VasEBoot_usb_bulk_write (VasEBoot_usb_device_t dev, struct VasEBoot_usb_desc_endp *endpoint, VasEBoot_size_t size, char *data) { return VasEBoot_usb_bulk_readwrite_packetize (dev, endpoint, VAS_EBOOT_USB_TRANSFER_TYPE_OUT, size, data); } VasEBoot_usb_err_t VasEBoot_usb_bulk_read (VasEBoot_usb_device_t dev, struct VasEBoot_usb_desc_endp *endpoint, VasEBoot_size_t size, char *data) { return VasEBoot_usb_bulk_readwrite_packetize (dev, endpoint, VAS_EBOOT_USB_TRANSFER_TYPE_IN, size, data); } VasEBoot_usb_err_t VasEBoot_usb_check_transfer (VasEBoot_usb_transfer_t transfer, VasEBoot_size_t *actual) { VasEBoot_usb_err_t err; VasEBoot_usb_device_t dev = transfer->dev; err = dev->controller.dev->check_transfer (&dev->controller, transfer, actual); if (err == VAS_EBOOT_USB_ERR_WAIT) return err; VasEBoot_usb_bulk_finish_readwrite (transfer); return err; } VasEBoot_usb_transfer_t VasEBoot_usb_bulk_read_background (VasEBoot_usb_device_t dev, struct VasEBoot_usb_desc_endp *endpoint, VasEBoot_size_t size, void *data) { VasEBoot_usb_err_t err; VasEBoot_usb_transfer_t transfer; transfer = VasEBoot_usb_bulk_setup_readwrite (dev, endpoint, size, data, VAS_EBOOT_USB_TRANSFER_TYPE_IN); if (!transfer) return NULL; err = dev->controller.dev->setup_transfer (&dev->controller, transfer); if (err) return NULL; return transfer; } void VasEBoot_usb_cancel_transfer (VasEBoot_usb_transfer_t transfer) { VasEBoot_usb_device_t dev = transfer->dev; dev->controller.dev->cancel_transfer (&dev->controller, transfer); VasEBoot_errno = VAS_EBOOT_ERR_NONE; } VasEBoot_usb_err_t VasEBoot_usb_bulk_read_extended (VasEBoot_usb_device_t dev, struct VasEBoot_usb_desc_endp *endpoint, VasEBoot_size_t size, char *data, int timeout, VasEBoot_size_t *actual) { return VasEBoot_usb_bulk_readwrite (dev, endpoint, size, data, VAS_EBOOT_USB_TRANSFER_TYPE_IN, timeout, actual); }