From 37c68bb8434c3b4f886f9de709667f1156551623 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 19 Aug 2012 10:26:41 +0000 Subject: [PATCH] Split off OHCI support into a separate device. Numerous bug fixes, most of which I can't remember after a break in development. Perhaps the most significant is that the interrupt tree is now built properly (the tree's structure was completely wrong before), increasing compatibility with devices that use interrupt endpoints. A lot of tidying and deduplication of code was also done. git-svn-id: https://svn.aros.org/svn/aros/trunk/AROS@45600 fb15a70f-31f2-0310-bbcc-cdcc74a49acc --- rom/usb/pciusbhc/ohci/buffer.c | 40 + rom/usb/pciusbhc/ohci/buffer_protos.h | 27 + rom/usb/pciusbhc/ohci/chip.c | 1721 ++++++++++++++++++++++++++++++++ rom/usb/pciusbhc/ohci/chip.h | 334 +++++++ rom/usb/pciusbhc/ohci/chip_protos.h | 18 + rom/usb/pciusbhc/ohci/cmd.c | 815 +++++++++++++++ rom/usb/pciusbhc/ohci/cmd_protos.h | 50 + rom/usb/pciusbhc/ohci/debug.c | 49 + rom/usb/pciusbhc/ohci/debug.h | 30 + rom/usb/pciusbhc/ohci/dev.c | 266 +++++ rom/usb/pciusbhc/ohci/dev.h | 184 ++++ rom/usb/pciusbhc/ohci/mmakefile.src | 21 + rom/usb/pciusbhc/ohci/ohci.conf | 16 + rom/usb/pciusbhc/ohci/pci.c | 451 +++++++++ rom/usb/pciusbhc/ohci/pci.h | 56 ++ rom/usb/pciusbhc/ohci/pci_protos.h | 18 + rom/usb/pciusbhc/ohci/roothub.c | 694 +++++++++++++ rom/usb/pciusbhc/ohci/roothub_protos.h | 18 + 18 files changed, 4808 insertions(+) create mode 100644 rom/usb/pciusbhc/ohci/buffer.c create mode 100644 rom/usb/pciusbhc/ohci/buffer_protos.h create mode 100644 rom/usb/pciusbhc/ohci/chip.c create mode 100644 rom/usb/pciusbhc/ohci/chip.h create mode 100644 rom/usb/pciusbhc/ohci/chip_protos.h create mode 100644 rom/usb/pciusbhc/ohci/cmd.c create mode 100644 rom/usb/pciusbhc/ohci/cmd_protos.h create mode 100644 rom/usb/pciusbhc/ohci/debug.c create mode 100644 rom/usb/pciusbhc/ohci/debug.h create mode 100644 rom/usb/pciusbhc/ohci/dev.c create mode 100644 rom/usb/pciusbhc/ohci/dev.h create mode 100644 rom/usb/pciusbhc/ohci/mmakefile.src create mode 100644 rom/usb/pciusbhc/ohci/ohci.conf create mode 100644 rom/usb/pciusbhc/ohci/pci.c create mode 100644 rom/usb/pciusbhc/ohci/pci.h create mode 100644 rom/usb/pciusbhc/ohci/pci_protos.h create mode 100644 rom/usb/pciusbhc/ohci/roothub.c create mode 100644 rom/usb/pciusbhc/ohci/roothub_protos.h diff --git a/rom/usb/pciusbhc/ohci/buffer.c b/rom/usb/pciusbhc/ohci/buffer.c new file mode 100644 index 0000000000..e0c698388e --- /dev/null +++ b/rom/usb/pciusbhc/ohci/buffer.c @@ -0,0 +1,40 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ + */ + +#include +#include + +#include + +#if __WORDSIZE == 64 + +APTR usbGetBuffer(APTR data, ULONG len, UWORD dir) +{ + APTR ret = data; + + if (((IPTR) data + len - 1) >> 32) + { + ret = AllocVec(len, MEMF_31BIT | MEMF_PUBLIC); + + if (ret && (dir == UHDIR_OUT)) + CopyMem(data, ret, len); + } + + return ret; +} + +void usbReleaseBuffer(APTR buffer, APTR data, ULONG len, UWORD dir) +{ + if (buffer && (buffer != data)) + { + if (len && (dir == UHDIR_IN)) + CopyMem(buffer, data, len); + + FreeVec(buffer); + } +} + +#endif diff --git a/rom/usb/pciusbhc/ohci/buffer_protos.h b/rom/usb/pciusbhc/ohci/buffer_protos.h new file mode 100644 index 0000000000..a6a1ea02ae --- /dev/null +++ b/rom/usb/pciusbhc/ohci/buffer_protos.h @@ -0,0 +1,27 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#ifndef BUFFER_PROTOS_H +#define BUFFER_PROTOS_H + +#include + +#if (__WORDSIZE == 64) + +APTR usbGetBuffer(APTR data, ULONG len, UWORD dir); +void usbReleaseBuffer(APTR buffer, APTR data, ULONG len, UWORD dir); + +#else + +/* On 32-bit systems we don't need mirroring */ + +#define usbGetBuffer(data, len, dir) ({ (void)(len); (void)(dir); (data);}) +#define usbReleaseBuffer(buffer, data, len, dir) \ + do { (void)(buffer); (void)(data); (void)(len); (void)(dir); } while (0) + +#endif + +#endif /* BUFFER_PROTOS_H */ diff --git a/rom/usb/pciusbhc/ohci/chip.c b/rom/usb/pciusbhc/ohci/chip.c new file mode 100644 index 0000000000..6498121aa7 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/chip.c @@ -0,0 +1,1721 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ + */ + +#include +#include + +#include +#include +#include + +#include "debug.h" +#include "chip.h" +#include "pci.h" + +#include "chip_protos.h" +#include "roothub_protos.h" +#include "buffer_protos.h" +#include "cmd_protos.h" +#include "pci_protos.h" + +#undef HiddPCIDeviceAttrBase +#define HiddPCIDeviceAttrBase (hd->hd_HiddPCIDeviceAB) +#undef HiddAttrBase +#define HiddAttrBase (hd->hd_HiddAB) + +ULONG start_masks[] = {OCSF_CTRLENABLE, OCSF_BULKENABLE, 0UL, 0UL}; +ULONG current_ed_regs[] = {OHCI_CTRL_ED, OHCI_BULK_ED, 0UL, 0UL}; + +static ULONG ScheduleED(struct PCIController *hc, UWORD xfer_type, + struct IOUsbHWReq *ioreq); +static ULONG FillED(struct PCIController *hc, struct EDNode *ed, + UWORD xfer_type, struct IOUsbHWReq *ioreq, UWORD dir); + +/* /// "AddHeadPhy()" */ +static void AddHeadED(ULONG * list, struct EDNode *ed) +{ + ed->ed_ED.NextED = *list; + *list = ed->ed_Self; + CacheClearE(&ed->ed_ED.EPCaps, 16, CACRF_ClearD); + CacheClearE(list, 4, CACRF_ClearD); +} +/* \\\ */ + +/* /// "AllocED()" */ +static struct EDNode *AllocED(struct PCIController *hc) +{ + struct EDNode *ed = + (struct EDNode *)RemHead((struct List *)&hc->hc_FreeEDList); + + if (ed != NULL) + { + ed->ed_ED.HeadPtr = 0UL; + ed->ed_ED.TailPtr = hc->hc_TermTD->td_Self; + } + if (ed == NULL) + KPRINTF(20, ("Out of EDs!\n")); + + return ed; +} +/* \\\ */ + +/* /// "FreeED()" */ +static void FreeED(struct PCIController *hc, struct EDNode *ed) +{ + CONSTWRITEMEM32_LE(&ed->ed_ED.EPCaps, OECF_SKIP); + SYNC; + + ed->ed_IOReq = NULL; + ed->ed_Buffer = NULL; + ed->ed_SetupData = NULL; + AddTail((struct List *)&hc->hc_FreeEDList, (struct Node *)ed); + ed->ed_ED.HeadPtr = ed->ed_ED.TailPtr = 0UL; +} +/* \\\ */ + +/* /// "AllocTD()" */ +static struct TDNode *AllocTD(struct PCIController *hc) +{ + struct TDNode *td = + (struct TDNode *)RemHead((struct List *)&hc->hc_FreeTDList); + + if (td == NULL) + KPRINTF(20, ("Out of TDs!\n")); + + return td; +} +/* \\\ */ + +/* /// "FreeTD()" */ +static void FreeTD(struct PCIController *hc, struct TDNode *td) +{ + td->td_TD.NextTD = 0UL; + SYNC; + + td->td_ED = NULL; + AddTail((struct List *)&hc->hc_FreeTDList, (struct Node *)td); +} +/* \\\ */ + +/* /// "DisableED()" */ +/* note: does not work on EDs in the interrupt tree */ +static void DisableED(struct EDNode *ed) +{ + ULONG ctrlstatus, succ_ed_phy, dma_size; + struct EDNode *pred_ed, *succ_ed; + + // disable ED + ctrlstatus = READMEM32_LE(&ed->ed_ED.EPCaps); + ctrlstatus |= OECF_SKIP; + WRITEMEM32_LE(&ed->ed_ED.EPCaps, ctrlstatus); + + // unlink from schedule + succ_ed = (struct EDNode *)ed->ed_Node.mln_Succ; + pred_ed = (struct EDNode *)ed->ed_Node.mln_Pred; + if (succ_ed->ed_Node.mln_Succ != NULL) + succ_ed_phy = succ_ed->ed_Self; + else + succ_ed_phy = 0L; + if (pred_ed->ed_Node.mln_Pred != NULL) + pred_ed->ed_ED.NextED = succ_ed_phy; + + Remove((struct Node *)ed); + ed->ed_IOReq = NULL; + dma_size = sizeof(struct EndpointDescriptor); + CachePreDMA(&ed->ed_ED, &dma_size, 0); + SYNC; +} +/* \\\ */ + +/* /// "DisableInt()" */ +static void DisableInt(struct PCIController *hc, ULONG mask) +{ + WRITEREG32_LE(hc->hc_RegBase, OHCI_INTDIS, mask); + hc->hc_PCIIntEnMask &= ~mask; +} +/* \\\ */ + +/* /// "EnableInt()" */ +static void EnableInt(struct PCIController *hc, ULONG mask) +{ + WRITEREG32_LE(hc->hc_RegBase, OHCI_INTSTATUS, mask); + hc->hc_PCIIntEnMask |= mask; + WRITEREG32_LE(hc->hc_RegBase, OHCI_INTEN, mask); +} +/* \\\ */ + +#ifdef DEBUG_TD + +/* /// "PrintTD()" */ +static void PrintTD(const char *txt, ULONG ptd, struct PCIController *hc) +{ + KPrintF("HC 0x%p %s TD list:", hc, txt); + + while (ptd) + { + struct TDNode *td = + (struct TDNode *)((IPTR) ptd - hc->hc_PCIVirtualAdjust - + offsetof(struct TDNode, td_TD.Ctrl)); + + KPrintF(" 0x%p", td); + ptd = READMEM32_LE(&td->td_TD.NextTD); + } + RawPutChar('\n'); +} +/* \\\ */ + +#else +#define PrintTD(txt, ptd, hc) +#endif + +#ifdef DEBUG_ED + +/* /// "PrintED()" */ +static void PrintED(const char *txt, struct EDNode *ed, + struct PCIController *hc) +{ + struct TDNode *td; + + KPrintF + ("%s ED 0x%p: EPCaps=%08lx, HeadPtr=%08lx, TailPtr=%08lx," + " NextED=%08lx\n", + txt, ed, READMEM32_LE(&ed->ed_ED.EPCaps), + READMEM32_LE(&ed->ed_ED.HeadPtr), READMEM32_LE(&ed->ed_ED.TailPtr), + READMEM32_LE(&ed->ed_ED.NextED)); + + KPrintF("...TD list:\n"); + for (td = (struct TDNode *)ed->ed_TDList.mlh_Head; td->td_Node.mln_Succ; + td = (struct TDNode *)td->td_Node.mln_Succ) + KPrintF + ("TD 0x%p: td_TD.Ctrl=%lx BufferPtr=%lx NextTD=%lx" + " BufferEnd=%lx\n", + td, td->td_TD.Ctrl, td->td_TD.BufferPtr, td->td_TD.NextTD, + td->td_TD.BufferEnd); +} +/* \\\ */ + +#else +#define PrintED(txt, ed, hc) +#endif + +/* /// "ResetHandler()" */ +static AROS_UFIH1(ResetHandler, struct PCIController *, hc) +{ + AROS_USERFUNC_INIT + // reset controller + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS, OCSF_HCRESET); + + return FALSE; + +AROS_USERFUNC_EXIT} +/* \\\ */ + +/* /// "AddTailTD()" */ +static void AddTailTD(struct EDNode *ed, struct TDNode *td) +{ + struct TDNode *old_tail_td = NULL; + ULONG dma_size, td_phy; + + if ((ed->ed_ED.HeadPtr & OHCI_PTRMASK) != 0UL) + old_tail_td = (struct TDNode *)ed->ed_TDList.mlh_TailPred; + + td->td_TD.NextTD = ed->ed_ED.TailPtr; + td->td_ED = ed; + + dma_size = sizeof(struct TransferDescriptor); + td_phy = (ULONG) (IPTR) CachePreDMA(&td->td_TD, &dma_size, 0); + + if (old_tail_td != NULL) + { + old_tail_td->td_TD.NextTD = td_phy; + dma_size = sizeof(struct TransferDescriptor); + CachePreDMA(&old_tail_td->td_TD, &dma_size, 0); + } + else + { + ed->ed_ED.HeadPtr |= td->td_Self; + dma_size = sizeof(struct EndpointDescriptor); + CachePreDMA(&ed->ed_ED, &dma_size, 0); + } +} +/* \\\ */ + +/* /// "FreeTDChain()" */ +static void FreeTDChain(struct PCIController *hc, struct MinList *tdlist) +{ + struct TDNode *td; + + while ((td = (struct TDNode *)RemHead((struct List *)tdlist)) != NULL) + { + KPRINTF(1, ("FreeTD %p\n", td)); + FreeTD(hc, td); + } +} +/* \\\ */ + +/* /// "FreeEDContext()" */ +static void FreeEDContext(struct PCIController *hc, struct EDNode *ed, + struct IOUsbHWReq *ioreq) +{ + UWORD dir; + + KPRINTF(5, ("Freeing EDContext 0x%p IOReq 0x%p\n", ed, ioreq)); + + if (ioreq->iouh_Req.io_Command == UHCMD_CONTROLXFER) + dir = + (ioreq-> + iouh_SetupData.bmRequestType & URTF_IN) ? UHDIR_IN : UHDIR_OUT; + else + dir = ioreq->iouh_Dir; + + usbReleaseBuffer(ed->ed_Buffer, ioreq->iouh_Data, ioreq->iouh_Actual, + dir); + usbReleaseBuffer(ed->ed_SetupData, &ioreq->iouh_SetupData, 8, UHDIR_IN); + + Disable(); + FreeTDChain(hc, &ed->ed_TDList); + FreeED(hc, ed); + Enable(); +} +/* \\\ */ + +/* /// "UpdateIntTree()" */ +static void UpdateIntTree(struct PCIController *hc) +{ + struct EDNode *ed; + UWORD i, j, k, l; + ULONG *queue_heads = hc->hc_HCCA->ha_IntEDs; + + // initialise every queue head to point at the terminal ED by default + for (i = 0; i < 32; i++) + { + queue_heads[i] = hc->hc_TermED->ed_Self; + } + + // put each ED in the right number of queues for its interval level. + // we balance the tree by incrementing the slot we start at for each ED + for (i = 0; i < INT_LIST_COUNT; i++) + { + ed = (struct EDNode *)hc->hc_EDLists[INT_XFER + i].mlh_Head; + for (j = 0; ed->ed_Node.mln_Succ != NULL; j++) + { + for (k = 0, l = j; k < 1 << (INT_LIST_COUNT - i - 1); k++) + { + AddHeadED(&queue_heads[l % 32], ed); + l += 1 << i; + } + ed = (struct EDNode *)ed->ed_Node.mln_Succ; + } + } +} +/* \\\ */ + +/* /// "HandleFinishedTDs()" */ +static void HandleFinishedTDs(struct PCIController *hc) +{ + struct IOUsbHWReq *ioreq; + struct IOUsbHWReq *nextioreq; + struct EDNode *ed = NULL; + struct TDNode *td, *nexttd; + ULONG len; + ULONG ctrlstatus; + ULONG epcaps; + UWORD target; + BOOL direction_in; + BOOL updatetree = FALSE; + ULONG donehead, ptr; + BOOL retire; + ULONG oldenables; + struct PCIUnit *unit = hc->hc_Unit; + ULONG dma_size; + + KPRINTF(100, ("Checking for work done...\n")); + Disable(); + donehead = hc->hc_DoneQueue; + hc->hc_DoneQueue = 0UL; + Enable(); + if (!donehead) + { + KPRINTF(1, ("Nothing to do!\n")); + return; + } + td = (struct TDNode *)((IPTR) donehead - hc->hc_PCIVirtualAdjust - + offsetof(struct TDNode, td_TD.Ctrl)); + KPRINTF(100, ("DoneHead=%08lx, TD=%p, Frame=%ld\n", donehead, td, + READREG32_LE(hc->hc_RegBase, OHCI_FRAMECOUNT))); + PrintTD("Done", donehead, hc); + + do + { + dma_size = sizeof(struct TransferDescriptor); + CachePostDMA(&td->td_TD, &dma_size, 0); + ed = td->td_ED; + if (!ed) + { + KPRINTF(1000, + ("Came across a rogue TD 0x%p that already has been freed!\n", + td)); + ptr = READMEM32_LE(&td->td_TD.NextTD) & OHCI_PTRMASK; + if (!ptr) + { + break; + } + td = (struct TDNode *)((IPTR) ptr - hc->hc_PCIVirtualAdjust - + offsetof(struct TDNode, td_TD)); + continue; + } + dma_size = sizeof(struct EndpointDescriptor); + CachePostDMA(&ed->ed_ED, &dma_size, 0); + + ctrlstatus = READMEM32_LE(&td->td_TD.Ctrl); + KPRINTF(100, ("TD: %08lx - %08lx\n", + READMEM32_LE(&td->td_TD.BufferPtr), + READMEM32_LE(&td->td_TD.BufferEnd))); + if (td->td_TD.BufferPtr) + { + // FIXME: this will blow up if physical memory is ever going to + // be discontinuous + len = + READMEM32_LE(&td->td_TD.BufferPtr) - + (READMEM32_LE(&td->td_TD.BufferEnd) + 1 - td->td_Length); + } + else + { + len = td->td_Length; + } + + ioreq = ed->ed_IOReq; + + KPRINTF(100, + ("Examining TD %p for ED %p (IOReq=%p), Status %08lx, len=%ld\n", + td, ed, ioreq, ctrlstatus, len)); + if (!ioreq) + { + /* You should never see this (very weird inconsistency), but who + * knows... */ + KPRINTF(1000, + ("Came across a rogue ED 0x%p that already has been replied! " + "TD 0x%p,\n", + ed, td)); + ptr = READMEM32_LE(&td->td_TD.NextTD) & OHCI_PTRMASK; + if (!ptr) + { + break; + } + td = (struct TDNode *)((IPTR) ptr - hc->hc_PCIVirtualAdjust - + offsetof(struct TDNode, td_TD.Ctrl)); + continue; + } + + if (len) + { + epcaps = READMEM32_LE(&ed->ed_ED.EPCaps); + direction_in = ((epcaps & OECM_DIRECTION) == OECF_DIRECTION_TD) + ? (ioreq->iouh_SetupData.bmRequestType & URTF_IN) + : (epcaps & OECF_DIRECTION_IN); + // FIXME: CachePostDMA() should be passed a virtual pointer + CachePostDMA((APTR) (IPTR) READMEM32_LE(&td->td_TD.BufferEnd) - + len + 1, &len, direction_in ? 0 : DMA_ReadFromRAM); + } + + ioreq->iouh_Actual += len; + retire = TRUE; + switch ((ctrlstatus & OTCM_COMPLETIONCODE)) + { + case OTCF_CC_CRCERROR: + case OTCF_CC_BABBLE: + case OTCF_CC_PIDCORRUPT: + case OTCF_CC_WRONGPID: + ioreq->iouh_Req.io_Error = UHIOERR_CRCERROR; + break; + case OTCF_CC_STALL: + ioreq->iouh_Req.io_Error = UHIOERR_STALL; + break; + case OTCF_CC_TIMEOUT: + ioreq->iouh_Req.io_Error = UHIOERR_TIMEOUT; + break; + case OTCF_CC_OVERFLOW: + ioreq->iouh_Req.io_Error = UHIOERR_OVERFLOW; + break; + case OTCF_CC_SHORTPKT: + if ((!ioreq->iouh_Req.io_Error) + && (!(ioreq->iouh_Flags & UHFF_ALLOWRUNTPKTS))) + { + ioreq->iouh_Req.io_Error = UHIOERR_RUNTPACKET; + } + break; + case OTCF_CC_OVERRUN: + case OTCF_CC_UNDERRUN: + ioreq->iouh_Req.io_Error = UHIOERR_HOSTERROR; + break; + case OTCF_CC_NOERROR: + case OTCF_CC_WRONGTOGGLE: + case OTCF_CC_INVALID: + default: + retire = FALSE; + break; + } + if (retire) + KPRINTF(200, ("Bad completion code: %d\n", + (ctrlstatus & OTCM_COMPLETIONCODE) >> + OTCS_COMPLETIONCODE)); + if ((ctrlstatus & OTCM_DELAYINT) != OTCF_NOINT) + { + KPRINTF(10, ("TD 0x%p Terminator detected\n", td)); + retire = TRUE; + } + if (READMEM32_LE(&ed->ed_ED.HeadPtr) & OEHF_HALTED) + { + KPRINTF(100, ("ED halted!\n")); + retire = TRUE; + } + + if (retire) + { + KPRINTF(50, ("ED 0x%p stopped at TD 0x%p\n", ed, td)); + Remove(&ioreq->iouh_Req.io_Message.mn_Node); + AddHead(&hc->hc_RetireQueue, + &ioreq->iouh_Req.io_Message.mn_Node); + } + + ptr = READMEM32_LE(&td->td_TD.NextTD) & OHCI_PTRMASK; + KPRINTF(1, ("NextTD=0x%08lx\n", ptr)); + if (!ptr) + { + break; + } + td = (struct TDNode *)((IPTR) ptr - hc->hc_PCIVirtualAdjust - + offsetof(struct TDNode, td_TD.Ctrl)); + KPRINTF(1, ("NextTD = %p\n", td)); + } + while (TRUE); + + ioreq = (struct IOUsbHWReq *)hc->hc_RetireQueue.lh_Head; + while ((nextioreq = + (struct IOUsbHWReq *)((struct Node *)ioreq)->ln_Succ)) + { + Remove(&ioreq->iouh_Req.io_Message.mn_Node); + ed = (struct EDNode *)ioreq->iouh_DriverPrivate1; + if (ed) + { + KPRINTF(50, + ("HC 0x%p Retiring IOReq=0x%p Command=%ld ED=0x%p, Frame=%ld\n", + hc, ioreq, ioreq->iouh_Req.io_Command, ed, + READREG32_LE(hc->hc_RegBase, OHCI_FRAMECOUNT))); + + if (ed->ed_Continue) + { + // reinitialise physical links in ED and its TD list + td = (struct TDNode *)ed->ed_TDList.mlh_Head; + ed->ed_ED.HeadPtr = td->td_Self; + while (td->td_Node.mln_Succ != NULL) + { + nexttd = (struct TDNode *)td->td_Node.mln_Succ; + if (nexttd != (struct TDNode *)&ed->ed_TDList.mlh_Tail) + td->td_TD.NextTD = nexttd->td_Self; + else + td->td_TD.NextTD = hc->hc_TermTD->td_Self; + td = nexttd; + } + + // Refill ED with next data block + FillED(hc, ed, BULK_XFER, ioreq, ioreq->iouh_Dir); + PrintED("Continued bulk", ed, hc); + + Disable(); + AddTail(&hc->hc_TDQueue, (struct Node *)ioreq); + oldenables = READREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS); + oldenables |= OCSF_BULKENABLE; + WRITEREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS, oldenables); + SYNC; + Enable(); + } + else + { + // disable ED + if (ioreq->iouh_Req.io_Command == UHCMD_INTXFER) + { + updatetree = TRUE; + Remove((struct Node *)ed); + } + else + DisableED(ed); + PrintED("Completed", ed, hc); + + target = + (ioreq->iouh_DevAddr << 5) + ioreq->iouh_Endpoint + + ((ioreq->iouh_Dir == UHDIR_IN) ? 0x10 : 0); + unit->hu_DevBusyReq[target] = NULL; + unit->hu_DevDataToggle[target] = + (READMEM32_LE(&ed-> + ed_ED.HeadPtr) & OEHF_DATA1) ? TRUE : FALSE; + FreeEDContext(hc, ed, ioreq); + + // check for successful clear feature and set address ctrl + // transfers + if ((!ioreq->iouh_Req.io_Error) + && (ioreq->iouh_Req.io_Command == UHCMD_CONTROLXFER)) + { + CheckSpecialCtrlTransfers(hc, ioreq); + } + ReplyMsg(&ioreq->iouh_Req.io_Message); + } + } + else + { + KPRINTF(20, ("IOReq=%p has no ED!\n", ioreq)); + } + ioreq = nextioreq; + } + if (updatetree) + { + UpdateIntTree(hc); + } +} +/* \\\ */ + +/* /// "HandleAbortedEDs()" */ +static ULONG HandleAbortedEDs(struct PCIController *hc) +{ + struct IOUsbHWReq *ioreq; + ULONG restartmask = 0; + UWORD target; + struct EDNode *ed; + struct PCIUnit *unit = hc->hc_Unit; + + KPRINTF(50, ("Processing abort queue...\n")); + + // We don't need this any more + DisableInt(hc, OISF_SOF); + + /* + * If the aborted IORequest was replied in HandleFinishedTDs(), + * it was already Remove()d from this queue. It's safe to do no checks. + * io_Error was set earlier. + */ + while ((ioreq = (struct IOUsbHWReq *)RemHead(&hc->hc_AbortQueue))) + { + KPRINTF(70, ("HC 0x%p Aborted IOReq 0x%p\n", hc, ioreq)); + PrintED("Aborted", ioreq->iouh_DriverPrivate1, hc); + + ed = ioreq->iouh_DriverPrivate1; + target = + (ioreq->iouh_DevAddr << 5) + ioreq->iouh_Endpoint + + ((ioreq->iouh_Dir == UHDIR_IN) ? 0x10 : 0); + unit->hu_DevBusyReq[target] = NULL; + unit->hu_DevDataToggle[target] = + (READMEM32_LE(&ed->ed_ED.HeadPtr) & OEHF_DATA1) ? TRUE : FALSE; + FreeEDContext(hc, ed, ioreq); + ReplyMsg(&ioreq->iouh_Req.io_Message); + } + + /* Restart stopped queues */ + if (hc->hc_Flags & HCF_STOP_CTRL) + { + KPRINTF(50, ("Restarting control transfers\n")); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CTRL_ED, 0); + restartmask |= OCSF_CTRLENABLE; + } + + if (hc->hc_Flags & HCF_STOP_BULK) + { + KPRINTF(50, ("Restarting bulk transfers\n")); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_BULK_ED, 0); + restartmask |= OCSF_BULKENABLE; + } + + /* Everything is enabled again, aborting done */ + hc->hc_Flags &= ~(HCF_STOP_CTRL | HCF_STOP_BULK | HCF_ABORT); + + /* We will accumulate flags and start queues only once, when everything + * is set up */ + return restartmask; +} +/* \\\ */ + +/* /// "FillED()" */ +static ULONG FillED(struct PCIController *hc, struct EDNode *ed, + UWORD xfer_type, struct IOUsbHWReq *ioreq, UWORD dir) +{ + BOOL success = TRUE, is_new_td; + struct TDNode *td; + ULONG actual; + ULONG ctrl; + ULONG len; + ULONG phyaddr; + ULONG dma_size; + + if (xfer_type == CTRL_XFER) + { + // construct set-up TD + td = AllocTD(hc); + if (td != NULL) + { + // fill setup td + td->td_Length = 0; // don't increase io_Actual for that transfer + CONSTWRITEMEM32_LE(&td->td_TD.Ctrl, + OTCF_CC_INVALID | OTCF_TOGGLEFROMTD | OTCF_NOINT | + OTCF_PIDCODE_SETUP | OTCF_ALLOWSHORTPKT); + len = 8; + + ed->ed_SetupData = + usbGetBuffer(&ioreq->iouh_SetupData, len, UHDIR_OUT); + phyaddr = + (ULONG) CachePreDMA(ed->ed_SetupData, &len, + DMA_ReadFromRAM); + WRITEMEM32_LE(&td->td_TD.BufferPtr, phyaddr); + WRITEMEM32_LE(&td->td_TD.BufferEnd, phyaddr + len - 1); + + KPRINTF(1, ("TD send: %08lx - %08lx\n", + READMEM32_LE(&td->td_TD.BufferPtr), + READMEM32_LE(&td->td_TD.BufferEnd))); + + AddTailTD(ed, td); + AddTail((struct List *)&ed->ed_TDList, (struct Node *)td); + } + else + success = FALSE; + } + + if (success) + { + // put data into a series of TDs + actual = ioreq->iouh_Actual; + ctrl = + OTCF_CC_INVALID | OTCF_NOINT | (dir == + UHDIR_IN ? OTCF_PIDCODE_IN : OTCF_PIDCODE_OUT); + if (xfer_type == CTRL_XFER) + ctrl |= OTCF_TOGGLEFROMTD | OTCF_DATA1; + if (xfer_type == CTRL_XFER + || !(ioreq->iouh_Flags & UHFF_NOSHORTPKT)) + ctrl |= OTCF_ALLOWSHORTPKT; + + ed->ed_Buffer = + usbGetBuffer(ioreq->iouh_Data, ioreq->iouh_Length, dir); + if (ed->ed_Buffer == NULL && ioreq->iouh_Data != NULL) + success = FALSE; + if (xfer_type == BULK_XFER) + td = (struct TDNode *)ed->ed_TDList.mlh_Head; + else + td = (struct TDNode *)&ed->ed_TDList.mlh_Tail; + + while (success && actual < ioreq->iouh_Length + && (actual - ioreq->iouh_Actual < OHCI_TD_BULK_LIMIT + || xfer_type != BULK_XFER)) + { + // reuse the next old TD or get a new one + if (td == (struct TDNode *)&ed->ed_TDList.mlh_Tail) + { + td = AllocTD(hc); + if (td == NULL) + success = FALSE; + is_new_td = TRUE; + } + else + is_new_td = FALSE; + + if (success) + { + len = ioreq->iouh_Length - actual; + if (len > OHCI_PAGE_SIZE) + { + len = OHCI_PAGE_SIZE; + } + td->td_Length = len; + KPRINTF(1, ("TD with %ld bytes. Status=%lx\n", len, ctrl)); + WRITEMEM32_LE(&td->td_TD.Ctrl, ctrl); + phyaddr = + (ULONG) (IPTR) CachePreDMA(ed->ed_Buffer + actual, &len, + dir == UHDIR_IN ? 0 : DMA_ReadFromRAM); + WRITEMEM32_LE(&td->td_TD.BufferPtr, phyaddr); + phyaddr += len - 1; + WRITEMEM32_LE(&td->td_TD.BufferEnd, phyaddr); + + KPRINTF(1, ("TD send: %08lx - %08lx\n", + READMEM32_LE(&td->td_TD.BufferPtr), + READMEM32_LE(&td->td_TD.BufferEnd))); + + actual += len; + + if (is_new_td) + { + AddTailTD(ed, td); + AddTail((struct List *)&ed->ed_TDList, + (struct Node *)td); + } + else + { + dma_size = sizeof(struct TransferDescriptor); + CachePreDMA(&td->td_TD, &dma_size, 0); + } + } + td = (struct TDNode *)td->td_Node.mln_Succ; + } + } + + // construct control-status TD or empty-bulk TD + if (success) + { + if (xfer_type == CTRL_XFER || xfer_type == BULK_XFER + && dir == UHDIR_OUT && actual == ioreq->iouh_Length + && (!(ioreq->iouh_Flags & UHFF_NOSHORTPKT)) + && actual % ioreq->iouh_MaxPktSize == 0) + { + if (td == (struct TDNode *)&ed->ed_TDList.mlh_Tail) + { + td = AllocTD(hc); + if (td == NULL) + success = FALSE; + is_new_td = TRUE; + } + else + is_new_td = FALSE; + + if (success) + { + if (xfer_type == CTRL_XFER) + { + ctrl ^= + OTCF_NOINT | OTCF_PIDCODE_IN | OTCF_PIDCODE_OUT | + OTCF_ALLOWSHORTPKT; + ctrl |= OTCF_TOGGLEFROMTD | OTCF_DATA1; + } + else + ctrl ^= OTCF_NOINT; + + td->td_Length = 0; + CONSTWRITEMEM32_LE(&td->td_TD.Ctrl, ctrl); + CONSTWRITEMEM32_LE(&td->td_TD.BufferPtr, 0); + CONSTWRITEMEM32_LE(&td->td_TD.BufferEnd, 0); + + if (is_new_td) + { + AddTailTD(ed, td); + AddTail((struct List *)&ed->ed_TDList, + (struct Node *)td); + } + else + { + td->td_TD.NextTD = hc->hc_TermTD->td_Self; + dma_size = sizeof(struct TransferDescriptor); + CachePreDMA(&td->td_TD, &dma_size, 0); + } + } + } + else + { + if (xfer_type == BULK_XFER) + ed->ed_Continue = (actual < ioreq->iouh_Length); + td = (struct TDNode *)td->td_Node.mln_Pred; + td->td_TD.NextTD = hc->hc_TermTD->td_Self; + CONSTWRITEMEM32_LE(&td->td_TD.Ctrl, OTCF_CC_INVALID); + dma_size = sizeof(struct TransferDescriptor); + CachePreDMA(&td->td_TD, &dma_size, 0); + } + } + + if (!success) + { + FreeEDContext(hc, ed, ioreq); + } + + return success; +} +/* \\\ */ + +/* /// "ScheduleED()" */ +static ULONG ScheduleED(struct PCIController *hc, UWORD xfer_type, + struct IOUsbHWReq *ioreq) +{ + BOOL success = TRUE; + struct PCIUnit *unit = hc->hc_Unit; + UWORD target; + UWORD dir, list_no, list_index, interval; + struct EDNode *ed; + struct EDNode *pred_ed; + ULONG epcaps, dma_size, phy_addr; + + ed = AllocED(hc); + if (ed == NULL) + success = FALSE; + + if (success) + { + ed->ed_IOReq = ioreq; + + if (xfer_type == CTRL_XFER) + dir = + (ioreq-> + iouh_SetupData.bmRequestType & URTF_IN) ? UHDIR_IN : + UHDIR_OUT; + else + dir = ioreq->iouh_Dir; + + target = (ioreq->iouh_DevAddr << 5) + ioreq->iouh_Endpoint; + if (xfer_type != CTRL_XFER && dir == UHDIR_IN) + target |= 0x10; + + epcaps = + (ioreq-> + iouh_DevAddr << OECS_DEVADDR) | (ioreq->iouh_Endpoint << + OECS_ENDPOINT) | (ioreq->iouh_MaxPktSize << OECS_MAXPKTLEN); + if (xfer_type == CTRL_XFER) + epcaps |= OECF_DIRECTION_TD; + else + epcaps |= + dir == UHDIR_IN ? OECF_DIRECTION_IN : OECF_DIRECTION_OUT; + + if (ioreq->iouh_Flags & UHFF_LOWSPEED) + { + KPRINTF(5, ("*** LOW SPEED ***\n")); + epcaps |= OECF_LOWSPEED; + } + + WRITEMEM32_LE(&ed->ed_ED.EPCaps, epcaps); + + if (xfer_type != CTRL_XFER && unit->hu_DevDataToggle[target]) + WRITEMEM32_LE(&ed->ed_ED.HeadPtr, OEHF_DATA1); + + if (!FillED(hc, ed, xfer_type, ioreq, dir)) + success = FALSE; + } + + if (success) + { + Remove(&ioreq->iouh_Req.io_Message.mn_Node); + ioreq->iouh_DriverPrivate1 = ed; + + // choose logical list to add ED to + list_index = 0; + if (xfer_type == INT_XFER) + { + interval = ioreq->iouh_Interval; + if (interval < 32) + { + while (interval > 1) + { + interval >>= 1; + list_index++; + } + } + else + list_index = INT_LIST_COUNT - 1; + } + list_no = xfer_type + list_index; + + // manage endpoint going busy + Disable(); + unit->hu_DevBusyReq[target] = ioreq; + unit->hu_NakTimeoutFrame[target] = + (ioreq->iouh_Flags & UHFF_NAKTIMEOUT) ? hc->hc_FrameCounter + + ioreq->iouh_NakTimeout : 0; + + AddTail(&hc->hc_TDQueue, (struct Node *)ioreq); + + // looks good to me, now enqueue this entry + AddTail((struct List *)&hc->hc_EDLists[list_no], (struct Node *)ed); + + if (xfer_type == INT_XFER) + { + UpdateIntTree(hc); + } + else + { + ed->ed_ED.NextED = 0L; + dma_size = sizeof(struct EndpointDescriptor); + phy_addr = (ULONG) (IPTR) CachePreDMA(&ed->ed_ED, &dma_size, 0); + + pred_ed = (struct EDNode *)ed->ed_Node.mln_Pred; + if (pred_ed->ed_Node.mln_Pred != NULL) + { + pred_ed->ed_ED.NextED = phy_addr; + dma_size = sizeof(struct EndpointDescriptor); + CachePreDMA(&pred_ed->ed_ED, &dma_size, 0); + } + else + WRITEREG32_LE(hc->hc_RegBase, (xfer_type == CTRL_XFER) ? + OHCI_CTRL_HEAD_ED : OHCI_BULK_HEAD_ED, ed->ed_Self); + } + + SYNC; + + PrintED(xfer_names[xfer_type], ed, hc); + + Enable(); + } + + if (!success) + { + FreeEDContext(hc, ed, ioreq); + } + + return success; +} +/* \\\ */ + +/* /// "ScheduleXfers()" */ +static ULONG ScheduleXfers(struct PCIController *hc, UWORD xfer_type) +{ + BOOL success = TRUE; + struct PCIUnit *unit = hc->hc_Unit; + struct IOUsbHWReq *ioreq; + UWORD target; + UWORD dir; + ULONG oldenables; + ULONG startmask = 0; + + KPRINTF(1, ("Scheduling new %s transfers...\n", xfer_names[xfer_type])); + ioreq = (struct IOUsbHWReq *)hc->hc_XferQueues[xfer_type].lh_Head; + while (success && ((struct Node *)ioreq)->ln_Succ) + { + if (xfer_type == CTRL_XFER) + dir = + (ioreq-> + iouh_SetupData.bmRequestType & URTF_IN) ? UHDIR_IN : + UHDIR_OUT; + else + dir = ioreq->iouh_Dir; + + target = (ioreq->iouh_DevAddr << 5) + ioreq->iouh_Endpoint; + if (xfer_type != CTRL_XFER && dir == UHDIR_IN) + target |= 0x10; + KPRINTF(10, ("New %s transfer to %ld.%ld: %ld bytes\n", + xfer_names[xfer_type], ioreq->iouh_DevAddr, + ioreq->iouh_Endpoint, ioreq->iouh_Length)); + /* is endpoint already in use or do we have to wait for next + * transaction */ + if (unit->hu_DevBusyReq[target]) + { + KPRINTF(5, ("Endpoint %02lx in use!\n", target)); + ioreq = (struct IOUsbHWReq *)((struct Node *)ioreq)->ln_Succ; + continue; + } + + success = ScheduleED(hc, xfer_type, ioreq); + + ioreq = (struct IOUsbHWReq *)hc->hc_XferQueues[xfer_type].lh_Head; + } + + if (success) + { + /* + * If we are going to start the queue but it's not running yet, + * reset current ED pointer to zero. This will cause the HC to + * start over from the head. + */ + startmask = start_masks[xfer_type]; + oldenables = READREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS); + if (!(oldenables & startmask)) + { + CONSTWRITEREG32_LE(hc->hc_RegBase, current_ed_regs[xfer_type], + 0); + } + } + + return startmask; +} +/* \\\ */ + +/* /// "UpdateFrameCounter()" */ +void UpdateFrameCounter(struct PCIController *hc) +{ + + Disable(); + hc->hc_FrameCounter = + (hc->hc_FrameCounter & 0xffff0000) | (READREG32_LE(hc->hc_RegBase, + OHCI_FRAMECOUNT) & 0xffff); + Enable(); +} +/* \\\ */ + +/* /// "CompleteInt()" */ +static AROS_UFIH1(CompleteInt, struct PCIController *, hc) +{ + AROS_USERFUNC_INIT ULONG restartmask = 0; + + KPRINTF(1, ("CompleteInt!\n")); + + UpdateFrameCounter(hc); + + /* **************** PROCESS DONE TRANSFERS **************** */ + + WRITEREG32_LE(&hc->hc_RegBase, OHCI_INTDIS, OISF_DONEHEAD); + if (hc->hc_DoneQueue) + HandleFinishedTDs(hc); + + if (hc->hc_Flags & HCF_ABORT) + restartmask = HandleAbortedEDs(hc); + WRITEREG32_LE(&hc->hc_RegBase, OHCI_INTEN, OISF_DONEHEAD); + + if ((!(hc->hc_Flags & HCF_STOP_CTRL)) + && hc->hc_XferQueues[CTRL_XFER].lh_Head->ln_Succ) + restartmask |= ScheduleXfers(hc, CTRL_XFER); + + if (hc->hc_XferQueues[INT_XFER].lh_Head->ln_Succ) + ScheduleXfers(hc, INT_XFER); + + if ((!(hc->hc_Flags & HCF_STOP_BULK)) + && hc->hc_XferQueues[BULK_XFER].lh_Head->ln_Succ) + restartmask |= ScheduleXfers(hc, BULK_XFER); + + /* + * Restart queues. In restartmask we have accumulated which queues need + * to be started. + * + * We do it here only once, after everything is set up, because + * otherwise HC goes nuts in some cases. For example, the following + * situation caused TD queue loop: we are simultaneously scheduling two + * control EDs and one of them completes with error. If we attempt to + * start the queue right after an ED is scheduled (this is how the code + * originally worked), it looks like the HC manages to deal with the + * first ED right before the second one is scheduled. At this moment the + * first TD is HALTed with HeadPtr pointing to the failed TD, which went + * to the DoneQueue (which will be picked up only on next ISR round, we + * are still in ScheduleCtrlEDs()). The second ED is scheduled (first + * one is not removed yet!) and we re-trigger control queue to start. + * It causes errorneous TD to reappear on the DoneQueue, effectively + * looping it. DoneQueue loop causes HandleFinishedTDs() to never exit. + * Restarting queues here in this manner actually fixed the problem. + */ + if (restartmask) + { + restartmask |= READREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS); + WRITEREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS, restartmask); + SYNC; + } + + KPRINTF(1, ("CompleteDone\n")); + + return 0; + +AROS_USERFUNC_EXIT} +/* \\\ */ + +/* /// "IntCode()" */ +static AROS_UFIH1(IntCode, struct PCIController *, hc) +{ + AROS_USERFUNC_INIT struct PCIUnit *unit = hc->hc_Unit; + ULONG intr = 0; + ULONG donehead; + ULONG dma_size; + + dma_size = sizeof(struct HCCA); + CachePostDMA(hc->hc_HCCA, &dma_size, 0); + + donehead = READMEM32_LE(&hc->hc_HCCA->ha_DoneHead); + + if (donehead) + { + if (donehead & ~1) + intr = OISF_DONEHEAD; + if (donehead & 1) + { + intr |= READREG32_LE(hc->hc_RegBase, OHCI_INTSTATUS); + } + donehead &= OHCI_PTRMASK; + + CONSTWRITEMEM32_LE(&hc->hc_HCCA->ha_DoneHead, 0); + } + else + { + intr = READREG32_LE(hc->hc_RegBase, OHCI_INTSTATUS); + + if (intr & OISF_DONEHEAD) + { + KPRINTF(1, ("DONEHEAD WAS EMPTY!\n")); + donehead = + READMEM32_LE(&hc->hc_HCCA->ha_DoneHead) & OHCI_PTRMASK; + CONSTWRITEMEM32_LE(&hc->hc_HCCA->ha_DoneHead, 0); + + KPRINTF(500, ("New Donehead %08lx for old %08lx\n", donehead, + hc->hc_DoneQueue)); + } + } + dma_size = sizeof(struct HCCA); + CachePreDMA(hc->hc_HCCA, &dma_size, 0); + + intr &= ~OISF_MASTERENABLE; + + if (intr & hc->hc_PCIIntEnMask) + { + KPRINTF(1, ("IntCode(0x%p) interrupts 0x%08lx, mask 0x%08lx\n", hc, + intr, hc->hc_PCIIntEnMask)); + + if (intr & OISF_HOSTERROR) + { + KPRINTF(200, ("Host ERROR!\n")); + } + if (intr & OISF_SCHEDOVERRUN) + { + KPRINTF(200, ("Schedule overrun!\n")); + } + if (!(hc->hc_Flags & HCF_ONLINE)) + { + if (READREG32_LE(hc->hc_RegBase, + OHCI_INTSTATUS) & OISF_HUBCHANGE) + { + // if the driver is not online and the controller has a broken + // hub change interrupt, make sure we don't run into infinite + // interrupt by disabling the interrupt bit + DisableInt(hc, OISF_HUBCHANGE); + } + return FALSE; + } + WRITEREG32_LE(hc->hc_RegBase, OHCI_INTEN, OISF_HUBCHANGE); + if (intr & OISF_FRAMECOUNTOVER) + { + hc->hc_FrameCounter |= 0x7fff; + hc->hc_FrameCounter++; + hc->hc_FrameCounter |= + READMEM16_LE(&hc->hc_HCCA->ha_FrameCount); + KPRINTF(10, ("HCI 0x%p: Frame Counter Rollover %ld\n", hc, + hc->hc_FrameCounter)); + } + if (intr & OISF_HUBCHANGE) + { + UWORD hciport; + ULONG oldval; + UWORD portreg = OHCI_PORTSTATUS; + BOOL clearbits = FALSE; + + if (READREG32_LE(hc->hc_RegBase, + OHCI_INTSTATUS) & OISF_HUBCHANGE) + { + /* Some OHCI implementations will keep the interrupt bit + * stuck until all port changes have been cleared, which is + * wrong according to the OHCI spec. As a workaround we will + * clear all change bits, which should be no problem as the + * port changes are reflected in the PortChangeMap array. + */ + clearbits = TRUE; + } + for (hciport = 0; hciport < hc->hc_NumPorts; + hciport++, portreg += 4) + { + oldval = READREG32_LE(hc->hc_RegBase, portreg); + hc->hc_PortChangeMap[hciport] |= TranslatePortFlags(oldval, + OHPF_OVERCURRENTCHG | OHPF_RESETCHANGE | + OHPF_ENABLECHANGE | OHPF_CONNECTCHANGE | + OHPF_RESUMEDTX); + if (clearbits) + { + WRITEREG32_LE(hc->hc_RegBase, portreg, + OHPF_CONNECTCHANGE | OHPF_ENABLECHANGE | + OHPF_RESUMEDTX | OHPF_OVERCURRENTCHG | + OHPF_RESETCHANGE); + } + + KPRINTF(20, ("PCI Int Port %ld (glob %ld) Change %08lx\n", + hciport, hc->hc_PortNum20[hciport] + 1, oldval)); + if (hc->hc_PortChangeMap[hciport]) + { + unit->hu_RootPortChanges |= + 1UL << (hc->hc_PortNum20[hciport] + 1); + } + } + CheckRootHubChanges(unit); + if (clearbits) + { + // again try to get rid of any bits that may be causing the + // interrupt + WRITEREG32_LE(hc->hc_RegBase, OHCI_HUBSTATUS, + OHSF_OVERCURRENTCHG); + WRITEREG32_LE(hc->hc_RegBase, OHCI_INTSTATUS, + OISF_HUBCHANGE); + } + } + if (intr & OISF_DONEHEAD) + { + KPRINTF(10, ("DoneHead Frame=%ld\n", + READREG32_LE(hc->hc_RegBase, OHCI_FRAMECOUNT))); + + if (hc->hc_DoneQueue) + { + struct TDNode *donetd = + (struct TDNode *)((IPTR) donehead - + hc->hc_PCIVirtualAdjust - offsetof(struct TDNode, + td_TD.Ctrl)); + + CacheClearE(&donetd->td_TD, 16, CACRF_InvalidateD); + while (donetd->td_TD.NextTD) + { + donetd = + (struct TDNode *)((IPTR) donetd->td_TD.NextTD - + hc->hc_PCIVirtualAdjust - offsetof(struct TDNode, + td_TD.Ctrl)); + CacheClearE(&donetd->td_TD, 16, CACRF_InvalidateD); + } + WRITEMEM32_LE(&donetd->td_TD.NextTD, hc->hc_DoneQueue); + CacheClearE(&donetd->td_TD, 16, CACRF_ClearD); + + KPRINTF(10, + ("Attached old DoneHead 0x%08lx to TD 0x%08lx\n", + hc->hc_DoneQueue, donetd->td_Self)); + } + hc->hc_DoneQueue = donehead; + } + if (intr & OISF_SOF) + { + /* Aborted EDs are available for freeing */ + hc->hc_Flags |= HCF_ABORT; + } + + if (intr & (OISF_SOF | OISF_DONEHEAD)) + { + /* + * These two are leveraged down to SoftInt. + * This is done in order to keep queues rotation synchronized. + */ + Cause(&hc->hc_CompleteInt); + } + + KPRINTF(1, ("Exiting IntCode(0x%p)\n", unit)); + } + + WRITEREG32_LE(hc->hc_RegBase, OHCI_INTSTATUS, intr); + + /* Unlock interrupts */ + WRITEREG32_LE(&hc->hc_RegBase, OHCI_INTEN, OISF_MASTERENABLE); + + return FALSE; + +AROS_USERFUNC_EXIT} +/* \\\ */ + +/* /// "AbortRequest()" */ +void AbortRequest(struct PCIController *hc, struct IOUsbHWReq *ioreq) +{ + struct PCIUnit *unit = hc->hc_Unit; + struct EDNode *ed = ioreq->iouh_DriverPrivate1; + UWORD target = + (ioreq->iouh_DevAddr << 5) + ioreq->iouh_Endpoint + + ((ioreq->iouh_Dir == UHDIR_IN) ? 0x10 : 0); + ULONG disablemask = 0; + ULONG ctrlstatus; + + KPRINTF(70, ("HC 0x%p Aborting request 0x%p, command %ld, " + "endpoint 0x%04lx, Frame=%ld\n", + hc, ioreq, ioreq->iouh_Req.io_Command, target, + READREG32_LE(hc->hc_RegBase, OHCI_FRAMECOUNT))); + PrintED("Aborting", ed, hc); + + /* Removing control and bulk EDs requires to stop the appropriate HC + * queue first (according to specification) */ + switch (ioreq->iouh_Req.io_Command) + { + case UHCMD_CONTROLXFER: + KPRINTF(50, ("Stopping control queue\n")); + hc->hc_Flags |= HCF_STOP_CTRL; + disablemask = OCSF_CTRLENABLE; + break; + + case UHCMD_BULKXFER: + KPRINTF(50, ("Stopping bulk queue\n")); + hc->hc_Flags |= HCF_STOP_BULK; + disablemask = OCSF_BULKENABLE; + break; + } + + /* Stop selected queue(s) */ + if (disablemask) + { + ctrlstatus = READREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS); + ctrlstatus &= ~disablemask; + WRITEREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS, ctrlstatus); + SYNC; + } + + // disable ED + DisableED(ed); + + /* + * ...and move to abort queue. + * We can't reply the request right now because some of its TDs + * can be used by the HC right now. This means it does something + * to the data buffer referred to by the request. + * We reply the request only when the HC stops doing this. Otherwise + * we may end up in trashed memory. + */ + Remove(&ioreq->iouh_Req.io_Message.mn_Node); + AddTail(&hc->hc_AbortQueue, &ioreq->iouh_Req.io_Message.mn_Node); + + if (ioreq->iouh_Req.io_Command == UHCMD_INTXFER) + UpdateIntTree(hc); + + unit->hu_DevDataToggle[target] = + (READMEM32_LE(&ed->ed_ED.HeadPtr) & OEHF_DATA1) ? TRUE : FALSE; + + /* + * Request StartOfFrame interrupt. Upon next frame this ED + * is guaranteed to be out of use and can be freed. + */ + EnableInt(hc, OISF_SOF); +} +/* \\\ */ + +/* /// "InitController()" */ +BOOL InitController(struct PCIController *hc, struct PCIUnit *hu) +{ + + struct PCIDevice *hd = hu->hu_Device; + + struct EDNode *ed; + struct TDNode *td; + ULONG *tabptr; + UBYTE *memptr; + ULONG hubdesca; + ULONG cmdstatus; + ULONG control; + ULONG timeout; + ULONG frameival; + UWORD i; + ULONG cnt; + ULONG dma_size; + ULONG phy_addr; + + struct TagItem pciActivateMem[] = { + {aHidd_PCIDevice_isMEM, TRUE}, + {TAG_DONE, 0UL}, + }; + + struct TagItem pciActivateBusmaster[] = { + {aHidd_PCIDevice_isMaster, TRUE}, + {TAG_DONE, 0UL}, + }; + + struct TagItem pciDeactivateBusmaster[] = { + {aHidd_PCIDevice_isMaster, FALSE}, + {TAG_DONE, 0UL}, + }; + + hc->hc_CompleteInt.is_Node.ln_Type = NT_INTERRUPT; + hc->hc_CompleteInt.is_Node.ln_Name = "OHCI CompleteInt"; + hc->hc_CompleteInt.is_Node.ln_Pri = 0; + hc->hc_CompleteInt.is_Data = hc; + hc->hc_CompleteInt.is_Code = (VOID_FUNC) CompleteInt; + + hc->hc_PCIMemSize = OHCI_HCCA_SIZE + OHCI_HCCA_ALIGNMENT + 1; + hc->hc_PCIMemSize += sizeof(struct EDNode) * OHCI_ED_POOLSIZE; + hc->hc_PCIMemSize += sizeof(struct TDNode) * OHCI_TD_POOLSIZE; + + memptr = + HIDD_PCIDriver_AllocPCIMem(hc->hc_PCIDriverObject, + hc->hc_PCIMemSize); + hc->hc_PCIMem = (APTR) memptr; + if (memptr) + { + // PhysicalAddress - VirtualAdjust = VirtualAddress + // VirtualAddress + VirtualAdjust = PhysicalAddress + hc->hc_PCIVirtualAdjust = + pciGetPhysical(hc, memptr) - (APTR) memptr; + KPRINTF(10, ("VirtualAdjust 0x%08lx\n", hc->hc_PCIVirtualAdjust)); + + // align memory + memptr = + (UBYTE *) (((IPTR) hc->hc_PCIMem + + OHCI_HCCA_ALIGNMENT) & (~OHCI_HCCA_ALIGNMENT)); + hc->hc_HCCA = (struct HCCA *)memptr; + KPRINTF(10, ("HCCA 0x%p\n", hc->hc_HCCA)); + memptr += OHCI_HCCA_SIZE; + + // build up ED pool + NewList((struct List *)&hc->hc_FreeEDList); + ed = (struct EDNode *)memptr; + cnt = OHCI_ED_POOLSIZE; + do + { + // minimal initialization + AddTail((struct List *)&hc->hc_FreeEDList, (struct Node *)ed); + NewList((struct List *)&ed->ed_TDList); + WRITEMEM32_LE(&ed->ed_Self, + (IPTR) (&ed->ed_ED.EPCaps) + hc->hc_PCIVirtualAdjust); + ed++; + } + while (--cnt); + memptr += sizeof(struct EDNode) * OHCI_ED_POOLSIZE; + + // build up TD pool + NewList((struct List *)&hc->hc_FreeTDList); + td = (struct TDNode *)memptr; + cnt = OHCI_TD_POOLSIZE - 1; + do + { + AddTail((struct List *)&hc->hc_FreeTDList, (struct Node *)td); + WRITEMEM32_LE(&td->td_Self, + (IPTR) (&td->td_TD.Ctrl) + hc->hc_PCIVirtualAdjust); + td++; + } + while (--cnt); + WRITEMEM32_LE(&td->td_Self, + (IPTR) (&td->td_TD.Ctrl) + hc->hc_PCIVirtualAdjust); + memptr += sizeof(struct TDNode) * OHCI_TD_POOLSIZE; + + // terminating ED + hc->hc_TermED = ed = AllocED(hc); + ed->ed_Node.mln_Succ = NULL; + ed->ed_Node.mln_Pred = NULL; + CONSTWRITEMEM32_LE(&ed->ed_ED.EPCaps, OECF_SKIP); + ed->ed_ED.NextED = 0L; + + // terminating TD + hc->hc_TermTD = td = AllocTD(hc); + td->td_Node.mln_Succ = NULL; + td->td_Node.mln_Pred = NULL; + td->td_TD.NextTD = 0; + + for (cnt = 0; cnt < XFER_COUNT + INT_LIST_COUNT - 1; cnt++) + NewList((struct List *)&hc->hc_EDLists[cnt]); + + UpdateIntTree(hc); + + // fill in framelist with IntED entry points based on interval + tabptr = hc->hc_HCCA->ha_IntEDs; + for (cnt = 0; cnt < 32; cnt++) + { + *tabptr++ = hc->hc_TermED->ed_Self; + } + + // time to initialize hardware... + OOP_GetAttr(hc->hc_PCIDeviceObject, aHidd_PCIDevice_Base0, + (IPTR *) & hc->hc_RegBase); + hc->hc_RegBase = (APTR) (((IPTR) hc->hc_RegBase) & (~0xf)); + KPRINTF(10, ("RegBase = 0x%p\n", hc->hc_RegBase)); + + // enable memory + OOP_SetAttrs(hc->hc_PCIDeviceObject, (struct TagItem *)pciActivateMem); + + hubdesca = READREG32_LE(hc->hc_RegBase, OHCI_HUBDESCA); + hc->hc_NumPorts = (hubdesca & OHAM_NUMPORTS) >> OHAS_NUMPORTS; + KPRINTF(20, ("Found OHCI Controller %p FuncNum = %ld, Rev %02lx, " + "with %ld ports\n", + hc->hc_PCIDeviceObject, hc->hc_FunctionNum, + READREG32_LE(hc->hc_RegBase, OHCI_REVISION) & 0xFF, + hc->hc_NumPorts)); + + KPRINTF(20, ("Powerswitching: %s %s\n", + hubdesca & OHAF_NOPOWERSWITCH ? "Always on" : "Available", + hubdesca & OHAF_INDIVIDUALPS ? "per port" : "global")); + + control = READREG32_LE(hc->hc_RegBase, OHCI_CONTROL); + KPRINTF(10, ("OHCI control state: 0x%08lx\n", control)); + + // disable BIOS legacy support + if (control & OCLF_SMIINT) + { + KPRINTF(10, + ("BIOS still has hands on OHCI, trying to get rid of it\n")); + + cmdstatus = READREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS); + cmdstatus |= OCSF_OWNERCHANGEREQ; + WRITEREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS, cmdstatus); + timeout = 100; + do + { + control = READREG32_LE(hc->hc_RegBase, OHCI_CONTROL); + if (!(control & OCLF_SMIINT)) + { + KPRINTF(10, ("BIOS gave up on OHCI. Pwned!\n")); + break; + } + DelayMS(10, hu); + } + while (--timeout); + if (!timeout) + { + KPRINTF(10, + ("BIOS didn't release OHCI. Forcing and praying...\n")); + control &= ~OCLF_SMIINT; + WRITEREG32_LE(hc->hc_RegBase, OHCI_CONTROL, control); + } + } + + OOP_SetAttrs(hc->hc_PCIDeviceObject, + (struct TagItem *)pciDeactivateBusmaster); // no busmaster yet + + KPRINTF(10, ("Resetting OHCI HC\n")); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS, OCSF_HCRESET); + cnt = 100; + do + { + if (!(READREG32_LE(hc->hc_RegBase, + OHCI_CMDSTATUS) & OCSF_HCRESET)) + { + break; + } + DelayMS(1, hu); + } + while (--cnt); + +#ifdef DEBUG + if (cnt == 0) + { + KPRINTF(20, ("Reset Timeout!\n")); + } + else + { + KPRINTF(20, ("Reset finished after %ld ticks\n", 100 - cnt)); + } +#endif + + OOP_SetAttrs(hc->hc_PCIDeviceObject, + (struct TagItem *)pciActivateBusmaster); // enable busmaster + + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_FRAMECOUNT, 0); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_PERIODICSTART, 10800); + // 10% of 12000 + frameival = READREG32_LE(hc->hc_RegBase, OHCI_FRAMEINTERVAL); + KPRINTF(10, ("FrameInterval=%08lx\n", frameival)); + frameival &= ~OIVM_BITSPERFRAME; + frameival |= OHCI_DEF_BITSPERFRAME << OIVS_BITSPERFRAME; + frameival |= OIVF_TOGGLE; + WRITEREG32_LE(hc->hc_RegBase, OHCI_FRAMEINTERVAL, frameival); + + // make sure nothing is running + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_PERIODIC_ED, 0); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CTRL_HEAD_ED, 0); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CTRL_ED, 0); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_BULK_HEAD_ED, 0); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_BULK_ED, 0); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_DONEHEAD, 0); + + dma_size = sizeof(struct HCCA); + phy_addr = + (ULONG) (IPTR) CachePreDMA(hc->hc_HCCA, &dma_size, + DMA_ReadFromRAM); + WRITEREG32_LE(hc->hc_RegBase, OHCI_HCCA, phy_addr); + + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_INTSTATUS, OISF_ALL_INTS); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_INTDIS, OISF_ALL_INTS); + SYNC; + + // install reset handler + hc->hc_ResetInt.is_Code = (VOID_FUNC) ResetHandler; + hc->hc_ResetInt.is_Data = hc; + AddResetCallback(&hc->hc_ResetInt); + + // add interrupt + hc->hc_PCIIntHandler.is_Node.ln_Name = + hu->hu_Device->hd_Library.lib_Node.ln_Name; + hc->hc_PCIIntHandler.is_Node.ln_Pri = 5; + hc->hc_PCIIntHandler.is_Code = (VOID_FUNC) IntCode; + hc->hc_PCIIntHandler.is_Data = hc; + AddIntServer(INTB_KERNEL + hc->hc_PCIIntLine, + &hc->hc_PCIIntHandler); + + hc->hc_PCIIntEnMask = + OISF_DONEHEAD | OISF_RESUMEDTX | OISF_HOSTERROR | + OISF_FRAMECOUNTOVER | OISF_HUBCHANGE; + + WRITEREG32_LE(hc->hc_RegBase, OHCI_INTEN, + hc->hc_PCIIntEnMask | OISF_MASTERENABLE); + + /* Reset controller twice (needed for some OHCI chips) */ + for (i = 0; i < 2; i++) + { + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CONTROL, + OCLF_PERIODICENABLE | OCLF_CTRLENABLE | OCLF_BULKENABLE | + OCLF_ISOENABLE | OCLF_USBRESET); + SYNC; + KPRINTF(10, ("POST-RESET FrameInterval=%08lx\n", + READREG32_LE(hc->hc_RegBase, OHCI_FRAMEINTERVAL))); + WRITEREG32_LE(hc->hc_RegBase, OHCI_FRAMEINTERVAL, frameival); + } + + // make sure the ports are on with chipset quirk workaround + hubdesca = READREG32_LE(hc->hc_RegBase, OHCI_HUBDESCA); + hubdesca |= OHAF_NOPOWERSWITCH; + hubdesca &= ~OHAF_INDIVIDUALPS; + WRITEREG32_LE(hc->hc_RegBase, OHCI_HUBDESCA, hubdesca); + + WRITEREG32_LE(hc->hc_RegBase, OHCI_HUBSTATUS, OHSF_POWERHUB); + + DelayMS(50, hu); + WRITEREG32_LE(hc->hc_RegBase, OHCI_HUBDESCA, hubdesca); + + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CONTROL, + OCLF_PERIODICENABLE | OCLF_CTRLENABLE | OCLF_BULKENABLE | + OCLF_ISOENABLE | OCLF_USBOPER); + SYNC; + + KPRINTF(20, ("Init returns TRUE...\n")); + return TRUE; + } + + KPRINTF(1000, ("Init returns FALSE...\n")); + return FALSE; +} +/* \\\ */ + +/* /// "FreeController()" */ +void FreeController(struct PCIController *hc, struct PCIUnit *hu) +{ + + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + KPRINTF(20, ("Shutting down OHCI %p\n", hc)); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_INTDIS, OISF_ALL_INTS); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_INTSTATUS, OISF_ALL_INTS); + + // disable all ports + WRITEREG32_LE(hc->hc_RegBase, OHCI_HUBDESCB, 0); + WRITEREG32_LE(hc->hc_RegBase, OHCI_HUBSTATUS, OHSF_UNPOWERHUB); + + DelayMS(50, hu); + KPRINTF(20, ("Stopping OHCI %p\n", hc)); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CONTROL, 0); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS, 0); + SYNC; + + //KPRINTF(20, ("Reset done OHCI %08lx\n", hc)); + DelayMS(10, hu); + KPRINTF(20, ("Resetting OHCI %p\n", hc)); + CONSTWRITEREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS, OCSF_HCRESET); + SYNC; + DelayMS(50, hu); + + KPRINTF(20, ("Shutting down OHCI done.\n")); + + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } +} +/* \\\ */ + +/* /// "TranslatePortFlags()" */ +UWORD TranslatePortFlags(ULONG flags, ULONG mask) +{ + UWORD new_flags = 0; + + flags &= mask; + + if (flags & OHPF_PORTPOWER) + new_flags |= UPSF_PORT_POWER; + if (flags & OHPF_OVERCURRENT) + new_flags |= UPSF_PORT_OVER_CURRENT; + if (flags & OHPF_PORTCONNECTED) + new_flags |= UPSF_PORT_CONNECTION; + if (flags & OHPF_PORTENABLE) + new_flags |= UPSF_PORT_ENABLE; + if (flags & OHPF_LOWSPEED) + new_flags |= UPSF_PORT_LOW_SPEED; + if (flags & OHPF_PORTRESET) + new_flags |= UPSF_PORT_RESET; + if (flags & OHPF_PORTSUSPEND) + new_flags |= UPSF_PORT_SUSPEND; + if (flags & OHPF_OVERCURRENTCHG) + new_flags |= UPSF_PORT_OVER_CURRENT; + if (flags & OHPF_RESETCHANGE) + new_flags |= UPSF_PORT_RESET; + if (flags & OHPF_ENABLECHANGE) + new_flags |= UPSF_PORT_ENABLE; + if (flags & OHPF_CONNECTCHANGE) + new_flags |= UPSF_PORT_CONNECTION; + if (flags & OHPF_RESUMEDTX) + new_flags |= UPSF_PORT_SUSPEND; + + return new_flags; +} +/* \\\ */ diff --git a/rom/usb/pciusbhc/ohci/chip.h b/rom/usb/pciusbhc/ohci/chip.h new file mode 100644 index 0000000000..a1b81632a0 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/chip.h @@ -0,0 +1,334 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#ifndef CHIP_H +#define CHIP_H + +#include + +/* Framelist stuff + + - Framelist contains all the same entries pointing to ISO-TD + - ISO-TD: is inactive by default. Links to Control-QH + - Control-QH: - Head links to Int-Queue + - Element: Links to dummy-TD if empty (inactive) + - Element: otherwise links to QH for control transfer + - Int-Queue : - Head links to Bulk-Queue + + + */ + +/* + * --------------------- OHCI registers ------------------------ + * Warning: These are BYTE offsets! + */ + +/* operational registers */ +#define OHCI_REVISION 0x000 /* Host Controller Revision (r) */ +#define OHCI_CONTROL 0x004 /* Control register (r/w) */ +#define OHCI_CMDSTATUS 0x008 /* Command and Status */ +#define OHCI_INTSTATUS 0x00c /* Interrupt Status (r/wc) */ +#define OHCI_INTEN 0x010 /* Interrupt Enable (r/ws) */ +#define OHCI_INTDIS 0x014 /* Interrupt Disable (r/wc) */ +#define OHCI_HCCA 0x018 /* Pointer to HCCA */ +#define OHCI_PERIODIC_ED 0x01c /* Current periodic ED */ +#define OHCI_CTRL_HEAD_ED 0x020 /* Control Head ED */ +#define OHCI_CTRL_ED 0x024 /* Current control ED */ +#define OHCI_BULK_HEAD_ED 0x028 /* Bulk Head ED */ +#define OHCI_BULK_ED 0x02c /* Current bulk ED */ +#define OHCI_DONEHEAD 0x030 /* Done head pointer */ +#define OHCI_FRAMEINTERVAL 0x034 /* Frame interval */ +#define OHCI_FRAMEREMAINING 0x038 /* Frame remaining time */ +#define OHCI_FRAMECOUNT 0x03c /* Frame number */ +#define OHCI_PERIODICSTART 0x040 /* Periodic start (usually 10% of 12000 == 0x3e67) */ +#define OHCI_LSTHRESHOLD 0x044 /* Lowspeed threshold (usually 0x0628) */ +#define OHCI_HUBDESCA 0x048 /* Root Hub Descriptor A */ +#define OHCI_HUBDESCB 0x04c /* Root Hub Descriptor B */ +#define OHCI_HUBSTATUS 0x050 /* Root Hub Status */ +#define OHCI_PORTSTATUS 0x054 /* Port Status */ + +/* OHCI_CONTROL defines */ +#define OCLS_CBSR 0 /* Control Bulk Service Ratio */ +#define OCLB_PERIODICENABLE 2 /* Periodic Enable */ +#define OCLB_ISOENABLE 3 /* Isochronous Enable */ +#define OCLB_CTRLENABLE 4 /* Control List enable */ +#define OCLB_BULKENABLE 5 /* Bulk List enable */ +#define OCLS_USBSTATE 6 /* Host controller functional state */ +#define OCLB_SMIINT 8 /* SMI Interrupt routing */ +#define OCLB_REMOTEWAKEUP 10 /* Remote wakeup enabled */ + +#define OCLF_PERIODICENABLE (1UL< + +#include +#include +#include +#include + +#include "debug.h" + +#include "cmd_protos.h" +#include "roothub_protos.h" +#include "chip_protos.h" +#include "pci_protos.h" + +struct my_NSDeviceQueryResult +{ + ULONG DevQueryFormat; /* this is type 0 */ + ULONG SizeAvailable; /* bytes available */ + UWORD DeviceType; /* what the device does */ + UWORD DeviceSubType; /* depends on the main type */ + const UWORD *SupportedCommands; /* 0 terminated list of cmd's */ +}; + +static BOOL OpenTimer(struct PCIUnit *unit, struct PCIDevice *base); +static void CloseTimer(struct PCIUnit *unit, struct PCIDevice *base); +static UWORD GetUsbState(struct IOUsbHWReq *ioreq, + struct PCIUnit *unit, struct PCIDevice *base); + +static const struct TagItem query_tags[] = { + {UHA_Manufacturer, (IPTR) "Chris Hodges, AROS Development Team"}, + {UHA_Description, (IPTR) "Open Host Controller Interface driver"}, + {UHA_Copyright, + (IPTR) "©2007-2012 Chris Hodges,\n AROS Development Team"}, + {UHA_Version, VERSION_NUMBER}, + {UHA_Revision, REVISION_NUMBER}, + {UHA_DriverVersion, 0x220}, + {TAG_END, 0} +}; + +/* /// "OpenTimer()" */ +static BOOL OpenTimer(struct PCIUnit *unit, struct PCIDevice *base) +{ + if ((unit->hu_MsgPort = CreateMsgPort())) + { + if ((unit->hu_TimerReq = + (struct timerequest *)CreateIORequest(unit->hu_MsgPort, + sizeof(struct timerequest)))) + { + if (!OpenDevice("timer.device", UNIT_MICROHZ, + (struct IORequest *)unit->hu_TimerReq, 0)) + { + unit->hu_TimerReq->tr_node.io_Message.mn_Node.ln_Name = + "PCI hardware"; + unit->hu_TimerReq->tr_node.io_Command = TR_ADDREQUEST; + KPRINTF(1, ("opened timer device\n")); + return TRUE; + } + DeleteIORequest((struct IORequest *)unit->hu_TimerReq); + unit->hu_TimerReq = NULL; + } + DeleteMsgPort(unit->hu_MsgPort); + unit->hu_MsgPort = NULL; + } + KPRINTF(5, ("failed to open timer.device\n")); + return FALSE; +} +/* \\\ */ + +/* /// "DelayMS()" */ +void DelayMS(ULONG milli, struct PCIUnit *unit) +{ + unit->hu_TimerReq->tr_time.tv_secs = 0; + unit->hu_TimerReq->tr_time.tv_micro = milli * 1000; + DoIO((struct IORequest *)unit->hu_TimerReq); +} +/* \\\ */ + +/* /// "CloseTimer()" */ +static void CloseTimer(struct PCIUnit *unit, struct PCIDevice *base) +{ + if (unit->hu_MsgPort) + { + if (unit->hu_TimerReq) + { + KPRINTF(1, ("closing timer.device\n")); + CloseDevice((APTR) unit->hu_TimerReq); + DeleteIORequest((struct IORequest *)unit->hu_TimerReq); + unit->hu_TimerReq = NULL; + } + DeleteMsgPort(unit->hu_MsgPort); + unit->hu_MsgPort = NULL; + } +} +/* \\\ */ + +/* /// "Open_Unit()" */ +struct Unit *Open_Unit(struct IOUsbHWReq *ioreq, + LONG unitnr, struct PCIDevice *base) +{ + struct PCIUnit *unit = NULL; + + if (!base->hd_ScanDone) + { + base->hd_ScanDone = TRUE; + if (!pciInit(base)) + { + return NULL; + } + } + unit = (struct PCIUnit *)base->hd_Units.lh_Head; + while (((struct Node *)unit)->ln_Succ) + { + if (unit->hu_UnitNo == unitnr) + { + break; + } + unit = (struct PCIUnit *)((struct Node *)unit)->ln_Succ; + } + if (!((struct Node *)unit)->ln_Succ) + { + KPRINTF(20, ("Unit %ld does not exist!\n", unitnr)); + return NULL; + } + if (unit->hu_UnitAllocated) + { + ioreq->iouh_Req.io_Error = IOERR_UNITBUSY; + KPRINTF(5, ("Unit %ld already open!\n", unitnr)); + return NULL; + } + + if (OpenTimer(unit, base)) + { + + if (pciAllocUnit(unit)) // hardware self test + { + unit->hu_UnitAllocated = TRUE; + unit->hu_NakTimeoutInt.is_Node.ln_Type = NT_INTERRUPT; + unit->hu_NakTimeoutInt.is_Node.ln_Name = "PCI NakTimeout"; + unit->hu_NakTimeoutInt.is_Node.ln_Pri = -16; + unit->hu_NakTimeoutInt.is_Data = unit; + unit->hu_NakTimeoutInt.is_Code = (VOID_FUNC) NakTimeoutInt; + + CopyMem(unit->hu_TimerReq, &unit->hu_NakTimeoutReq, + sizeof(struct timerequest)); + unit->hu_NakTimeoutReq.tr_node.io_Message.mn_ReplyPort = + &unit->hu_NakTimeoutMsgPort; + unit->hu_NakTimeoutMsgPort.mp_Node.ln_Type = NT_MSGPORT; + unit->hu_NakTimeoutMsgPort.mp_Flags = PA_SOFTINT; + unit->hu_NakTimeoutMsgPort.mp_SigTask = &unit->hu_NakTimeoutInt; + NewList(&unit->hu_NakTimeoutMsgPort.mp_MsgList); + Cause(&unit->hu_NakTimeoutInt); + return &unit->hu_Unit; + } + else + { + ioreq->iouh_Req.io_Error = IOERR_SELFTEST; + KPRINTF(20, ("Hardware allocation failure!\n")); + } + CloseTimer(unit, base); + } + return NULL; +} +/* \\\ */ + +/* /// "Close_Unit()" */ +void Close_Unit(struct PCIDevice *base, + struct PCIUnit *unit, struct IOUsbHWReq *ioreq) +{ + /* Disable all interrupts */ + unit->hu_NakTimeoutMsgPort.mp_Flags = PA_IGNORE; + unit->hu_NakTimeoutInt.is_Node.ln_Type = NT_SOFTINT; + AbortIO((APTR) & unit->hu_NakTimeoutReq); + + pciFreeUnit(unit); + + CloseTimer(unit, base); + unit->hu_UnitAllocated = FALSE; +} +/* \\\ */ + +/* /// "GetUsbState()" */ +static UWORD GetUsbState(struct IOUsbHWReq *ioreq, + struct PCIUnit *unit, struct PCIDevice *base) +{ + return ioreq->iouh_State = UHSF_OPERATIONAL; +} +/* \\\ */ + +/* /// "cmdReset()" */ +/* + *====================================================================== + * cmdReset(ioreq, unit, base) + *====================================================================== + * + * This is the device CMD_RESET routine. + * + * Resets the whole USB hardware. Goes into USBOperational mode right + * after. Must NOT be called from an interrupt. + * + */ + +WORD cmdReset(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + KPRINTF(10, ("CMD_RESET ioreq: 0x%p\n", ioreq)); + + DelayMS(1, unit); + GetUsbState(ioreq, unit, base); + + if (ioreq->iouh_State & UHSF_OPERATIONAL) + { + return RC_OK; + } + return UHIOERR_USBOFFLINE; +} +/* \\\ */ + +/* /// "cmdUsbReset()" */ +/* + *====================================================================== + * cmdUsbReset(ioreq, unit, base) + *====================================================================== + * + * This is the device UHCMD_USBRESET routine. + * + * Resets the USB bus. Goes into USBOperational mode right after. Must + * NOT be called from an interrupt. + * + */ + +WORD cmdUsbReset(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + KPRINTF(10, ("UHCMD_USBRESET ioreq: 0x%p\n", ioreq)); + + /* FIXME */ + GetUsbState(ioreq, unit, base); + + unit->hu_FrameCounter = 1; + unit->hu_RootHubAddr = 0; + + if (ioreq->iouh_State & UHSF_OPERATIONAL) + { + return RC_OK; + } + return UHIOERR_USBOFFLINE; +} +/* \\\ */ + +/* /// "cmdUsbResume()" */ +/* + *====================================================================== + * cmdUsbResume(ioreq, unit, base) + *====================================================================== + * + * This is the device UHCMD_USBRESUME routine. + * + * Tries to resume from USBSuspend mode into USBOperational. + * Must NOT be called from an interrupt. + * + */ + +WORD cmdUsbResume(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + KPRINTF(10, ("UHCMD_USBRESUME ioreq: 0x%p\n", ioreq)); + + /* FIXME */ + GetUsbState(ioreq, unit, base); + if (ioreq->iouh_State & UHSF_OPERATIONAL) + { + return RC_OK; + } + return UHIOERR_USBOFFLINE; +} +/* \\\ */ + +/* /// "cmdUsbSuspend()" */ +/* + *====================================================================== + * cmdUsbSuspend(ioreq, unit, base) + *====================================================================== + * + * This is the device UHCMD_USBSUSPEND routine. + * + * Sets the USB into USBSuspend mode. + * Must NOT be called from an interrupt. + * + */ + +WORD cmdUsbSuspend(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + KPRINTF(10, ("UHCMD_USBSUSPEND ioreq: 0x%p\n", ioreq)); + + /* FIXME */ + GetUsbState(ioreq, unit, base); + if (ioreq->iouh_State & UHSF_SUSPENDED) + { + return RC_OK; + } + return UHIOERR_USBOFFLINE; +} +/* \\\ */ + +/* /// "cmdUsbOper()" */ +/* + *====================================================================== + * cmdUsbOper(ioreq, unit, base) + *====================================================================== + * + * This is the device UHCMD_USBOPER routine. + * + * Sets the USB into USBOperational mode. + * Must NOT be called from an interrupt. + * + */ + +WORD cmdUsbOper(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + KPRINTF(10, ("UHCMD_USBOPER ioreq: 0x%p\n", ioreq)); + + /* FIXME */ + GetUsbState(ioreq, unit, base); + if (ioreq->iouh_State & UHSF_OPERATIONAL) + { + return RC_OK; + } + return UHIOERR_USBOFFLINE; +} +/* \\\ */ + +/* /// "cmdQueryDevice()" */ +/* + *====================================================================== + * cmdQueryDevice(ioreq, unit, base) + *====================================================================== + * + * This is the device UHCMD_QUERYDEVICE routine. + * + * Returns information about the hardware. + * + */ + +WORD cmdQueryDevice(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + struct TagItem *taglist = (struct TagItem *)ioreq->iouh_Data; + struct TagItem *tag; + ULONG count = 0; + + KPRINTF(10, ("UHCMD_QUERYDEVICE ioreq: 0x%p, taglist: 0x%p\n", ioreq, + taglist)); + + while ((tag = NextTagItem(&taglist)) != NULL) + { + switch (tag->ti_Tag) + { + case UHA_State: + *((ULONG *) tag->ti_Data) = + (ULONG) GetUsbState(ioreq, unit, base); + break; + case UHA_ProductName: + *((STRPTR *) tag->ti_Data) = unit->hu_ProductName; + break; + default: + *((IPTR *) tag->ti_Data) = + GetTagData(tag->ti_Tag, *((IPTR *) tag->ti_Data), + query_tags); + } + count++; + } + + ioreq->iouh_Actual = count; + return RC_OK; +} +/* \\\ */ + +WORD cmdXFer(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + struct PCIController *hc; + UWORD xfer_type = 0; + + // get transfer type + switch (ioreq->iouh_Req.io_Command) + { + case UHCMD_CONTROLXFER: + xfer_type = CTRL_XFER; + break; + case UHCMD_ISOXFER: + xfer_type = ISO_XFER; + break; + case UHCMD_INTXFER: + xfer_type = INT_XFER; + break; + case UHCMD_BULKXFER: + xfer_type = BULK_XFER; + break; + } + + KPRINTF(10, ("UHCMD_%sXFER ioreq: 0x%p\n", xfer_names[xfer_type], + ioreq)); + GetUsbState(ioreq, unit, base); + if (!(ioreq->iouh_State & UHSF_OPERATIONAL)) + { + return UHIOERR_USBOFFLINE; + } + + if (xfer_type == CTRL_XFER) + { + /* Root Hub Emulation */ + if (ioreq->iouh_DevAddr == unit->hu_RootHubAddr) + { + return cmdControlXFerRootHub(ioreq, unit, base); + } + } + else if (xfer_type == INT_XFER) + { + /* Root Hub Emulation */ + if (ioreq->iouh_DevAddr == unit->hu_RootHubAddr) + { + return cmdIntXFerRootHub(ioreq, unit, base); + } + } + else if (xfer_type == BULK_XFER || xfer_type == ISO_XFER) + { + if (ioreq->iouh_Flags & UHFF_LOWSPEED) + { + return UHIOERR_BADPARAMS; + } + } + + hc = unit->hu_DevControllers[ioreq->iouh_DevAddr]; + if (!hc) + { + return UHIOERR_HOSTERROR; + } + + ioreq->iouh_Req.io_Flags &= ~IOF_QUICK; + ioreq->iouh_Actual = 0; + ioreq->iouh_DriverPrivate1 = NULL; + + Disable(); + AddTail(&hc->hc_XferQueues[xfer_type], (struct Node *)ioreq); + Enable(); + Cause(&hc->hc_CompleteInt); + + KPRINTF(10, ("UHCMD_%sXFER processed ioreq: 0x%p\n", + xfer_names[xfer_type], ioreq)); + return RC_DONTREPLY; +} +/* \\\ */ + +/* /// "cmdFlush()" */ +/* + *====================================================================== + * cmdFlush(ioreq, base) + *====================================================================== + * + * This is the device CMD_FLUSH routine. + * + * This routine abort all pending transfer requests. + * + */ + +WORD cmdFlush(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + struct IOUsbHWReq *cmpioreq; + struct PCIController *hc; + UWORD i; + struct List *list; + + KPRINTF(10, ("CMD_FLUSH ioreq: 0x%p\n", ioreq)); + + Disable(); + cmpioreq = (struct IOUsbHWReq *)unit->hu_RHIOQueue.lh_Head; + while (((struct Node *)cmpioreq)->ln_Succ) + { + Remove(&cmpioreq->iouh_Req.io_Message.mn_Node); + cmpioreq->iouh_Req.io_Error = IOERR_ABORTED; + ReplyMsg(&cmpioreq->iouh_Req.io_Message); + cmpioreq = (struct IOUsbHWReq *)unit->hu_RHIOQueue.lh_Head; + } + hc = (struct PCIController *)unit->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + for (i = 0; i < XFER_COUNT; i++) + { + list = (struct List *)&hc->hc_XferQueues[i]; + while ((cmpioreq = (struct IOUsbHWReq *)RemHead(list)) != NULL) + { + cmpioreq->iouh_Req.io_Error = IOERR_ABORTED; + ReplyMsg(&cmpioreq->iouh_Req.io_Message); + } + } + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } + Enable(); + /* Return success + */ + return RC_OK; +} +/* \\\ */ + +/* /// "NSD stuff" */ + +static const UWORD NSDSupported[] = { + CMD_FLUSH, CMD_RESET, + UHCMD_QUERYDEVICE, UHCMD_USBRESET, + UHCMD_USBRESUME, UHCMD_USBSUSPEND, + UHCMD_USBOPER, UHCMD_CONTROLXFER, + UHCMD_ISOXFER, UHCMD_INTXFER, + UHCMD_BULKXFER, + NSCMD_DEVICEQUERY, 0 +}; + +WORD cmdNSDeviceQuery(struct IOStdReq *ioreq, + struct PCIUnit *unit, struct PCIDevice *base) +{ + struct my_NSDeviceQueryResult *query; + + query = (struct my_NSDeviceQueryResult *)ioreq->io_Data; + + KPRINTF(10, ("NSCMD_DEVICEQUERY ioreq: 0x%p query: 0x%p\n", ioreq, + query)); + + /* NULL ptr? + Enough data? + Valid request? + */ + if ((!query) || + (ioreq->io_Length < sizeof(struct my_NSDeviceQueryResult)) || + (query->DevQueryFormat != 0) || (query->SizeAvailable != 0)) + { + /* Return error. This is special handling, since iorequest is only + guaranteed to be sizeof(struct IOStdReq). If we'd let our + devBeginIO dispatcher return the error, it would trash some + memory past end of the iorequest (ios2_WireError field). + */ + ioreq->io_Error = IOERR_NOCMD; + TermIO((struct IOUsbHWReq *)ioreq, base); + + /* Don't reply, we already did. + */ + return RC_DONTREPLY; + } + + ioreq->io_Actual = query->SizeAvailable + = sizeof(struct my_NSDeviceQueryResult); + query->DeviceType = NSDEVTYPE_USBHARDWARE; + query->DeviceSubType = 0; + query->SupportedCommands = NSDSupported; + + /* Return success (note that this will NOT poke ios2_WireError). + */ + return RC_OK; +} +/* \\\ */ + +/* /// "TermIO()" */ +/* + *=========================================================== + * TermIO(ioreq, base) + *=========================================================== + * + * Return completed ioreq to sender. + * + */ + +void TermIO(struct IOUsbHWReq *ioreq, struct PCIDevice *base) +{ + ioreq->iouh_Req.io_Message.mn_Node.ln_Type = NT_FREEMSG; + + /* If not quick I/O, reply the message + */ + if (!(ioreq->iouh_Req.io_Flags & IOF_QUICK)) + { + ReplyMsg(&ioreq->iouh_Req.io_Message); + } +} +/* \\\ */ + +/* /// "cmdAbortIO()" */ +BOOL cmdAbortIO(struct IOUsbHWReq *ioreq, struct PCIDevice *base) +{ + struct PCIUnit *unit = (struct PCIUnit *)ioreq->iouh_Req.io_Unit; + struct IOUsbHWReq *cmpioreq; + struct PCIController *hc; + BOOL foundit = FALSE; + UWORD i; + + KPRINTF(10, ("cmdAbort(%p)\n", ioreq)); + + Disable(); + cmpioreq = (struct IOUsbHWReq *)unit->hu_RHIOQueue.lh_Head; + while (((struct Node *)cmpioreq)->ln_Succ) + { + if (ioreq == cmpioreq) + { + Remove(&ioreq->iouh_Req.io_Message.mn_Node); + Enable(); + ioreq->iouh_Req.io_Error = IOERR_ABORTED; + TermIO(ioreq, base); + return TRUE; + } + cmpioreq = + (struct IOUsbHWReq *)cmpioreq->iouh_Req.io_Message. + mn_Node.ln_Succ; + } + + hc = (struct PCIController *)unit->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + for (i = 0; i < XFER_COUNT && !foundit; i++) + { + for (cmpioreq = + (struct IOUsbHWReq *)hc->hc_XferQueues[i].lh_Head; + ((struct Node *)cmpioreq)->ln_Succ != NULL && !foundit; + cmpioreq = + (struct IOUsbHWReq *)cmpioreq->iouh_Req.io_Message. + mn_Node.ln_Succ) + { + if (ioreq == cmpioreq) + foundit = TRUE; + } + } + if (!foundit) + { + // IOReq is probably pending in some transfer structure + cmpioreq = (struct IOUsbHWReq *)hc->hc_TDQueue.lh_Head; + while (((struct Node *)cmpioreq)->ln_Succ) + { + if (ioreq == cmpioreq) + { + /* + * Request's ED is in use by the HC, as well as its TDs + * and data buffers. + * Schedule abort on the HC driver and reply the request + * only when done. However return success. + */ + ioreq->iouh_Req.io_Error = IOERR_ABORTED; + AbortRequest(hc, ioreq); + Enable(); + return TRUE; + } + cmpioreq = + (struct IOUsbHWReq *)cmpioreq->iouh_Req. + io_Message.mn_Node.ln_Succ; + } + break; + + } + if (foundit) + { + Remove(&ioreq->iouh_Req.io_Message.mn_Node); + break; + } + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } + Enable(); + + if (foundit) + { + ioreq->iouh_Req.io_Error = IOERR_ABORTED; + TermIO(ioreq, base); + } + else + { + KPRINTF(20, ("WARNING, could not abort unknown IOReq %p\n", ioreq)); + } + return foundit; +} +/* \\\ */ + +/* /// "CheckSpecialCtrlTransfers()" */ +void CheckSpecialCtrlTransfers(struct PCIController *hc, + struct IOUsbHWReq *ioreq) +{ + struct PCIUnit *unit = hc->hc_Unit; + + /* Clear Feature(Endpoint halt) */ + if ((ioreq->iouh_SetupData.bmRequestType == + (URTF_STANDARD | URTF_ENDPOINT)) + && (ioreq->iouh_SetupData.bRequest == USR_CLEAR_FEATURE) + && (ioreq->iouh_SetupData.wValue == + AROS_WORD2LE(UFS_ENDPOINT_HALT))) + { + KPRINTF(10, ("Resetting toggle bit for endpoint %ld\n", + AROS_WORD2LE(ioreq->iouh_SetupData.wIndex) & 0xf)); + unit-> + hu_DevDataToggle[(ioreq->iouh_DevAddr << 5) | + (AROS_WORD2LE(ioreq-> + iouh_SetupData.wIndex) & 0xf) | ((AROS_WORD2LE(ioreq-> + iouh_SetupData.wIndex) & 0x80) >> 3)] = 0; + } + else if ((ioreq->iouh_SetupData.bmRequestType == + (URTF_STANDARD | URTF_DEVICE)) + && (ioreq->iouh_SetupData.bRequest == USR_SET_ADDRESS)) + { + /* Set Address -> clear all endpoints */ + ULONG epnum; + ULONG adr = AROS_WORD2BE(ioreq->iouh_SetupData.wValue) >> 3; + KPRINTF(10, ("Resetting toggle bits for device address %ld\n", + adr >> 5)); + for (epnum = 0; epnum < 31; epnum++) + { + unit->hu_DevDataToggle[adr + epnum] = 0; + } + // transfer host controller ownership + unit->hu_DevControllers[ioreq->iouh_DevAddr] = NULL; + unit->hu_DevControllers[adr >> 5] = hc; + } + else if ((ioreq->iouh_SetupData.bmRequestType == + (URTF_CLASS | URTF_OTHER)) + && (ioreq->iouh_SetupData.bRequest == USR_SET_FEATURE) + && (ioreq->iouh_SetupData.wValue == AROS_WORD2LE(UFS_PORT_RESET))) + { + // a hub will be enumerating a device on this host controller soon! + KPRINTF(10, ("Hub RESET caught, assigning Dev0 to %p!\n", hc)); + unit->hu_DevControllers[0] = hc; + } +} +/* \\\ */ + +/* /// "NakTimeoutInt()" */ +AROS_UFIH1(NakTimeoutInt, struct PCIUnit *, unit) +{ + AROS_USERFUNC_INIT struct PCIController *hc; + struct IOUsbHWReq *ioreq; + UWORD target; + ULONG framecnt; + + KPRINTF(1, ("Enter NakTimeoutInt(0x%p)\n", unit)); + + // check for NAK Timeouts + hc = (struct PCIController *)unit->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + if (!(hc->hc_Flags & HCF_ONLINE)) + { + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + continue; + } + UpdateFrameCounter(hc); + framecnt = hc->hc_FrameCounter; + // NakTimeout + ioreq = (struct IOUsbHWReq *)hc->hc_TDQueue.lh_Head; + while (((struct Node *)ioreq)->ln_Succ) + { + // Remember the successor because AbortRequest() will move the request to another list + struct IOUsbHWReq *succ = + (struct IOUsbHWReq *)ioreq->iouh_Req.io_Message. + mn_Node.ln_Succ; + + if (ioreq->iouh_Flags & UHFF_NAKTIMEOUT) + { + KPRINTF(1, ("Examining IOReq=%p with OED=%p\n", ioreq, + ioreq->iouh_DriverPrivate1)); + if (ioreq->iouh_DriverPrivate1) + { + KPRINTF(1, + ("CTRL=%04lx, CMD=%01lx, F=%ld, hccaDH=%08lx, " + "hcDH=%08lx, CH=%08lx, CCH=%08lx, " + "IntStatus=%08lx, IntEn=%08lx\n", + READREG32_LE(hc->hc_RegBase, OHCI_CONTROL), + READREG32_LE(hc->hc_RegBase, OHCI_CMDSTATUS), + READREG32_LE(hc->hc_RegBase, OHCI_FRAMECOUNT), + READMEM32_LE(&hc->hc_HCCA->ha_DoneHead), + READREG32_LE(hc->hc_RegBase, OHCI_DONEHEAD), + READREG32_LE(hc->hc_RegBase, OHCI_CTRL_HEAD_ED), + READREG32_LE(hc->hc_RegBase, OHCI_CTRL_ED), + READREG32_LE(hc->hc_RegBase, OHCI_INTSTATUS), + READREG32_LE(hc->hc_RegBase, OHCI_INTEN))); + + target = + (ioreq->iouh_DevAddr << 5) + ioreq->iouh_Endpoint + + ((ioreq->iouh_Dir == UHDIR_IN) ? 0x10 : 0); + if (framecnt > unit->hu_NakTimeoutFrame[target]) + { + // give the thing the chance to exit gracefully + KPRINTF(200, + ("HC 0x%p NAK timeout %ld > %ld, IOReq=%p\n", + hc, framecnt, + unit->hu_NakTimeoutFrame[target], ioreq)); + ioreq->iouh_Req.io_Error = UHIOERR_NAKTIMEOUT; + AbortRequest(hc, ioreq); + } + } + } + ioreq = succ; + } + + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } + + CheckRootHubChanges(unit); + + unit->hu_NakTimeoutReq.tr_time.tv_micro = 150 * 1000; + SendIO((APTR) & unit->hu_NakTimeoutReq); + + KPRINTF(1, ("Exit NakTimeoutInt(0x%p)\n", unit)); + + return FALSE; + +AROS_USERFUNC_EXIT} +/* \\\ */ diff --git a/rom/usb/pciusbhc/ohci/cmd_protos.h b/rom/usb/pciusbhc/ohci/cmd_protos.h new file mode 100644 index 0000000000..eba229294e --- /dev/null +++ b/rom/usb/pciusbhc/ohci/cmd_protos.h @@ -0,0 +1,50 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#ifndef CMD_PROTOS_H +#define CMD_PROTOS_H + +#include "dev.h" + +struct Unit *Open_Unit(struct IOUsbHWReq *ioreq, LONG unitnr, + struct PCIDevice *base); +void Close_Unit(struct PCIDevice *base, struct PCIUnit *unit, + struct IOUsbHWReq *ioreq); + +void DelayMS(ULONG milli, struct PCIUnit *unit); +void CheckSpecialCtrlTransfers(struct PCIController *hc, + struct IOUsbHWReq *ioreq); + +WORD cmdReset(struct IOUsbHWReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); +WORD cmdUsbReset(struct IOUsbHWReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); +WORD cmdUsbResume(struct IOUsbHWReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); +WORD cmdUsbSuspend(struct IOUsbHWReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); +WORD cmdUsbOper(struct IOUsbHWReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); + +WORD cmdQueryDevice(struct IOUsbHWReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); + +WORD cmdXFer(struct IOUsbHWReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); + +WORD cmdFlush(struct IOUsbHWReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); + +WORD cmdNSDeviceQuery(struct IOStdReq *ioreq, struct PCIUnit *unit, + struct PCIDevice *base); + +BOOL cmdAbortIO(struct IOUsbHWReq *ioreq, struct PCIDevice *base); + +void TermIO(struct IOUsbHWReq *ioreq, struct PCIDevice *base); + +AROS_UFIP(NakTimeoutInt); + +#endif /* CMD_PROTOS_H */ diff --git a/rom/usb/pciusbhc/ohci/debug.c b/rom/usb/pciusbhc/ohci/debug.c new file mode 100644 index 0000000000..f6153e54ea --- /dev/null +++ b/rom/usb/pciusbhc/ohci/debug.c @@ -0,0 +1,49 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ + */ + +#include "debug.h" + +#ifdef DEBUG +void dumpmem_pciusb(void *mem, unsigned long int len) +{ + unsigned char *p; + + if (!mem || !len) + { + return; + } + + p = (unsigned char *)mem; + + KPrintF("\n"); + + do + { + unsigned char b, c, str[17]; + + for (b = 0; b < 16; b++) + { + c = *p++; + str[b] = ((c >= ' ') && (c <= 'z')) ? c : '.'; + str[b + 1] = 0; + KPrintF("%02lx ", c); + if (--len == 0) + break; + } + + while (++b < 16) + { + KPrintF(" "); + } + + KPrintF(" %s\n", str); + } + while (len); + + KPrintF("\n\n"); +} + +#endif /* DEBUG */ diff --git a/rom/usb/pciusbhc/ohci/debug.h b/rom/usb/pciusbhc/ohci/debug.h new file mode 100644 index 0000000000..bdecba5a97 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/debug.h @@ -0,0 +1,30 @@ +/* This file can be included multiple times with different DB_LEVEL */ +#undef DB +#undef KPRINTF + +#ifndef DB_LEVEL +#define DB_LEVEL 10 +#endif + +#define DEBUG 0 + +#include + +// DEBUG 0 should equal undefined DEBUG +#ifdef DEBUG +#if DEBUG == 0 +#undef DEBUG +#endif +#endif + +#ifdef DEBUG +#define KPRINTF(l, x) do { if ((l) >= DB_LEVEL) \ + { KPrintF("%s/%lu: ", __FUNCTION__, __LINE__); KPrintF x;} } while (0) +#define DB(x) x +void dumpmem_pciusb(void *mem, unsigned long int len); +#else /* !DEBUG */ + +#define KPRINTF(l, x) ((void) 0) +#define DB(x) + +#endif /* DEBUG */ diff --git a/rom/usb/pciusbhc/ohci/dev.c b/rom/usb/pciusbhc/ohci/dev.c new file mode 100644 index 0000000000..968d3c7cf7 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/dev.c @@ -0,0 +1,266 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2010-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#include +#include +#include + +#include "debug.h" + +#include "cmd_protos.h" +#include "pci_protos.h" + +#define UtilityBase base->hd_UtilityBase + +const char devname[] = MOD_NAME_STRING; +CONST_STRPTR xfer_names[] = {"CONTROL", "BULK", "ISO", "INT"}; + +static int devInit(LIBBASETYPEPTR base) +{ + KPRINTF(10, ("devInit base: 0x%p SysBase: 0x%p\n", base, SysBase)); + + base->hd_UtilityBase = (APTR) OpenLibrary("utility.library", 39); + + if (UtilityBase) + { + base->hd_MemPool = + CreatePool(MEMF_PUBLIC | MEMF_CLEAR | MEMF_SEM_PROTECTED, 16384, + 4096); + if (base->hd_MemPool) + { + NewList(&base->hd_Units); + + KPRINTF(10, ("devInit: Ok\n")); + } + else + { + KPRINTF(10, ("devInit: CreatePool() failed!\n")); + CloseLibrary((struct Library *)UtilityBase); + base = NULL; + } + } + else + { + KPRINTF(10, + ("devInit: OpenLibrary(\"utility.library\", 39) failed!\n")); + base = NULL; + } + + KPRINTF(10, ("devInit: openCnt = %ld\n", base->hd_Library.lib_OpenCnt)); + + return base ? TRUE : FALSE; +} + +/* + *=========================================================== + * devOpen(ioreq, unit, flags, base) + *=========================================================== + * + * This is the the DEV_OPEN function. + * + */ +static int devOpen(LIBBASETYPEPTR base, struct IOUsbHWReq *ioreq, + ULONG unit, ULONG flags) +{ + KPRINTF(10, + ("devOpen ioreq: 0x%p unit: %ld flags: 0x%08lx base: 0x%p\n", ioreq, + unit, flags, base)); + + KPRINTF(10, ("devOpen: openCnt = %ld\n", base->hd_Library.lib_OpenCnt)); + + if (ioreq->iouh_Req.io_Message.mn_Length < sizeof(struct IOUsbHWReq)) + { + KPRINTF(20, ("devOpen: invalid MN_LENGTH!\n")); + + ioreq->iouh_Req.io_Error = IOERR_BADLENGTH; + } + else + { + /* Default to open failure. */ + ioreq->iouh_Req.io_Error = IOERR_OPENFAIL; + + ioreq->iouh_Req.io_Unit = Open_Unit(ioreq, unit, base); + if (!ioreq->iouh_Req.io_Unit) + { + KPRINTF(20, ("devOpen: could not open unit!\n")); + } + else + { + /* Opended ok! */ + ioreq->iouh_Req.io_Message.mn_Node.ln_Type = NT_REPLYMSG; + ioreq->iouh_Req.io_Error = 0; + + return TRUE; + } + } + + return FALSE; +} + + +/* + *=========================================================== + * devClose(ioreq, base) + *=========================================================== + * + * This is the the DEV_EXPUNGE function. + * + */ + +static int devClose(LIBBASETYPEPTR base, struct IOUsbHWReq *ioreq) +{ + KPRINTF(10, ("devClose ioreq: 0x%p base: 0x%p\n", ioreq, base)); + + Close_Unit(base, (struct PCIUnit *)ioreq->iouh_Req.io_Unit, ioreq); + + ioreq->iouh_Req.io_Unit = (APTR) - 1; + ioreq->iouh_Req.io_Device = (APTR) - 1; + return TRUE; +} + + +static int devExpunge(LIBBASETYPEPTR base) +{ + pciExpunge(base); + + DeletePool(base->hd_MemPool); + + KPRINTF(5, ("devExpunge: closelibrary utilitybase 0x%p\n", + UtilityBase)); + CloseLibrary((struct Library *)UtilityBase); + return TRUE; +} + +ADD2INITLIB(devInit, 0) +ADD2OPENDEV(devOpen, 0) +ADD2CLOSEDEV(devClose, 0) ADD2EXPUNGELIB(devExpunge, 0) + /* + *=========================================================== + * devBeginIO(ioreq, base) + *=========================================================== + * + * This is the DEV_BEGINIO vector of the device. + * + */ + AROS_LH1(void, devBeginIO, + AROS_LHA(struct IOUsbHWReq *, ioreq, A1), LIBBASETYPEPTR, base, 5, ohci) +{ + AROS_LIBFUNC_INIT + struct PCIUnit *unit = (struct PCIUnit *)ioreq->iouh_Req.io_Unit; + WORD ret; + + ioreq->iouh_Req.io_Message.mn_Node.ln_Type = NT_MESSAGE; + ioreq->iouh_Req.io_Error = UHIOERR_NO_ERROR; + + if (ioreq->iouh_Req.io_Command < NSCMD_DEVICEQUERY) + { + switch (ioreq->iouh_Req.io_Command) + { + case CMD_RESET: + ret = cmdReset(ioreq, unit, base); + break; + + case CMD_FLUSH: + ret = cmdFlush(ioreq, unit, base); + break; + + case UHCMD_QUERYDEVICE: + ret = cmdQueryDevice(ioreq, unit, base); + break; + + case UHCMD_USBRESET: + ret = cmdUsbReset(ioreq, unit, base); + break; + + case UHCMD_USBRESUME: + ret = cmdUsbResume(ioreq, unit, base); + break; + + case UHCMD_USBSUSPEND: + ret = cmdUsbSuspend(ioreq, unit, base); + break; + + case UHCMD_USBOPER: + ret = cmdUsbOper(ioreq, unit, base); + break; + + case UHCMD_CONTROLXFER: + ret = cmdXFer(ioreq, unit, base); + break; + + case UHCMD_BULKXFER: + ret = cmdXFer(ioreq, unit, base); + break; + + case UHCMD_INTXFER: + ret = cmdXFer(ioreq, unit, base); + break; + + case UHCMD_ISOXFER: + ret = cmdXFer(ioreq, unit, base); + break; + + default: + ret = IOERR_NOCMD; + break; + } + } + else + { + switch (ioreq->iouh_Req.io_Command) + { + case NSCMD_DEVICEQUERY: + ret = cmdNSDeviceQuery((struct IOStdReq *)ioreq, unit, base); + break; + + default: + ret = IOERR_NOCMD; + break; + } + } + + if (ret != RC_DONTREPLY) + { + KPRINTF(1, ("TermIO\n")); + if (ret != RC_OK) + { + /* Set error codes */ + ioreq->iouh_Req.io_Error = ret & 0xff; + } + /* Terminate the iorequest */ + TermIO(ioreq, base); + } + +AROS_LIBFUNC_EXIT} + +/* + *=========================================================== + * devAbortIO(ioreq, base) + *=========================================================== + * + * This is the DEV_ABORTIO vector of the device. It abort + * the given iorequest, and set + * + */ +AROS_LH1(LONG, devAbortIO, + AROS_LHA(struct IOUsbHWReq *, ioreq, A1), LIBBASETYPEPTR, base, 6, ohci) +{ + AROS_LIBFUNC_INIT + KPRINTF(50, ("devAbortIO ioreq: 0x%p, command %ld, status %ld\n", + ioreq, ioreq->iouh_Req.io_Command, + ioreq->iouh_Req.io_Message.mn_Node.ln_Type)); + + /* Is it pending? */ + if (ioreq->iouh_Req.io_Message.mn_Node.ln_Type == NT_MESSAGE) + { + if (cmdAbortIO(ioreq, base)) + { + return 0; + } + } + return -1; + +AROS_LIBFUNC_EXIT} diff --git a/rom/usb/pciusbhc/ohci/dev.h b/rom/usb/pciusbhc/ohci/dev.h new file mode 100644 index 0000000000..d6d9d75c87 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/dev.h @@ -0,0 +1,184 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ + */ + +#ifndef DEV_H +#define DEV_H + +#include LC_LIBDEFS_FILE + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +/* Reply the iorequest with success */ +#define RC_OK 0 + +/* Magic cookie, don't set error fields & don't reply the ioreq */ +#define RC_DONTREPLY -1 + +#define MAX_ROOT_PORTS 16 + +enum +{ + CTRL_XFER, + BULK_XFER, + ISO_XFER, + INT_XFER, + XFER_COUNT +}; + +#define INT_LIST_COUNT 6 + +extern CONST_STRPTR xfer_names[]; + +#define PCI_CLASS_SERIAL_USB 0x0c03 + +/* The unit node - private */ +struct PCIUnit +{ + struct Unit hu_Unit; + LONG hu_UnitNo; + + struct PCIDevice *hu_Device; /* Uplink */ + + struct MsgPort *hu_MsgPort; + struct timerequest *hu_TimerReq; /* Timer I/O Request */ + + struct timerequest hu_LateIOReq; /* Timer I/O Request */ + struct MsgPort hu_LateMsgPort; + + struct timerequest hu_NakTimeoutReq; + struct MsgPort hu_NakTimeoutMsgPort; + struct Interrupt hu_NakTimeoutInt; + + BOOL hu_UnitAllocated; /* Unit opened */ + + ULONG hu_DevID; /* Device ID (BusID+DevNo) */ + struct List hu_Controllers; /* List of controllers */ + UWORD hu_RootHub11Ports; + UWORD hu_RootHub20Ports; + UWORD hu_RootHubPorts; + UWORD hu_RootHubAddr; /* Root Hub Address */ + UWORD hu_RootPortChanges; /* Merged root hub changes */ + ULONG hu_FrameCounter; /* Common frame counter */ + struct List hu_RHIOQueue; /* Root Hub Pending IO Requests */ + + struct PCIController *hu_PortMap11[MAX_ROOT_PORTS]; /* Maps from Global Port to USB 1.1 controller */ + struct PCIController *hu_PortMap20[MAX_ROOT_PORTS]; /* Maps from Global Port to USB 2.0 controller */ + UBYTE hu_PortNum11[MAX_ROOT_PORTS]; /* Maps from Global Port to USB 1.1 companion controller port */ + UBYTE hu_EhciOwned[MAX_ROOT_PORTS]; /* TRUE, if currently owned by EHCI */ + UBYTE hu_ProductName[80]; /* for Query device */ + struct PCIController *hu_DevControllers[128]; /* maps from Device address to controller */ + struct IOUsbHWReq *hu_DevBusyReq[128 * 16 * 2]; /* pointer to io assigned to the Endpoint */ + ULONG hu_NakTimeoutFrame[128 * 16 * 2]; /* Nak Timeout framenumber */ + UBYTE hu_DevDataToggle[128 * 16 * 2]; /* Data toggle bit for endpoints */ +}; + +struct PCIController +{ + struct Node hc_Node; + struct PCIDevice *hc_Device; /* Uplink */ + struct PCIUnit *hc_Unit; /* Uplink */ + + OOP_Object *hc_PCIDeviceObject; + OOP_Object *hc_PCIDriverObject; + ULONG hc_DevID; + UWORD hc_FunctionNum; + UWORD hc_HCIType; + UWORD hc_NumPorts; + UWORD hc_Flags; /* See below */ + + volatile APTR hc_RegBase; + + APTR hc_PCIMem; + ULONG hc_PCIMemSize; + IPTR hc_PCIVirtualAdjust; + IPTR hc_PCIIntLine; + struct Interrupt hc_PCIIntHandler; + ULONG hc_PCIIntEnMask; + + struct MinList hc_EDLists[XFER_COUNT + INT_LIST_COUNT - 1]; + struct EDNode *hc_TermED; + struct TDNode *hc_TermTD; + struct HCCA *hc_HCCA; + struct MinList hc_FreeEDList; + struct MinList hc_FreeTDList; + struct EDNode *hc_AsyncFreeED; + ULONG hc_DoneQueue; + struct List hc_RetireQueue; + + ULONG hc_FrameCounter; + struct List hc_TDQueue; + struct List hc_AbortQueue; + struct List hc_PeriodicTDQueue; + struct List hc_XferQueues[XFER_COUNT]; + + struct Interrupt hc_CompleteInt; + struct Interrupt hc_ResetInt; + + UBYTE hc_PortNum20[MAX_ROOT_PORTS]; /* Global Port number the local controller port corresponds with */ + + UWORD hc_PortChangeMap[MAX_ROOT_PORTS]; /* Port Change Map */ + + BOOL hc_complexrouting; + ULONG hc_portroute; + +}; + +/* hc_Flags */ +#define HCF_ALLOCATED 0x0001 /* PCI board allocated */ +#define HCF_ONLINE 0x0002 /* Online */ +#define HCF_STOP_BULK 0x0004 /* Bulk transfers stopped */ +#define HCF_STOP_CTRL 0x0008 /* Control transfers stopped */ +#define HCF_ABORT 0x0010 /* Aborted requests available */ + +/* The device node - private + */ +struct PCIDevice +{ + struct Library hd_Library; /* standard */ + UWORD hd_Flags; /* various flags */ + + struct UtilityBase *hd_UtilityBase; /* for tags etc */ + + struct List hd_TempHCIList; + OOP_Object *hd_PCIHidd; + OOP_AttrBase hd_HiddAB; + OOP_AttrBase hd_HiddPCIDeviceAB; + + BOOL hd_ScanDone; /* PCI scan done? */ + APTR hd_MemPool; /* Memory Pool */ + + struct List hd_Units; /* List of units */ +}; + +/* hd_Flags */ +#define HDF_FORCEPOWER 0x01 + +#endif /* DEV_H */ diff --git a/rom/usb/pciusbhc/ohci/mmakefile.src b/rom/usb/pciusbhc/ohci/mmakefile.src new file mode 100644 index 0000000000..f03ec1321b --- /dev/null +++ b/rom/usb/pciusbhc/ohci/mmakefile.src @@ -0,0 +1,21 @@ +# $Id$ +include $(TOP)/config/make.cfg + +FILES := pci buffer chip roothub dev cmd debug +USER_CFLAGS := -Wno-parentheses + +WBUSBHARDWAREDIR := $(AROSDIR)/Devs/USBHardware + +#MM +kernel-usb-pciusb-directories :: + %mkdirs_q $(WBUSBHARDWAREDIRS) + +#MM- kernel-usb-hw-ohci : kernel-usb-pciusb-directories + +%build_module mmake=kernel-usb-hw-ohci \ + modname=ohci modtype=device \ + moduledir=Devs/USBHardware \ + files="$(FILES)" \ + uselibs="oop hiddstubs debug" + +%common diff --git a/rom/usb/pciusbhc/ohci/ohci.conf b/rom/usb/pciusbhc/ohci/ohci.conf new file mode 100644 index 0000000000..6d49d68289 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/ohci.conf @@ -0,0 +1,16 @@ +##begin config +version 1.1 +libbase base +libbasetype struct PCIDevice +libbasetypeextern struct Device +residentpri 48 +basename ohci +beginio_func devBeginIO +abortio_func devAbortIO +##end config +##begin cdef +#include +##end cdef +##begin cdefprivate +#include "dev.h" +##end cdefprivate diff --git a/rom/usb/pciusbhc/ohci/pci.c b/rom/usb/pciusbhc/ohci/pci.c new file mode 100644 index 0000000000..c605b67358 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/pci.c @@ -0,0 +1,451 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "debug.h" +#include "chip.h" +#include "dev.h" + +#include "chip_protos.h" +#include "cmd_protos.h" + +#ifdef __i386__ +#define HAVE_ACPI +#endif +#ifdef __x86_64__ +#define HAVE_ACPI +#endif + +#undef HiddPCIDeviceAttrBase +#undef HiddAttrBase + +#define HiddPCIDeviceAttrBase (hd->hd_HiddPCIDeviceAB) +#define HiddAttrBase (hd->hd_HiddAB) + +static TEXT product_name[] = "PCI OHCI USB 1.1 Host Controller"; + +AROS_UFH3(void, pciEnumerator, + AROS_UFHA(struct Hook *, hook, A0), + AROS_UFHA(OOP_Object *, pciDevice, A2), AROS_UFHA(APTR, message, A1)) +{ + AROS_USERFUNC_INIT + struct PCIDevice *hd = (struct PCIDevice *)hook->h_Data; + struct PCIController *hc; + IPTR bus; + IPTR dev; + IPTR sub; + IPTR intline; + ULONG devid; + UWORD i; + + OOP_GetAttr(pciDevice, aHidd_PCIDevice_Bus, &bus); + OOP_GetAttr(pciDevice, aHidd_PCIDevice_Dev, &dev); + OOP_GetAttr(pciDevice, aHidd_PCIDevice_Dev, &sub); + OOP_GetAttr(pciDevice, aHidd_PCIDevice_INTLine, &intline); + + devid = (bus << 16) | dev; + + KPRINTF(10, ("Found PCI device 0x%lx Intline=%ld\n", devid, intline)); + + if (intline == 255) + { + // we can't work without the correct interrupt line. + // BIOS needs plug & play os option disabled. + // Alternatively AROS must support APIC reconfiguration + KPRINTF(200, ("ERROR: PCI card has no interrupt line assigned " + "by BIOS, disable Plug & Play OS!\n")); + } + else + { + KPRINTF(10, ("Setting up device...\n")); + + hc = AllocPooled(hd->hd_MemPool, sizeof(struct PCIController)); + if (hc) + { + hc->hc_Device = hd; + hc->hc_DevID = devid; + hc->hc_FunctionNum = sub; + hc->hc_PCIDeviceObject = pciDevice; + hc->hc_PCIIntLine = intline; + + OOP_GetAttr(pciDevice, aHidd_PCIDevice_Driver, + (IPTR *) & hc->hc_PCIDriverObject); + + for (i = 0; i < XFER_COUNT; i++) + NewList(&hc->hc_XferQueues[i]); + NewList(&hc->hc_TDQueue); + NewList(&hc->hc_AbortQueue); + NewList(&hc->hc_PeriodicTDQueue); + NewList(&hc->hc_RetireQueue); + AddTail(&hd->hd_TempHCIList, &hc->hc_Node); + } + } + +AROS_USERFUNC_EXIT} + +/* /// "pciInit()" */ +BOOL pciInit(struct PCIDevice *hd) +{ + struct PCIController *hc; + struct PCIController *nexthc; + struct PCIUnit *hu; + ULONG unitno = 0; + + KPRINTF(10, ("*** pciInit(%p) ***\n", hd)); + + NewList(&hd->hd_TempHCIList); + + if ((hd->hd_PCIHidd = + OOP_NewObject(NULL, (STRPTR) CLID_Hidd_PCI, NULL))) + { + struct TagItem tags[] = { + {tHidd_PCI_Class, (PCI_CLASS_SERIAL_USB >> 8) & 0xff}, + {tHidd_PCI_SubClass, (PCI_CLASS_SERIAL_USB & 0xff)}, + {tHidd_PCI_Interface, 0x10}, + {TAG_DONE, 0UL} + }; + + struct OOP_ABDescr attrbases[] = { + {(STRPTR) IID_Hidd, &hd->hd_HiddAB}, + {(STRPTR) IID_Hidd_PCIDevice, &hd->hd_HiddPCIDeviceAB}, + {NULL, NULL} + }; + + struct Hook findHook = { + h_Entry:(IPTR(*)())pciEnumerator, + h_Data:hd, + }; + + OOP_ObtainAttrBases(attrbases); + + KPRINTF(20, ("Searching for devices...\n")); + + HIDD_PCI_EnumDevices(hd->hd_PCIHidd, &findHook, + (struct TagItem *)&tags); + } + else + { + KPRINTF(20, ("Unable to create PCIHidd object!\n")); + return FALSE; + } + + // Create units with a list of host controllers having the same bus + // and device number. + while (hd->hd_TempHCIList.lh_Head->ln_Succ) + { + hu = AllocPooled(hd->hd_MemPool, sizeof(struct PCIUnit)); + if (!hu) + { + // actually, we should get rid of the allocated memory first, + // but we don't care as DeletePool() will take care of this + // eventually + return FALSE; + } + hu->hu_Device = hd; + hu->hu_UnitNo = unitno; + hu->hu_DevID = + ((struct PCIController *)hd->hd_TempHCIList.lh_Head)->hc_DevID; + + NewList(&hu->hu_Controllers); + NewList(&hu->hu_RHIOQueue); + + hc = (struct PCIController *)hd->hd_TempHCIList.lh_Head; + while ((nexthc = (struct PCIController *)hc->hc_Node.ln_Succ)) + { + if (hc->hc_DevID == hu->hu_DevID) + { + Remove(&hc->hc_Node); + hc->hc_Unit = hu; + AddTail(&hu->hu_Controllers, &hc->hc_Node); + } + hc = nexthc; + } + AddTail(&hd->hd_Units, (struct Node *)hu); + unitno++; + } + return TRUE; +} +/* \\\ */ + +/* /// "pciAllocUnit()" */ +BOOL pciAllocUnit(struct PCIUnit * hu) +{ + struct PCIController *hc; + + BOOL allocgood = TRUE; + ULONG usb11ports = 0; + ULONG usb20ports = 0; + ULONG cnt; + + ULONG ohcicnt = 0; + + KPRINTF(10, ("*** pciAllocUnit(%p) ***\n", hu)); + + // allocate necessary memory + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + allocgood = InitController(hc, hu); + if (allocgood) + { + ohcicnt++; + } + + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } + + if (!allocgood) + { + return FALSE; + } + + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + if (hc->hc_complexrouting) + { + ULONG locport = 0; + for (cnt = 0; cnt < usb20ports; cnt++) + { + if (((hc->hc_portroute >> (cnt << 2)) & 0xf) == + hc->hc_FunctionNum) + { + KPRINTF(10, + ("CHC %ld Port %ld assigned to global Port %ld\n", + hc->hc_FunctionNum, locport, cnt)); + hu->hu_PortMap11[cnt] = hc; + hu->hu_PortNum11[cnt] = locport; + hc->hc_PortNum20[locport] = cnt; + locport++; + } + } + } + else + { + for (cnt = usb11ports; cnt < usb11ports + hc->hc_NumPorts; + cnt++) + { + hu->hu_PortMap11[cnt] = hc; + hu->hu_PortNum11[cnt] = cnt - usb11ports; + hc->hc_PortNum20[cnt - usb11ports] = cnt; + } + } + usb11ports += hc->hc_NumPorts; + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } + if ((usb11ports != usb20ports) && usb20ports) + { + KPRINTF(20, + ("Warning! #EHCI Ports (%ld) does not match USB 1.1 Ports (%ld)!\n", + usb20ports, usb11ports)); + } + + hu->hu_RootHub11Ports = usb11ports; + hu->hu_RootHub20Ports = usb20ports; + hu->hu_RootHubPorts = + (usb11ports > usb20ports) ? usb11ports : usb20ports; + for (cnt = 0; cnt < hu->hu_RootHubPorts; cnt++) + { + hu->hu_EhciOwned[cnt] = hu->hu_PortMap20[cnt] ? TRUE : FALSE; + } + + KPRINTF(10, ("Unit %ld: USB Board %08lx has %ld USB1.1 ports\n", + hu->hu_UnitNo, hu->hu_DevID, hu->hu_RootHub11Ports)); + + hu->hu_FrameCounter = 1; + hu->hu_RootHubAddr = 0; + + // put em online + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + hc->hc_Flags |= HCF_ONLINE; + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } + + // create product name of device + CopyMem(product_name, hu->hu_ProductName, sizeof(product_name)); + + KPRINTF(10, ("Unit allocated!\n")); + + return TRUE; +} +/* \\\ */ + +/* /// "pciFreeUnit()" */ +void pciFreeUnit(struct PCIUnit *hu) +{ + struct PCIDevice *hd = hu->hu_Device; + struct PCIController *hc; + + struct TagItem pciDeactivate[] = { + {aHidd_PCIDevice_isIO, FALSE}, + {aHidd_PCIDevice_isMEM, FALSE}, + {aHidd_PCIDevice_isMaster, FALSE}, + {TAG_DONE, 0UL}, + }; + + KPRINTF(10, ("*** pciFreeUnit(%p) ***\n", hu)); + + // put em offline + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + hc->hc_Flags &= ~HCF_ONLINE; + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } + + FreeController(hc, hu); + + // FIXME: (x/e/o/u)hciFree routines actually ONLY stops the chip NOT + // free anything as below... + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + if (hc->hc_PCIMem) + { + HIDD_PCIDriver_FreePCIMem(hc->hc_PCIDriverObject, + hc->hc_PCIMem); + hc->hc_PCIMem = NULL; + } + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } + + // disable and free board + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + // deactivate busmaster and IO/Mem + OOP_SetAttrs(hc->hc_PCIDeviceObject, (struct TagItem *)pciDeactivate); + if (hc->hc_PCIIntHandler.is_Node.ln_Name) + { + RemIntServer(INTB_KERNEL + hc->hc_PCIIntLine, + &hc->hc_PCIIntHandler); + hc->hc_PCIIntHandler.is_Node.ln_Name = NULL; + } + hc = (struct PCIController *)hc->hc_Node.ln_Succ; + } +} +/* \\\ */ + +/* /// "pciExpunge()" */ +void pciExpunge(struct PCIDevice *hd) +{ + struct PCIController *hc; + struct PCIUnit *hu; + + KPRINTF(10, ("*** pciExpunge(%p) ***\n", hd)); + + hu = (struct PCIUnit *)hd->hd_Units.lh_Head; + while (((struct Node *)hu)->ln_Succ) + { + Remove((struct Node *)hu); + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + Remove(&hc->hc_Node); + FreePooled(hd->hd_MemPool, hc, sizeof(struct PCIController)); + hc = (struct PCIController *)hu->hu_Controllers.lh_Head; + } + FreePooled(hd->hd_MemPool, hu, sizeof(struct PCIUnit)); + hu = (struct PCIUnit *)hd->hd_Units.lh_Head; + } + if (hd->hd_PCIHidd) + { + struct OOP_ABDescr attrbases[] = { + {(STRPTR) IID_Hidd, &hd->hd_HiddAB}, + {(STRPTR) IID_Hidd_PCIDevice, &hd->hd_HiddPCIDeviceAB}, + {NULL, NULL} + }; + + OOP_ReleaseAttrBases(attrbases); + + OOP_DisposeObject(hd->hd_PCIHidd); + } +} +/* \\\ */ + +/* /// "pciGetPhysical()" */ +APTR pciGetPhysical(struct PCIController *hc, APTR virtaddr) +{ + //struct PCIDevice *hd = hc->hc_Device; + return HIDD_PCIDriver_CPUtoPCI(hc->hc_PCIDriverObject, virtaddr); +} +/* \\\ */ + +/* + * Process some AROS-specific arguments. + * 'usbpoweron' helps to bring up USB ports on IntelMac, + * whose firmware sets them up incorrectly. + */ +static int getArguments(struct PCIDevice *base) +{ + APTR BootLoaderBase; +#ifdef HAVE_ACPI + struct ACPIBase *ACPIBase; + + ACPIBase = OpenResource("acpi.resource"); + if (ACPIBase) + { + /* + * Use ACPI IDs to identify known machines which need HDF_FORCEPOWER + * to work. Currently we know only MacMini. + */ + struct ACPI_TABLE_DEF_HEADER *dsdt = + ACPI_FindSDT(ACPI_MAKE_ID('D', 'S', 'D', 'T')); + + if (dsdt) + { + /* Yes, the last byte in ID is zero */ + if (strcmp(dsdt->oem_table_id, "Macmini") == 0) + { + base->hd_Flags = HDF_FORCEPOWER; + return TRUE; + } + } + } +#endif + + BootLoaderBase = OpenResource("bootloader.resource"); + if (BootLoaderBase) + { + struct List *args = GetBootInfo(BL_Args); + + if (args) + { + struct Node *node; + + for (node = args->lh_Head; node->ln_Succ; node = node->ln_Succ) + { + if (stricmp(node->ln_Name, "forceusbpower") == 0) + { + base->hd_Flags = HDF_FORCEPOWER; + break; + } + } + } + } + + return TRUE; +} + +ADD2INITLIB(getArguments, 10) diff --git a/rom/usb/pciusbhc/ohci/pci.h b/rom/usb/pciusbhc/ohci/pci.h new file mode 100644 index 0000000000..7bfcfe6335 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/pci.h @@ -0,0 +1,56 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#ifndef PCI_H +#define PCI_H + +#include + +#undef SYNC +#ifdef __powerpc__ +#define SYNC asm volatile("eieio"); +#else +#define SYNC +#endif + +#define READMEM16_LE(rb) AROS_WORD2LE(*((volatile UWORD *) (rb))) +#define READMEM32_LE(rb) AROS_LONG2LE(*((volatile ULONG *) (rb))) +#define WRITEMEM32_LE(adr, value) \ + *((volatile ULONG *) (adr)) = AROS_LONG2LE(value) +#define CONSTWRITEMEM32_LE(adr, value) \ + *((volatile ULONG *) (adr)) = AROS_LONG2LE(value) + +#define CONSTWRITEREG16_LE(rb, offset, value) \ + *((volatile UWORD *) (((UBYTE *) (rb)) + ((ULONG) (offset)))) = \ + AROS_WORD2LE(value) +#define CONSTWRITEREG32_LE(rb, offset, value) \ + *((volatile ULONG *) (((UBYTE *) (rb)) + ((ULONG) (offset)))) = \ + AROS_LONG2LE(value) +#define WRITEREG16_LE(rb, offset, value) \ + *((volatile UWORD *) (((UBYTE *) (rb)) + ((ULONG) (offset)))) = \ + AROS_WORD2LE(value) +#define WRITEREG32_LE(rb, offset, value) \ + *((volatile ULONG *) (((UBYTE *) (rb)) + ((ULONG) (offset)))) = \ + AROS_LONG2LE(value) +#define WRITEREG64_LE(rb, offset, value) \ + *((volatile UQUAD *) (((UBYTE *) (rb)) + ((ULONG) (offset)))) = \ + AROS_QUAD2LE(value) + +#define READREG16_LE(rb, offset) \ + AROS_WORD2LE(*((volatile UWORD *) (((UBYTE *) (rb)) + ((ULONG) (offset))))) +#define READREG32_LE(rb, offset) \ + AROS_LONG2LE(*((volatile ULONG *) (((UBYTE *) (rb)) + ((ULONG) (offset))))) +#define READREG64_LE(rb, offset) \ + AROS_QUAD2LE(*((volatile UQUAD *) (((UBYTE *) (rb)) + ((ULONG) (offset))))) + +#define READIO16_LE(rb, offset) \ + AROS_WORD2LE(WORDIN((((UBYTE *) (rb)) + ((ULONG) (offset))))) +#define WRITEIO16_LE(rb, offset, value) \ + WORDOUT((((UBYTE *) (rb)) + ((ULONG) (offset))), AROS_WORD2LE(value)) +#define WRITEIO32_LE(rb, offset, value) \ + LONGOUT((((UBYTE *) (rb)) + ((ULONG) (offset))), AROS_WORD2LE(value)) + +#endif // PCI_H diff --git a/rom/usb/pciusbhc/ohci/pci_protos.h b/rom/usb/pciusbhc/ohci/pci_protos.h new file mode 100644 index 0000000000..b8311e7a6b --- /dev/null +++ b/rom/usb/pciusbhc/ohci/pci_protos.h @@ -0,0 +1,18 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#ifndef PCI_PROTOS_H +#define PCI_PROTOS_H + +#include "dev.h" + +BOOL pciInit(struct PCIDevice *hd); +void pciExpunge(struct PCIDevice *hd); +BOOL pciAllocUnit(struct PCIUnit *hu); +void pciFreeUnit(struct PCIUnit *hu); +APTR pciGetPhysical(struct PCIController *hc, APTR virtaddr); + +#endif /* PCI_PROTOS_H */ diff --git a/rom/usb/pciusbhc/ohci/roothub.c b/rom/usb/pciusbhc/ohci/roothub.c new file mode 100644 index 0000000000..49bdee0d8e --- /dev/null +++ b/rom/usb/pciusbhc/ohci/roothub.c @@ -0,0 +1,694 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#include +#include +#include +#include + +#include + +#include "debug.h" +#include "chip.h" +#include "pci.h" + +#include "cmd_protos.h" +#include "chip_protos.h" + +/* we cannot use AROS_WORD2LE in struct initializer */ +#if AROS_BIG_ENDIAN +#define WORD2LE(w) (UWORD)(((w) >> 8) & 0x00FF) | (((w) << 8) & 0xFF00) +#else +#define WORD2LE(w) (w) +#endif + +/* Root hub data */ +static const struct UsbStdDevDesc RHDevDesc = +{ + sizeof(struct UsbStdDevDesc), UDT_DEVICE, WORD2LE(0x0110), + HUB_CLASSCODE, 0, 0, 8, WORD2LE(0x0000), WORD2LE(0x0000), + WORD2LE(0x0100), 1, 2, 0, 1 +}; + +static const struct UsbStdCfgDesc RHCfgDesc = +{ + 9, UDT_CONFIGURATION, WORD2LE(9 + 9 + 7), 1, 1, 3, + USCAF_ONE | USCAF_SELF_POWERED, 0 +}; +static const struct UsbStdIfDesc RHIfDesc = + {9, UDT_INTERFACE, 0, 0, 1, HUB_CLASSCODE, 0, 0, 4}; +static const struct UsbStdEPDesc RHEPDesc = + {7, UDT_ENDPOINT, URTF_IN | 1, USEAF_INTERRUPT, WORD2LE(8), 255}; +static const struct UsbHubDesc RHHubDesc = +{ + 9, + UDT_HUB, + 0, + WORD2LE(UHCF_INDIVID_POWER | UHCF_INDIVID_OVP), + 0, + 1, + 1, + 0 +}; + +static const CONST_STRPTR RHStrings[] = +{ + "Chris Hodges", "PCI Root Hub Unit x", "Standard Config", + "Hub interface" +}; + +/* /// "cmdControlXFerRootHub()" */ +WORD cmdControlXFerRootHub(struct IOUsbHWReq *ioreq, + struct PCIUnit *unit, struct PCIDevice *base) +{ + struct PCIController *hc; + struct PCIController *chc; + UWORD rt = ioreq->iouh_SetupData.bmRequestType; + UWORD req = ioreq->iouh_SetupData.bRequest; + UWORD idx = AROS_WORD2LE(ioreq->iouh_SetupData.wIndex); + UWORD val = AROS_WORD2LE(ioreq->iouh_SetupData.wValue); + UWORD len = AROS_WORD2LE(ioreq->iouh_SetupData.wLength); + UWORD hciport, i; + ULONG numports = unit->hu_RootHubPorts, reg_val, flag; + BOOL cmdgood; + + if (ioreq->iouh_Endpoint) + { + return UHIOERR_STALL; + } + + if (len != ioreq->iouh_Length) + { + KPRINTF(20, ("RH: Len (%ld != %ld) mismatch!\n", + len != ioreq->iouh_Length)); + return UHIOERR_STALL; + } + switch (rt) + { + case (URTF_STANDARD | URTF_DEVICE): + switch (req) + { + case USR_SET_ADDRESS: + KPRINTF(1, ("RH: SetAddress = %ld\n", val)); + unit->hu_RootHubAddr = val; + ioreq->iouh_Actual = len; + return 0; + + case USR_SET_CONFIGURATION: + KPRINTF(1, ("RH: SetConfiguration=%ld\n", val)); + ioreq->iouh_Actual = len; + return 0; + } + break; + + case (URTF_IN | URTF_STANDARD | URTF_DEVICE): + switch (req) + { + case USR_GET_STATUS: + { + UWORD *mptr = ioreq->iouh_Data; + if (len != sizeof(struct UsbPortStatus)) + { + return UHIOERR_STALL; + } + if ((!idx) && (idx > numports)) + { + KPRINTF(20, ("Port %ld out of range\n", idx)); + return UHIOERR_STALL; + } + hc = unit->hu_PortMap11[idx - 1]; + hciport = unit->hu_PortNum11[idx - 1]; + { + UWORD portreg = OHCI_PORTSTATUS + (hciport << 2); + ULONG oldval = READREG32_LE(hc->hc_RegBase, portreg); + + *mptr = AROS_WORD2LE(TranslatePortFlags(oldval, + OHPF_PORTPOWER | OHPF_OVERCURRENT + | OHPF_PORTCONNECTED | OHPF_PORTENABLE + | OHPF_LOWSPEED | OHPF_PORTRESET + | OHPF_PORTSUSPEND)); + + KPRINTF(5, ("OHCI Port %ld (glob. %ld) is %s\n", + hciport, idx, + oldval & OHPF_LOWSPEED ? "LOWSPEED" : + "FULLSPEED")); + KPRINTF(5, ("OHCI Port %ld Status %08lx (%08lx)\n", idx, + *mptr, oldval)); + + mptr++; + hc->hc_PortChangeMap[hciport] |= + TranslatePortFlags(oldval, + OHPF_OVERCURRENTCHG | OHPF_RESETCHANGE | + OHPF_ENABLECHANGE | OHPF_CONNECTCHANGE | + OHPF_RESUMEDTX); + *mptr = AROS_WORD2LE(hc->hc_PortChangeMap[hciport]); + KPRINTF(5, ("OHCI Port %ld Change %08lx\n", idx, + *mptr)); + return 0; + } + + return 0; + } + + case USR_GET_DESCRIPTOR: + switch (val >> 8) + { + case UDT_DEVICE: + KPRINTF(1, ("RH: GetDeviceDescriptor (%ld)\n", len)); + ioreq->iouh_Actual = + (len > + sizeof(struct UsbStdDevDesc)) ? sizeof(struct + UsbStdDevDesc) : len; + CopyMem((APTR) & RHDevDesc, ioreq->iouh_Data, + ioreq->iouh_Actual); + if (ioreq->iouh_Length >= sizeof(struct UsbStdDevDesc)) + { + if (unit->hu_RootHub20Ports) + { + struct UsbStdDevDesc *usdd = + (struct UsbStdDevDesc *)ioreq->iouh_Data; + + // signal a highspeed root hub + usdd->bcdUSB = AROS_WORD2LE(0x0200); + + usdd->bDeviceProtocol = 1; // single TT + } + } + return 0; + + case UDT_CONFIGURATION: + { + UBYTE tmpbuf[9 + 9 + 7]; + KPRINTF(1, ("RH: GetConfigDescriptor (%ld)\n", len)); + CopyMem((APTR) & RHCfgDesc, tmpbuf, 9); + CopyMem((APTR) & RHIfDesc, &tmpbuf[9], 9); + CopyMem((APTR) & RHEPDesc, &tmpbuf[9 + 9], 7); + if (unit->hu_RootHub20Ports) + { + struct UsbStdEPDesc *usepd = + (struct UsbStdEPDesc *)&tmpbuf[9 + 9]; + usepd->bInterval = 12; // 2048 µFrames + } + ioreq->iouh_Actual = + (len > 9 + 9 + 7) ? 9 + 9 + 7 : len; + CopyMem(tmpbuf, ioreq->iouh_Data, ioreq->iouh_Actual); + return 0; + } + + case UDT_STRING: + if (val & 0xff) /* get lang array */ + { + CONST_STRPTR source = NULL; + UWORD *mptr = ioreq->iouh_Data; + UWORD slen = 1; + KPRINTF(1, ("RH: GetString %04lx (%ld)\n", val, len)); + if ((val & 0xff) > 4) /* index too high? */ + { + return UHIOERR_STALL; + } + source = RHStrings[(val & 0xff) - 1]; + if (len > 1) + { + ioreq->iouh_Actual = 2; + while (*source++) + { + slen++; + } + source = RHStrings[(val & 0xff) - 1]; + *mptr++ = AROS_WORD2BE((slen << 9) | UDT_STRING); + while (ioreq->iouh_Actual + 1 < len) + { + // special hack for unit number in root hub string + if (((val & 0xff) == 2) && (source[1] == 0)) + { + *mptr++ = + AROS_WORD2LE('0' + unit->hu_UnitNo); + } + else + { + *mptr++ = AROS_WORD2LE(*source); + } + source++; + ioreq->iouh_Actual += 2; + if (!(*source)) + { + break; + } + } + } + } + else + { + UWORD *mptr = ioreq->iouh_Data; + KPRINTF(1, ("RH: GetLangArray %04lx (%ld)\n", val, + len)); + if (len > 1) + { + ioreq->iouh_Actual = 2; + mptr[0] = AROS_WORD2BE((4 << 8) | UDT_STRING); + if (len > 3) + { + ioreq->iouh_Actual += 2; + mptr[1] = AROS_WORD2LE(0x0409); + } + } + } + return 0; + + default: + KPRINTF(1, ("RH: Unsupported Descriptor %04lx\n", idx)); + } + break; + + case USR_GET_CONFIGURATION: + if (len == 1) + { + KPRINTF(1, ("RH: GetConfiguration\n")); + ((UBYTE *) ioreq->iouh_Data)[0] = 1; + ioreq->iouh_Actual = len; + return 0; + } + break; + } + break; + + case (URTF_CLASS | URTF_OTHER): + switch (req) + { + case USR_SET_FEATURE: + if ((!idx) && (idx > numports)) + { + KPRINTF(20, ("Port %ld out of range\n", idx)); + return UHIOERR_STALL; + } + chc = unit->hu_PortMap11[idx - 1]; + hc = chc; + hciport = unit->hu_PortNum11[idx - 1]; + KPRINTF(10, + ("Set Feature %ld maps from global Port %ld " + "to local Port %ld\n", + val, idx, hciport)); + cmdgood = TRUE; + UWORD portreg = OHCI_PORTSTATUS + (hciport << 2); + if (val == UFS_PORT_RESET) + { + KPRINTF(10, ("Resetting Port (%s)\n", + READREG32_LE(hc->hc_RegBase, + portreg) & OHPF_PORTRESET ? "already" : "ok")); + // make sure we have at least 50ms of reset time here, + // as required for a root hub port + for (i = 0; i < 5; i++) + { + WRITEREG32_LE(hc->hc_RegBase, portreg, OHPF_PORTRESET); + DelayMS(10, unit); + } + DelayMS(5, unit); + + ULONG oldval = READREG32_LE(hc->hc_RegBase, portreg); + KPRINTF(10, ("OHCI Reset release (%s %s)\n", + oldval & OHPF_PORTRESET ? "didn't turn off" : + "okay", + oldval & OHPF_PORTENABLE ? "enabled" : + "not enabled")); + if (oldval & OHPF_PORTRESET) + { + DelayMS(40, unit); + oldval = READREG32_LE(hc->hc_RegBase, portreg); + KPRINTF(10, ("OHCI Reset 2nd release (%s %s)\n", + oldval & OHPF_PORTRESET ? "didn't turn off" : + "okay", + oldval & OHPF_PORTENABLE ? "enabled" : + "still not enabled")); + } + // make enumeration possible + unit->hu_DevControllers[0] = hc; + return 0; + } + else + { + switch (val) + { + /* case UFS_PORT_CONNECTION: not possible */ + case UFS_PORT_ENABLE: + KPRINTF(10, ("Enabling Port (%s)\n", + oldval & OHPF_PORTENABLE ? "already" : "ok")); + reg_val = OHPF_PORTENABLE; + break; + + case UFS_PORT_SUSPEND: + KPRINTF(10, ("Suspending Port (%s)\n", + oldval & OHPF_PORTSUSPEND ? "already" : "ok")); + reg_val = OHPF_PORTSUSPEND; + break; + + /* case UFS_PORT_OVER_CURRENT: not possible */ + case UFS_PORT_POWER: + KPRINTF(10, ("Powering Port (%s)\n", + oldval & OHPF_PORTPOWER ? "already" : "ok")); + reg_val = OHPF_PORTPOWER; + break; + + /* case UFS_PORT_LOW_SPEED: not possible */ + /* case UFS_C_PORT_CONNECTION: + case UFS_C_PORT_ENABLE: + case UFS_C_PORT_SUSPEND: + case UFS_C_PORT_OVER_CURRENT: + case UFS_C_PORT_RESET: */ + default: + cmdgood = FALSE; + } + if (cmdgood) + { + WRITEREG32_LE(hc->hc_RegBase, portreg, reg_val); + return 0; + } + break; + } + + break; + + case USR_CLEAR_FEATURE: + if ((!idx) && (idx > numports)) + { + KPRINTF(20, ("Port %ld out of range\n", idx)); + return UHIOERR_STALL; + } + if (unit->hu_EhciOwned[idx - 1]) + { + hc = unit->hu_PortMap20[idx - 1]; + hciport = idx - 1; + } + else + { + hc = unit->hu_PortMap11[idx - 1]; + hciport = unit->hu_PortNum11[idx - 1]; + } + KPRINTF(10, + ("Clear Feature %ld maps from global Port %ld " + "to local Port %ld\n", + val, idx, hciport)); + cmdgood = TRUE; + flag = 0; + { + UWORD portreg = OHCI_PORTSTATUS + (hciport << 2); + ULONG __unused oldval = + READREG32_LE(hc->hc_RegBase, portreg); + + switch (val) + { + case UFS_PORT_ENABLE: + KPRINTF(10, ("Disabling Port (%s)\n", + oldval & OHPF_PORTENABLE ? "ok" : "already")); + reg_val = OHPF_PORTDISABLE; + break; + + case UFS_PORT_SUSPEND: + KPRINTF(10, ("Resuming Port (%s)\n", + oldval & OHPF_PORTSUSPEND ? "ok" : "already")); + //flag = UPSF_PORT_SUSPEND; // manually fake suspend change + reg_val = OHPF_RESUME; + break; + + case UFS_PORT_POWER: + KPRINTF(10, ("Unpowering Port (%s)\n", + oldval & OHPF_PORTPOWER ? "ok" : "already")); + reg_val = OHPF_PORTUNPOWER; + break; + + case UFS_C_PORT_CONNECTION: + reg_val = OHPF_CONNECTCHANGE; + flag = UPSF_PORT_CONNECTION; + break; + + case UFS_C_PORT_ENABLE: + reg_val = OHPF_ENABLECHANGE; + flag = UPSF_PORT_ENABLE; + break; + + case UFS_C_PORT_SUSPEND: + reg_val = OHPF_RESUMEDTX; + flag = UPSF_PORT_SUSPEND; + break; + + case UFS_C_PORT_OVER_CURRENT: + reg_val = OHPF_OVERCURRENTCHG; + flag = UPSF_PORT_OVER_CURRENT; + break; + + case UFS_C_PORT_RESET: + reg_val = OHPF_RESETCHANGE; + flag = UPSF_PORT_RESET; + break; + default: + cmdgood = FALSE; + } + if (cmdgood) + { + WRITEREG32_LE(hc->hc_RegBase, portreg, reg_val); + hc->hc_PortChangeMap[hciport] &= ~flag; + return 0; + } + break; + } + + break; + } + break; + + case (URTF_IN | URTF_CLASS | URTF_OTHER): + switch (req) + { + case USR_GET_STATUS: + { + UWORD *mptr = ioreq->iouh_Data; + if (len != sizeof(struct UsbPortStatus)) + { + return UHIOERR_STALL; + } + if ((!idx) && (idx > numports)) + { + KPRINTF(20, ("Port %ld out of range\n", idx)); + return UHIOERR_STALL; + } + if (unit->hu_EhciOwned[idx - 1]) + { + hc = unit->hu_PortMap20[idx - 1]; + hciport = idx - 1; + } + else + { + hc = unit->hu_PortMap11[idx - 1]; + hciport = unit->hu_PortNum11[idx - 1]; + } + { + UWORD portreg = OHCI_PORTSTATUS + (hciport << 2); + ULONG oldval = READREG32_LE(hc->hc_RegBase, portreg); + + *mptr = 0; + *mptr = AROS_WORD2LE(TranslatePortFlags(oldval, + OHPF_PORTPOWER | OHPF_OVERCURRENT + | OHPF_PORTCONNECTED | OHPF_PORTENABLE + | OHPF_LOWSPEED | OHPF_PORTRESET + | OHPF_PORTSUSPEND)); + + KPRINTF(5, ("OHCI Port %ld (glob. %ld) is %s\n", + hciport, idx, + oldval & OHPF_LOWSPEED ? "LOWSPEED" : + "FULLSPEED")); + KPRINTF(5, ("OHCI Port %ld Status %08lx (%08lx)\n", idx, + *mptr, oldval)); + + mptr++; + hc->hc_PortChangeMap[hciport] |= + TranslatePortFlags(oldval, + OHPF_OVERCURRENTCHG | OHPF_RESETCHANGE + | OHPF_ENABLECHANGE | OHPF_CONNECTCHANGE + | OHPF_RESUMEDTX); + *mptr = AROS_WORD2LE(hc->hc_PortChangeMap[hciport]); + KPRINTF(5, ("OHCI Port %ld Change %08lx\n", idx, + *mptr)); + return 0; + } + + return 0; + } + + } + break; + + case (URTF_IN | URTF_CLASS | URTF_DEVICE): + switch (req) + { + case USR_GET_STATUS: + { + UWORD *mptr = ioreq->iouh_Data; + if (len < sizeof(struct UsbHubStatus)) + { + return UHIOERR_STALL; + } + *mptr++ = 0; + *mptr++ = 0; + ioreq->iouh_Actual = 4; + return 0; + } + + case USR_GET_DESCRIPTOR: + switch (val >> 8) + { + case UDT_HUB: + { + ULONG hubdesclen = 9; + ULONG powergood = 1; + + struct UsbHubDesc *uhd = + (struct UsbHubDesc *)ioreq->iouh_Data; + KPRINTF(1, ("RH: GetHubDescriptor (%ld)\n", len)); + + if (unit->hu_RootHubPorts > 7) + { + hubdesclen += 2; // needs two bytes for port masks + } + + ioreq->iouh_Actual = + (len > hubdesclen) ? hubdesclen : len; + CopyMem((APTR) & RHHubDesc, ioreq->iouh_Data, + ioreq->iouh_Actual); + + if (ioreq->iouh_Length) + { + uhd->bLength = hubdesclen; + } + + if (ioreq->iouh_Length >= 6) + { + hc = (struct PCIController *)unit-> + hu_Controllers.lh_Head; + while (hc->hc_Node.ln_Succ) + { + { + ULONG localpwgood = + (READREG32_LE(hc->hc_RegBase, + OHCI_HUBDESCA) & OHAM_POWERGOOD) >> + OHAS_POWERGOOD; + if (localpwgood > powergood) + { + powergood = localpwgood; + KPRINTF(10, + ("Increasing power good time to %ld\n", + powergood)); + } + } + hc = (struct PCIController *)hc-> + hc_Node.ln_Succ; + } + + uhd->bPwrOn2PwrGood = powergood; + } + if (ioreq->iouh_Length >= hubdesclen) + { + uhd->bNbrPorts = unit->hu_RootHubPorts; + if (hubdesclen == 9) + { + uhd->DeviceRemovable = 0; + uhd->PortPwrCtrlMask = + (1 << (unit->hu_RootHubPorts + 2)) - 2; + } + else + { + // each field is now 16 bits wide + uhd->DeviceRemovable = 0; + uhd->PortPwrCtrlMask = 0; + ((UBYTE *) ioreq->iouh_Data)[9] = + (1 << (unit->hu_RootHubPorts + 2)) - 2; + ((UBYTE *) ioreq->iouh_Data)[10] = + ((1 << (unit->hu_RootHubPorts + 2)) - + 2) >> 8; + } + } + return 0; + } + + default: + KPRINTF(20, ("RH: Unsupported Descriptor %04lx\n", idx)); + } + break; + } + + } + KPRINTF(20, ("RH: Unsupported command %02x %02x %04x %04x %04x!\n", + (UWORD) rt, (UWORD) req, idx, val, len)); + return UHIOERR_STALL; +} +/* \\\ */ + +/* /// "cmdIntXFerRootHub()" */ +WORD cmdIntXFerRootHub(struct IOUsbHWReq * ioreq, + struct PCIUnit * unit, struct PCIDevice * base) +{ + if ((ioreq->iouh_Endpoint != 1) || (!ioreq->iouh_Length)) + { + return UHIOERR_STALL; + } + + if (unit->hu_RootPortChanges) + { + KPRINTF(1, ("Immediate Portchange map %04lx\n", + unit->hu_RootPortChanges)); + if ((unit->hu_RootHubPorts < 8) || (ioreq->iouh_Length == 1)) + { + *((UBYTE *) ioreq->iouh_Data) = unit->hu_RootPortChanges; + ioreq->iouh_Actual = 1; + } + else + { + ((UBYTE *) ioreq->iouh_Data)[0] = unit->hu_RootPortChanges; + ((UBYTE *) ioreq->iouh_Data)[1] = unit->hu_RootPortChanges >> 8; + ioreq->iouh_Actual = 2; + } + unit->hu_RootPortChanges = 0; + return 0; + } + ioreq->iouh_Req.io_Flags &= ~IOF_QUICK; + Disable(); + AddTail(&unit->hu_RHIOQueue, (struct Node *)ioreq); + Enable(); + return RC_DONTREPLY; +} +/* \\\ */ + +/* /// "CheckRootHubChanges()" */ +void CheckRootHubChanges(struct PCIUnit *unit) +{ + struct IOUsbHWReq *ioreq; + + if (unit->hu_RootPortChanges && unit->hu_RHIOQueue.lh_Head->ln_Succ) + { + KPRINTF(1, ("Portchange map %04lx\n", unit->hu_RootPortChanges)); + Disable(); + ioreq = (struct IOUsbHWReq *)unit->hu_RHIOQueue.lh_Head; + while (((struct Node *)ioreq)->ln_Succ) + { + Remove(&ioreq->iouh_Req.io_Message.mn_Node); + if ((unit->hu_RootHubPorts < 8) || (ioreq->iouh_Length == 1)) + { + *((UBYTE *) ioreq->iouh_Data) = unit->hu_RootPortChanges; + ioreq->iouh_Actual = 1; + } + else + { + ((UBYTE *) ioreq->iouh_Data)[0] = unit->hu_RootPortChanges; + ((UBYTE *) ioreq->iouh_Data)[1] = + unit->hu_RootPortChanges >> 8; + ioreq->iouh_Actual = 2; + } + ReplyMsg(&ioreq->iouh_Req.io_Message); + ioreq = (struct IOUsbHWReq *)unit->hu_RHIOQueue.lh_Head; + } + unit->hu_RootPortChanges = 0; + Enable(); + } +} +/* \\\ */ diff --git a/rom/usb/pciusbhc/ohci/roothub_protos.h b/rom/usb/pciusbhc/ohci/roothub_protos.h new file mode 100644 index 0000000000..e9702c0851 --- /dev/null +++ b/rom/usb/pciusbhc/ohci/roothub_protos.h @@ -0,0 +1,18 @@ +/* + Copyright © 2002-2009, Chris Hodges. All rights reserved. + Copyright © 2009-2012, The AROS Development Team. All rights reserved. + $Id$ +*/ + +#ifndef ROOTHUB_H +#define ROOTHUB_H + +#include "dev.h" + +void CheckRootHubChanges(struct PCIUnit *unit); +WORD cmdControlXFerRootHub(struct IOUsbHWReq *ioreq, + struct PCIUnit *unit, struct PCIDevice *base); +WORD cmdIntXFerRootHub(struct IOUsbHWReq *ioreq, + struct PCIUnit *unit, struct PCIDevice *base); + +#endif /* ROOTHUB_H */ -- 2.11.4.GIT