873 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			873 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* uhci.c - UHCI Support.  */
 | ||
| /*
 | ||
|  *  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/mm.h>
 | ||
| #include <grub/misc.h>
 | ||
| #include <grub/usb.h>
 | ||
| #include <grub/usbtrans.h>
 | ||
| #include <grub/pci.h>
 | ||
| #include <grub/cpu/io.h>
 | ||
| #include <grub/time.h>
 | ||
| #include <grub/cpu/pci.h>
 | ||
| #include <grub/disk.h>
 | ||
| 
 | ||
| GRUB_MOD_LICENSE ("GPLv3+");
 | ||
| 
 | ||
| #define GRUB_UHCI_IOMASK	(0x7FF << 5)
 | ||
| 
 | ||
| #define N_QH  256
 | ||
| #define N_TD  640
 | ||
| 
 | ||
| typedef enum
 | ||
|   {
 | ||
|     GRUB_UHCI_REG_USBCMD = 0x00,
 | ||
|     GRUB_UHCI_REG_USBINTR = 0x04,
 | ||
|     GRUB_UHCI_REG_FLBASEADD = 0x08,
 | ||
|     GRUB_UHCI_REG_PORTSC1 = 0x10,
 | ||
|     GRUB_UHCI_REG_PORTSC2 = 0x12,
 | ||
|     GRUB_UHCI_REG_USBLEGSUP = 0xc0
 | ||
|   } grub_uhci_reg_t;
 | ||
| 
 | ||
| enum
 | ||
|   {
 | ||
|     GRUB_UHCI_DETECT_CHANGED = (1 << 1),
 | ||
|     GRUB_UHCI_DETECT_HAVE_DEVICE = 1,
 | ||
|     GRUB_UHCI_DETECT_LOW_SPEED = (1 << 8)
 | ||
|   };
 | ||
| 
 | ||
| /* R/WC legacy support bits */
 | ||
| enum
 | ||
|   {
 | ||
|     GRUB_UHCI_LEGSUP_END_A20GATE = (1 << 15),
 | ||
|     GRUB_UHCI_TRAP_BY_64H_WSTAT = (1 << 11),
 | ||
|     GRUB_UHCI_TRAP_BY_64H_RSTAT = (1 << 10),
 | ||
|     GRUB_UHCI_TRAP_BY_60H_WSTAT = (1 <<  9),
 | ||
|     GRUB_UHCI_TRAP_BY_60H_RSTAT = (1 <<  8)
 | ||
|   };
 | ||
| 
 | ||
| /* Reset all legacy support - clear all R/WC bits and all R/W bits */
 | ||
| #define GRUB_UHCI_RESET_LEGSUP_SMI ( GRUB_UHCI_LEGSUP_END_A20GATE \
 | ||
|                                      | GRUB_UHCI_TRAP_BY_64H_WSTAT \
 | ||
|                                      | GRUB_UHCI_TRAP_BY_64H_RSTAT \
 | ||
|                                      | GRUB_UHCI_TRAP_BY_60H_WSTAT \
 | ||
|                                      | GRUB_UHCI_TRAP_BY_60H_RSTAT )
 | ||
| 
 | ||
| /* Some UHCI commands */
 | ||
| #define GRUB_UHCI_CMD_RUN_STOP (1 << 0)
 | ||
| #define GRUB_UHCI_CMD_HCRESET  (1 << 1)
 | ||
| #define GRUB_UHCI_CMD_MAXP     (1 << 7)
 | ||
| 
 | ||
| /* Important bits in structures */
 | ||
| #define GRUB_UHCI_LINK_TERMINATE	1
 | ||
| #define GRUB_UHCI_LINK_QUEUE_HEAD	2
 | ||
| 
 | ||
| enum
 | ||
|   {
 | ||
|     GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED = 0x0002,
 | ||
|     GRUB_UHCI_REG_PORTSC_PORT_ENABLED    = 0x0004,
 | ||
|     GRUB_UHCI_REG_PORTSC_RESUME          = 0x0040,
 | ||
|     GRUB_UHCI_REG_PORTSC_RESET           = 0x0200,
 | ||
|     GRUB_UHCI_REG_PORTSC_SUSPEND         = 0x1000,
 | ||
|     GRUB_UHCI_REG_PORTSC_RW = GRUB_UHCI_REG_PORTSC_PORT_ENABLED
 | ||
|     | GRUB_UHCI_REG_PORTSC_RESUME | GRUB_UHCI_REG_PORTSC_RESET
 | ||
|     | GRUB_UHCI_REG_PORTSC_SUSPEND,
 | ||
|     /* These bits should not be written as 1 unless we really need it */
 | ||
|     GRUB_UHCI_PORTSC_RWC = ((1 << 1) | (1 << 3) | (1 << 11) | (3 << 13))
 | ||
|   };
 | ||
| 
 | ||
| /* UHCI Queue Head.  */
 | ||
| struct grub_uhci_qh
 | ||
| {
 | ||
|   /* Queue head link pointer which points to the next queue head.  */
 | ||
|   grub_uint32_t linkptr;
 | ||
| 
 | ||
|   /* Queue element link pointer which points to the first data object
 | ||
|      within the queue.  */
 | ||
|   grub_uint32_t elinkptr;
 | ||
| 
 | ||
|   /* Queue heads are aligned on 16 bytes, pad so a queue head is 16
 | ||
|      bytes so we can store many in a 4K page.  */
 | ||
|   grub_uint8_t pad[8];
 | ||
| } GRUB_PACKED;
 | ||
| 
 | ||
| /* UHCI Transfer Descriptor.  */
 | ||
| struct grub_uhci_td
 | ||
| {
 | ||
|   /* Pointer to the next TD in the list.  */
 | ||
|   grub_uint32_t linkptr;
 | ||
| 
 | ||
|   /* Control and status bits.  */
 | ||
|   grub_uint32_t ctrl_status;
 | ||
| 
 | ||
|   /* All information required to transfer the Token packet.  */
 | ||
|   grub_uint32_t token;
 | ||
| 
 | ||
|   /* A pointer to the data buffer, UHCI requires this pointer to be 32
 | ||
|      bits.  */
 | ||
|   grub_uint32_t buffer;
 | ||
| 
 | ||
|   /* Another linkptr that is not overwritten by the Host Controller.
 | ||
|      This is GRUB specific.  */
 | ||
|   grub_uint32_t linkptr2;
 | ||
| 
 | ||
|   /* 3 additional 32 bits words reserved for the Host Controller Driver.  */
 | ||
|   grub_uint32_t data[3];
 | ||
| } GRUB_PACKED;
 | ||
| 
 | ||
| typedef volatile struct grub_uhci_td *grub_uhci_td_t;
 | ||
| typedef volatile struct grub_uhci_qh *grub_uhci_qh_t;
 | ||
| 
 | ||
| struct grub_uhci
 | ||
| {
 | ||
|   grub_port_t iobase;
 | ||
|   volatile grub_uint32_t *framelist_virt;
 | ||
|   grub_uint32_t framelist_phys;
 | ||
|   struct grub_pci_dma_chunk *framelist_chunk;
 | ||
| 
 | ||
|   /* N_QH Queue Heads.  */
 | ||
|   struct grub_pci_dma_chunk *qh_chunk;
 | ||
|   volatile grub_uhci_qh_t qh_virt;
 | ||
|   grub_uint32_t qh_phys;
 | ||
| 
 | ||
|   /* N_TD Transfer Descriptors.  */
 | ||
|   struct grub_pci_dma_chunk *td_chunk;
 | ||
|   volatile grub_uhci_td_t td_virt;
 | ||
|   grub_uint32_t td_phys;
 | ||
| 
 | ||
|   /* Free Transfer Descriptors.  */
 | ||
|   grub_uhci_td_t tdfree;
 | ||
| 
 | ||
|   int qh_busy[N_QH];
 | ||
| 
 | ||
|   struct grub_uhci *next;
 | ||
| };
 | ||
| 
 | ||
| static struct grub_uhci *uhci;
 | ||
| 
 | ||
| static grub_uint16_t
 | ||
| grub_uhci_readreg16 (struct grub_uhci *u, grub_uhci_reg_t reg)
 | ||
| {
 | ||
|   return grub_inw (u->iobase + reg);
 | ||
| }
 | ||
| 
 | ||
| #if 0
 | ||
| static grub_uint32_t
 | ||
| grub_uhci_readreg32 (struct grub_uhci *u, grub_uhci_reg_t reg)
 | ||
| {
 | ||
|   return grub_inl (u->iobase + reg);
 | ||
| }
 | ||
| #endif
 | ||
| 
 | ||
| static void
 | ||
| grub_uhci_writereg16 (struct grub_uhci *u,
 | ||
| 		      grub_uhci_reg_t reg, grub_uint16_t val)
 | ||
| {
 | ||
|   grub_outw (val, u->iobase + reg);
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| grub_uhci_writereg32 (struct grub_uhci *u,
 | ||
| 		    grub_uhci_reg_t reg, grub_uint32_t val)
 | ||
| {
 | ||
|   grub_outl (val, u->iobase + reg);
 | ||
| }
 | ||
| 
 | ||
| /* Iterate over all PCI devices.  Determine if a device is an UHCI
 | ||
|    controller.  If this is the case, initialize it.  */
 | ||
| static int
 | ||
| grub_uhci_pci_iter (grub_pci_device_t dev,
 | ||
| 		    grub_pci_id_t pciid __attribute__((unused)),
 | ||
| 		    void *data __attribute__ ((unused)))
 | ||
| {
 | ||
|   grub_uint32_t class_code;
 | ||
|   grub_uint32_t class;
 | ||
|   grub_uint32_t subclass;
 | ||
|   grub_uint32_t interf;
 | ||
|   grub_uint32_t base;
 | ||
|   grub_uint32_t fp;
 | ||
|   grub_pci_address_t addr;
 | ||
|   struct grub_uhci *u;
 | ||
|   int i;
 | ||
| 
 | ||
|   addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
 | ||
|   class_code = grub_pci_read (addr) >> 8;
 | ||
| 
 | ||
|   interf = class_code & 0xFF;
 | ||
|   subclass = (class_code >> 8) & 0xFF;
 | ||
|   class = class_code >> 16;
 | ||
| 
 | ||
|   /* If this is not an UHCI controller, just return.  */
 | ||
|   if (class != 0x0c || subclass != 0x03 || interf != 0x00)
 | ||
|     return 0;
 | ||
| 
 | ||
|   /* Determine IO base address.  */
 | ||
|   addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG4);
 | ||
|   base = grub_pci_read (addr);
 | ||
|   /* Stop if there is no IO space base address defined.  */
 | ||
|   if ((base & GRUB_PCI_ADDR_SPACE_MASK) != GRUB_PCI_ADDR_SPACE_IO)
 | ||
|     return 0;
 | ||
| 
 | ||
|   if ((base & GRUB_UHCI_IOMASK) == 0)
 | ||
|     return 0;
 | ||
| 
 | ||
|   /* Set bus master - needed for coreboot or broken BIOSes */
 | ||
|   addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
 | ||
|   grub_pci_write_word(addr, GRUB_PCI_COMMAND_IO_ENABLED
 | ||
| 		      | GRUB_PCI_COMMAND_BUS_MASTER
 | ||
| 		      | GRUB_PCI_COMMAND_MEM_ENABLED
 | ||
| 		      | grub_pci_read_word (addr));
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "base = %x\n", base);
 | ||
| 
 | ||
|   /* Allocate memory for the controller and register it.  */
 | ||
|   u = grub_zalloc (sizeof (*u));
 | ||
|   if (! u)
 | ||
|     return 1;
 | ||
| 
 | ||
|   u->iobase = (base & GRUB_UHCI_IOMASK) + GRUB_MACHINE_PCI_IO_BASE;
 | ||
| 
 | ||
|   /* Reset PIRQ and SMI */
 | ||
|   addr = grub_pci_make_address (dev, GRUB_UHCI_REG_USBLEGSUP);       
 | ||
|   grub_pci_write_word(addr, GRUB_UHCI_RESET_LEGSUP_SMI);
 | ||
|   /* Reset the HC */
 | ||
|   grub_uhci_writereg16(u, GRUB_UHCI_REG_USBCMD, GRUB_UHCI_CMD_HCRESET); 
 | ||
|   grub_millisleep(5);
 | ||
|   /* Disable interrupts and commands (just to be safe) */
 | ||
|   grub_uhci_writereg16(u, GRUB_UHCI_REG_USBINTR, 0);
 | ||
|   /* Finish HC reset, HC remains disabled */
 | ||
|   grub_uhci_writereg16(u, GRUB_UHCI_REG_USBCMD, 0);
 | ||
|   /* Read back to be sure PCI write is done */
 | ||
|   grub_uhci_readreg16(u, GRUB_UHCI_REG_USBCMD);
 | ||
| 
 | ||
|   /* Reserve a page for the frame list.  */
 | ||
|   u->framelist_chunk = grub_memalign_dma32 (4096, 4096);
 | ||
|   if (! u->framelist_chunk)
 | ||
|     goto fail;
 | ||
|   u->framelist_virt = grub_dma_get_virt (u->framelist_chunk);
 | ||
|   u->framelist_phys = grub_dma_get_phys (u->framelist_chunk);
 | ||
| 
 | ||
|   grub_dprintf ("uhci",
 | ||
| 		"class=0x%02x 0x%02x interface 0x%02x base=0x%x framelist=%p\n",
 | ||
| 		class, subclass, interf, u->iobase, u->framelist_virt);
 | ||
| 
 | ||
|   /* The QH pointer of UHCI is only 32 bits, make sure this
 | ||
|      code works on on 64 bits architectures.  */
 | ||
|   u->qh_chunk = grub_memalign_dma32 (4096, sizeof(struct grub_uhci_qh) * N_QH);
 | ||
|   if (! u->qh_chunk)
 | ||
|     goto fail;
 | ||
|   u->qh_virt = grub_dma_get_virt (u->qh_chunk);
 | ||
|   u->qh_phys = grub_dma_get_phys (u->qh_chunk);
 | ||
| 
 | ||
|   /* The TD pointer of UHCI is only 32 bits, make sure this
 | ||
|      code works on on 64 bits architectures.  */
 | ||
|   u->td_chunk = grub_memalign_dma32 (4096, sizeof(struct grub_uhci_td) * N_TD);
 | ||
|   if (! u->td_chunk)
 | ||
|     goto fail;
 | ||
|   u->td_virt = grub_dma_get_virt (u->td_chunk);
 | ||
|   u->td_phys = grub_dma_get_phys (u->td_chunk);
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "QH=%p, TD=%p\n",
 | ||
| 		u->qh_virt, u->td_virt);
 | ||
| 
 | ||
|   /* Link all Transfer Descriptors in a list of available Transfer
 | ||
|      Descriptors.  */
 | ||
|   for (i = 0; i < N_TD; i++)
 | ||
|     u->td_virt[i].linkptr = u->td_phys + (i + 1) * sizeof(struct grub_uhci_td);
 | ||
|   u->td_virt[N_TD - 2].linkptr = 0;
 | ||
|   u->tdfree = u->td_virt;
 | ||
| 
 | ||
|   /* Setup the frame list pointers.  Since no isochronous transfers
 | ||
|      are and will be supported, they all point to the (same!) queue
 | ||
|      head.  */
 | ||
|   fp = u->qh_phys & (~15);
 | ||
|   /* Mark this as a queue head.  */
 | ||
|   fp |= 2;
 | ||
|   for (i = 0; i < 1024; i++)
 | ||
|     u->framelist_virt[i] = fp;
 | ||
|   /* Program the framelist address into the UHCI controller.  */
 | ||
|   grub_uhci_writereg32 (u, GRUB_UHCI_REG_FLBASEADD, u->framelist_phys);
 | ||
| 
 | ||
|   /* Make the Queue Heads point to each other.  */
 | ||
|   for (i = 0; i < N_QH; i++)
 | ||
|     {
 | ||
|       /* Point to the next QH.  */
 | ||
|       u->qh_virt[i].linkptr = ((u->qh_phys
 | ||
| 				+ (i + 1) * sizeof(struct grub_uhci_qh))
 | ||
| 			  & (~15));
 | ||
| 
 | ||
|       /* This is a QH.  */
 | ||
|       u->qh_virt[i].linkptr |= GRUB_UHCI_LINK_QUEUE_HEAD;
 | ||
| 
 | ||
|       /* For the moment, do not point to a Transfer Descriptor.  These
 | ||
| 	 are set at transfer time, so just terminate it.  */
 | ||
|       u->qh_virt[i].elinkptr = 1;
 | ||
|     }
 | ||
| 
 | ||
|   /* The last Queue Head should terminate.  */
 | ||
|   u->qh_virt[N_QH - 1].linkptr = 1;
 | ||
| 
 | ||
|   /* Enable UHCI again.  */
 | ||
|   grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD,
 | ||
|                         GRUB_UHCI_CMD_RUN_STOP | GRUB_UHCI_CMD_MAXP);
 | ||
| 
 | ||
|   /* UHCI is initialized and ready for transfers.  */
 | ||
|   grub_dprintf ("uhci", "UHCI initialized\n");
 | ||
| 
 | ||
| 
 | ||
| #if 0
 | ||
|   {
 | ||
|     int i;
 | ||
|     for (i = 0; i < 10; i++)
 | ||
|       {
 | ||
| 	grub_uint16_t frnum;
 | ||
| 
 | ||
| 	frnum = grub_uhci_readreg16 (u, 6);
 | ||
| 	grub_dprintf ("uhci", "Framenum=%d\n", frnum);
 | ||
| 	grub_millisleep (100);
 | ||
|       }
 | ||
|   }
 | ||
| #endif
 | ||
| 
 | ||
|   /* Link to uhci now that initialisation is successful.  */
 | ||
|   u->next = uhci;
 | ||
|   uhci = u;
 | ||
| 
 | ||
|   return 0;
 | ||
| 
 | ||
|  fail:
 | ||
|   if (u)
 | ||
|     {
 | ||
|       grub_dma_free (u->qh_chunk);
 | ||
|       grub_dma_free (u->framelist_chunk);
 | ||
|     }
 | ||
|   grub_free (u);
 | ||
| 
 | ||
|   return 1;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| grub_uhci_inithw (void)
 | ||
| {
 | ||
|   grub_pci_iterate (grub_uhci_pci_iter, NULL);
 | ||
| }
 | ||
| 
 | ||
| static grub_uhci_td_t
 | ||
| grub_alloc_td (struct grub_uhci *u)
 | ||
| {
 | ||
|   grub_uhci_td_t ret;
 | ||
| 
 | ||
|   /* Check if there is a Transfer Descriptor available.  */
 | ||
|   if (! u->tdfree)
 | ||
|     return NULL;
 | ||
| 
 | ||
|   ret = u->tdfree;
 | ||
|   u->tdfree = grub_dma_phys2virt (u->tdfree->linkptr, u->td_chunk);
 | ||
| 
 | ||
|   return ret;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| grub_free_td (struct grub_uhci *u, grub_uhci_td_t td)
 | ||
| {
 | ||
|   td->linkptr = grub_dma_virt2phys (u->tdfree, u->td_chunk);
 | ||
|   u->tdfree = td;
 | ||
| }
 | ||
| 
 | ||
| static void
 | ||
| grub_free_queue (struct grub_uhci *u, grub_uhci_qh_t qh, grub_uhci_td_t td,
 | ||
|                  grub_usb_transfer_t transfer, grub_size_t *actual)
 | ||
| {
 | ||
|   int i; /* Index of TD in transfer */
 | ||
| 
 | ||
|   u->qh_busy[qh - u->qh_virt] = 0;
 | ||
| 
 | ||
|   *actual = 0;
 | ||
|   
 | ||
|   /* Free the TDs in this queue and set last_trans.  */
 | ||
|   for (i=0; td; i++)
 | ||
|     {
 | ||
|       grub_uhci_td_t tdprev;
 | ||
| 
 | ||
|       grub_dprintf ("uhci", "Freeing %p\n", td);
 | ||
|       /* Check state of TD and possibly set last_trans */
 | ||
|       if (transfer && (td->linkptr & 1))
 | ||
|         transfer->last_trans = i;
 | ||
| 
 | ||
|       *actual += (td->ctrl_status + 1) & 0x7ff;
 | ||
|       
 | ||
|       /* Unlink the queue.  */
 | ||
|       tdprev = td;
 | ||
|       if (!td->linkptr2)
 | ||
| 	td = 0;
 | ||
|       else
 | ||
| 	td = grub_dma_phys2virt (td->linkptr2, u->td_chunk);
 | ||
| 
 | ||
|       /* Free the TD.  */
 | ||
|       grub_free_td (u, tdprev);
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| static grub_uhci_qh_t
 | ||
| grub_alloc_qh (struct grub_uhci *u,
 | ||
| 	       grub_transaction_type_t tr __attribute__((unused)))
 | ||
| {
 | ||
|   int i;
 | ||
|   grub_uhci_qh_t qh;
 | ||
| 
 | ||
|   /* Look for a Queue Head for this transfer.  Skip the first QH if
 | ||
|      this is a Interrupt Transfer.  */
 | ||
| #if 0
 | ||
|   if (tr == GRUB_USB_TRANSACTION_TYPE_INTERRUPT)
 | ||
|     i = 0;
 | ||
|   else
 | ||
| #endif
 | ||
|     i = 1;
 | ||
| 
 | ||
|   for (; i < N_QH; i++)
 | ||
|     {
 | ||
|       if (!u->qh_busy[i])
 | ||
| 	break;
 | ||
|     }
 | ||
|   qh = &u->qh_virt[i];
 | ||
|   if (i == N_QH)
 | ||
|     {
 | ||
|       grub_error (GRUB_ERR_OUT_OF_MEMORY,
 | ||
| 		  "no free queue heads available");
 | ||
|       return NULL;
 | ||
|     }
 | ||
| 
 | ||
|   u->qh_busy[qh - u->qh_virt] = 1;
 | ||
| 
 | ||
|   return qh;
 | ||
| }
 | ||
| 
 | ||
| static grub_uhci_td_t
 | ||
| grub_uhci_transaction (struct grub_uhci *u, unsigned int endp,
 | ||
| 		       grub_transfer_type_t type, unsigned int addr,
 | ||
| 		       unsigned int toggle, grub_size_t size,
 | ||
| 		       grub_uint32_t data, grub_usb_speed_t speed)
 | ||
| {
 | ||
|   grub_uhci_td_t td;
 | ||
|   static const unsigned int tf[] = { 0x69, 0xE1, 0x2D };
 | ||
| 
 | ||
|   /* XXX: Check if data is <4GB.  If it isn't, just copy stuff around.
 | ||
|      This is only relevant for 64 bits architectures.  */
 | ||
| 
 | ||
|   /* Grab a free Transfer Descriptor and initialize it.  */
 | ||
|   td = grub_alloc_td (u);
 | ||
|   if (! td)
 | ||
|     {
 | ||
|       grub_error (GRUB_ERR_OUT_OF_MEMORY,
 | ||
| 		  "no transfer descriptors available for UHCI transfer");
 | ||
|       return 0;
 | ||
|     }
 | ||
| 
 | ||
|   grub_dprintf ("uhci",
 | ||
| 		"transaction: endp=%d, type=%d, addr=%d, toggle=%d, size=%lu data=0x%x td=%p\n",
 | ||
| 		endp, type, addr, toggle, (unsigned long) size, data, td);
 | ||
| 
 | ||
|   /* Don't point to any TD, just terminate.  */
 | ||
|   td->linkptr = 1;
 | ||
| 
 | ||
|   /* Active!  Only retry a transfer 3 times.  */
 | ||
|   td->ctrl_status = (1 << 23) | (3 << 27) |
 | ||
|                     ((speed == GRUB_USB_SPEED_LOW) ? (1 << 26) : 0);
 | ||
| 
 | ||
|   /* If zero bytes are transmitted, size is 0x7FF.  Otherwise size is
 | ||
|      size-1.  */
 | ||
|   if (size == 0)
 | ||
|     size = 0x7FF;
 | ||
|   else
 | ||
|     size = size - 1;
 | ||
| 
 | ||
|   /* Setup whatever is required for the token packet.  */
 | ||
|   td->token = ((size << 21) | (toggle << 19) | (endp << 15)
 | ||
| 	       | (addr << 8) | tf[type]);
 | ||
| 
 | ||
|   td->buffer = data;
 | ||
| 
 | ||
|   return td;
 | ||
| }
 | ||
| 
 | ||
| struct grub_uhci_transfer_controller_data
 | ||
| {
 | ||
|   grub_uhci_qh_t qh;
 | ||
|   grub_uhci_td_t td_first;
 | ||
| };
 | ||
| 
 | ||
| static grub_usb_err_t
 | ||
| grub_uhci_setup_transfer (grub_usb_controller_t dev,
 | ||
| 			  grub_usb_transfer_t transfer)
 | ||
| {
 | ||
|   struct grub_uhci *u = (struct grub_uhci *) dev->data;
 | ||
|   grub_uhci_td_t td;
 | ||
|   grub_uhci_td_t td_prev = NULL;
 | ||
|   int i;
 | ||
|   struct grub_uhci_transfer_controller_data *cdata;
 | ||
| 
 | ||
|   cdata = grub_malloc (sizeof (*cdata));
 | ||
|   if (!cdata)
 | ||
|     return GRUB_USB_ERR_INTERNAL;
 | ||
| 
 | ||
|   cdata->td_first = NULL;
 | ||
| 
 | ||
|   /* Allocate a queue head for the transfer queue.  */
 | ||
|   cdata->qh = grub_alloc_qh (u, GRUB_USB_TRANSACTION_TYPE_CONTROL);
 | ||
|   if (! cdata->qh)
 | ||
|     {
 | ||
|       grub_free (cdata);
 | ||
|       return GRUB_USB_ERR_INTERNAL;
 | ||
|     }
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "transfer, iobase:%08x\n", u->iobase);
 | ||
|   
 | ||
|   for (i = 0; i < transfer->transcnt; i++)
 | ||
|     {
 | ||
|       grub_usb_transaction_t tr = &transfer->transactions[i];
 | ||
| 
 | ||
|       td = grub_uhci_transaction (u, transfer->endpoint & 15, tr->pid,
 | ||
| 				  transfer->devaddr, tr->toggle,
 | ||
| 				  tr->size, tr->data,
 | ||
| 				  transfer->dev->speed);
 | ||
|       if (! td)
 | ||
| 	{
 | ||
| 	  grub_size_t actual = 0;
 | ||
| 	  /* Terminate and free.  */
 | ||
| 	  if (td_prev)
 | ||
| 	    {
 | ||
| 	      td_prev->linkptr2 = 0;
 | ||
| 	      td_prev->linkptr = 1;
 | ||
| 	    }
 | ||
| 
 | ||
| 	  if (cdata->td_first)
 | ||
| 	    grub_free_queue (u, cdata->qh, cdata->td_first, NULL, &actual);
 | ||
| 
 | ||
| 	  grub_free (cdata);
 | ||
| 	  return GRUB_USB_ERR_INTERNAL;
 | ||
| 	}
 | ||
| 
 | ||
|       if (! cdata->td_first)
 | ||
| 	cdata->td_first = td;
 | ||
|       else
 | ||
| 	{
 | ||
| 	  td_prev->linkptr2 = grub_dma_virt2phys (td, u->td_chunk);
 | ||
| 	  td_prev->linkptr = grub_dma_virt2phys (td, u->td_chunk);
 | ||
| 	  td_prev->linkptr |= 4;
 | ||
| 	}
 | ||
|       td_prev = td;
 | ||
|     }
 | ||
|   td_prev->linkptr2 = 0;
 | ||
|   td_prev->linkptr = 1;
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "setup transaction %d\n", transfer->type);
 | ||
| 
 | ||
|   /* Link it into the queue and terminate.  Now the transaction can
 | ||
|      take place.  */
 | ||
|   cdata->qh->elinkptr = grub_dma_virt2phys (cdata->td_first, u->td_chunk);
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "initiate transaction\n");
 | ||
| 
 | ||
|   transfer->controller_data = cdata;
 | ||
| 
 | ||
|   return GRUB_USB_ERR_NONE;
 | ||
| }
 | ||
| 
 | ||
| static grub_usb_err_t
 | ||
| grub_uhci_check_transfer (grub_usb_controller_t dev,
 | ||
| 			  grub_usb_transfer_t transfer,
 | ||
| 			  grub_size_t *actual)
 | ||
| {
 | ||
|   struct grub_uhci *u = (struct grub_uhci *) dev->data;
 | ||
|   grub_uhci_td_t errtd;
 | ||
|   struct grub_uhci_transfer_controller_data *cdata = transfer->controller_data;
 | ||
| 
 | ||
|   *actual = 0;
 | ||
| 
 | ||
|   if (cdata->qh->elinkptr & ~0x0f)
 | ||
|     errtd = grub_dma_phys2virt (cdata->qh->elinkptr & ~0x0f, u->qh_chunk);
 | ||
|   else
 | ||
|     errtd = 0;
 | ||
|   
 | ||
|   if (errtd)
 | ||
|     {
 | ||
|       grub_dprintf ("uhci", ">t status=0x%02x data=0x%02x td=%p, %x\n",
 | ||
| 		    errtd->ctrl_status, errtd->buffer & (~15), errtd,
 | ||
| 		    cdata->qh->elinkptr);
 | ||
|     }
 | ||
| 
 | ||
|   /* Check if the transaction completed.  */
 | ||
|   if (cdata->qh->elinkptr & 1)
 | ||
|     {
 | ||
|       grub_dprintf ("uhci", "transaction complete\n");
 | ||
| 
 | ||
|       /* Place the QH back in the free list and deallocate the associated
 | ||
| 	 TDs.  */
 | ||
|       cdata->qh->elinkptr = 1;
 | ||
|       grub_free_queue (u, cdata->qh, cdata->td_first, transfer, actual);
 | ||
|       grub_free (cdata);
 | ||
|       return GRUB_USB_ERR_NONE;
 | ||
|     }
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "t status=0x%02x\n", errtd->ctrl_status);
 | ||
| 
 | ||
|   if (!(errtd->ctrl_status & (1 << 23)))
 | ||
|     {
 | ||
|       grub_usb_err_t err = GRUB_USB_ERR_NONE;
 | ||
| 
 | ||
|       /* Check if the endpoint is stalled.  */
 | ||
|       if (errtd->ctrl_status & (1 << 22))
 | ||
| 	err = GRUB_USB_ERR_STALL;
 | ||
|       
 | ||
|       /* Check if an error related to the data buffer occurred.  */
 | ||
|       else if (errtd->ctrl_status & (1 << 21))
 | ||
| 	err = GRUB_USB_ERR_DATA;
 | ||
|       
 | ||
|       /* Check if a babble error occurred.  */
 | ||
|       else if (errtd->ctrl_status & (1 << 20))
 | ||
| 	err = GRUB_USB_ERR_BABBLE;
 | ||
|       
 | ||
|       /* Check if a NAK occurred.  */
 | ||
|       else if (errtd->ctrl_status & (1 << 19))
 | ||
| 	err = GRUB_USB_ERR_NAK;
 | ||
|       
 | ||
|       /* Check if a timeout occurred.  */
 | ||
|       else if (errtd->ctrl_status & (1 << 18))
 | ||
| 	err = GRUB_USB_ERR_TIMEOUT;
 | ||
|       
 | ||
|       /* Check if a bitstuff error occurred.  */
 | ||
|       else if (errtd->ctrl_status & (1 << 17))
 | ||
| 	err = GRUB_USB_ERR_BITSTUFF;
 | ||
|       
 | ||
|       if (err)
 | ||
| 	{
 | ||
| 	  grub_dprintf ("uhci", "transaction failed\n");
 | ||
| 
 | ||
| 	  /* Place the QH back in the free list and deallocate the associated
 | ||
| 	     TDs.  */
 | ||
| 	  cdata->qh->elinkptr = 1;
 | ||
| 	  grub_free_queue (u, cdata->qh, cdata->td_first, transfer, actual);
 | ||
| 	  grub_free (cdata);
 | ||
| 
 | ||
| 	  return err;
 | ||
| 	}
 | ||
|     }
 | ||
| 
 | ||
|   /* Fall through, no errors occurred, so the QH might be
 | ||
|      updated.  */
 | ||
|   grub_dprintf ("uhci", "transaction fallthrough\n");
 | ||
| 
 | ||
|   return GRUB_USB_ERR_WAIT;
 | ||
| }
 | ||
| 
 | ||
| static grub_usb_err_t
 | ||
| grub_uhci_cancel_transfer (grub_usb_controller_t dev,
 | ||
| 			   grub_usb_transfer_t transfer)
 | ||
| {
 | ||
|   struct grub_uhci *u = (struct grub_uhci *) dev->data;
 | ||
|   grub_size_t actual;
 | ||
|   struct grub_uhci_transfer_controller_data *cdata = transfer->controller_data;
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "transaction cancel\n");
 | ||
| 
 | ||
|   /* Place the QH back in the free list and deallocate the associated
 | ||
|      TDs.  */
 | ||
|   cdata->qh->elinkptr = 1;
 | ||
|   grub_free_queue (u, cdata->qh, cdata->td_first, transfer, &actual);
 | ||
|   grub_free (cdata);
 | ||
| 
 | ||
|   return GRUB_USB_ERR_NONE;
 | ||
| }
 | ||
| 
 | ||
| static int
 | ||
| grub_uhci_iterate (grub_usb_controller_iterate_hook_t hook, void *hook_data)
 | ||
| {
 | ||
|   struct grub_uhci *u;
 | ||
|   struct grub_usb_controller dev;
 | ||
| 
 | ||
|   for (u = uhci; u; u = u->next)
 | ||
|     {
 | ||
|       dev.data = u;
 | ||
|       if (hook (&dev, hook_data))
 | ||
| 	return 1;
 | ||
|     }
 | ||
| 
 | ||
|   return 0;
 | ||
| }
 | ||
| 
 | ||
| static grub_usb_err_t
 | ||
| grub_uhci_portstatus (grub_usb_controller_t dev,
 | ||
| 		      unsigned int port, unsigned int enable)
 | ||
| {
 | ||
|   struct grub_uhci *u = (struct grub_uhci *) dev->data;
 | ||
|   int reg;
 | ||
|   unsigned int status;
 | ||
|   grub_uint64_t endtime;
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "portstatus, iobase:%08x\n", u->iobase);
 | ||
|   
 | ||
|   grub_dprintf ("uhci", "enable=%d port=%d\n", enable, port);
 | ||
| 
 | ||
|   if (port == 0)
 | ||
|     reg = GRUB_UHCI_REG_PORTSC1;
 | ||
|   else if (port == 1)
 | ||
|     reg = GRUB_UHCI_REG_PORTSC2;
 | ||
|   else
 | ||
|     return GRUB_USB_ERR_INTERNAL;
 | ||
| 
 | ||
|   status = grub_uhci_readreg16 (u, reg);
 | ||
|   grub_dprintf ("uhci", "detect=0x%02x\n", status);
 | ||
| 
 | ||
|   if (!enable) /* We don't need reset port */
 | ||
|     {
 | ||
|       /* Disable the port.  */
 | ||
|       grub_uhci_writereg16 (u, reg, 0 << 2);
 | ||
|       grub_dprintf ("uhci", "waiting for the port to be disabled\n");
 | ||
|       endtime = grub_get_time_ms () + 1000;
 | ||
|       while ((grub_uhci_readreg16 (u, reg) & (1 << 2)))
 | ||
|         if (grub_get_time_ms () > endtime)
 | ||
|           return GRUB_USB_ERR_TIMEOUT;
 | ||
| 
 | ||
|       status = grub_uhci_readreg16 (u, reg);
 | ||
|       grub_dprintf ("uhci", ">3detect=0x%02x\n", status);
 | ||
|       return GRUB_USB_ERR_NONE;
 | ||
|     }
 | ||
|     
 | ||
|   /* Reset the port.  */
 | ||
|   status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
 | ||
|   grub_uhci_writereg16 (u, reg, status | (1 << 9));
 | ||
|   grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
 | ||
| 
 | ||
|   /* Wait for the reset to complete.  XXX: How long exactly?  */
 | ||
|   grub_millisleep (50); /* For root hub should be nominaly 50ms */
 | ||
|   status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
 | ||
|   grub_uhci_writereg16 (u, reg, status & ~(1 << 9));
 | ||
|   grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
 | ||
| 
 | ||
|   /* Note: some debug prints were removed because they affected reset/enable timing. */
 | ||
| 
 | ||
|   grub_millisleep (1); /* Probably not needed at all or only few microsecs. */
 | ||
| 
 | ||
|   /* Reset bits Connect & Enable Status Change */
 | ||
|   status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
 | ||
|   grub_uhci_writereg16 (u, reg, status | (1 << 3) | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED);
 | ||
|   grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
 | ||
| 
 | ||
|   /* Enable the port.  */
 | ||
|   status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC;
 | ||
|   grub_uhci_writereg16 (u, reg, status | (1 << 2));
 | ||
|   grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */
 | ||
| 
 | ||
|   endtime = grub_get_time_ms () + 1000;
 | ||
|   while (! ((status = grub_uhci_readreg16 (u, reg)) & (1 << 2)))
 | ||
|     if (grub_get_time_ms () > endtime)
 | ||
|       return GRUB_USB_ERR_TIMEOUT;
 | ||
| 
 | ||
|   /* Reset recovery time */
 | ||
|   grub_millisleep (10);
 | ||
| 
 | ||
|   /* Read final port status */
 | ||
|   status = grub_uhci_readreg16 (u, reg);
 | ||
|   grub_dprintf ("uhci", ">3detect=0x%02x\n", status);
 | ||
| 
 | ||
| 
 | ||
|   return GRUB_USB_ERR_NONE;
 | ||
| }
 | ||
| 
 | ||
| static grub_usb_speed_t
 | ||
| grub_uhci_detect_dev (grub_usb_controller_t dev, int port, int *changed)
 | ||
| {
 | ||
|   struct grub_uhci *u = (struct grub_uhci *) dev->data;
 | ||
|   int reg;
 | ||
|   unsigned int status;
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "detect_dev, iobase:%08x\n", u->iobase);
 | ||
|   
 | ||
|   if (port == 0)
 | ||
|     reg = GRUB_UHCI_REG_PORTSC1;
 | ||
|   else if (port == 1)
 | ||
|     reg = GRUB_UHCI_REG_PORTSC2;
 | ||
|   else
 | ||
|     return GRUB_USB_SPEED_NONE;
 | ||
| 
 | ||
|   status = grub_uhci_readreg16 (u, reg);
 | ||
| 
 | ||
|   grub_dprintf ("uhci", "detect=0x%02x port=%d\n", status, port);
 | ||
| 
 | ||
|   /* Connect Status Change bit - it detects change of connection */
 | ||
|   if (status & GRUB_UHCI_DETECT_CHANGED)
 | ||
|     {
 | ||
|       *changed = 1;
 | ||
|       /* Reset bit Connect Status Change */
 | ||
|       grub_uhci_writereg16 (u, reg, (status & GRUB_UHCI_REG_PORTSC_RW)
 | ||
| 			    | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED);
 | ||
|     }
 | ||
|   else
 | ||
|     *changed = 0;
 | ||
|     
 | ||
|   if (! (status & GRUB_UHCI_DETECT_HAVE_DEVICE))
 | ||
|     return GRUB_USB_SPEED_NONE;
 | ||
|   else if (status & GRUB_UHCI_DETECT_LOW_SPEED)
 | ||
|     return GRUB_USB_SPEED_LOW;
 | ||
|   else
 | ||
|     return GRUB_USB_SPEED_FULL;
 | ||
| }
 | ||
| 
 | ||
| static int
 | ||
| grub_uhci_hubports (grub_usb_controller_t dev __attribute__((unused)))
 | ||
| {
 | ||
|   /* The root hub has exactly two ports.  */
 | ||
|   return 2;
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| static struct grub_usb_controller_dev usb_controller =
 | ||
| {
 | ||
|   .name = "uhci",
 | ||
|   .iterate = grub_uhci_iterate,
 | ||
|   .setup_transfer = grub_uhci_setup_transfer,
 | ||
|   .check_transfer = grub_uhci_check_transfer,
 | ||
|   .cancel_transfer = grub_uhci_cancel_transfer,
 | ||
|   .hubports = grub_uhci_hubports,
 | ||
|   .portstatus = grub_uhci_portstatus,
 | ||
|   .detect_dev = grub_uhci_detect_dev,
 | ||
|   /* estimated max. count of TDs for one bulk transfer */
 | ||
|   .max_bulk_tds = N_TD * 3 / 4
 | ||
| };
 | ||
| 
 | ||
| GRUB_MOD_INIT(uhci)
 | ||
| {
 | ||
|   grub_stop_disk_firmware ();
 | ||
| 
 | ||
|   grub_uhci_inithw ();
 | ||
|   grub_usb_controller_dev_register (&usb_controller);
 | ||
|   grub_dprintf ("uhci", "registered\n");
 | ||
| }
 | ||
| 
 | ||
| GRUB_MOD_FINI(uhci)
 | ||
| {
 | ||
|   struct grub_uhci *u;
 | ||
| 
 | ||
|   /* Disable all UHCI controllers.  */
 | ||
|   for (u = uhci; u; u = u->next)
 | ||
|     grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD, 0);
 | ||
| 
 | ||
|   /* Unregister the controller.  */
 | ||
|   grub_usb_controller_dev_unregister (&usb_controller);
 | ||
| }
 |