diff options
Diffstat (limited to '')
-rw-r--r-- | grub-core/bus/usb/usbtrans.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/grub-core/bus/usb/usbtrans.c b/grub-core/bus/usb/usbtrans.c new file mode 100644 index 0000000..85f081f --- /dev/null +++ b/grub-core/bus/usb/usbtrans.c @@ -0,0 +1,462 @@ +/* usbtrans.c - USB Transfers and Transactions. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB 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. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/dl.h> +#include <grub/dma.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/usb.h> +#include <grub/usbtrans.h> +#include <grub/time.h> +#include <grub/cache.h> + + +static inline unsigned int +grub_usb_bulk_maxpacket (grub_usb_device_t dev, + struct grub_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 grub_usb_err_t +grub_usb_execute_and_wait_transfer (grub_usb_device_t dev, + grub_usb_transfer_t transfer, + int timeout, grub_size_t *actual) +{ + grub_usb_err_t err; + grub_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 = grub_get_time_ms () + timeout; + while (1) + { + err = dev->controller.dev->check_transfer (&dev->controller, transfer, + actual); + if (!err) + return GRUB_USB_ERR_NONE; + if (err != GRUB_USB_ERR_WAIT) + return err; + if (grub_get_time_ms () > endtime) + { + err = dev->controller.dev->cancel_transfer (&dev->controller, + transfer); + if (err) + return err; + return GRUB_USB_ERR_TIMEOUT; + } + grub_cpu_idle (); + } +} + +grub_usb_err_t +grub_usb_control_msg (grub_usb_device_t dev, + grub_uint8_t reqtype, + grub_uint8_t request, + grub_uint16_t value, + grub_uint16_t index, + grub_size_t size0, char *data_in) +{ + int i; + grub_usb_transfer_t transfer; + int datablocks; + volatile struct grub_usb_packet_setup *setupdata; + grub_uint32_t setupdata_addr; + grub_usb_err_t err; + unsigned int max; + struct grub_pci_dma_chunk *data_chunk, *setupdata_chunk; + volatile char *data; + grub_uint32_t data_addr; + grub_size_t size = size0; + grub_size_t actual; + + /* FIXME: avoid allocation any kind of buffer in a first place. */ + data_chunk = grub_memalign_dma32 (128, size ? : 16); + if (!data_chunk) + return GRUB_USB_ERR_INTERNAL; + data = grub_dma_get_virt (data_chunk); + data_addr = grub_dma_get_phys (data_chunk); + grub_memcpy ((char *) data, data_in, size); + + grub_arch_sync_dma_caches (data, size); + + grub_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 = grub_malloc (sizeof (*transfer)); + if (! transfer) + { + grub_dma_free (data_chunk); + return GRUB_USB_ERR_INTERNAL; + } + + setupdata_chunk = grub_memalign_dma32 (32, sizeof (*setupdata)); + if (! setupdata_chunk) + { + grub_free (transfer); + grub_dma_free (data_chunk); + return GRUB_USB_ERR_INTERNAL; + } + + setupdata = grub_dma_get_virt (setupdata_chunk); + setupdata_addr = grub_dma_get_phys (setupdata_chunk); + + /* Determine the maximum packet size. */ + if (dev->descdev.maxsize0) + max = dev->descdev.maxsize0; + else + max = 64; + + grub_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 = GRUB_USB_TRANSACTION_TYPE_CONTROL; + transfer->max = max; + transfer->dev = dev; + + /* Allocate an array of transfer data structures. */ + transfer->transactions = grub_malloc (transfer->transcnt + * sizeof (struct grub_usb_transfer)); + if (! transfer->transactions) + { + grub_free (transfer); + grub_dma_free (setupdata_chunk); + grub_dma_free (data_chunk); + return GRUB_USB_ERR_INTERNAL; + } + + /* Build a Setup packet. XXX: Endianness. */ + setupdata->reqtype = reqtype; + setupdata->request = request; + setupdata->value = value; + setupdata->index = index; + setupdata->length = size; + grub_arch_sync_dma_caches (setupdata, sizeof (*setupdata)); + + transfer->transactions[0].size = sizeof (*setupdata); + transfer->transactions[0].pid = GRUB_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++) + { + grub_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 = GRUB_USB_TRANSFER_TYPE_IN; + else + tr->pid = GRUB_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 = GRUB_USB_TRANSFER_TYPE_OUT; + else + transfer->transactions[datablocks + 1].pid = GRUB_USB_TRANSFER_TYPE_IN; + + transfer->transactions[datablocks + 1].toggle = 1; + + err = grub_usb_execute_and_wait_transfer (dev, transfer, 1000, &actual); + + grub_dprintf ("usb", "control: err=%d\n", err); + + grub_free (transfer->transactions); + + grub_free (transfer); + grub_dma_free (setupdata_chunk); + + grub_arch_sync_dma_caches (data, size0); + grub_memcpy (data_in, (char *) data, size0); + + grub_dma_free (data_chunk); + + return err; +} + +static grub_usb_transfer_t +grub_usb_bulk_setup_readwrite (grub_usb_device_t dev, + struct grub_usb_desc_endp *endpoint, + grub_size_t size0, char *data_in, + grub_transfer_type_t type) +{ + int i; + grub_usb_transfer_t transfer; + int datablocks; + unsigned int max; + volatile char *data; + grub_uint32_t data_addr; + struct grub_pci_dma_chunk *data_chunk; + grub_size_t size = size0; + int toggle = dev->toggle[endpoint->endp_addr]; + + grub_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 = grub_memalign_dma32 (128, size); + if (!data_chunk) + return NULL; + data = grub_dma_get_virt (data_chunk); + data_addr = grub_dma_get_phys (data_chunk); + if (type == GRUB_USB_TRANSFER_TYPE_OUT) + { + grub_memcpy ((char *) data, data_in, size); + grub_arch_sync_dma_caches (data, size); + } + + /* Create a transfer. */ + transfer = grub_malloc (sizeof (struct grub_usb_transfer)); + if (! transfer) + { + grub_dma_free (data_chunk); + return NULL; + } + + max = grub_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 = GRUB_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 = grub_malloc (transfer->transcnt + * sizeof (struct grub_usb_transfer)); + if (! transfer->transactions) + { + grub_free (transfer); + grub_dma_free (data_chunk); + return NULL; + } + + /* Set up all transfers. */ + for (i = 0; i < datablocks; i++) + { + grub_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 +grub_usb_bulk_finish_readwrite (grub_usb_transfer_t transfer) +{ + grub_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 */ + grub_dprintf ("usb", "bulk: toggle=%d\n", toggle); + dev->toggle[transfer->endpoint] = toggle; + + if (transfer->dir == GRUB_USB_TRANSFER_TYPE_IN) + { + grub_arch_sync_dma_caches (grub_dma_get_virt (transfer->data_chunk), + transfer->size + 1); + grub_memcpy (transfer->data, (void *) + grub_dma_get_virt (transfer->data_chunk), + transfer->size + 1); + } + + grub_free (transfer->transactions); + grub_dma_free (transfer->data_chunk); + grub_free (transfer); +} + +static grub_usb_err_t +grub_usb_bulk_readwrite (grub_usb_device_t dev, + struct grub_usb_desc_endp *endpoint, + grub_size_t size0, char *data_in, + grub_transfer_type_t type, int timeout, + grub_size_t *actual) +{ + grub_usb_err_t err; + grub_usb_transfer_t transfer; + + transfer = grub_usb_bulk_setup_readwrite (dev, endpoint, size0, + data_in, type); + if (!transfer) + return GRUB_USB_ERR_INTERNAL; + err = grub_usb_execute_and_wait_transfer (dev, transfer, timeout, actual); + + grub_usb_bulk_finish_readwrite (transfer); + + return err; +} + +static grub_usb_err_t +grub_usb_bulk_readwrite_packetize (grub_usb_device_t dev, + struct grub_usb_desc_endp *endpoint, + grub_transfer_type_t type, + grub_size_t size, char *data) +{ + grub_size_t actual, transferred; + grub_usb_err_t err = GRUB_USB_ERR_NONE; + grub_size_t current_size, position; + grub_size_t max_bulk_transfer_len = MAX_USB_TRANSFER_LEN; + grub_size_t max; + + if (dev->controller.dev->max_bulk_tds) + { + max = grub_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 = grub_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 = GRUB_USB_ERR_DATA; + return err; +} + +grub_usb_err_t +grub_usb_bulk_write (grub_usb_device_t dev, + struct grub_usb_desc_endp *endpoint, + grub_size_t size, char *data) +{ + return grub_usb_bulk_readwrite_packetize (dev, endpoint, + GRUB_USB_TRANSFER_TYPE_OUT, + size, data); +} + +grub_usb_err_t +grub_usb_bulk_read (grub_usb_device_t dev, + struct grub_usb_desc_endp *endpoint, + grub_size_t size, char *data) +{ + return grub_usb_bulk_readwrite_packetize (dev, endpoint, + GRUB_USB_TRANSFER_TYPE_IN, + size, data); +} + +grub_usb_err_t +grub_usb_check_transfer (grub_usb_transfer_t transfer, grub_size_t *actual) +{ + grub_usb_err_t err; + grub_usb_device_t dev = transfer->dev; + + err = dev->controller.dev->check_transfer (&dev->controller, transfer, + actual); + if (err == GRUB_USB_ERR_WAIT) + return err; + + grub_usb_bulk_finish_readwrite (transfer); + + return err; +} + +grub_usb_transfer_t +grub_usb_bulk_read_background (grub_usb_device_t dev, + struct grub_usb_desc_endp *endpoint, + grub_size_t size, void *data) +{ + grub_usb_err_t err; + grub_usb_transfer_t transfer; + + transfer = grub_usb_bulk_setup_readwrite (dev, endpoint, size, + data, GRUB_USB_TRANSFER_TYPE_IN); + if (!transfer) + return NULL; + + err = dev->controller.dev->setup_transfer (&dev->controller, transfer); + if (err) + return NULL; + + return transfer; +} + +void +grub_usb_cancel_transfer (grub_usb_transfer_t transfer) +{ + grub_usb_device_t dev = transfer->dev; + dev->controller.dev->cancel_transfer (&dev->controller, transfer); + grub_errno = GRUB_ERR_NONE; +} + +grub_usb_err_t +grub_usb_bulk_read_extended (grub_usb_device_t dev, + struct grub_usb_desc_endp *endpoint, + grub_size_t size, char *data, + int timeout, grub_size_t *actual) +{ + return grub_usb_bulk_readwrite (dev, endpoint, size, data, + GRUB_USB_TRANSFER_TYPE_IN, timeout, actual); +} |