| /* 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); |
| } |