12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469 |
- /* ohci.c - OHCI 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/usb.h>
- #include <grub/usbtrans.h>
- #include <grub/misc.h>
- #include <grub/pci.h>
- #include <grub/cpu/pci.h>
- #include <grub/cpu/io.h>
- #include <grub/time.h>
- #include <grub/cs5536.h>
- #include <grub/loader.h>
- #include <grub/disk.h>
- GRUB_MOD_LICENSE ("GPLv3+");
- struct grub_ohci_hcca
- {
- /* Pointers to Interrupt Endpoint Descriptors. Not used by
- GRUB. */
- grub_uint32_t inttable[32];
- /* Current frame number. */
- grub_uint16_t framenumber;
- grub_uint16_t pad;
- /* List of completed TDs. */
- grub_uint32_t donehead;
- grub_uint8_t reserved[116];
- } GRUB_PACKED;
- /* OHCI General Transfer Descriptor */
- struct grub_ohci_td
- {
- /* Information used to construct the TOKEN packet. */
- grub_uint32_t token;
- grub_uint32_t buffer; /* LittleEndian physical address */
- grub_uint32_t next_td; /* LittleEndian physical address */
- grub_uint32_t buffer_end; /* LittleEndian physical address */
- /* next values are not for OHCI HW */
- volatile struct grub_ohci_td *link_td; /* pointer to next free/chained TD
- * pointer as uint32 */
- grub_uint32_t prev_td_phys; /* we need it to find previous TD
- * physical address in CPU endian */
- grub_uint32_t tr_index; /* index of TD in transfer */
- grub_uint8_t pad[8 - sizeof (volatile struct grub_ohci_td *)]; /* padding to 32 bytes */
- } GRUB_PACKED;
- /* OHCI Endpoint Descriptor. */
- struct grub_ohci_ed
- {
- grub_uint32_t target;
- grub_uint32_t td_tail;
- grub_uint32_t td_head;
- grub_uint32_t next_ed;
- } GRUB_PACKED;
- typedef volatile struct grub_ohci_td *grub_ohci_td_t;
- typedef volatile struct grub_ohci_ed *grub_ohci_ed_t;
- /* Experimental change of ED/TD allocation */
- /* Little bit similar as in UHCI */
- /* Implementation assumes:
- * 32-bits architecture - XXX: fix for 64-bits
- * memory allocated by grub_memalign_dma32 must be continuous
- * in virtual and also in physical memory */
- struct grub_ohci
- {
- volatile grub_uint32_t *iobase;
- volatile struct grub_ohci_hcca *hcca;
- grub_uint32_t hcca_addr;
- struct grub_pci_dma_chunk *hcca_chunk;
- grub_ohci_ed_t ed_ctrl; /* EDs for CONTROL */
- grub_uint32_t ed_ctrl_addr;
- struct grub_pci_dma_chunk *ed_ctrl_chunk;
- grub_ohci_ed_t ed_bulk; /* EDs for BULK */
- grub_uint32_t ed_bulk_addr;
- struct grub_pci_dma_chunk *ed_bulk_chunk;
- grub_ohci_td_t td; /* TDs */
- grub_uint32_t td_addr;
- struct grub_pci_dma_chunk *td_chunk;
- struct grub_ohci *next;
- grub_ohci_td_t td_free; /* Pointer to first free TD */
- };
- static struct grub_ohci *ohci;
- typedef enum
- {
- GRUB_OHCI_REG_REVISION = 0x00,
- GRUB_OHCI_REG_CONTROL,
- GRUB_OHCI_REG_CMDSTATUS,
- GRUB_OHCI_REG_INTSTATUS,
- GRUB_OHCI_REG_INTENA,
- GRUB_OHCI_REG_INTDIS,
- GRUB_OHCI_REG_HCCA,
- GRUB_OHCI_REG_PERIODIC,
- GRUB_OHCI_REG_CONTROLHEAD,
- GRUB_OHCI_REG_CONTROLCURR,
- GRUB_OHCI_REG_BULKHEAD,
- GRUB_OHCI_REG_BULKCURR,
- GRUB_OHCI_REG_DONEHEAD,
- GRUB_OHCI_REG_FRAME_INTERVAL,
- GRUB_OHCI_REG_PERIODIC_START = 16,
- GRUB_OHCI_REG_RHUBA = 18,
- GRUB_OHCI_REG_RHUBPORT = 21,
- GRUB_OHCI_REG_LEGACY_CONTROL = 0x100,
- GRUB_OHCI_REG_LEGACY_INPUT = 0x104,
- GRUB_OHCI_REG_LEGACY_OUTPUT = 0x108,
- GRUB_OHCI_REG_LEGACY_STATUS = 0x10c
- } grub_ohci_reg_t;
- #define GRUB_OHCI_RHUB_PORT_POWER_MASK 0x300
- #define GRUB_OHCI_RHUB_PORT_ALL_POWERED 0x200
- #define GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_MASK 0x8fff0000
- #define GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT 16
- #define GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT 0
- /* XXX: Is this choice of timings sane? */
- #define GRUB_OHCI_FSMPS 0x2778
- #define GRUB_OHCI_PERIODIC_START 0x257f
- #define GRUB_OHCI_FRAME_INTERVAL 0x2edf
- #define GRUB_OHCI_SET_PORT_ENABLE (1 << 1)
- #define GRUB_OHCI_CLEAR_PORT_ENABLE (1 << 0)
- #define GRUB_OHCI_SET_PORT_RESET (1 << 4)
- #define GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE (1 << 20)
- #define GRUB_OHCI_REG_CONTROL_BULK_ENABLE (1 << 5)
- #define GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE (1 << 4)
- #define GRUB_OHCI_RESET_CONNECT_CHANGE (1 << 16)
- #define GRUB_OHCI_CTRL_EDS 256
- #define GRUB_OHCI_BULK_EDS 510
- #define GRUB_OHCI_TDS 640
- #define GRUB_OHCI_ED_ADDR_MASK 0x7ff
- static inline grub_ohci_ed_t
- grub_ohci_ed_phys2virt (struct grub_ohci *o, int bulk, grub_uint32_t x)
- {
- if (!x)
- return NULL;
- if (bulk)
- return (grub_ohci_ed_t) (x - o->ed_bulk_addr
- + (grub_uint8_t *) o->ed_bulk);
- return (grub_ohci_ed_t) (x - o->ed_ctrl_addr
- + (grub_uint8_t *) o->ed_ctrl);
- }
- static grub_uint32_t
- grub_ohci_virt_to_phys (struct grub_ohci *o, int bulk, grub_ohci_ed_t x)
- {
- if (!x)
- return 0;
- if (bulk)
- return (grub_uint8_t *) x - (grub_uint8_t *) o->ed_bulk + o->ed_bulk_addr;
- return (grub_uint8_t *) x - (grub_uint8_t *) o->ed_ctrl + o->ed_ctrl_addr;
- }
- static inline grub_ohci_td_t
- grub_ohci_td_phys2virt (struct grub_ohci *o, grub_uint32_t x)
- {
- if (!x)
- return NULL;
- return (grub_ohci_td_t) (x - o->td_addr + (grub_uint8_t *) o->td);
- }
- static grub_uint32_t
- grub_ohci_td_virt2phys (struct grub_ohci *o, grub_ohci_td_t x)
- {
- if (!x)
- return 0;
- return (grub_uint8_t *)x - (grub_uint8_t *)o->td + o->td_addr;
- }
- static grub_uint32_t
- grub_ohci_readreg32 (struct grub_ohci *o, grub_ohci_reg_t reg)
- {
- return grub_le_to_cpu32 (*(o->iobase + reg));
- }
- static void
- grub_ohci_writereg32 (struct grub_ohci *o,
- grub_ohci_reg_t reg, grub_uint32_t val)
- {
- *(o->iobase + reg) = grub_cpu_to_le32 (val);
- }
- /* Iterate over all PCI devices. Determine if a device is an OHCI
- controller. If this is the case, initialize it. */
- static int
- grub_ohci_pci_iter (grub_pci_device_t dev, grub_pci_id_t pciid,
- void *data __attribute__ ((unused)))
- {
- grub_uint32_t interf;
- grub_uint32_t base;
- grub_pci_address_t addr;
- struct grub_ohci *o;
- grub_uint32_t revision;
- int j;
- /* Determine IO base address. */
- grub_dprintf ("ohci", "pciid = %x\n", pciid);
- if (pciid == GRUB_CS5536_PCIID)
- {
- grub_uint64_t basereg;
- basereg = grub_cs5536_read_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE);
- if (!(basereg & GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE))
- {
- /* Shouldn't happen. */
- grub_dprintf ("ohci", "No OHCI address is assigned\n");
- return 0;
- }
- base = (basereg & GRUB_CS5536_MSR_USB_BASE_ADDR_MASK);
- basereg |= GRUB_CS5536_MSR_USB_BASE_BUS_MASTER;
- basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_ENABLED;
- basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_STATUS;
- grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE, basereg);
- }
- else
- {
- grub_uint32_t class_code;
- grub_uint32_t class;
- grub_uint32_t subclass;
- 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 OHCI controller, just return. */
- if (class != 0x0c || subclass != 0x03 || interf != 0x10)
- return 0;
- addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG0);
- base = grub_pci_read (addr);
- base &= GRUB_PCI_ADDR_MEM_MASK;
- if (!base)
- {
- grub_dprintf ("ehci",
- "EHCI: EHCI is not mapper\n");
- return 0;
- }
- /* Set bus master - needed for coreboot, VMware, broken BIOSes etc. */
- addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
- grub_pci_write_word(addr,
- GRUB_PCI_COMMAND_MEM_ENABLED
- | GRUB_PCI_COMMAND_BUS_MASTER
- | grub_pci_read_word(addr));
- grub_dprintf ("ohci", "class=0x%02x 0x%02x interface 0x%02x\n",
- class, subclass, interf);
- }
- /* Allocate memory for the controller and register it. */
- o = grub_malloc (sizeof (*o));
- if (! o)
- return 1;
- grub_memset ((void*)o, 0, sizeof (*o));
- o->iobase = grub_pci_device_map_range (dev, base, 0x800);
- grub_dprintf ("ohci", "base=%p\n", o->iobase);
- /* Reserve memory for the HCCA. */
- o->hcca_chunk = grub_memalign_dma32 (256, 256);
- if (! o->hcca_chunk)
- goto fail;
- o->hcca = grub_dma_get_virt (o->hcca_chunk);
- o->hcca_addr = grub_dma_get_phys (o->hcca_chunk);
- grub_memset ((void*)o->hcca, 0, sizeof(*o->hcca));
- grub_dprintf ("ohci", "hcca: chunk=%p, virt=%p, phys=0x%02x\n",
- o->hcca_chunk, o->hcca, o->hcca_addr);
- /* Reserve memory for ctrl EDs. */
- o->ed_ctrl_chunk = grub_memalign_dma32 (16, sizeof(struct grub_ohci_ed)
- * GRUB_OHCI_CTRL_EDS);
- if (! o->ed_ctrl_chunk)
- goto fail;
- o->ed_ctrl = grub_dma_get_virt (o->ed_ctrl_chunk);
- o->ed_ctrl_addr = grub_dma_get_phys (o->ed_ctrl_chunk);
- /* Preset EDs */
- grub_memset ((void *) o->ed_ctrl, 0, sizeof (struct grub_ohci_ed)
- * GRUB_OHCI_CTRL_EDS);
- for (j=0; j < GRUB_OHCI_CTRL_EDS; j++)
- o->ed_ctrl[j].target = grub_cpu_to_le32_compile_time (1 << 14); /* skip */
- grub_dprintf ("ohci", "EDs-C: chunk=%p, virt=%p, phys=0x%02x\n",
- o->ed_ctrl_chunk, o->ed_ctrl, o->ed_ctrl_addr);
- /* Reserve memory for bulk EDs. */
- o->ed_bulk_chunk = grub_memalign_dma32 (16, sizeof (struct grub_ohci_ed)
- * GRUB_OHCI_BULK_EDS);
- if (! o->ed_bulk_chunk)
- goto fail;
- o->ed_bulk = grub_dma_get_virt (o->ed_bulk_chunk);
- o->ed_bulk_addr = grub_dma_get_phys (o->ed_bulk_chunk);
- /* Preset EDs */
- grub_memset ((void*)o->ed_bulk, 0, sizeof(struct grub_ohci_ed) * GRUB_OHCI_BULK_EDS);
- for (j=0; j < GRUB_OHCI_BULK_EDS; j++)
- o->ed_bulk[j].target = grub_cpu_to_le32_compile_time (1 << 14); /* skip */
- grub_dprintf ("ohci", "EDs-B: chunk=%p, virt=%p, phys=0x%02x\n",
- o->ed_bulk_chunk, o->ed_bulk, o->ed_bulk_addr);
- /* Reserve memory for TDs. */
- o->td_chunk = grub_memalign_dma32 (32, sizeof(struct grub_ohci_td)*GRUB_OHCI_TDS);
- /* Why is it aligned on 32 boundary if spec. says 16 ?
- * We have structure 32 bytes long and we don't want cross
- * 4K boundary inside structure. */
- if (! o->td_chunk)
- goto fail;
- o->td_free = o->td = grub_dma_get_virt (o->td_chunk);
- o->td_addr = grub_dma_get_phys (o->td_chunk);
- /* Preset free TDs chain in TDs */
- grub_memset ((void*)o->td, 0, sizeof(struct grub_ohci_td) * GRUB_OHCI_TDS);
- for (j=0; j < (GRUB_OHCI_TDS-1); j++)
- o->td[j].link_td = &o->td[j+1];
- grub_dprintf ("ohci", "TDs: chunk=%p, virt=%p, phys=0x%02x\n",
- o->td_chunk, o->td, o->td_addr);
- /* Check if the OHCI revision is actually 1.0 as supported. */
- revision = grub_ohci_readreg32 (o, GRUB_OHCI_REG_REVISION);
- grub_dprintf ("ohci", "OHCI revision=0x%02x\n", revision & 0xFF);
- if ((revision & 0xFF) != 0x10)
- goto fail;
- {
- grub_uint32_t control;
- /* Check SMM/BIOS ownership of OHCI (SMM = USB Legacy Support driver for BIOS) */
- control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
- if ((control & 0x100) != 0)
- {
- unsigned i;
- grub_dprintf("ohci", "OHCI is owned by SMM\n");
- /* Do change of ownership */
- /* Ownership change request */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, (1<<3)); /* XXX: Magic. */
- /* Waiting for SMM deactivation */
- for (i=0; i < 10; i++)
- {
- if ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & 0x100) == 0)
- {
- grub_dprintf("ohci", "Ownership changed normally.\n");
- break;
- }
- grub_millisleep (100);
- }
- if (i >= 10)
- {
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & ~0x100);
- grub_dprintf("ohci", "Ownership changing timeout, change forced !\n");
- }
- }
- else if (((control & 0x100) == 0) &&
- ((control & 0xc0) != 0)) /* Not owned by SMM nor reset */
- {
- grub_dprintf("ohci", "OHCI is owned by BIOS\n");
- /* Do change of ownership - not implemented yet... */
- /* In fact we probably need to do nothing ...? */
- }
- else
- {
- grub_dprintf("ohci", "OHCI is not owned by SMM nor BIOS\n");
- /* We can setup OHCI. */
- }
- }
- /* Suspend the OHCI by issuing a reset. */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic. */
- grub_millisleep (1);
- grub_dprintf ("ohci", "OHCI reset\n");
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_FRAME_INTERVAL,
- (GRUB_OHCI_FSMPS
- << GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT)
- | (GRUB_OHCI_FRAME_INTERVAL
- << GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT));
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_PERIODIC_START,
- GRUB_OHCI_PERIODIC_START);
- /* Setup the HCCA. */
- o->hcca->donehead = 0;
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, o->hcca_addr);
- grub_dprintf ("ohci", "OHCI HCCA\n");
- /* Misc. pre-sets. */
- o->hcca->donehead = 0;
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
- /* We don't want modify CONTROL/BULK HEAD registers.
- * So we assign to HEAD registers zero ED from related array
- * and we will not use this ED, it will be always skipped.
- * It should not produce notable performance penalty (I hope). */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
- /* Check OHCI Legacy Support */
- if ((revision & 0x100) != 0)
- {
- grub_dprintf ("ohci", "Legacy Support registers detected\n");
- grub_dprintf ("ohci", "Current state of legacy control reg.: 0x%04x\n",
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL));
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL,
- (grub_ohci_readreg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL)) & ~1);
- grub_dprintf ("ohci", "OHCI Legacy Support disabled.\n");
- }
- /* Enable the OHCI + enable CONTROL and BULK LIST. */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
- (2 << 6)
- | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
- | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
- grub_dprintf ("ohci", "OHCI enable: 0x%02x\n",
- (grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) >> 6) & 3);
- /* Power on all ports */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBA,
- (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA)
- & ~GRUB_OHCI_RHUB_PORT_POWER_MASK)
- | GRUB_OHCI_RHUB_PORT_ALL_POWERED);
- #if 0 /* We don't need it at all, handled via hotplugging */
- /* Now we have hot-plugging, we need to wait for stable power only */
- grub_millisleep (100);
- #endif
- /* Link to ohci now that initialisation is successful. */
- o->next = ohci;
- ohci = o;
- return 0;
- fail:
- if (o)
- {
- grub_dma_free (o->td_chunk);
- grub_dma_free (o->ed_bulk_chunk);
- grub_dma_free (o->ed_ctrl_chunk);
- grub_dma_free (o->hcca_chunk);
- }
- grub_free (o);
- return 0;
- }
- static void
- grub_ohci_inithw (void)
- {
- grub_pci_iterate (grub_ohci_pci_iter, NULL);
- }
- static int
- grub_ohci_iterate (grub_usb_controller_iterate_hook_t hook, void *hook_data)
- {
- struct grub_ohci *o;
- struct grub_usb_controller dev;
- for (o = ohci; o; o = o->next)
- {
- dev.data = o;
- if (hook (&dev, hook_data))
- return 1;
- }
- return 0;
- }
- static grub_ohci_ed_t
- grub_ohci_find_ed (struct grub_ohci *o, int bulk, grub_uint32_t target)
- {
- grub_ohci_ed_t ed, ed_next;
- grub_uint32_t target_addr = target & GRUB_OHCI_ED_ADDR_MASK;
- int count;
- int i;
- /* Use proper values and structures. */
- if (bulk)
- {
- count = GRUB_OHCI_BULK_EDS;
- ed = o->ed_bulk;
- ed_next = grub_ohci_ed_phys2virt(o, bulk,
- grub_le_to_cpu32 (ed->next_ed) );
- }
- else
- {
- count = GRUB_OHCI_CTRL_EDS;
- ed = o->ed_ctrl;
- ed_next = grub_ohci_ed_phys2virt(o, bulk,
- grub_le_to_cpu32 (ed->next_ed) );
- }
- /* First try to find existing ED with proper target address */
- for (i = 0; ; )
- {
- if (i && /* We ignore zero ED */
- ((ed->target & GRUB_OHCI_ED_ADDR_MASK) == target_addr))
- return ed; /* Found proper existing ED */
- i++;
- if (ed_next && (i < count))
- {
- ed = ed_next;
- ed_next = grub_ohci_ed_phys2virt(o, bulk,
- grub_le_to_cpu32 (ed->next_ed) );
- continue;
- }
- break;
- }
- /* ED with target_addr does not exist, we have to add it */
- /* Have we any free ED in array ? */
- if (i >= count) /* No. */
- return NULL;
- /* Currently we simply take next ED in array, no allocation
- * function is used. It should be no problem until hot-plugging
- * will be implemented, i.e. until we will need to de-allocate EDs
- * of unplugged devices. */
- /* We can link new ED to previous ED safely as the new ED should
- * still have set skip bit. */
- ed->next_ed = grub_cpu_to_le32 ( grub_ohci_virt_to_phys (o,
- bulk, &ed[1]));
- return &ed[1];
- }
- static grub_ohci_td_t
- grub_ohci_alloc_td (struct grub_ohci *o)
- {
- grub_ohci_td_t ret;
- /* Check if there is a Transfer Descriptor available. */
- if (! o->td_free)
- return NULL;
- ret = o->td_free; /* Take current free TD */
- o->td_free = (grub_ohci_td_t)ret->link_td; /* Advance to next free TD in chain */
- ret->link_td = 0; /* Reset link_td in allocated TD */
- return ret;
- }
- static void
- grub_ohci_free_td (struct grub_ohci *o, grub_ohci_td_t td)
- {
- grub_memset ( (void*)td, 0, sizeof(struct grub_ohci_td) );
- td->link_td = o->td_free; /* Cahin new free TD & rest */
- o->td_free = td; /* Change address of first free TD */
- }
- static void
- grub_ohci_free_tds (struct grub_ohci *o, grub_ohci_td_t td)
- {
- if (!td)
- return;
- /* Unchain first TD from previous TD if it is chained */
- if (td->prev_td_phys)
- {
- grub_ohci_td_t td_prev_virt = grub_ohci_td_phys2virt(o,
- td->prev_td_phys);
- if (td == (grub_ohci_td_t) td_prev_virt->link_td)
- td_prev_virt->link_td = 0;
- }
- /* Free all TDs from td (chained by link_td) */
- while (td)
- {
- grub_ohci_td_t tdprev;
- /* Unlink the queue. */
- tdprev = td;
- td = (grub_ohci_td_t) td->link_td;
- /* Free the TD. */
- grub_ohci_free_td (o, tdprev);
- }
- }
- static void
- grub_ohci_transaction (grub_ohci_td_t td,
- grub_transfer_type_t type, unsigned int toggle,
- grub_size_t size, grub_uint32_t data)
- {
- grub_uint32_t token;
- grub_uint32_t buffer;
- grub_uint32_t buffer_end;
- grub_dprintf ("ohci", "OHCI transaction td=%p type=%d, toggle=%d, size=%lu\n",
- td, type, toggle, (unsigned long) size);
- switch (type)
- {
- case GRUB_USB_TRANSFER_TYPE_SETUP:
- token = 0 << 19;
- break;
- case GRUB_USB_TRANSFER_TYPE_IN:
- token = 2 << 19;
- break;
- case GRUB_USB_TRANSFER_TYPE_OUT:
- token = 1 << 19;
- break;
- default:
- token = 0;
- break;
- }
- /* Set the token */
- token |= ( 7 << 21); /* Never generate interrupt */
- token |= toggle << 24;
- token |= 1 << 25;
- /* Set "Not accessed" error code */
- token |= 15 << 28;
- buffer = data;
- buffer_end = buffer + size - 1;
- /* Set correct buffer values in TD if zero transfer occurs */
- if (size)
- {
- buffer = (grub_uint32_t) data;
- buffer_end = buffer + size - 1;
- td->buffer = grub_cpu_to_le32 (buffer);
- td->buffer_end = grub_cpu_to_le32 (buffer_end);
- }
- else
- {
- td->buffer = 0;
- td->buffer_end = 0;
- }
- /* Set the rest of TD */
- td->token = grub_cpu_to_le32 (token);
- td->next_td = 0;
- }
- struct grub_ohci_transfer_controller_data
- {
- grub_uint32_t tderr_phys;
- grub_uint32_t td_last_phys;
- grub_ohci_ed_t ed_virt;
- grub_ohci_td_t td_current_virt;
- grub_ohci_td_t td_head_virt;
- };
- static grub_usb_err_t
- grub_ohci_setup_transfer (grub_usb_controller_t dev,
- grub_usb_transfer_t transfer)
- {
- struct grub_ohci *o = (struct grub_ohci *) dev->data;
- int bulk = 0;
- grub_ohci_td_t td_next_virt;
- grub_uint32_t target;
- grub_uint32_t td_head_phys;
- grub_uint32_t td_tail_phys;
- int i;
- struct grub_ohci_transfer_controller_data *cdata;
- cdata = grub_zalloc (sizeof (*cdata));
- if (!cdata)
- return GRUB_USB_ERR_INTERNAL;
- /* Pre-set target for ED - we need it to find proper ED */
- /* Set the device address. */
- target = transfer->devaddr;
- /* Set the endpoint. It should be masked, we need 4 bits only. */
- target |= (transfer->endpoint & 15) << 7;
- /* Set the device speed. */
- target |= (transfer->dev->speed == GRUB_USB_SPEED_LOW) << 13;
- /* Set the maximum packet size. */
- target |= transfer->max << 16;
- /* Determine if transfer type is bulk - we need to select proper ED */
- switch (transfer->type)
- {
- case GRUB_USB_TRANSACTION_TYPE_BULK:
- bulk = 1;
- break;
- case GRUB_USB_TRANSACTION_TYPE_CONTROL:
- break;
- default:
- grub_free (cdata);
- return GRUB_USB_ERR_INTERNAL;
- }
- /* Find proper ED or add new ED */
- cdata->ed_virt = grub_ohci_find_ed (o, bulk, target);
- if (!cdata->ed_virt)
- {
- grub_dprintf ("ohci","Fatal: No free ED !\n");
- grub_free (cdata);
- return GRUB_USB_ERR_INTERNAL;
- }
- /* Take pointer to first TD from ED */
- td_head_phys = grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf;
- td_tail_phys = grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf;
- /* Sanity check - td_head should be equal to td_tail */
- if (td_head_phys != td_tail_phys) /* Should never happen ! */
- {
- grub_dprintf ("ohci", "Fatal: HEAD is not equal to TAIL !\n");
- grub_dprintf ("ohci", "HEAD = 0x%02x, TAIL = 0x%02x\n",
- td_head_phys, td_tail_phys);
- /* XXX: Fix: What to do ? */
- grub_free (cdata);
- return GRUB_USB_ERR_INTERNAL;
- }
- /* Now we should handle first TD. If ED is newly allocated,
- * we must allocate the first TD. */
- if (!td_head_phys)
- {
- cdata->td_head_virt = grub_ohci_alloc_td (o);
- if (!cdata->td_head_virt)
- {
- grub_free (cdata);
- return GRUB_USB_ERR_INTERNAL; /* We don't need de-allocate ED */
- }
- /* We can set td_head only when ED is not active, i.e.
- * when it is newly allocated. */
- cdata->ed_virt->td_head
- = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_head_virt));
- cdata->ed_virt->td_tail = cdata->ed_virt->td_head;
- }
- else
- cdata->td_head_virt = grub_ohci_td_phys2virt ( o, td_head_phys );
- /* Set TDs */
- cdata->td_last_phys = td_head_phys; /* initial value to make compiler happy... */
- for (i = 0, cdata->td_current_virt = cdata->td_head_virt;
- i < transfer->transcnt; i++)
- {
- grub_usb_transaction_t tr = &transfer->transactions[i];
- grub_ohci_transaction (cdata->td_current_virt, tr->pid, tr->toggle,
- tr->size, tr->data);
- /* Set index of TD in transfer */
- cdata->td_current_virt->tr_index = (grub_uint32_t) i;
- /* Remember last used (processed) TD phys. addr. */
- cdata->td_last_phys = grub_ohci_td_virt2phys (o, cdata->td_current_virt);
- /* Allocate next TD */
- td_next_virt = grub_ohci_alloc_td (o);
- if (!td_next_virt) /* No free TD, cancel transfer and free TDs except head TD */
- {
- if (i) /* if i==0 we have nothing to free... */
- grub_ohci_free_tds (o, grub_ohci_td_phys2virt(o,
- grub_le_to_cpu32 (cdata->td_head_virt->next_td)));
- /* Reset head TD */
- grub_memset ( (void*)cdata->td_head_virt, 0,
- sizeof(struct grub_ohci_td) );
- grub_dprintf ("ohci", "Fatal: No free TD !");
- grub_free (cdata);
- return GRUB_USB_ERR_INTERNAL;
- }
- /* Chain TDs */
- cdata->td_current_virt->link_td = td_next_virt;
- cdata->td_current_virt->next_td = grub_cpu_to_le32 (
- grub_ohci_td_virt2phys (o,
- td_next_virt) );
- td_next_virt->prev_td_phys = grub_ohci_td_virt2phys (o,
- cdata->td_current_virt);
- cdata->td_current_virt = td_next_virt;
- }
- grub_dprintf ("ohci", "Tail TD (not processed) = %p\n",
- cdata->td_current_virt);
- /* Setup the Endpoint Descriptor for transfer. */
- /* First set necessary fields in TARGET but keep (or set) skip bit */
- /* Note: It could be simpler if speed, format and max. packet
- * size never change after first allocation of ED.
- * But unfortunately max. packet size may change during initial
- * setup sequence and we must handle it. */
- cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
- /* Set td_tail */
- cdata->ed_virt->td_tail
- = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_current_virt));
- /* Now reset skip bit */
- cdata->ed_virt->target = grub_cpu_to_le32 (target);
- /* ed_virt->td_head = grub_cpu_to_le32 (td_head); Must not be changed, it is maintained by OHCI */
- /* ed_virt->next_ed = grub_cpu_to_le32 (0); Handled by grub_ohci_find_ed, do not change ! */
- grub_dprintf ("ohci", "program OHCI\n");
- /* Program the OHCI to actually transfer. */
- switch (transfer->type)
- {
- case GRUB_USB_TRANSACTION_TYPE_BULK:
- {
- grub_dprintf ("ohci", "BULK list filled\n");
- /* Set BulkListFilled. */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1 << 2);
- /* Read back of register should ensure it is really written */
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
- break;
- }
- case GRUB_USB_TRANSACTION_TYPE_CONTROL:
- {
- grub_dprintf ("ohci", "CONTROL list filled\n");
- /* Set ControlListFilled. */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1 << 1);
- /* Read back of register should ensure it is really written */
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
- break;
- }
- }
- transfer->controller_data = cdata;
- return GRUB_USB_ERR_NONE;
- }
- static void
- pre_finish_transfer (grub_usb_controller_t dev,
- grub_usb_transfer_t transfer)
- {
- struct grub_ohci *o = dev->data;
- struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
- grub_uint32_t target;
- grub_uint32_t status;
- grub_uint32_t control;
- grub_uint32_t intstatus;
- /* There are many ways how the loop above can finish:
- * - normally without any error via INTSTATUS WDH bit
- * : tderr_phys == td_last_phys, td_head == td_tail
- * - normally with error via HALT bit in ED TD HEAD
- * : td_head = next TD after TD with error
- * : tderr_phys = last processed and retired TD with error,
- * i.e. should be != 0
- * : if bad_OHCI == TRUE, tderr_phys will be probably invalid
- * - unrecoverable error - I never seen it but it could be
- * : err_unrec == TRUE, other values can contain anything...
- * - timeout, it can be caused by:
- * -- bad USB device - some devices have some bugs, see Linux source
- * and related links
- * -- bad OHCI controller - e.g. lost interrupts or does not set
- * proper bits in INTSTATUS when real IRQ not enabled etc.,
- * see Linux source and related links
- * One known bug is handled - if transfer finished
- * successfully (i.e. HEAD==TAIL, last transfer TD is retired,
- * HALT bit is not set) and WDH bit is not set in INTSTATUS - in
- * this case we set o->bad_OHCI=TRUE and do alternate loop
- * and error handling - but there is problem how to find retired
- * TD with error code if HALT occurs and if DONEHEAD is not
- * working - we need to find TD previous to current ED HEAD
- * -- bad code of this driver or some unknown reasons - :-(
- * it can be e.g. bad handling of EDs/TDs/toggle bit...
- */
- /* Remember target for debug and set skip flag in ED */
- /* It should be normaly not necessary but we need it at least
- * in case of timeout */
- target = grub_le_to_cpu32 ( cdata->ed_virt->target );
- cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
- /* Read registers for debug - they should be read now because
- * debug prints case unwanted delays, so something can happen
- * in the meantime... */
- control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
- status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
- intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
- /* Now print debug values - to have full info what happened */
- grub_dprintf ("ohci", "loop finished: control=0x%02x status=0x%02x\n",
- control, status);
- grub_dprintf ("ohci", "intstatus=0x%02x, td_last_phys=0x%02x\n",
- intstatus, cdata->td_last_phys);
- grub_dprintf ("ohci", "TARGET=0x%02x, HEAD=0x%02x, TAIL=0x%02x\n",
- target,
- grub_le_to_cpu32 (cdata->ed_virt->td_head),
- grub_le_to_cpu32 (cdata->ed_virt->td_tail) );
- }
- static void
- finish_transfer (grub_usb_controller_t dev,
- grub_usb_transfer_t transfer)
- {
- struct grub_ohci *o = dev->data;
- struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
- /* Set empty ED - set HEAD = TAIL = last (not processed) TD */
- cdata->ed_virt->td_head = grub_cpu_to_le32 (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf);
- /* At this point always should be:
- * ED has skip bit set and halted or empty or after next SOF,
- * i.e. it is safe to free all TDs except last not processed
- * ED HEAD == TAIL == phys. addr. of td_current_virt */
- /* Un-chainig of last TD */
- if (cdata->td_current_virt->prev_td_phys)
- {
- grub_ohci_td_t td_prev_virt
- = grub_ohci_td_phys2virt (o, cdata->td_current_virt->prev_td_phys);
- if (cdata->td_current_virt == (grub_ohci_td_t) td_prev_virt->link_td)
- td_prev_virt->link_td = 0;
- cdata->td_current_virt->prev_td_phys = 0;
- }
- grub_dprintf ("ohci", "OHCI finished, freeing\n");
- grub_ohci_free_tds (o, cdata->td_head_virt);
- grub_free (cdata);
- }
- static grub_usb_err_t
- parse_halt (grub_usb_controller_t dev,
- grub_usb_transfer_t transfer,
- grub_size_t *actual)
- {
- struct grub_ohci *o = dev->data;
- struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
- grub_uint8_t errcode = 0;
- grub_usb_err_t err = GRUB_USB_ERR_NAK;
- grub_ohci_td_t tderr_virt = NULL;
- *actual = 0;
- pre_finish_transfer (dev, transfer);
- /* First we must get proper tderr_phys value */
- /* Retired TD with error should be previous TD to ED->td_head */
- cdata->tderr_phys = grub_ohci_td_phys2virt (o,
- grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf )
- ->prev_td_phys;
- /* Prepare pointer to last processed TD and get error code */
- tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
- /* Set index of last processed TD */
- if (tderr_virt)
- {
- errcode = grub_le_to_cpu32 (tderr_virt->token) >> 28;
- transfer->last_trans = tderr_virt->tr_index;
- }
- else
- transfer->last_trans = -1;
- /* Evaluation of error code */
- grub_dprintf ("ohci", "OHCI tderr_phys=0x%02x, errcode=0x%02x\n",
- cdata->tderr_phys, errcode);
- switch (errcode)
- {
- case 0:
- /* XXX: Should not happen! */
- grub_error (GRUB_ERR_IO, "OHCI failed without reporting the reason");
- err = GRUB_USB_ERR_INTERNAL;
- break;
- case 1:
- /* XXX: CRC error. */
- err = GRUB_USB_ERR_TIMEOUT;
- break;
- case 2:
- err = GRUB_USB_ERR_BITSTUFF;
- break;
- case 3:
- /* XXX: Data Toggle error. */
- err = GRUB_USB_ERR_DATA;
- break;
- case 4:
- err = GRUB_USB_ERR_STALL;
- break;
- case 5:
- /* XXX: Not responding. */
- err = GRUB_USB_ERR_TIMEOUT;
- break;
- case 6:
- /* XXX: PID Check bits failed. */
- err = GRUB_USB_ERR_BABBLE;
- break;
- case 7:
- /* XXX: PID unexpected failed. */
- err = GRUB_USB_ERR_BABBLE;
- break;
- case 8:
- /* XXX: Data overrun error. */
- err = GRUB_USB_ERR_DATA;
- grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n",
- tderr_virt, tderr_virt->tr_index);
- break;
- case 9:
- /* XXX: Data underrun error. */
- grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n",
- tderr_virt, tderr_virt->tr_index);
- if (transfer->last_trans == -1)
- break;
- *actual = transfer->transactions[transfer->last_trans].size
- - (grub_le_to_cpu32 (tderr_virt->buffer_end)
- - grub_le_to_cpu32 (tderr_virt->buffer))
- + transfer->transactions[transfer->last_trans].preceding;
- err = GRUB_USB_ERR_NONE;
- break;
- case 10:
- /* XXX: Reserved. */
- err = GRUB_USB_ERR_NAK;
- break;
- case 11:
- /* XXX: Reserved. */
- err = GRUB_USB_ERR_NAK;
- break;
- case 12:
- /* XXX: Buffer overrun. */
- err = GRUB_USB_ERR_DATA;
- break;
- case 13:
- /* XXX: Buffer underrun. */
- err = GRUB_USB_ERR_DATA;
- break;
- default:
- err = GRUB_USB_ERR_NAK;
- break;
- }
- finish_transfer (dev, transfer);
- return err;
- }
- static grub_usb_err_t
- parse_success (grub_usb_controller_t dev,
- grub_usb_transfer_t transfer,
- grub_size_t *actual)
- {
- struct grub_ohci *o = dev->data;
- struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
- grub_ohci_td_t tderr_virt = NULL;
- pre_finish_transfer (dev, transfer);
- /* I hope we can do it as transfer (most probably) finished OK */
- cdata->tderr_phys = cdata->td_last_phys;
- /* Prepare pointer to last processed TD */
- tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
- /* Set index of last processed TD */
- if (tderr_virt)
- transfer->last_trans = tderr_virt->tr_index;
- else
- transfer->last_trans = -1;
- *actual = transfer->size + 1;
- finish_transfer (dev, transfer);
- return GRUB_USB_ERR_NONE;
- }
- static grub_usb_err_t
- parse_unrec (grub_usb_controller_t dev,
- grub_usb_transfer_t transfer,
- grub_size_t *actual)
- {
- struct grub_ohci *o = dev->data;
- *actual = 0;
- pre_finish_transfer (dev, transfer);
- /* Don't try to get error code and last processed TD for proper
- * toggle bit value - anything can be invalid */
- grub_dprintf("ohci", "Unrecoverable error!");
- /* Do OHCI reset in case of unrecoverable error - maybe we will need
- * do more - re-enumerate bus etc. (?) */
- /* Suspend the OHCI by issuing a reset. */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic. */
- /* Read back of register should ensure it is really written */
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
- grub_millisleep (1);
- grub_dprintf ("ohci", "Unrecoverable error - OHCI reset\n");
- /* Misc. resets. */
- o->hcca->donehead = 0;
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
- /* Read back of register should ensure it is really written */
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
- /* Enable the OHCI. */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
- (2 << 6)
- | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
- | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
- finish_transfer (dev, transfer);
- return GRUB_USB_ERR_UNRECOVERABLE;
- }
- static grub_usb_err_t
- grub_ohci_check_transfer (grub_usb_controller_t dev,
- grub_usb_transfer_t transfer,
- grub_size_t *actual)
- {
- struct grub_ohci *o = dev->data;
- struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
- grub_uint32_t intstatus;
- /* Check transfer status */
- intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
- if ((intstatus & 0x10) != 0)
- /* Unrecoverable error - only reset can help...! */
- return parse_unrec (dev, transfer, actual);
- /* Detected a HALT. */
- if ((grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1))
- return parse_halt (dev, transfer, actual);
- /* Finished ED detection */
- if ( (grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xfU) ==
- (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xfU) ) /* Empty ED */
- {
- /* Check the HALT bit */
- /* It looks like nonsense - it was tested previously...
- * but it can change because OHCI is working
- * simultaneously via DMA... */
- if (grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1)
- return parse_halt (dev, transfer, actual);
- else
- return parse_success (dev, transfer, actual);
- }
- return GRUB_USB_ERR_WAIT;
- }
- static grub_usb_err_t
- grub_ohci_cancel_transfer (grub_usb_controller_t dev,
- grub_usb_transfer_t transfer)
- {
- struct grub_ohci *o = dev->data;
- struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
- grub_ohci_td_t tderr_virt = NULL;
- pre_finish_transfer (dev, transfer);
- grub_dprintf("ohci", "Timeout !\n");
- /* We should wait for next SOF to be sure that ED is unaccessed
- * by OHCI */
- /* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
- /* Wait for new SOF */
- while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) == 0);
- /* Possible retired TD with error should be previous TD to ED->td_head */
- cdata->tderr_phys
- = grub_ohci_td_phys2virt (o, grub_le_to_cpu32 (cdata->ed_virt->td_head)
- & ~0xf)->prev_td_phys;
- tderr_virt = grub_ohci_td_phys2virt (o,cdata-> tderr_phys);
- grub_dprintf ("ohci", "Cancel: tderr_phys=0x%x, tderr_virt=%p\n",
- cdata->tderr_phys, tderr_virt);
- if (tderr_virt)
- transfer->last_trans = tderr_virt->tr_index;
- else
- transfer->last_trans = -1;
- finish_transfer (dev, transfer);
- return GRUB_USB_ERR_NONE;
- }
- static grub_usb_err_t
- grub_ohci_portstatus (grub_usb_controller_t dev,
- unsigned int port, unsigned int enable)
- {
- struct grub_ohci *o = (struct grub_ohci *) dev->data;
- grub_uint64_t endtime;
- int i;
- grub_dprintf ("ohci", "begin of portstatus=0x%02x\n",
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
- if (!enable) /* We don't need reset port */
- {
- /* Disable the port and wait for it. */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
- GRUB_OHCI_CLEAR_PORT_ENABLE);
- endtime = grub_get_time_ms () + 1000;
- while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
- & (1 << 1)))
- if (grub_get_time_ms () > endtime)
- return GRUB_USB_ERR_TIMEOUT;
- grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
- return GRUB_USB_ERR_NONE;
- }
- /* OHCI does one reset signal 10ms long but USB spec.
- * requests 50ms for root hub (no need to be continuous).
- * So, we do reset 5 times... */
- for (i = 0; i < 5; i++)
- {
- /* Reset the port - timing of reset is done by OHCI */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
- GRUB_OHCI_SET_PORT_RESET);
- /* Wait for reset completion */
- endtime = grub_get_time_ms () + 1000;
- while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
- & GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE))
- if (grub_get_time_ms () > endtime)
- return GRUB_USB_ERR_TIMEOUT;
- /* End the reset signaling - reset the reset status change */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
- GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE);
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
- }
- /* Enable port */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
- GRUB_OHCI_SET_PORT_ENABLE);
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
- /* Wait for signal enabled */
- endtime = grub_get_time_ms () + 1000;
- while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
- & (1 << 1)))
- if (grub_get_time_ms () > endtime)
- return GRUB_USB_ERR_TIMEOUT;
- /* Reset bit Connect Status Change */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
- GRUB_OHCI_RESET_CONNECT_CHANGE);
- /* "Reset recovery time" (USB spec.) */
- grub_millisleep (10);
- grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
- return GRUB_USB_ERR_NONE;
- }
- static grub_usb_speed_t
- grub_ohci_detect_dev (grub_usb_controller_t dev, int port, int *changed)
- {
- struct grub_ohci *o = (struct grub_ohci *) dev->data;
- grub_uint32_t status;
- status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
- grub_dprintf ("ohci", "detect_dev status=0x%02x\n", status);
- /* Connect Status Change bit - it detects change of connection */
- if (status & GRUB_OHCI_RESET_CONNECT_CHANGE)
- {
- *changed = 1;
- /* Reset bit Connect Status Change */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
- GRUB_OHCI_RESET_CONNECT_CHANGE);
- }
- else
- *changed = 0;
- if (! (status & 1))
- return GRUB_USB_SPEED_NONE;
- else if (status & (1 << 9))
- return GRUB_USB_SPEED_LOW;
- else
- return GRUB_USB_SPEED_FULL;
- }
- static int
- grub_ohci_hubports (grub_usb_controller_t dev)
- {
- struct grub_ohci *o = (struct grub_ohci *) dev->data;
- grub_uint32_t portinfo;
- portinfo = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA);
- grub_dprintf ("ohci", "root hub ports=%d\n", portinfo & 0xFF);
- return portinfo & 0xFF;
- }
- static grub_err_t
- grub_ohci_fini_hw (int noreturn __attribute__ ((unused)))
- {
- struct grub_ohci *o;
- for (o = ohci; o; o = o->next)
- {
- int i, nports = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA) & 0xff;
- grub_uint64_t maxtime;
- /* Set skip in all EDs */
- if (o->ed_bulk)
- for (i=0; i < GRUB_OHCI_BULK_EDS; i++)
- o->ed_bulk[i].target |= grub_cpu_to_le32_compile_time (1 << 14); /* skip */
- if (o->ed_ctrl)
- for (i=0; i < GRUB_OHCI_CTRL_EDS; i++)
- o->ed_ctrl[i].target |= grub_cpu_to_le32_compile_time (1 << 14); /* skip */
- /* We should wait for next SOF to be sure that all EDs are
- * unaccessed by OHCI. But OHCI can be non-functional, so
- * more than 1ms timeout have to be applied. */
- /* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
- maxtime = grub_get_time_ms () + 2;
- /* Wait for new SOF or timeout */
- while ( ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4)
- == 0) || (grub_get_time_ms () >= maxtime) );
- for (i = 0; i < nports; i++)
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + i,
- GRUB_OHCI_CLEAR_PORT_ENABLE);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1);
- grub_millisleep (1);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_DONEHEAD, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, 0);
- /* Read back of register should ensure it is really written */
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
- #if 0 /* Is this necessary before booting? Probably not .(?)
- * But it must be done if module is removed ! (Or not ?)
- * How to do it ? - Probably grub_ohci_restore_hw should be more
- * complicated. (?)
- * (If we do it, we need to reallocate EDs and TDs in function
- * grub_ohci_restore_hw ! */
- /* Free allocated EDs and TDs */
- grub_dma_free (o->td_chunk);
- grub_dma_free (o->ed_bulk_chunk);
- grub_dma_free (o->ed_ctrl_chunk);
- grub_dma_free (o->hcca_chunk);
- #endif
- }
- grub_millisleep (10);
- return GRUB_ERR_NONE;
- }
- static grub_err_t
- grub_ohci_restore_hw (void)
- {
- struct grub_ohci *o;
- for (o = ohci; o; o = o->next)
- {
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, o->hcca_addr);
- o->hcca->donehead = 0;
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
- /* Read back of register should ensure it is really written */
- grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
- /* Enable the OHCI. */
- grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
- (2 << 6)
- | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
- | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
- }
- return GRUB_ERR_NONE;
- }
- static struct grub_usb_controller_dev usb_controller =
- {
- .name = "ohci",
- .iterate = grub_ohci_iterate,
- .setup_transfer = grub_ohci_setup_transfer,
- .check_transfer = grub_ohci_check_transfer,
- .cancel_transfer = grub_ohci_cancel_transfer,
- .hubports = grub_ohci_hubports,
- .portstatus = grub_ohci_portstatus,
- .detect_dev = grub_ohci_detect_dev,
- /* estimated max. count of TDs for one bulk transfer */
- .max_bulk_tds = GRUB_OHCI_TDS * 3 / 4
- };
- static struct grub_preboot *fini_hnd;
- GRUB_MOD_INIT(ohci)
- {
- COMPILE_TIME_ASSERT (sizeof (struct grub_ohci_td) == 32);
- COMPILE_TIME_ASSERT (sizeof (struct grub_ohci_ed) == 16);
- grub_stop_disk_firmware ();
- grub_ohci_inithw ();
- grub_usb_controller_dev_register (&usb_controller);
- fini_hnd = grub_loader_register_preboot_hook (grub_ohci_fini_hw,
- grub_ohci_restore_hw,
- GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
- }
- GRUB_MOD_FINI(ohci)
- {
- grub_ohci_fini_hw (0);
- grub_loader_unregister_preboot_hook (fini_hnd);
- grub_usb_controller_dev_unregister (&usb_controller);
- }
|