2016-11-25 17:18:40 +01:00
|
|
|
/*
|
|
|
|
* USB Open Host Controller Interface (Ohci) driver
|
|
|
|
*
|
|
|
|
* BUGS:
|
|
|
|
* - Missing isochronous input streams.
|
|
|
|
* - Too many delays and ilocks.
|
|
|
|
* - bandwidth admission control must be done per-frame.
|
|
|
|
* - Buffering could be handled like in uhci, to avoid
|
|
|
|
* needed block allocation and avoid allocs for small Tds.
|
|
|
|
* - must warn of power overruns.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "u.h"
|
|
|
|
#include "../port/lib.h"
|
|
|
|
#include "mem.h"
|
|
|
|
#include "dat.h"
|
|
|
|
#include "fns.h"
|
|
|
|
#include "io.h"
|
|
|
|
#include "../port/error.h"
|
|
|
|
|
|
|
|
#include "../port/usb.h"
|
|
|
|
|
|
|
|
typedef struct Ctlio Ctlio;
|
|
|
|
typedef struct Ctlr Ctlr;
|
|
|
|
typedef struct Ed Ed;
|
|
|
|
typedef struct Edpool Edpool;
|
|
|
|
typedef struct Epx Epx;
|
|
|
|
typedef struct Hcca Hcca;
|
|
|
|
typedef struct Isoio Isoio;
|
|
|
|
typedef struct Ohci Ohci;
|
|
|
|
typedef struct Qio Qio;
|
|
|
|
typedef struct Qtree Qtree;
|
|
|
|
typedef struct Td Td;
|
|
|
|
typedef struct Tdpool Tdpool;
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
Incr = 64, /* for Td and Ed pools */
|
|
|
|
|
|
|
|
Edalign = 0x10,
|
|
|
|
Tdalign = 0x20,
|
|
|
|
|
|
|
|
Abortdelay = 1, /* delay after cancelling Tds (ms) */
|
|
|
|
Tdatomic = 8, /* max nb. of Tds per bulk I/O op. */
|
|
|
|
Enabledelay = 100, /* waiting for a port to enable */
|
|
|
|
|
|
|
|
|
|
|
|
/* Queue states (software) */
|
|
|
|
Qidle = 0,
|
|
|
|
Qinstall,
|
|
|
|
Qrun,
|
|
|
|
Qdone,
|
|
|
|
Qclose,
|
|
|
|
Qfree,
|
|
|
|
|
|
|
|
/* Ed control bits */
|
|
|
|
Edmpsmask = 0x7ff, /* max packet size */
|
|
|
|
Edmpsshift = 16,
|
|
|
|
Edlow = 1 << 13, /* low speed */
|
|
|
|
Edskip = 1 << 14, /* skip this ed */
|
|
|
|
Ediso = 1 << 15, /* iso Tds used */
|
|
|
|
Edtddir = 0, /* get dir from td */
|
|
|
|
Edin = 2 << 11, /* direction in */
|
|
|
|
Edout = 1 << 11, /* direction out */
|
|
|
|
Eddirmask = 3 << 11, /* direction bits */
|
|
|
|
Edhalt = 1, /* halted (in head ptr) */
|
|
|
|
Edtoggle = 2, /* toggle (in head ptr) 1 == data1 */
|
|
|
|
|
|
|
|
/* Td control bits */
|
|
|
|
Tdround = 1<<18, /* (rounding) short packets ok */
|
|
|
|
Tdtoksetup = 0<<19, /* setup packet */
|
|
|
|
Tdtokin = 2<<19, /* in packet */
|
|
|
|
Tdtokout = 1<<19, /* out packet */
|
|
|
|
Tdtokmask = 3<<19, /* in/out/setup bits */
|
|
|
|
Tdnoioc = 7<<21, /* intr. cnt. value for no interrupt */
|
|
|
|
Tdusetog = 1<<25, /* use toggle from Td (1) or Ed (0) */
|
|
|
|
Tddata1 = 1<<24, /* data toggle (1 == data1) */
|
|
|
|
Tddata0 = 0<<24,
|
|
|
|
Tdfcmask = 7, /* frame count (iso) */
|
|
|
|
Tdfcshift = 24,
|
|
|
|
Tdsfmask = 0xFFFF, /* starting frame (iso) */
|
|
|
|
Tderrmask = 3, /* error counter */
|
|
|
|
Tderrshift = 26,
|
|
|
|
Tdccmask = 0xf, /* condition code (status) */
|
|
|
|
Tdccshift = 28,
|
|
|
|
Tdiccmask = 0xf, /* condition code (iso, offsets) */
|
|
|
|
Tdiccshift = 12,
|
|
|
|
|
|
|
|
Ntdframes = 0x10000, /* # of different iso frame numbers */
|
|
|
|
|
|
|
|
/* Td errors (condition code) */
|
|
|
|
Tdok = 0,
|
|
|
|
Tdcrc = 1,
|
|
|
|
Tdbitstuff = 2,
|
|
|
|
Tdbadtog = 3,
|
|
|
|
Tdstalled = 4,
|
|
|
|
Tdtmout = 5,
|
|
|
|
Tdpidchk = 6,
|
|
|
|
Tdbadpid = 7,
|
|
|
|
Tddataovr = 8,
|
|
|
|
Tddataund = 9,
|
|
|
|
Tdbufovr = 0xC,
|
|
|
|
Tdbufund = 0xD,
|
|
|
|
Tdnotacc = 0xE,
|
|
|
|
|
|
|
|
/* control register */
|
|
|
|
Cple = 0x04, /* periodic list enable */
|
|
|
|
Cie = 0x08, /* iso. list enable */
|
|
|
|
Ccle = 0x10, /* ctl list enable */
|
|
|
|
Cble = 0x20, /* bulk list enable */
|
|
|
|
Cir = 0x100, /* interrupt routing (smm active) */
|
|
|
|
Cfsmask = 3 << 6, /* functional state... */
|
|
|
|
Cfsreset = 0 << 6,
|
|
|
|
Cfsresume = 1 << 6,
|
|
|
|
Cfsoper = 2 << 6,
|
|
|
|
Cfssuspend = 3 << 6,
|
|
|
|
|
|
|
|
/* command status */
|
|
|
|
Socr = 1 << 3, /* ownership change request */
|
|
|
|
Sblf = 1 << 2, /* bulk list (load) flag */
|
|
|
|
Sclf = 1 << 1, /* control list (load) flag */
|
|
|
|
Shcr = 1 << 0, /* host controller reset */
|
|
|
|
|
|
|
|
/* intr enable */
|
|
|
|
Mie = 1 << 31,
|
|
|
|
Oc = 1 << 30,
|
|
|
|
Rhsc = 1 << 6,
|
|
|
|
Fno = 1 << 5,
|
|
|
|
Ue = 1 << 4,
|
|
|
|
Rd = 1 << 3,
|
|
|
|
Sf = 1 << 2,
|
|
|
|
Wdh = 1 << 1,
|
|
|
|
So = 1 << 0,
|
|
|
|
|
|
|
|
Fmaxpktmask = 0x7fff,
|
|
|
|
Fmaxpktshift = 16,
|
|
|
|
HcRhDescA_POTPGT_MASK = 0xff << 24,
|
|
|
|
HcRhDescA_POTPGT_SHIFT = 24,
|
|
|
|
|
|
|
|
/* Rh status */
|
|
|
|
Lps = 1 << 0,
|
|
|
|
Cgp = 1 << 0,
|
|
|
|
Oci = 1 << 1,
|
|
|
|
Psm = 1 << 8,
|
|
|
|
Nps = 1 << 9,
|
|
|
|
Drwe = 1 << 15,
|
|
|
|
Srwe = 1 << 15,
|
|
|
|
Lpsc = 1 << 16,
|
|
|
|
Ccic = 1 << 17,
|
|
|
|
Crwe = 1 << 31,
|
|
|
|
|
|
|
|
/* port status */
|
|
|
|
Ccs = 0x00001, /* current connect status */
|
|
|
|
Pes = 0x00002, /* port enable status */
|
|
|
|
Pss = 0x00004, /* port suspend status */
|
|
|
|
Poci = 0x00008, /* over current indicator */
|
|
|
|
Prs = 0x00010, /* port reset status */
|
|
|
|
Pps = 0x00100, /* port power status */
|
|
|
|
Lsda = 0x00200, /* low speed device attached */
|
|
|
|
Csc = 0x10000, /* connect status change */
|
|
|
|
Pesc = 0x20000, /* enable status change */
|
|
|
|
Pssc = 0x40000, /* suspend status change */
|
|
|
|
Ocic = 0x80000, /* over current ind. change */
|
|
|
|
Prsc = 0x100000, /* reset status change */
|
|
|
|
|
|
|
|
/* port status write bits */
|
|
|
|
Cpe = 0x001, /* clear port enable */
|
|
|
|
Spe = 0x002, /* set port enable */
|
|
|
|
Spr = 0x010, /* set port reset */
|
|
|
|
Spp = 0x100, /* set port power */
|
|
|
|
Cpp = 0x200, /* clear port power */
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Endpoint descriptor. (first 4 words used by hardware)
|
|
|
|
*/
|
|
|
|
struct Ed {
|
|
|
|
uint32_t ctrl;
|
|
|
|
uint32_t tail; /* transfer descriptor */
|
|
|
|
uint32_t head;
|
|
|
|
uint32_t nexted;
|
|
|
|
|
|
|
|
Ed* next; /* sw; in free list or next in list */
|
|
|
|
Td* tds; /* in use by current xfer; all for iso */
|
|
|
|
Ep* ep; /* debug/align */
|
|
|
|
Ed* inext; /* debug/align (dump interrupt eds). */
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Endpoint I/O state (software), per direction.
|
|
|
|
*/
|
|
|
|
struct Qio
|
|
|
|
{
|
|
|
|
QLock ql; /* for the entire I/O process */
|
|
|
|
Rendez r; /* wait for completion */
|
|
|
|
Ed* ed; /* to place Tds on it */
|
|
|
|
int sched; /* queue number (intr/iso) */
|
|
|
|
int toggle; /* Tddata0/Tddata1 */
|
|
|
|
uint32_t usbid; /* device/endpoint address */
|
|
|
|
int tok; /* Tdsetup, Tdtokin, Tdtokout */
|
|
|
|
int32_t iotime; /* last I/O time; to hold interrupt polls */
|
|
|
|
int debug; /* for the endpoint */
|
|
|
|
char* err; /* error status */
|
|
|
|
int state; /* Qidle -> Qinstall -> Qrun -> Qdone | Qclose */
|
|
|
|
int32_t bw; /* load (intr/iso) */
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Ctlio
|
|
|
|
{
|
|
|
|
Qio; /* single Ed for all transfers */
|
|
|
|
uint8_t* data; /* read from last ctl req. */
|
|
|
|
int ndata; /* number of bytes read */
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Isoio
|
|
|
|
{
|
|
|
|
Qio;
|
|
|
|
int nframes; /* number of frames for a full second */
|
|
|
|
Td* atds; /* Tds avail for further I/O */
|
|
|
|
int navail; /* number of avail Tds */
|
|
|
|
uint32_t frno; /* next frame number avail for I/O */
|
|
|
|
uint32_t left; /* remainder after rounding Hz to samples/ms */
|
|
|
|
int nerrs; /* consecutive errors on iso I/O */
|
|
|
|
int delay; /* maximum number of frames to buffer */
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Transfer descriptor. Size must be multiple of 32
|
|
|
|
* First block is used by hardware (aligned to 32).
|
|
|
|
*/
|
|
|
|
struct Td
|
|
|
|
{
|
|
|
|
uint32_t ctrl;
|
|
|
|
uint32_t cbp; /* current buffer pointer */
|
|
|
|
uint32_t nexttd;
|
|
|
|
uint32_t be;
|
|
|
|
uint16_t offsets[8]; /* used by Iso Tds only */
|
|
|
|
|
|
|
|
Td* next; /* in free or Ed tds list */
|
|
|
|
Td* anext; /* in avail td list (iso) */
|
|
|
|
Ep* ep; /* using this Td for I/O */
|
|
|
|
Qio* io; /* using this Td for I/O */
|
|
|
|
Block* bp; /* data for this Td */
|
|
|
|
uint32_t nbytes; /* bytes in this Td */
|
|
|
|
uint32_t cbp0; /* initial value for cbp */
|
|
|
|
uint32_t last; /* true for last Td in Qio */
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Host controller communication area (hardware)
|
|
|
|
*/
|
|
|
|
struct Hcca
|
|
|
|
{
|
|
|
|
uint32_t intrtable[32];
|
|
|
|
uint16_t framenumber;
|
|
|
|
uint16_t pad1;
|
|
|
|
uint32_t donehead;
|
|
|
|
uint8_t reserved[116];
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* I/O registers
|
|
|
|
*/
|
|
|
|
struct Ohci
|
|
|
|
{
|
|
|
|
/* control and status group */
|
|
|
|
uint32_t revision; /*00*/
|
|
|
|
uint32_t control; /*04*/
|
|
|
|
uint32_t cmdsts; /*08*/
|
|
|
|
uint32_t intrsts; /*0c*/
|
|
|
|
uint32_t intrenable; /*10*/
|
|
|
|
uint32_t intrdisable; /*14*/
|
|
|
|
|
|
|
|
/* memory pointer group */
|
|
|
|
uint32_t hcca; /*18*/
|
|
|
|
uint32_t periodcurred; /*1c*/
|
|
|
|
uint32_t ctlheaded; /*20*/
|
|
|
|
uint32_t ctlcurred; /*24*/
|
|
|
|
uint32_t bulkheaded; /*28*/
|
|
|
|
uint32_t bulkcurred; /*2c*/
|
|
|
|
uint32_t donehead; /*30*/
|
|
|
|
|
|
|
|
/* frame counter group */
|
|
|
|
uint32_t fminterval; /*34*/
|
|
|
|
uint32_t fmremaining; /*38*/
|
|
|
|
uint32_t fmnumber; /*3c*/
|
|
|
|
uint32_t periodicstart; /*40*/
|
|
|
|
uint32_t lsthreshold; /*44*/
|
|
|
|
|
|
|
|
/* root hub group */
|
|
|
|
uint32_t rhdesca; /*48*/
|
|
|
|
uint32_t rhdescb; /*4c*/
|
|
|
|
uint32_t rhsts; /*50*/
|
|
|
|
uint32_t rhportsts[15]; /*54*/
|
|
|
|
uint32_t pad25[20]; /*90*/
|
|
|
|
|
|
|
|
/* unknown */
|
|
|
|
uint32_t hostueaddr; /*e0*/
|
|
|
|
uint32_t hostuests; /*e4*/
|
|
|
|
uint32_t hosttimeoutctrl; /*e8*/
|
|
|
|
uint32_t pad59; /*ec*/
|
|
|
|
uint32_t pad60; /*f0*/
|
|
|
|
uint32_t hostrevision; /*f4*/
|
|
|
|
uint32_t pad62[2];
|
|
|
|
/*100*/
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Endpoint tree (software)
|
|
|
|
*/
|
|
|
|
struct Qtree
|
|
|
|
{
|
|
|
|
int nel;
|
|
|
|
int depth;
|
|
|
|
uint32_t* bw;
|
|
|
|
Ed** root;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Tdpool
|
|
|
|
{
|
|
|
|
Lock l;
|
|
|
|
Td* free;
|
|
|
|
int nalloc;
|
|
|
|
int ninuse;
|
|
|
|
int nfree;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Edpool
|
|
|
|
{
|
|
|
|
Lock l;
|
|
|
|
Ed* free;
|
|
|
|
int nalloc;
|
|
|
|
int ninuse;
|
|
|
|
int nfree;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Ctlr
|
|
|
|
{
|
|
|
|
Lock l; /* for ilock; lists and basic ctlr I/O */
|
|
|
|
QLock resetl; /* lock controller during USB reset */
|
|
|
|
int active;
|
|
|
|
Ctlr* next;
|
|
|
|
int nports;
|
|
|
|
|
|
|
|
Ohci* ohci; /* base I/O address */
|
|
|
|
Hcca* hcca; /* intr/done Td lists (used by hardware) */
|
|
|
|
int overrun; /* sched. overrun */
|
|
|
|
Ed* intrhd; /* list of intr. eds in tree */
|
|
|
|
Qtree* tree; /* tree for t Ep i/o */
|
|
|
|
int ntree; /* number of dummy Eds in tree */
|
|
|
|
Pcidev* pcidev;
|
|
|
|
};
|
|
|
|
|
2017-04-19 23:33:14 +02:00
|
|
|
#define dqprint if(debug || io && io->debug)jehanne_print
|
|
|
|
#define ddqprint if(debug>1 || (io && io->debug>1))jehanne_print
|
|
|
|
#define diprint if(debug || iso && iso->debug)jehanne_print
|
|
|
|
#define ddiprint if(debug>1 || (iso && iso->debug>1))jehanne_print
|
2016-11-25 17:18:40 +01:00
|
|
|
#define TRUNC(x, sz) ((x) & ((sz)-1))
|
|
|
|
|
|
|
|
static int ohciinterrupts[Nttypes];
|
|
|
|
static char* iosname[] = { "idle", "install", "run", "done", "close", "FREE" };
|
|
|
|
|
|
|
|
static int debug;
|
|
|
|
static Edpool edpool;
|
|
|
|
static Tdpool tdpool;
|
|
|
|
static Ctlr* ctlrs[Nhcis];
|
|
|
|
|
|
|
|
static char* errmsgs[] =
|
|
|
|
{
|
|
|
|
[Tdcrc] "crc error",
|
|
|
|
[Tdbitstuff] "bit stuffing error",
|
|
|
|
[Tdbadtog] "bad toggle",
|
|
|
|
[Tdstalled] Estalled,
|
|
|
|
[Tdtmout] "timeout error",
|
|
|
|
[Tdpidchk] "pid check error",
|
|
|
|
[Tdbadpid] "bad pid",
|
|
|
|
[Tddataovr] "data overrun",
|
|
|
|
[Tddataund] "data underrun",
|
|
|
|
[Tdbufovr] "buffer overrun",
|
|
|
|
[Tdbufund] "buffer underrun",
|
|
|
|
[Tdnotacc] "not accessed"
|
|
|
|
};
|
|
|
|
|
|
|
|
static void*
|
|
|
|
pa2ptr(uint32_t pa)
|
|
|
|
{
|
|
|
|
if(pa == 0)
|
|
|
|
return nil;
|
|
|
|
else
|
|
|
|
return KADDR(pa);
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t
|
|
|
|
ptr2pa(void *p)
|
|
|
|
{
|
|
|
|
if(p == nil)
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
return PADDR(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
waitSOF(Ctlr *ub)
|
|
|
|
{
|
|
|
|
int frame = ub->hcca->framenumber & 0x3f;
|
|
|
|
|
|
|
|
do {
|
|
|
|
delay(2);
|
|
|
|
} while(frame == (ub->hcca->framenumber & 0x3f));
|
|
|
|
}
|
|
|
|
|
|
|
|
static char*
|
|
|
|
errmsg(int err)
|
|
|
|
{
|
|
|
|
|
|
|
|
if(err < nelem(errmsgs))
|
|
|
|
return errmsgs[err];
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Ed*
|
|
|
|
ctlhd(Ctlr *ctlr)
|
|
|
|
{
|
|
|
|
return pa2ptr(ctlr->ohci->ctlheaded);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Ed*
|
|
|
|
bulkhd(Ctlr *ctlr)
|
|
|
|
{
|
|
|
|
return pa2ptr(ctlr->ohci->bulkheaded);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
edlinked(Ed *ed, Ed *next)
|
|
|
|
{
|
|
|
|
if(ed == nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("edlinked: nil ed: pc %#p\n", getcallerpc());
|
2016-11-25 17:18:40 +01:00
|
|
|
ed->nexted = ptr2pa(next);
|
|
|
|
ed->next = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
setctlhd(Ctlr *ctlr, Ed *ed)
|
|
|
|
{
|
|
|
|
ctlr->ohci->ctlheaded = ptr2pa(ed);
|
|
|
|
if(ed != nil)
|
|
|
|
ctlr->ohci->cmdsts |= Sclf; /* reload it on next pass */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
setbulkhd(Ctlr *ctlr, Ed *ed)
|
|
|
|
{
|
|
|
|
ctlr->ohci->bulkheaded = ptr2pa(ed);
|
|
|
|
if(ed != nil)
|
|
|
|
ctlr->ohci->cmdsts |= Sblf; /* reload it on next pass */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
unlinkctl(Ctlr *ctlr, Ed *ed)
|
|
|
|
{
|
|
|
|
Ed *this, *prev, *next;
|
|
|
|
|
|
|
|
ctlr->ohci->control &= ~Ccle;
|
|
|
|
waitSOF(ctlr);
|
|
|
|
this = ctlhd(ctlr);
|
|
|
|
ctlr->ohci->ctlcurred = 0;
|
|
|
|
prev = nil;
|
|
|
|
while(this != nil && this != ed){
|
|
|
|
prev = this;
|
|
|
|
this = this->next;
|
|
|
|
}
|
|
|
|
if(this == nil){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("unlinkctl: not found\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
next = this->next;
|
|
|
|
if(prev == nil)
|
|
|
|
setctlhd(ctlr, next);
|
|
|
|
else
|
|
|
|
edlinked(prev, next);
|
|
|
|
ctlr->ohci->control |= Ccle;
|
|
|
|
edlinked(ed, nil); /* wipe out next field */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
unlinkbulk(Ctlr *ctlr, Ed *ed)
|
|
|
|
{
|
|
|
|
Ed *this, *prev, *next;
|
|
|
|
|
|
|
|
ctlr->ohci->control &= ~Cble;
|
|
|
|
waitSOF(ctlr);
|
|
|
|
this = bulkhd(ctlr);
|
|
|
|
ctlr->ohci->bulkcurred = 0;
|
|
|
|
prev = nil;
|
|
|
|
while(this != nil && this != ed){
|
|
|
|
prev = this;
|
|
|
|
this = this->next;
|
|
|
|
}
|
|
|
|
if(this == nil){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("unlinkbulk: not found\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
next = this->next;
|
|
|
|
if(prev == nil)
|
|
|
|
setbulkhd(ctlr, next);
|
|
|
|
else
|
|
|
|
edlinked(prev, next);
|
|
|
|
ctlr->ohci->control |= Cble;
|
|
|
|
edlinked(ed, nil); /* wipe out next field */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
edsetaddr(Ed *ed, uint32_t addr)
|
|
|
|
{
|
|
|
|
uint32_t ctrl;
|
|
|
|
|
|
|
|
ctrl = ed->ctrl & ~((Epmax<<7)|Devmax);
|
|
|
|
ctrl |= (addr & ((Epmax<<7)|Devmax));
|
|
|
|
ed->ctrl = ctrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0 // NOT USED
|
|
|
|
static void
|
|
|
|
edsettog(Ed *ed, int c)
|
|
|
|
{
|
|
|
|
if(c != 0)
|
|
|
|
ed->head |= Edtoggle;
|
|
|
|
else
|
|
|
|
ed->head &= ~Edtoggle;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int
|
|
|
|
edtoggle(Ed *ed)
|
|
|
|
{
|
|
|
|
return ed->head & Edtoggle;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
edhalted(Ed *ed)
|
|
|
|
{
|
|
|
|
return ed->head & Edhalt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
edmaxpkt(Ed *ed)
|
|
|
|
{
|
|
|
|
return (ed->ctrl >> Edmpsshift) & Edmpsmask;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
edsetmaxpkt(Ed *ed, int m)
|
|
|
|
{
|
|
|
|
uint32_t c;
|
|
|
|
|
|
|
|
c = ed->ctrl & ~(Edmpsmask << Edmpsshift);
|
|
|
|
ed->ctrl = c | ((m&Edmpsmask) << Edmpsshift);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
tderrs(Td *td)
|
|
|
|
{
|
|
|
|
return (td->ctrl >> Tdccshift) & Tdccmask;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
tdtok(Td *td)
|
|
|
|
{
|
|
|
|
return (td->ctrl & Tdtokmask);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Td*
|
|
|
|
tdalloc(void)
|
|
|
|
{
|
|
|
|
uint8_t *pool;
|
|
|
|
Td *td;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
lock(&tdpool.l);
|
|
|
|
if(tdpool.free == nil){
|
|
|
|
ddprint("ohci: tdalloc %d Tds\n", Incr);
|
2017-04-19 23:33:14 +02:00
|
|
|
pool = jehanne_mallocalign(Incr*ROUNDUP(sizeof(Td), Tdalign), Tdalign, 0, 0);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(pool == nil)
|
|
|
|
panic("ohci: tdalloc");
|
|
|
|
for(i=Incr; --i>=0;){
|
|
|
|
td = (Td*)(pool + i*ROUNDUP(sizeof(Td), Tdalign));
|
|
|
|
td->next = tdpool.free;
|
|
|
|
tdpool.free = td;
|
|
|
|
}
|
|
|
|
tdpool.nalloc += Incr;
|
|
|
|
tdpool.nfree += Incr;
|
|
|
|
}
|
|
|
|
tdpool.ninuse++;
|
|
|
|
tdpool.nfree--;
|
|
|
|
td = tdpool.free;
|
|
|
|
tdpool.free = td->next;
|
|
|
|
unlock(&tdpool.l);
|
|
|
|
assert(((uintptr_t)td & 0x1F) == 0);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memset(td, 0, sizeof(Td));
|
2016-11-25 17:18:40 +01:00
|
|
|
return td;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tdfree(Td *td)
|
|
|
|
{
|
|
|
|
if(td == 0)
|
|
|
|
return;
|
|
|
|
freeb(td->bp);
|
|
|
|
td->bp = nil;
|
|
|
|
lock(&tdpool.l);
|
|
|
|
if(td->nexttd == 0x77777777)
|
|
|
|
panic("ohci: tdfree: double free");
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memset(td, 7, sizeof(Td)); /* poison */
|
2016-11-25 17:18:40 +01:00
|
|
|
td->next = tdpool.free;
|
|
|
|
tdpool.free = td;
|
|
|
|
tdpool.ninuse--;
|
|
|
|
tdpool.nfree++;
|
|
|
|
unlock(&tdpool.l);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Ed*
|
|
|
|
edalloc(void)
|
|
|
|
{
|
|
|
|
uint8_t *pool;
|
|
|
|
Ed *ed;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
lock(&edpool.l);
|
|
|
|
if(edpool.free == nil){
|
|
|
|
ddprint("ohci: edalloc %d Eds\n", Incr);
|
2017-04-19 23:33:14 +02:00
|
|
|
pool = jehanne_mallocalign(Incr*ROUNDUP(sizeof(Ed), Edalign), Edalign, 0, 0);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(pool == nil)
|
|
|
|
panic("ohci: edalloc");
|
|
|
|
for(i=Incr; --i>=0;){
|
|
|
|
ed = (Ed*)(pool + i*ROUNDUP(sizeof(Ed), Edalign));
|
|
|
|
ed->next = edpool.free;
|
|
|
|
edpool.free = ed;
|
|
|
|
}
|
|
|
|
edpool.nalloc += Incr;
|
|
|
|
edpool.nfree += Incr;
|
|
|
|
}
|
|
|
|
edpool.ninuse++;
|
|
|
|
edpool.nfree--;
|
|
|
|
ed = edpool.free;
|
|
|
|
edpool.free = ed->next;
|
|
|
|
unlock(&edpool.l);
|
|
|
|
assert(((uintptr_t)ed & 0xF) == 0);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memset(ed, 0, sizeof(Ed));
|
2016-11-25 17:18:40 +01:00
|
|
|
return ed;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
edfree(Ed *ed)
|
|
|
|
{
|
|
|
|
Td *td, *next;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if(ed == 0)
|
|
|
|
return;
|
|
|
|
i = 0;
|
|
|
|
for(td = ed->tds; td != nil; td = next){
|
|
|
|
next = td->next;
|
|
|
|
tdfree(td);
|
|
|
|
if(i++ > 2000){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: bug: ed with more than 2000 tds\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lock(&edpool.l);
|
|
|
|
if(ed->nexted == 0x99999999)
|
|
|
|
panic("ohci: edfree: double free");
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memset(ed, 9, sizeof(Ed)); /* poison */
|
2016-11-25 17:18:40 +01:00
|
|
|
ed->next = edpool.free;
|
|
|
|
edpool.free = ed;
|
|
|
|
edpool.ninuse--;
|
|
|
|
edpool.nfree++;
|
|
|
|
unlock(&edpool.l);
|
|
|
|
ddprint("edfree: ed %#p\n", ed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* return smallest power of 2 >= n
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
flog2(int n)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i = 0; (1 << i) < n; i++)
|
|
|
|
;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* return smallest power of 2 <= n
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
flog2lower(int n)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i = 0; (1 << (i + 1)) <= n; i++)
|
|
|
|
;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
pickschedq(Qtree *qt, int pollival, uint32_t bw, uint32_t limit)
|
|
|
|
{
|
|
|
|
int i, j, d, upperb, q;
|
|
|
|
uint32_t best, worst, total;
|
|
|
|
|
|
|
|
d = flog2lower(pollival);
|
|
|
|
if(d > qt->depth)
|
|
|
|
d = qt->depth;
|
|
|
|
q = -1;
|
|
|
|
worst = 0;
|
|
|
|
best = ~0;
|
|
|
|
upperb = (1 << (d+1)) - 1;
|
|
|
|
for(i = (1 << d) - 1; i < upperb; i++){
|
|
|
|
total = qt->bw[0];
|
|
|
|
for(j = i; j > 0; j = (j - 1) / 2)
|
|
|
|
total += qt->bw[j];
|
|
|
|
if(total < best){
|
|
|
|
best = total;
|
|
|
|
q = i;
|
|
|
|
}
|
|
|
|
if(total > worst)
|
|
|
|
worst = total;
|
|
|
|
}
|
|
|
|
if(worst + bw >= limit)
|
|
|
|
return -1;
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
schedq(Ctlr *ctlr, Qio *io, int pollival)
|
|
|
|
{
|
|
|
|
int q;
|
|
|
|
Ed *ted;
|
|
|
|
|
|
|
|
q = pickschedq(ctlr->tree, pollival, io->bw, ~0);
|
|
|
|
ddqprint("ohci: sched %#p q %d, ival %d, bw %ld\n", io, q, pollival, io->bw);
|
|
|
|
if(q < 0){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: no room for ed\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
ctlr->tree->bw[q] += io->bw;
|
|
|
|
ted = ctlr->tree->root[q];
|
|
|
|
io->sched = q;
|
|
|
|
edlinked(io->ed, ted->next);
|
|
|
|
edlinked(ted, io->ed);
|
|
|
|
io->ed->inext = ctlr->intrhd;
|
|
|
|
ctlr->intrhd = io->ed;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
unschedq(Ctlr *ctlr, Qio *qio)
|
|
|
|
{
|
|
|
|
int q;
|
|
|
|
Ed *prev, *this, *next;
|
|
|
|
Ed **l;
|
|
|
|
|
|
|
|
q = qio->sched;
|
|
|
|
if(q < 0)
|
|
|
|
return;
|
|
|
|
ctlr->tree->bw[q] -= qio->bw;
|
|
|
|
|
|
|
|
prev = ctlr->tree->root[q];
|
|
|
|
this = prev->next;
|
|
|
|
while(this != nil && this != qio->ed){
|
|
|
|
prev = this;
|
|
|
|
this = this->next;
|
|
|
|
}
|
|
|
|
if(this == nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: unschedq %d: not found\n", q);
|
2016-11-25 17:18:40 +01:00
|
|
|
else{
|
|
|
|
next = this->next;
|
|
|
|
edlinked(prev, next);
|
|
|
|
}
|
|
|
|
waitSOF(ctlr);
|
|
|
|
for(l = &ctlr->intrhd; *l != nil; l = &(*l)->inext)
|
|
|
|
if(*l == qio->ed){
|
|
|
|
*l = (*l)->inext;
|
|
|
|
return;
|
|
|
|
}
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: unschedq: ed %#p not found\n", qio->ed);
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static char*
|
|
|
|
seprinttdtok(char *s, char *e, int tok)
|
|
|
|
{
|
|
|
|
switch(tok){
|
|
|
|
case Tdtoksetup:
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " setup");
|
2016-11-25 17:18:40 +01:00
|
|
|
break;
|
|
|
|
case Tdtokin:
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " in");
|
2016-11-25 17:18:40 +01:00
|
|
|
break;
|
|
|
|
case Tdtokout:
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " out");
|
2016-11-25 17:18:40 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static char*
|
|
|
|
seprinttd(char *s, char *e, Td *td, int iso)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
Block *bp;
|
|
|
|
|
|
|
|
if(td == nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, e, "<nil td>\n");
|
|
|
|
s = jehanne_seprint(s, e, "%#p ep %#p ctrl %#lux", td, td->ep, td->ctrl);
|
|
|
|
s = jehanne_seprint(s, e, " cc=%#ulx", (td->ctrl >> Tdccshift) & Tdccmask);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(iso == 0){
|
|
|
|
if((td->ctrl & Tdround) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " rnd");
|
2016-11-25 17:18:40 +01:00
|
|
|
s = seprinttdtok(s, e, td->ctrl & Tdtokmask);
|
|
|
|
if((td->ctrl & Tdusetog) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " d%d", (td->ctrl & Tddata1) ? 1 : 0);
|
2016-11-25 17:18:40 +01:00
|
|
|
else
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " d-");
|
|
|
|
s = jehanne_seprint(s, e, " ec=%uld", (td->ctrl >> Tderrshift) & Tderrmask);
|
2016-11-25 17:18:40 +01:00
|
|
|
}else{
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " fc=%uld", (td->ctrl >> Tdfcshift) & Tdfcmask);
|
|
|
|
s = jehanne_seprint(s, e, " sf=%uld", td->ctrl & Tdsfmask);
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " cbp0 %#lux cbp %#lux next %#lux be %#lux %s",
|
2016-11-25 17:18:40 +01:00
|
|
|
td->cbp0, td->cbp, td->nexttd, td->be, td->last ? "last" : "");
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, "\n\t\t%ld bytes", td->nbytes);
|
2016-11-25 17:18:40 +01:00
|
|
|
if((bp = td->bp) != nil){
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " rp %#p wp %#p ", bp->rp, bp->wp);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(BLEN(bp) > 0)
|
|
|
|
s = seprintdata(s, e, bp->rp, bp->wp - bp->rp);
|
|
|
|
}
|
|
|
|
if(iso == 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, e, "\n");
|
|
|
|
s = jehanne_seprint(s, e, "\n\t\t");
|
2016-11-25 17:18:40 +01:00
|
|
|
/* we use only offsets[0] */
|
|
|
|
i = 0;
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, "[%d] %#ux cc=%#ux sz=%ud\n", i, td->offsets[i],
|
2016-11-25 17:18:40 +01:00
|
|
|
(td->offsets[i] >> Tdiccshift) & Tdiccmask,
|
|
|
|
td->offsets[i] & 0x7FF);
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dumptd(Td *td, char *p, int iso)
|
|
|
|
{
|
|
|
|
static char buf[512]; /* Too much */
|
|
|
|
char *s;
|
|
|
|
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(buf, buf+sizeof(buf), "%s: ", p);
|
2016-11-25 17:18:40 +01:00
|
|
|
s = seprinttd(s, buf+sizeof(buf), td, iso);
|
|
|
|
if(s > buf && s[-1] != '\n')
|
|
|
|
s[-1] = '\n';
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("\t%s", buf);
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dumptds(Td *td, char *p, int iso)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i = 0; td != nil; td = td->next){
|
|
|
|
dumptd(td, p, iso);
|
|
|
|
if(td->last)
|
|
|
|
break;
|
|
|
|
if(tdtok(td) == Tdtokin && ++i > 2){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("\t\t...\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dumped(Ed *ed)
|
|
|
|
{
|
|
|
|
char *buf, *s, *e;
|
|
|
|
|
|
|
|
if(ed == nil){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("<null ed>\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-04-19 23:33:14 +02:00
|
|
|
buf = jehanne_malloc(512);
|
2016-11-25 17:18:40 +01:00
|
|
|
/* no waserror; may want to use from interrupt context */
|
|
|
|
if(buf == nil)
|
|
|
|
return;
|
|
|
|
e = buf+512;
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(buf, e, "\ted %#p: ctrl %#lux", ed, ed->ctrl);
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ed->ctrl & Edskip) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " skip");
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ed->ctrl & Ediso) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " iso");
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ed->ctrl & Edlow) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " low");
|
|
|
|
s = jehanne_seprint(s, e, " d%d", (ed->head & Edtoggle) ? 1 : 0);
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ed->ctrl & Eddirmask) == Edin)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " in");
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ed->ctrl & Eddirmask) == Edout)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " out");
|
2016-11-25 17:18:40 +01:00
|
|
|
if(edhalted(ed))
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " hlt");
|
|
|
|
s = jehanne_seprint(s, e, " ep%uld.%uld", (ed->ctrl>>7)&Epmax, ed->ctrl&0x7f);
|
|
|
|
s = jehanne_seprint(s, e, " maxpkt %uld", (ed->ctrl>>Edmpsshift)&Edmpsmask);
|
|
|
|
jehanne_seprint(s, e, " tail %#lux head %#lux next %#lux\n",ed->tail,ed->head,ed->nexted);
|
|
|
|
jehanne_print("%s", buf);
|
|
|
|
jehanne_free(buf);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(ed->tds != nil && (ed->ctrl & Ediso) == 0)
|
|
|
|
dumptds(ed->tds, "td", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char*
|
|
|
|
seprintio(char *s, char *e, Qio *io, char *pref)
|
|
|
|
{
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, "%s qio %#p ed %#p", pref, io, io->ed);
|
|
|
|
s = jehanne_seprint(s, e, " tog %d iot %ld err %s id %#ulx",
|
2016-11-25 17:18:40 +01:00
|
|
|
io->toggle, io->iotime, io->err, io->usbid);
|
|
|
|
s = seprinttdtok(s, e, io->tok);
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, " %s\n", iosname[io->state]);
|
2016-11-25 17:18:40 +01:00
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char*
|
|
|
|
seprintep(char* s, char* e, Ep *ep)
|
|
|
|
{
|
|
|
|
Isoio *iso;
|
|
|
|
Qio *io;
|
|
|
|
Ctlio *cio;
|
|
|
|
|
|
|
|
if(ep == nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, e, "<nil ep>\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
if(ep->aux == nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, e, "no mdep\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
switch(ep->ttype){
|
|
|
|
case Tctl:
|
|
|
|
cio = ep->aux;
|
|
|
|
s = seprintio(s, e, cio, "c");
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, "\trepl %d ndata %d\n", ep->rhrepl, cio->ndata);
|
2016-11-25 17:18:40 +01:00
|
|
|
break;
|
|
|
|
case Tbulk:
|
|
|
|
case Tintr:
|
|
|
|
io = ep->aux;
|
|
|
|
if(ep->mode != OWRITE)
|
|
|
|
s = seprintio(s, e, &io[OREAD], "r");
|
|
|
|
if(ep->mode != OREAD)
|
|
|
|
s = seprintio(s, e, &io[OWRITE], "w");
|
|
|
|
break;
|
|
|
|
case Tiso:
|
|
|
|
iso = ep->aux;
|
|
|
|
s = seprintio(s, e, iso, "w");
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, e, "\tntds %d avail %d frno %uld left %uld next avail %#p\n",
|
2016-11-25 17:18:40 +01:00
|
|
|
iso->nframes, iso->navail, iso->frno, iso->left, iso->atds);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char*
|
|
|
|
seprintctl(char *s, char *se, uint32_t ctl)
|
|
|
|
{
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, se, "en=");
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ctl&Cple) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, se, "p");
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ctl&Cie) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, se, "i");
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ctl&Ccle) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, se, "c");
|
2016-11-25 17:18:40 +01:00
|
|
|
if((ctl&Cble) != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
s = jehanne_seprint(s, se, "b");
|
2016-11-25 17:18:40 +01:00
|
|
|
switch(ctl & Cfsmask){
|
|
|
|
case Cfsreset:
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, se, " reset");
|
2016-11-25 17:18:40 +01:00
|
|
|
case Cfsresume:
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, se, " resume");
|
2016-11-25 17:18:40 +01:00
|
|
|
case Cfsoper:
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, se, " run");
|
2016-11-25 17:18:40 +01:00
|
|
|
case Cfssuspend:
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, se, " suspend");
|
2016-11-25 17:18:40 +01:00
|
|
|
default:
|
2017-04-19 23:33:14 +02:00
|
|
|
return jehanne_seprint(s, se, " ???");
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dump(Hci *hp)
|
|
|
|
{
|
|
|
|
Ctlr *ctlr;
|
|
|
|
Ed *ed;
|
|
|
|
char cs[20];
|
|
|
|
|
|
|
|
ctlr = hp->aux;
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
seprintctl(cs, cs+sizeof(cs), ctlr->ohci->control);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci ctlr %#p: frno %#ux ctl %#lux %s sts %#lux intr %#lux\n",
|
2016-11-25 17:18:40 +01:00
|
|
|
ctlr, ctlr->hcca->framenumber, ctlr->ohci->control, cs,
|
|
|
|
ctlr->ohci->cmdsts, ctlr->ohci->intrsts);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ctlhd %#ulx cur %#ulx bulkhd %#ulx cur %#ulx done %#ulx\n",
|
2016-11-25 17:18:40 +01:00
|
|
|
ctlr->ohci->ctlheaded, ctlr->ohci->ctlcurred,
|
|
|
|
ctlr->ohci->bulkheaded, ctlr->ohci->bulkcurred,
|
|
|
|
ctlr->ohci->donehead);
|
|
|
|
if(ctlhd(ctlr) != nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("[ctl]\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
for(ed = ctlhd(ctlr); ed != nil; ed = ed->next)
|
|
|
|
dumped(ed);
|
|
|
|
if(bulkhd(ctlr) != nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("[bulk]\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
for(ed = bulkhd(ctlr); ed != nil; ed = ed->next)
|
|
|
|
dumped(ed);
|
|
|
|
if(ctlr->intrhd != nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("[intr]\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
for(ed = ctlr->intrhd; ed != nil; ed = ed->inext)
|
|
|
|
dumped(ed);
|
|
|
|
if(ctlr->tree->root[0]->next != nil)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("[iso]");
|
2016-11-25 17:18:40 +01:00
|
|
|
for(ed = ctlr->tree->root[0]->next; ed != nil; ed = ed->next)
|
|
|
|
dumped(ed);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("%d eds in tree\n", ctlr->ntree);
|
2016-11-25 17:18:40 +01:00
|
|
|
iunlock(&ctlr->l);
|
|
|
|
lock(&tdpool.l);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("%d tds allocated = %d in use + %d free\n",
|
2016-11-25 17:18:40 +01:00
|
|
|
tdpool.nalloc, tdpool.ninuse, tdpool.nfree);
|
|
|
|
unlock(&tdpool.l);
|
|
|
|
lock(&edpool.l);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("%d eds allocated = %d in use + %d free\n",
|
2016-11-25 17:18:40 +01:00
|
|
|
edpool.nalloc, edpool.ninuse, edpool.nfree);
|
|
|
|
unlock(&edpool.l);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compute size for the next iso Td and setup its
|
|
|
|
* descriptor for I/O according to the buffer size.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
isodtdinit(Ep *ep, Isoio *iso, Td *td)
|
|
|
|
{
|
|
|
|
Block *bp;
|
|
|
|
int32_t size;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
bp = td->bp;
|
|
|
|
assert(bp != nil && BLEN(bp) == 0);
|
|
|
|
size = (ep->hz+iso->left) * ep->pollival / 1000;
|
|
|
|
iso->left = (ep->hz+iso->left) * ep->pollival % 1000;
|
|
|
|
size *= ep->samplesz;
|
|
|
|
if(size > ep->maxpkt){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: ep%d.%d: size > maxpkt\n",
|
2016-11-25 17:18:40 +01:00
|
|
|
ep->dev->nb, ep->nb);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("size = %uld max = %ld\n", size, ep->maxpkt);
|
2016-11-25 17:18:40 +01:00
|
|
|
size = ep->maxpkt;
|
|
|
|
}
|
|
|
|
td->nbytes = size;
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memset(bp->wp, 0, size); /* in case we don't fill it on time */
|
2016-11-25 17:18:40 +01:00
|
|
|
td->cbp0 = td->cbp = ptr2pa(bp->rp) & ~0xFFF;
|
|
|
|
td->ctrl = TRUNC(iso->frno, Ntdframes);
|
|
|
|
td->offsets[0] = (ptr2pa(bp->rp) & 0xFFF);
|
|
|
|
td->offsets[0] |= (Tdnotacc << Tdiccshift);
|
|
|
|
/* in case the controller checks out the offests... */
|
|
|
|
for(i = 1; i < nelem(td->offsets); i++)
|
|
|
|
td->offsets[i] = td->offsets[0];
|
|
|
|
td->be = ptr2pa(bp->rp + size - 1);
|
|
|
|
td->ctrl |= (0 << Tdfcshift); /* frame count is 1 */
|
|
|
|
|
|
|
|
iso->frno = TRUNC(iso->frno + ep->pollival, Ntdframes);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* start I/O on the dummy td and setup a new dummy to fill up.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
isoadvance(Ep *ep, Isoio *iso, Td *td)
|
|
|
|
{
|
|
|
|
Td *dtd;
|
|
|
|
|
|
|
|
dtd = iso->atds;
|
|
|
|
iso->atds = dtd->anext;
|
|
|
|
iso->navail--;
|
|
|
|
dtd->anext = nil;
|
|
|
|
dtd->bp->wp = dtd->bp->rp;
|
|
|
|
dtd->nexttd = 0;
|
|
|
|
td->nexttd = ptr2pa(dtd);
|
|
|
|
isodtdinit(ep, iso, dtd);
|
|
|
|
iso->ed->tail = ptr2pa(dtd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
isocanwrite(void *a)
|
|
|
|
{
|
|
|
|
Isoio *iso;
|
|
|
|
|
|
|
|
iso = a;
|
|
|
|
return iso->state == Qclose || iso->err != nil ||
|
|
|
|
iso->navail > iso->nframes / 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
isodelay(void *a)
|
|
|
|
{
|
|
|
|
Isoio *iso;
|
|
|
|
|
|
|
|
iso = a;
|
|
|
|
if(iso->state == Qclose || iso->err != nil || iso->delay == 0)
|
|
|
|
return 1;
|
|
|
|
return (iso->nframes - iso->navail) <= iso->delay;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Service a completed/failed Td from the done queue.
|
|
|
|
* It may be of any transfer type.
|
|
|
|
* The queue is not in completion order.
|
|
|
|
* (It's actually in reverse completion order).
|
|
|
|
*
|
|
|
|
* When an error, a short packet, or a last Td is found
|
|
|
|
* we awake the process waiting for the transfer.
|
|
|
|
* Although later we will process other Tds completed
|
|
|
|
* before, epio won't be able to touch the current Td
|
|
|
|
* until interrupt returns and releases the lock on the
|
|
|
|
* controller.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
qhinterrupt(Ctlr * _, Ep *ep, Qio *io, Td *td, int __)
|
|
|
|
{
|
|
|
|
Block *bp;
|
|
|
|
int mode, err;
|
|
|
|
Ed *ed;
|
|
|
|
|
|
|
|
ed = io->ed;
|
|
|
|
if(io->state != Qrun)
|
|
|
|
return;
|
|
|
|
if(tdtok(td) == Tdtokin)
|
|
|
|
mode = OREAD;
|
|
|
|
else
|
|
|
|
mode = OWRITE;
|
|
|
|
bp = td->bp;
|
|
|
|
err = tderrs(td);
|
|
|
|
|
|
|
|
switch(err){
|
|
|
|
case Tddataovr: /* Overrun is not an error */
|
|
|
|
case Tdok:
|
|
|
|
/* virtualbox doesn't always report underflow on short packets */
|
|
|
|
if(td->cbp == 0)
|
|
|
|
break;
|
|
|
|
/* fall through */
|
|
|
|
case Tddataund:
|
|
|
|
/* short input packets are ok */
|
|
|
|
if(mode == OREAD){
|
|
|
|
if(td->cbp == 0)
|
|
|
|
panic("ohci: short packet but cbp == 0");
|
|
|
|
/*
|
|
|
|
* td->cbp and td->cbp0 are the real addresses
|
|
|
|
* corresponding to virtual addresses bp->wp and
|
|
|
|
* bp->rp respectively.
|
|
|
|
*/
|
|
|
|
bp->wp = bp->rp + (td->cbp - td->cbp0);
|
|
|
|
if(bp->wp < bp->rp)
|
|
|
|
panic("ohci: wp < rp");
|
|
|
|
/*
|
|
|
|
* It's ok. clear error and flag as last in xfer.
|
|
|
|
* epio must ignore following Tds.
|
|
|
|
*/
|
|
|
|
td->last = 1;
|
|
|
|
td->ctrl &= ~(Tdccmask << Tdccshift);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* else fall; it's an error */
|
|
|
|
case Tdcrc:
|
|
|
|
case Tdbitstuff:
|
|
|
|
case Tdbadtog:
|
|
|
|
case Tdstalled:
|
|
|
|
case Tdtmout:
|
|
|
|
case Tdpidchk:
|
|
|
|
case Tdbadpid:
|
|
|
|
bp->wp = bp->rp; /* no bytes in xfer. */
|
|
|
|
io->err = errmsg(err);
|
|
|
|
if(debug || ep->debug){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("tdinterrupt: failed err %d (%s)\n", err, io->err);
|
2016-11-25 17:18:40 +01:00
|
|
|
dumptd(td, "failed", ed->ctrl & Ediso);
|
|
|
|
}
|
|
|
|
td->last = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
panic("ohci: td cc %ud unknown", err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(td->last != 0){
|
|
|
|
/*
|
|
|
|
* clear td list and halt flag.
|
|
|
|
*/
|
|
|
|
ed->head = (ed->head & Edtoggle) | ed->tail;
|
|
|
|
ed->tds = pa2ptr(ed->tail);
|
|
|
|
io->state = Qdone;
|
|
|
|
wakeup(&io->r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* BUG: Iso input streams are not implemented.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
isointerrupt(Ctlr *ctlr, Ep *ep, Qio *io, Td *td, int _)
|
|
|
|
{
|
|
|
|
Isoio *iso;
|
|
|
|
Block *bp;
|
|
|
|
Ed *ed;
|
|
|
|
int err, isoerr;
|
|
|
|
|
|
|
|
iso = ep->aux;
|
|
|
|
ed = io->ed;
|
|
|
|
if(io->state == Qclose)
|
|
|
|
return;
|
|
|
|
bp = td->bp;
|
|
|
|
/*
|
|
|
|
* When we get more than half the frames consecutive errors
|
|
|
|
* we signal an actual error. Errors in the entire Td are
|
|
|
|
* more serious and are always singaled.
|
|
|
|
* Errors like overrun are not really errors. In fact, for
|
|
|
|
* output, errors cannot be really detected. The driver will
|
|
|
|
* hopefully notice I/O errors on input endpoints and detach the device.
|
|
|
|
*/
|
|
|
|
err = tderrs(td);
|
|
|
|
isoerr = (td->offsets[0] >> Tdiccshift) & Tdiccmask;
|
|
|
|
if(isoerr == Tdok || isoerr == Tdnotacc)
|
|
|
|
iso->nerrs = 0;
|
|
|
|
else if(iso->nerrs++ > iso->nframes/2)
|
|
|
|
err = Tdstalled;
|
|
|
|
if(err != Tdok && err != Tddataovr){
|
|
|
|
bp->wp = bp->rp;
|
|
|
|
io->err = errmsg(err);
|
|
|
|
if(debug || ep->debug){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: isointerrupt: ep%d.%d: err %d (%s) frnum 0x%lux\n",
|
2016-11-25 17:18:40 +01:00
|
|
|
ep->dev->nb, ep->nb,
|
|
|
|
err, errmsg(err), ctlr->ohci->fmnumber);
|
|
|
|
dumptd(td, "failed", ed->ctrl & Ediso);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
td->bp->wp = td->bp->rp;
|
|
|
|
td->nbytes = 0;
|
|
|
|
td->anext = iso->atds;
|
|
|
|
iso->atds = td;
|
|
|
|
iso->navail++;
|
|
|
|
/*
|
|
|
|
* If almost all Tds are avail the user is not doing I/O at the
|
|
|
|
* required rate. We put another Td in place to keep the polling rate.
|
|
|
|
*/
|
|
|
|
if(iso->err == nil && iso->navail > iso->nframes - 10)
|
|
|
|
isoadvance(ep, iso, pa2ptr(iso->ed->tail));
|
|
|
|
/*
|
|
|
|
* If there's enough buffering futher I/O can be done.
|
|
|
|
*/
|
|
|
|
if(isocanwrite(iso))
|
|
|
|
wakeup(&iso->r);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
interrupt(Ureg * _, void *arg)
|
|
|
|
{
|
|
|
|
Td *td, *ntd;
|
|
|
|
Hci *hp;
|
|
|
|
Ctlr *ctlr;
|
|
|
|
uint32_t status, curred, done;
|
|
|
|
int i, frno;
|
|
|
|
|
|
|
|
hp = arg;
|
|
|
|
ctlr = hp->aux;
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
done = ctlr->hcca->donehead;
|
|
|
|
status = ctlr->ohci->intrsts;
|
|
|
|
if(status == ~0){
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(done & ~0xF){
|
|
|
|
ctlr->hcca->donehead = 0;
|
|
|
|
status |= Wdh;
|
|
|
|
}
|
|
|
|
else if(status & Wdh){
|
|
|
|
done = ctlr->hcca->donehead;
|
|
|
|
ctlr->hcca->donehead = 0;
|
|
|
|
}
|
|
|
|
status &= ~Mie;
|
|
|
|
if(status == 0){
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ctlr->ohci->intrsts = status;
|
|
|
|
status &= ctlr->ohci->intrenable;
|
|
|
|
status &= Oc|Rhsc|Fno|Ue|Rd|Sf|Wdh|So;
|
|
|
|
if(status & Wdh){
|
|
|
|
frno = TRUNC(ctlr->ohci->fmnumber, Ntdframes);
|
|
|
|
td = pa2ptr(done & ~0xF);
|
|
|
|
for(i = 0; td != nil && i < 1024; i++){
|
|
|
|
if(0)ddprint("ohci tdinterrupt: td %#p\n", td);
|
|
|
|
ntd = pa2ptr(td->nexttd & ~0xF);
|
|
|
|
td->nexttd = 0;
|
|
|
|
if(td->ep == nil || td->io == nil)
|
|
|
|
panic("ohci: interrupt: ep %#p io %#p", td->ep, td->io);
|
|
|
|
ohciinterrupts[td->ep->ttype]++;
|
|
|
|
if(td->ep->ttype == Tiso)
|
|
|
|
isointerrupt(ctlr, td->ep, td->io, td, frno);
|
|
|
|
else
|
|
|
|
qhinterrupt(ctlr, td->ep, td->io, td, frno);
|
|
|
|
td = ntd;
|
|
|
|
}
|
|
|
|
if(i == 1024)
|
|
|
|
iprint("ohci: bug: more than 1024 done Tds?\n");
|
|
|
|
status &= ~Wdh;
|
|
|
|
}
|
|
|
|
status &= ~Sf;
|
|
|
|
if(status & So){
|
|
|
|
iprint("ohci: sched overrun: too much load\n");
|
|
|
|
ctlr->overrun++;
|
|
|
|
status &= ~So;
|
|
|
|
}
|
|
|
|
if(status & Ue){
|
|
|
|
curred = ctlr->ohci->periodcurred;
|
|
|
|
iprint("ohci: unrecoverable error frame 0x%.8lux ed 0x%.8lux, "
|
|
|
|
"ints %d %d %d %d\n",
|
|
|
|
ctlr->ohci->fmnumber, curred,
|
|
|
|
ohciinterrupts[Tctl], ohciinterrupts[Tintr],
|
|
|
|
ohciinterrupts[Tbulk], ohciinterrupts[Tiso]);
|
|
|
|
if(curred != 0)
|
|
|
|
dumped(pa2ptr(curred));
|
|
|
|
status &= ~Ue;
|
|
|
|
}
|
|
|
|
if(status != 0){
|
|
|
|
iprint("ohci interrupt: unhandled sts 0x%.8lux\n", status);
|
|
|
|
ctlr->ohci->intrdisable = status;
|
|
|
|
}
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The old dummy Td is used to implement the new Td.
|
|
|
|
* A new dummy is linked at the end of the old one and
|
|
|
|
* returned, to link further Tds if needed.
|
|
|
|
*/
|
|
|
|
static Td*
|
|
|
|
epgettd(Ep *ep, Qio *io, Td **dtdp, int flags, void *a, int count)
|
|
|
|
{
|
|
|
|
Td *td, *dtd;
|
|
|
|
Block *bp;
|
|
|
|
|
|
|
|
if(count <= PGSZ)
|
|
|
|
bp = allocb(count);
|
|
|
|
else{
|
|
|
|
if(count > 2*PGSZ)
|
|
|
|
panic("ohci: transfer > two pages");
|
|
|
|
/* maximum of one physical page crossing allowed */
|
|
|
|
bp = allocb(count+PGSZ);
|
|
|
|
bp->rp = (uint8_t*)ROUND((uintptr_t)bp->rp, PGSZ);
|
|
|
|
bp->wp = bp->rp;
|
|
|
|
}
|
|
|
|
dtd = *dtdp;
|
|
|
|
td = dtd;
|
|
|
|
td->bp = bp;
|
|
|
|
if(count > 0){
|
|
|
|
td->cbp0 = td->cbp = ptr2pa(bp->wp);
|
|
|
|
td->be = ptr2pa(bp->wp + count - 1);
|
|
|
|
if(a != nil){
|
|
|
|
/* validaddr((uintptr_t)a, count, 0); DEBUG */
|
|
|
|
assert(bp != nil);
|
|
|
|
assert(bp->wp != nil);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memmove(bp->wp, a, count);
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
bp->wp += count;
|
|
|
|
}
|
|
|
|
td->nbytes = count;
|
|
|
|
td->ctrl = io->tok|Tdusetog|io->toggle|flags;
|
|
|
|
if(io->toggle == Tddata0)
|
|
|
|
io->toggle = Tddata1;
|
|
|
|
else
|
|
|
|
io->toggle = Tddata0;
|
|
|
|
assert(td->ep == ep);
|
|
|
|
td->io = io;
|
|
|
|
dtd = tdalloc(); /* new dummy */
|
|
|
|
dtd->ep = ep;
|
|
|
|
td->nexttd = ptr2pa(dtd);
|
|
|
|
td->next = dtd;
|
|
|
|
*dtdp = dtd;
|
|
|
|
return td;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try to get them idle
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
aborttds(Qio *io)
|
|
|
|
{
|
|
|
|
Ed *ed;
|
|
|
|
Td *td;
|
|
|
|
|
|
|
|
ed = io->ed;
|
|
|
|
if(ed == nil)
|
|
|
|
return;
|
|
|
|
ed->ctrl |= Edskip;
|
|
|
|
for(td = ed->tds; td != nil; td = td->next)
|
|
|
|
if(td->bp != nil)
|
|
|
|
td->bp->wp = td->bp->rp;
|
|
|
|
ed->head = (ed->head&0xF) | ed->tail;
|
|
|
|
if((ed->ctrl & Ediso) == 0)
|
|
|
|
ed->tds = pa2ptr(ed->tail);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
epiodone(void *a)
|
|
|
|
{
|
|
|
|
Qio *io;
|
|
|
|
|
|
|
|
io = a;
|
|
|
|
return io->state != Qrun;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
epiowait(Ctlr *ctlr, Qio *io, int tmout, uint32_t _)
|
|
|
|
{
|
|
|
|
Ed *ed;
|
|
|
|
int timedout;
|
|
|
|
|
|
|
|
ed = io->ed;
|
|
|
|
if(0)ddqprint("ohci io %#p sleep on ed %#p state %s\n",
|
|
|
|
io, ed, iosname[io->state]);
|
|
|
|
timedout = 0;
|
|
|
|
if(waserror()){
|
|
|
|
dqprint("ohci io %#p ed %#p timed out\n", io, ed);
|
|
|
|
timedout++;
|
|
|
|
}else{
|
|
|
|
if(tmout == 0)
|
|
|
|
sleep(&io->r, epiodone, io);
|
|
|
|
else
|
|
|
|
tsleep(&io->r, epiodone, io, tmout);
|
|
|
|
poperror();
|
|
|
|
}
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
if(io->state == Qrun)
|
|
|
|
timedout = 1;
|
|
|
|
else if(io->state != Qdone && io->state != Qclose)
|
|
|
|
panic("epio: ed not done and not closed");
|
|
|
|
if(timedout){
|
|
|
|
aborttds(io);
|
|
|
|
io->err = "request timed out";
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
if(!waserror()){
|
|
|
|
tsleep(&up->sleep, return0, 0, Abortdelay);
|
|
|
|
poperror();
|
|
|
|
}
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
}
|
|
|
|
if(io->state != Qclose)
|
|
|
|
io->state = Qidle;
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Non iso I/O.
|
|
|
|
* To make it work for control transfers, the caller may
|
|
|
|
* lock the Qio for the entire control transfer.
|
|
|
|
*/
|
|
|
|
static int32_t
|
|
|
|
epio(Ep *ep, Qio *io, void *a, int32_t count, int mustlock)
|
|
|
|
{
|
|
|
|
Ed *ed;
|
|
|
|
Ctlr *ctlr;
|
|
|
|
char buf[80];
|
|
|
|
char *err;
|
|
|
|
uint8_t *c;
|
|
|
|
Td *td, *ltd, *ntd, *td0;
|
|
|
|
int last, ntds, tmout;
|
|
|
|
int32_t tot, n;
|
|
|
|
uint32_t load;
|
|
|
|
|
|
|
|
ed = io->ed;
|
|
|
|
ctlr = ep->hp->aux;
|
|
|
|
io->debug = ep->debug;
|
|
|
|
tmout = ep->tmout;
|
|
|
|
ddeprint("ohci: %s ep%d.%d io %#p count %ld\n",
|
|
|
|
io->tok == Tdtokin ? "in" : "out",
|
|
|
|
ep->dev->nb, ep->nb, io, count);
|
|
|
|
if((debug > 1 || ep->debug > 1) && io->tok != Tdtokin){
|
|
|
|
seprintdata(buf, buf+sizeof(buf), a, count);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("\t%s\n", buf);
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
if(mustlock){
|
2017-08-11 01:47:15 +02:00
|
|
|
eqlock(&io->ql);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(waserror()){
|
|
|
|
qunlock(&io->ql);
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
io->err = nil;
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
if(io->state == Qclose){ /* Tds released by cancelio */
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
error(io->err ? io->err : Eio);
|
|
|
|
}
|
|
|
|
if(io->state != Qidle)
|
|
|
|
panic("epio: qio not idle");
|
|
|
|
io->state = Qinstall;
|
|
|
|
|
|
|
|
c = a;
|
|
|
|
ltd = td0 = ed->tds;
|
|
|
|
load = tot = 0;
|
|
|
|
do{
|
|
|
|
n = 2*PGSZ;
|
|
|
|
if(count-tot < n)
|
|
|
|
n = count-tot;
|
|
|
|
if(c != nil && io->tok != Tdtokin)
|
|
|
|
td = epgettd(ep, io, <d, 0, c+tot, n);
|
|
|
|
else
|
|
|
|
td = epgettd(ep, io, <d, 0, nil, n);
|
|
|
|
tot += n;
|
|
|
|
load += ep->load;
|
|
|
|
}while(tot < count);
|
|
|
|
if(td0 == nil || ltd == nil || td0 == ltd)
|
|
|
|
panic("epio: no td");
|
|
|
|
td->last = 1;
|
|
|
|
if(debug > 2 || ep->debug > 2)
|
|
|
|
dumptds(td0, "put td", ep->ttype == Tiso);
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
if(io->state != Qclose){
|
|
|
|
io->iotime = TK2MS(sys->ticks);
|
|
|
|
io->state = Qrun;
|
|
|
|
ed->tail = ptr2pa(ltd);
|
|
|
|
if(ep->ttype == Tctl)
|
|
|
|
ctlr->ohci->cmdsts |= Sclf;
|
|
|
|
else if(ep->ttype == Tbulk)
|
|
|
|
ctlr->ohci->cmdsts |= Sblf;
|
|
|
|
}
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
|
|
|
|
epiowait(ctlr, io, tmout, load);
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
if(debug > 1 || ep->debug > 1)
|
|
|
|
dumptds(td0, "got td", 0);
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
|
|
|
|
tot = 0;
|
|
|
|
c = a;
|
|
|
|
ntds = last = 0;
|
|
|
|
for(td = td0; td != ltd; td = ntd){
|
|
|
|
ntds++;
|
|
|
|
/*
|
|
|
|
* If the Td is flagged as last we must
|
|
|
|
* ignore any following Td. The block may
|
|
|
|
* seem to have bytes but interrupt has not seen
|
|
|
|
* those Tds through the done queue, and they are void.
|
|
|
|
*/
|
|
|
|
if(last == 0 && tderrs(td) == Tdok){
|
|
|
|
n = BLEN(td->bp);
|
|
|
|
tot += n;
|
|
|
|
if(c != nil && tdtok(td) == Tdtokin && n > 0){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memmove(c, td->bp->rp, n);
|
2016-11-25 17:18:40 +01:00
|
|
|
c += n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
last |= td->last;
|
|
|
|
ntd = td->next;
|
|
|
|
tdfree(td);
|
|
|
|
}
|
|
|
|
if(edtoggle(ed) == 0)
|
|
|
|
io->toggle = Tddata0;
|
|
|
|
else
|
|
|
|
io->toggle = Tddata1;
|
|
|
|
|
|
|
|
err = io->err;
|
|
|
|
if(mustlock){
|
|
|
|
qunlock(&io->ql);
|
|
|
|
poperror();
|
|
|
|
}
|
|
|
|
ddeprint("ohci: io %#p: %d tds: return %ld err '%s'\n\n",
|
|
|
|
io, ntds, tot, err);
|
|
|
|
if(err != nil)
|
|
|
|
error(err);
|
|
|
|
if(tot < 0)
|
|
|
|
error(Eio);
|
|
|
|
return tot;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* halt condition was cleared on the endpoint. update our toggles.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
clrhalt(Ep *ep)
|
|
|
|
{
|
|
|
|
Qio *io;
|
|
|
|
|
|
|
|
ep->clrhalt = 0;
|
|
|
|
switch(ep->ttype){
|
|
|
|
case Tbulk:
|
|
|
|
case Tintr:
|
|
|
|
io = ep->aux;
|
|
|
|
if(ep->mode != OREAD){
|
|
|
|
qlock(&io[OWRITE].ql);
|
|
|
|
io[OWRITE].toggle = Tddata0;
|
|
|
|
deprint("ep clrhalt for io %#p\n", io+OWRITE);
|
|
|
|
qunlock(&io[OWRITE].ql);
|
|
|
|
}
|
|
|
|
if(ep->mode != OWRITE){
|
|
|
|
qlock(&io[OREAD].ql);
|
|
|
|
io[OREAD].toggle = Tddata0;
|
|
|
|
deprint("ep clrhalt for io %#p\n", io+OREAD);
|
|
|
|
qunlock(&io[OREAD].ql);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int32_t
|
|
|
|
epread(Ep *ep, void *a, int32_t count)
|
|
|
|
{
|
|
|
|
Ctlio *cio;
|
|
|
|
Qio *io;
|
|
|
|
char buf[80];
|
|
|
|
uint32_t delta;
|
|
|
|
|
|
|
|
if(ep->aux == nil)
|
|
|
|
panic("epread: not open");
|
|
|
|
|
|
|
|
switch(ep->ttype){
|
|
|
|
case Tctl:
|
|
|
|
cio = ep->aux;
|
2017-08-11 01:47:15 +02:00
|
|
|
eqlock(&cio->ql);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(waserror()){
|
|
|
|
qunlock(&cio->ql);
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
ddeprint("epread ctl ndata %d\n", cio->ndata);
|
|
|
|
if(cio->ndata < 0)
|
|
|
|
error("request expected");
|
|
|
|
else if(cio->ndata == 0){
|
|
|
|
cio->ndata = -1;
|
|
|
|
count = 0;
|
|
|
|
}else{
|
|
|
|
if(count > cio->ndata)
|
|
|
|
count = cio->ndata;
|
|
|
|
if(count > 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memmove(a, cio->data, count);
|
2016-11-25 17:18:40 +01:00
|
|
|
/* BUG for big transfers */
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_free(cio->data);
|
2016-11-25 17:18:40 +01:00
|
|
|
cio->data = nil;
|
|
|
|
cio->ndata = 0; /* signal EOF next time */
|
|
|
|
}
|
|
|
|
qunlock(&cio->ql);
|
|
|
|
poperror();
|
|
|
|
if(debug>1 || ep->debug){
|
|
|
|
seprintdata(buf, buf+sizeof(buf), a, count);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("epread: %s\n", buf);
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
return count;
|
|
|
|
case Tbulk:
|
|
|
|
io = ep->aux;
|
|
|
|
if(ep->clrhalt)
|
|
|
|
clrhalt(ep);
|
|
|
|
return epio(ep, &io[OREAD], a, count, 1);
|
|
|
|
case Tintr:
|
|
|
|
io = ep->aux;
|
|
|
|
delta = TK2MS(sys->ticks) - io[OREAD].iotime + 1;
|
|
|
|
if(delta < ep->pollival / 2)
|
|
|
|
tsleep(&up->sleep, return0, 0, ep->pollival/2 - delta);
|
|
|
|
if(ep->clrhalt)
|
|
|
|
clrhalt(ep);
|
|
|
|
return epio(ep, &io[OREAD], a, count, 1);
|
|
|
|
case Tiso:
|
|
|
|
error("iso read not implemented");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
panic("epread: bad ep ttype %d", ep->ttype);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Control transfers are one setup write (data0)
|
|
|
|
* plus zero or more reads/writes (data1, data0, ...)
|
|
|
|
* plus a final write/read with data1 to ack.
|
|
|
|
* For both host to device and device to host we perform
|
|
|
|
* the entire transfer when the user writes the request,
|
|
|
|
* and keep any data read from the device for a later read.
|
|
|
|
* We call epio three times instead of placing all Tds at
|
|
|
|
* the same time because doing so leads to crc/tmout errors
|
|
|
|
* for some devices.
|
|
|
|
* Upon errors on the data phase we must still run the status
|
|
|
|
* phase or the device may cease responding in the future.
|
|
|
|
*/
|
|
|
|
static int32_t
|
|
|
|
epctlio(Ep *ep, Ctlio *cio, void *a, int32_t count)
|
|
|
|
{
|
|
|
|
uint8_t *c;
|
|
|
|
int32_t len;
|
|
|
|
|
|
|
|
ddeprint("epctlio: cio %#p ep%d.%d count %ld\n",
|
|
|
|
cio, ep->dev->nb, ep->nb, count);
|
|
|
|
if(count < Rsetuplen)
|
|
|
|
error("short usb command");
|
2017-08-11 01:47:15 +02:00
|
|
|
eqlock(&cio->ql);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_free(cio->data);
|
2016-11-25 17:18:40 +01:00
|
|
|
cio->data = nil;
|
|
|
|
cio->ndata = 0;
|
|
|
|
if(waserror()){
|
|
|
|
qunlock(&cio->ql);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_free(cio->data);
|
2016-11-25 17:18:40 +01:00
|
|
|
cio->data = nil;
|
|
|
|
cio->ndata = 0;
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set the address if unset and out of configuration state */
|
|
|
|
if(ep->dev->state != Dconfig && ep->dev->state != Dreset)
|
|
|
|
if(cio->usbid == 0){
|
|
|
|
cio->usbid = (ep->nb&Epmax)<<7 | (ep->dev->nb&Devmax);
|
|
|
|
edsetaddr(cio->ed, cio->usbid);
|
|
|
|
}
|
|
|
|
/* adjust maxpkt if the user has learned a different one */
|
|
|
|
if(edmaxpkt(cio->ed) != ep->maxpkt)
|
|
|
|
edsetmaxpkt(cio->ed, ep->maxpkt);
|
|
|
|
c = a;
|
|
|
|
cio->tok = Tdtoksetup;
|
|
|
|
cio->toggle = Tddata0;
|
|
|
|
if(epio(ep, cio, a, Rsetuplen, 0) < Rsetuplen)
|
|
|
|
error(Eio);
|
|
|
|
|
|
|
|
a = c + Rsetuplen;
|
|
|
|
count -= Rsetuplen;
|
|
|
|
|
|
|
|
cio->toggle = Tddata1;
|
|
|
|
if(c[Rtype] & Rd2h){
|
|
|
|
cio->tok = Tdtokin;
|
|
|
|
len = GET2(c+Rcount);
|
|
|
|
if(len <= 0)
|
|
|
|
error("bad length in d2h request");
|
|
|
|
if(len > Maxctllen)
|
|
|
|
error("d2h data too large to fit in ohci");
|
|
|
|
a = cio->data = smalloc(len+1);
|
|
|
|
}else{
|
|
|
|
cio->tok = Tdtokout;
|
|
|
|
len = count;
|
|
|
|
}
|
|
|
|
if(len > 0)
|
|
|
|
if(waserror())
|
|
|
|
len = -1;
|
|
|
|
else{
|
|
|
|
len = epio(ep, cio, a, len, 0);
|
|
|
|
poperror();
|
|
|
|
}
|
|
|
|
if(c[Rtype] & Rd2h){
|
|
|
|
count = Rsetuplen;
|
|
|
|
cio->ndata = len;
|
|
|
|
cio->tok = Tdtokout;
|
|
|
|
}else{
|
|
|
|
if(len < 0)
|
|
|
|
count = -1;
|
|
|
|
else
|
|
|
|
count = Rsetuplen + len;
|
|
|
|
cio->tok = Tdtokin;
|
|
|
|
}
|
|
|
|
cio->toggle = Tddata1;
|
|
|
|
epio(ep, cio, nil, 0, 0);
|
|
|
|
qunlock(&cio->ql);
|
|
|
|
poperror();
|
|
|
|
ddeprint("epctlio cio %#p return %ld\n", cio, count);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put new samples in the dummy Td.
|
|
|
|
*/
|
|
|
|
static int32_t
|
|
|
|
putsamples(Ctlr *ctlr, Ep *ep, Isoio *iso, uint8_t *b, int32_t n)
|
|
|
|
{
|
|
|
|
Td *td;
|
|
|
|
|
|
|
|
td = pa2ptr(iso->ed->tail);
|
|
|
|
if(n > td->nbytes - BLEN(td->bp))
|
|
|
|
n = td->nbytes - BLEN(td->bp);
|
|
|
|
assert(td->bp->wp + n <= td->bp->lim);
|
|
|
|
iunlock(&ctlr->l); /* We could page fault here */
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memmove(td->bp->wp, b, n);
|
2016-11-25 17:18:40 +01:00
|
|
|
ilock(&ctlr->l);
|
|
|
|
if(td == pa2ptr(iso->ed->tail)){
|
|
|
|
td->bp->wp += n;
|
|
|
|
if(BLEN(td->bp) == td->nbytes) /* full Td: activate it */
|
|
|
|
isoadvance(ep, iso, td);
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int32_t
|
|
|
|
episowrite(Ep *ep, void *a, int32_t count)
|
|
|
|
{
|
|
|
|
int32_t tot, nw;
|
|
|
|
char *err;
|
|
|
|
uint8_t *b;
|
|
|
|
Ctlr *ctlr;
|
|
|
|
Isoio *iso;
|
|
|
|
|
|
|
|
ctlr = ep->hp->aux;
|
|
|
|
iso = ep->aux;
|
|
|
|
iso->delay = (ep->sampledelay*ep->samplesz + ep->maxpkt-1) / ep->maxpkt;
|
|
|
|
iso->debug = ep->debug;
|
|
|
|
|
2017-08-11 01:47:15 +02:00
|
|
|
eqlock(&iso->ql);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(waserror()){
|
|
|
|
qunlock(&iso->ql);
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
diprint("ohci: episowrite: %#p ep%d.%d\n", iso, ep->dev->nb, ep->nb);
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
if(iso->state == Qclose){
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
error(iso->err ? iso->err : Eio);
|
|
|
|
}
|
|
|
|
iso->state = Qrun;
|
|
|
|
b = a;
|
|
|
|
for(tot = 0; tot < count; tot += nw){
|
|
|
|
while(isocanwrite(iso) == 0){
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
diprint("ohci: episowrite: %#p sleep\n", iso);
|
|
|
|
if(waserror()){
|
|
|
|
if(iso->err == nil)
|
|
|
|
iso->err = "I/O timed out";
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
tsleep(&iso->r, isocanwrite, iso, ep->tmout);
|
|
|
|
poperror();
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
}
|
|
|
|
err = iso->err;
|
|
|
|
iso->err = nil;
|
|
|
|
if(iso->state == Qclose || err != nil){
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
error(err ? err : Eio);
|
|
|
|
}
|
|
|
|
if(iso->state != Qrun)
|
|
|
|
panic("episowrite: iso not running");
|
|
|
|
nw = putsamples(ctlr, ep, iso, b+tot, count-tot);
|
|
|
|
}
|
|
|
|
while(isodelay(iso) == 0){
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
sleep(&iso->r, isodelay, iso);
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
}
|
|
|
|
if(iso->state != Qclose)
|
|
|
|
iso->state = Qdone;
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
err = iso->err; /* in case it failed early */
|
|
|
|
iso->err = nil;
|
|
|
|
qunlock(&iso->ql);
|
|
|
|
poperror();
|
|
|
|
if(err != nil)
|
|
|
|
error(err);
|
|
|
|
diprint("ohci: episowrite: %#p %ld bytes\n", iso, tot);
|
|
|
|
return tot;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int32_t
|
|
|
|
epwrite(Ep *ep, void *a, int32_t count)
|
|
|
|
{
|
|
|
|
Qio *io;
|
|
|
|
Ctlio *cio;
|
|
|
|
uint32_t delta;
|
|
|
|
uint8_t *b;
|
|
|
|
int32_t tot, nw;
|
|
|
|
|
|
|
|
if(ep->aux == nil)
|
|
|
|
panic("ohci: epwrite: not open");
|
|
|
|
switch(ep->ttype){
|
|
|
|
case Tctl:
|
|
|
|
cio = ep->aux;
|
|
|
|
return epctlio(ep, cio, a, count);
|
|
|
|
case Tbulk:
|
|
|
|
io = ep->aux;
|
|
|
|
if(ep->clrhalt)
|
|
|
|
clrhalt(ep);
|
|
|
|
/*
|
|
|
|
* Put at most Tdatomic Tds (512 bytes) at a time.
|
|
|
|
* Otherwise some devices produce babble errors.
|
|
|
|
*/
|
|
|
|
b = a;
|
|
|
|
assert(a != nil);
|
|
|
|
for(tot = 0; tot < count ; tot += nw){
|
|
|
|
nw = count - tot;
|
|
|
|
if(nw > Tdatomic * ep->maxpkt)
|
|
|
|
nw = Tdatomic * ep->maxpkt;
|
|
|
|
nw = epio(ep, &io[OWRITE], b+tot, nw, 1);
|
|
|
|
}
|
|
|
|
return tot;
|
|
|
|
case Tintr:
|
|
|
|
io = ep->aux;
|
|
|
|
delta = TK2MS(sys->ticks) - io[OWRITE].iotime + 1;
|
|
|
|
if(delta < ep->pollival)
|
|
|
|
tsleep(&up->sleep, return0, 0, ep->pollival - delta);
|
|
|
|
if(ep->clrhalt)
|
|
|
|
clrhalt(ep);
|
|
|
|
return epio(ep, &io[OWRITE], a, count, 1);
|
|
|
|
case Tiso:
|
|
|
|
return episowrite(ep, a, count);
|
|
|
|
default:
|
|
|
|
panic("ohci: epwrite: bad ep ttype %d", ep->ttype);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Ed*
|
|
|
|
newed(Ctlr *ctlr, Ep *ep, Qio *io, char * _)
|
|
|
|
{
|
|
|
|
Ed *ed;
|
|
|
|
Td *td;
|
|
|
|
|
|
|
|
ed = io->ed = edalloc(); /* no errors raised here, really */
|
|
|
|
td = tdalloc();
|
|
|
|
td->ep = ep;
|
|
|
|
td->io = io;
|
|
|
|
ed->tail = ptr2pa(td);
|
|
|
|
ed->head = ptr2pa(td);
|
|
|
|
ed->tds = td;
|
|
|
|
ed->ep = ep;
|
|
|
|
ed->ctrl = (ep->maxpkt & Edmpsmask) << Edmpsshift;
|
|
|
|
if(ep->ttype == Tiso)
|
|
|
|
ed->ctrl |= Ediso;
|
|
|
|
if(waserror()){
|
|
|
|
edfree(ed);
|
|
|
|
io->ed = nil;
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
/* For setup endpoints we start with the config address */
|
|
|
|
if(ep->ttype != Tctl)
|
|
|
|
edsetaddr(io->ed, io->usbid);
|
|
|
|
if(ep->dev->speed == Lowspeed)
|
|
|
|
ed->ctrl |= Edlow;
|
|
|
|
switch(io->tok){
|
|
|
|
case Tdtokin:
|
|
|
|
ed->ctrl |= Edin;
|
|
|
|
break;
|
|
|
|
case Tdtokout:
|
|
|
|
ed->ctrl |= Edout;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ed->ctrl |= Edtddir; /* Td will say */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(ep->ttype){
|
|
|
|
case Tctl:
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
edlinked(ed, ctlhd(ctlr));
|
|
|
|
setctlhd(ctlr, ed);
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
break;
|
|
|
|
case Tbulk:
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
edlinked(ed, bulkhd(ctlr));
|
|
|
|
setbulkhd(ctlr, ed);
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
break;
|
|
|
|
case Tintr:
|
|
|
|
case Tiso:
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
schedq(ctlr, io, ep->pollival);
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
panic("ohci: newed: bad ttype");
|
|
|
|
}
|
|
|
|
poperror();
|
|
|
|
return ed;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
isoopen(Ctlr *ctlr, Ep *ep)
|
|
|
|
{
|
|
|
|
Td *td, *edtds;
|
|
|
|
Isoio *iso;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
iso = ep->aux;
|
|
|
|
iso->usbid = (ep->nb&Epmax)<<7 | (ep->dev->nb&Devmax);
|
|
|
|
iso->bw = ep->hz * ep->samplesz; /* bytes/sec */
|
|
|
|
if(ep->mode != OWRITE){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: bug: iso input streams not implemented\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
error("ohci iso input streams not implemented");
|
|
|
|
}else
|
|
|
|
iso->tok = Tdtokout;
|
|
|
|
|
|
|
|
iso->left = 0;
|
|
|
|
iso->nerrs = 0;
|
|
|
|
iso->frno = TRUNC(ctlr->ohci->fmnumber + 10, Ntdframes);
|
|
|
|
iso->nframes = 1000 / ep->pollival;
|
|
|
|
if(iso->nframes < 10){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: isoopen: less than 10 frames; using 10.\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
iso->nframes = 10;
|
|
|
|
}
|
|
|
|
iso->navail = iso->nframes;
|
|
|
|
iso->atds = edtds = nil;
|
|
|
|
for(i = 0; i < iso->nframes-1; i++){ /* -1 for dummy */
|
|
|
|
td = tdalloc();
|
|
|
|
td->ep = ep;
|
|
|
|
td->io = iso;
|
|
|
|
td->bp = allocb(ep->maxpkt);
|
|
|
|
td->anext = iso->atds; /* link as avail */
|
|
|
|
iso->atds = td;
|
|
|
|
td->next = edtds;
|
|
|
|
edtds = td;
|
|
|
|
}
|
|
|
|
newed(ctlr, ep, iso, "iso"); /* allocates a dummy td */
|
|
|
|
iso->ed->tds->bp = allocb(ep->maxpkt); /* but not its block */
|
|
|
|
iso->ed->tds->next = edtds;
|
|
|
|
isodtdinit(ep, iso, iso->ed->tds);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate the endpoint and set it up for I/O
|
|
|
|
* in the controller. This must follow what's said
|
|
|
|
* in Ep regarding configuration, including perhaps
|
|
|
|
* the saved toggles (saved on a previous close of
|
|
|
|
* the endpoint data file by epclose).
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
epopen(Ep *ep)
|
|
|
|
{
|
|
|
|
Ctlr *ctlr;
|
|
|
|
Qio *io;
|
|
|
|
Ctlio *cio;
|
|
|
|
uint32_t usbid;
|
|
|
|
|
|
|
|
ctlr = ep->hp->aux;
|
|
|
|
deprint("ohci: epopen ep%d.%d\n", ep->dev->nb, ep->nb);
|
|
|
|
if(ep->aux != nil)
|
|
|
|
panic("ohci: epopen called with open ep");
|
|
|
|
if(waserror()){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_free(ep->aux);
|
2016-11-25 17:18:40 +01:00
|
|
|
ep->aux = nil;
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
switch(ep->ttype){
|
|
|
|
case Tnone:
|
|
|
|
error("endpoint not configured");
|
|
|
|
case Tiso:
|
|
|
|
ep->aux = smalloc(sizeof(Isoio));
|
|
|
|
isoopen(ctlr, ep);
|
|
|
|
break;
|
|
|
|
case Tctl:
|
|
|
|
cio = ep->aux = smalloc(sizeof(Ctlio));
|
|
|
|
cio->debug = ep->debug;
|
|
|
|
cio->ndata = -1;
|
|
|
|
cio->data = nil;
|
|
|
|
cio->tok = -1; /* invalid; Tds will say */
|
|
|
|
if(ep->dev->isroot != 0 && ep->nb == 0) /* root hub */
|
|
|
|
break;
|
|
|
|
newed(ctlr, ep, cio, "epc");
|
|
|
|
break;
|
|
|
|
case Tbulk:
|
|
|
|
ep->pollival = 1; /* assume this; doesn't really matter */
|
|
|
|
/* and fall... */
|
|
|
|
case Tintr:
|
2017-01-03 00:42:37 +01:00
|
|
|
io = ep->aux = smalloc(sizeof(Qio)*3);
|
2016-11-25 17:18:40 +01:00
|
|
|
io[OREAD].debug = io[OWRITE].debug = ep->debug;
|
|
|
|
usbid = (ep->nb&Epmax)<<7 | (ep->dev->nb&Devmax);
|
|
|
|
if(ep->mode != OREAD){
|
|
|
|
if(ep->toggle[OWRITE] != 0)
|
|
|
|
io[OWRITE].toggle = Tddata1;
|
|
|
|
else
|
|
|
|
io[OWRITE].toggle = Tddata0;
|
|
|
|
io[OWRITE].tok = Tdtokout;
|
|
|
|
io[OWRITE].usbid = usbid;
|
|
|
|
io[OWRITE].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */
|
|
|
|
newed(ctlr, ep, io+OWRITE, "epw");
|
|
|
|
}
|
|
|
|
if(ep->mode != OWRITE){
|
|
|
|
if(ep->toggle[OREAD] != 0)
|
|
|
|
io[OREAD].toggle = Tddata1;
|
|
|
|
else
|
|
|
|
io[OREAD].toggle = Tddata0;
|
|
|
|
io[OREAD].tok = Tdtokin;
|
|
|
|
io[OREAD].usbid = usbid;
|
|
|
|
io[OREAD].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */
|
|
|
|
newed(ctlr, ep, io+OREAD, "epr");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
deprint("ohci: epopen done:\n");
|
|
|
|
if(debug || ep->debug)
|
|
|
|
dump(ep->hp);
|
|
|
|
poperror();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cancelio(Ep *ep, Qio *io)
|
|
|
|
{
|
|
|
|
Ed *ed;
|
|
|
|
Ctlr *ctlr;
|
|
|
|
|
|
|
|
ctlr = ep->hp->aux;
|
|
|
|
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
if(io == nil || io->state == Qclose){
|
|
|
|
assert(io == nil || io->ed == nil);
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ed = io->ed;
|
|
|
|
io->state = Qclose;
|
|
|
|
io->err = Eio;
|
|
|
|
aborttds(io);
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
if(!waserror()){
|
|
|
|
tsleep(&up->sleep, return0, 0, Abortdelay);
|
|
|
|
poperror();
|
|
|
|
}
|
|
|
|
|
|
|
|
wakeup(&io->r);
|
|
|
|
qlock(&io->ql);
|
|
|
|
/* wait for epio if running */
|
|
|
|
qunlock(&io->ql);
|
|
|
|
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
switch(ep->ttype){
|
|
|
|
case Tctl:
|
|
|
|
unlinkctl(ctlr, ed);
|
|
|
|
break;
|
|
|
|
case Tbulk:
|
|
|
|
unlinkbulk(ctlr, ed);
|
|
|
|
break;
|
|
|
|
case Tintr:
|
|
|
|
case Tiso:
|
|
|
|
unschedq(ctlr, io);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
panic("ohci cancelio: bad ttype");
|
|
|
|
}
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
edfree(io->ed);
|
|
|
|
io->ed = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
epclose(Ep *ep)
|
|
|
|
{
|
|
|
|
Ctlio *cio;
|
|
|
|
Isoio *iso;
|
|
|
|
Qio *io;
|
|
|
|
|
|
|
|
deprint("ohci: epclose ep%d.%d\n", ep->dev->nb, ep->nb);
|
|
|
|
if(ep->aux == nil)
|
|
|
|
panic("ohci: epclose called with closed ep");
|
|
|
|
switch(ep->ttype){
|
|
|
|
case Tctl:
|
|
|
|
cio = ep->aux;
|
|
|
|
cancelio(ep, cio);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_free(cio->data);
|
2016-11-25 17:18:40 +01:00
|
|
|
cio->data = nil;
|
|
|
|
break;
|
|
|
|
case Tbulk:
|
|
|
|
case Tintr:
|
|
|
|
io = ep->aux;
|
|
|
|
if(ep->mode != OWRITE){
|
|
|
|
cancelio(ep, &io[OREAD]);
|
|
|
|
if(io[OREAD].toggle == Tddata1)
|
|
|
|
ep->toggle[OREAD] = 1;
|
|
|
|
}
|
|
|
|
if(ep->mode != OREAD){
|
|
|
|
cancelio(ep, &io[OWRITE]);
|
|
|
|
if(io[OWRITE].toggle == Tddata1)
|
|
|
|
ep->toggle[OWRITE] = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Tiso:
|
|
|
|
iso = ep->aux;
|
|
|
|
cancelio(ep, iso);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
panic("epclose: bad ttype %d", ep->ttype);
|
|
|
|
}
|
|
|
|
|
|
|
|
deprint("ohci: epclose ep%d.%d: done\n", ep->dev->nb, ep->nb);
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_free(ep->aux);
|
2016-11-25 17:18:40 +01:00
|
|
|
ep->aux = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
portreset(Hci *hp, int port, int on)
|
|
|
|
{
|
|
|
|
Ctlr *ctlr;
|
|
|
|
Ohci *ohci;
|
|
|
|
|
|
|
|
if(on == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ctlr = hp->aux;
|
2017-08-11 01:47:15 +02:00
|
|
|
eqlock(&ctlr->resetl);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(waserror()){
|
|
|
|
qunlock(&ctlr->resetl);
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
ohci = ctlr->ohci;
|
|
|
|
ohci->rhportsts[port - 1] = Spp;
|
|
|
|
if((ohci->rhportsts[port - 1] & Ccs) == 0){
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
error("port not connected");
|
|
|
|
}
|
|
|
|
ohci->rhportsts[port - 1] = Spr;
|
|
|
|
while((ohci->rhportsts[port - 1] & Prsc) == 0){
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
dprint("ohci: portreset, wait for reset complete\n");
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
}
|
|
|
|
ohci->rhportsts[port - 1] = Prsc;
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
poperror();
|
|
|
|
qunlock(&ctlr->resetl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
portenable(Hci *hp, int port, int on)
|
|
|
|
{
|
|
|
|
Ctlr *ctlr;
|
|
|
|
|
|
|
|
ctlr = hp->aux;
|
|
|
|
dprint("ohci: %#p port %d enable=%d\n", ctlr->ohci, port, on);
|
2017-08-11 01:47:15 +02:00
|
|
|
eqlock(&ctlr->resetl);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(waserror()){
|
|
|
|
qunlock(&ctlr->resetl);
|
|
|
|
nexterror();
|
|
|
|
}
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
if(on)
|
|
|
|
ctlr->ohci->rhportsts[port - 1] = Spe | Spp;
|
|
|
|
else
|
|
|
|
ctlr->ohci->rhportsts[port - 1] = Cpe;
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
tsleep(&up->sleep, return0, 0, Enabledelay);
|
|
|
|
poperror();
|
|
|
|
qunlock(&ctlr->resetl);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
portstatus(Hci *hp, int port)
|
|
|
|
{
|
|
|
|
int v;
|
|
|
|
Ctlr *ub;
|
|
|
|
uint32_t ohcistatus;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We must return status bits as a
|
|
|
|
* get port status hub request would do.
|
|
|
|
*/
|
|
|
|
ub = hp->aux;
|
|
|
|
ohcistatus = ub->ohci->rhportsts[port - 1];
|
|
|
|
v = 0;
|
|
|
|
if(ohcistatus & Ccs)
|
|
|
|
v |= HPpresent;
|
|
|
|
if(ohcistatus & Pes)
|
|
|
|
v |= HPenable;
|
|
|
|
if(ohcistatus & Pss)
|
|
|
|
v |= HPsuspend;
|
|
|
|
if(ohcistatus & Prs)
|
|
|
|
v |= HPreset;
|
|
|
|
else {
|
|
|
|
/* port is not in reset; these potential writes are ok */
|
|
|
|
if(ohcistatus & Csc){
|
|
|
|
v |= HPstatuschg;
|
|
|
|
ub->ohci->rhportsts[port - 1] = Csc;
|
|
|
|
}
|
|
|
|
if(ohcistatus & Pesc){
|
|
|
|
v |= HPchange;
|
|
|
|
ub->ohci->rhportsts[port - 1] = Pesc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(ohcistatus & Lsda)
|
|
|
|
v |= HPslow;
|
|
|
|
if(v & (HPstatuschg|HPchange))
|
|
|
|
ddprint("ohci port %d sts %#ulx hub sts %#x\n", port, ohcistatus, v);
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dumpohci(Ctlr *ctlr)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
uint32_t *ohci;
|
|
|
|
|
|
|
|
ohci = &ctlr->ohci->revision;
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci registers: \n");
|
2016-11-25 17:18:40 +01:00
|
|
|
for(i = 0; i < sizeof(Ohci)/sizeof(uint32_t); i++)
|
|
|
|
if(i < 3 || ohci[i] != 0)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("\t[%#2.2x]\t%#8.8ulx\n", i * 4, ohci[i]);
|
|
|
|
jehanne_print("\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
init(Hci *hp)
|
|
|
|
{
|
|
|
|
Ctlr *ctlr;
|
|
|
|
Ohci *ohci;
|
|
|
|
int i;
|
|
|
|
uint32_t ival, ctrl, fmi;
|
|
|
|
|
|
|
|
ctlr = hp->aux;
|
|
|
|
dprint("ohci %#p init\n", ctlr->ohci);
|
|
|
|
ohci = ctlr->ohci;
|
|
|
|
|
|
|
|
fmi = ohci->fminterval;
|
|
|
|
ohci->cmdsts = Shcr; /* reset the block */
|
|
|
|
for(i = 0; i<100; i++){
|
|
|
|
if((ohci->cmdsts & Shcr) == 0)
|
|
|
|
break;
|
|
|
|
delay(1); /* wait till reset complete, Ohci says 10us max. */
|
|
|
|
}
|
|
|
|
if(i == 100)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: reset timed out\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
ohci->fminterval = fmi;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* now that soft reset is done we are in suspend state.
|
|
|
|
* Setup registers which take in suspend state
|
|
|
|
* (will only be here for 2ms).
|
|
|
|
*/
|
|
|
|
|
|
|
|
ctlr->ohci->hcca = ptr2pa(ctlr->hcca);
|
|
|
|
setctlhd(ctlr, nil);
|
|
|
|
ctlr->ohci->ctlcurred = 0;
|
|
|
|
setbulkhd(ctlr, nil);
|
|
|
|
ctlr->ohci->bulkcurred = 0;
|
|
|
|
|
|
|
|
ohci->intrenable = Mie | Wdh | Ue;
|
|
|
|
ohci->control |= Ccle | Cble | Cple | Cie | Cfsoper;
|
|
|
|
|
|
|
|
/* set frame after operational */
|
|
|
|
ohci->rhdesca = Nps; /* no power switching */
|
|
|
|
if(ohci->rhdesca & Nps){
|
|
|
|
dprint("ohci: ports are not power switched\n");
|
|
|
|
}else{
|
|
|
|
dprint("ohci: ports are power switched\n");
|
|
|
|
ohci->rhdesca &= ~Psm;
|
|
|
|
ohci->rhsts &= ~Lpsc;
|
|
|
|
}
|
|
|
|
for(i = 0; i < ctlr->nports; i++) /* paranoia */
|
|
|
|
ohci->rhportsts[i] = 0; /* this has no effect */
|
|
|
|
delay(50);
|
|
|
|
|
|
|
|
for(i = 0; i < ctlr->nports; i++){
|
|
|
|
ohci->rhportsts[i] = Spp;
|
|
|
|
if((ohci->rhportsts[i] & Ccs) != 0)
|
|
|
|
ohci->rhportsts[i] |= Spr;
|
|
|
|
}
|
|
|
|
delay(100);
|
|
|
|
|
|
|
|
ctrl = ohci->control;
|
|
|
|
if((ctrl & Cfsmask) != Cfsoper){
|
|
|
|
ctrl = (ctrl & ~Cfsmask) | Cfsoper;
|
|
|
|
ohci->control = ctrl;
|
|
|
|
ohci->rhsts = Lpsc;
|
|
|
|
}
|
|
|
|
ival = ohci->fminterval & ~(Fmaxpktmask << Fmaxpktshift);
|
|
|
|
ohci->fminterval = ival | (5120 << Fmaxpktshift);
|
|
|
|
|
|
|
|
if(debug > 1)
|
|
|
|
dumpohci(ctlr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
scanpci(void)
|
|
|
|
{
|
|
|
|
uint32_t mem;
|
|
|
|
Ctlr *ctlr;
|
|
|
|
Pcidev *p;
|
|
|
|
int i;
|
|
|
|
static int already = 0;
|
|
|
|
|
|
|
|
if(already)
|
|
|
|
return;
|
|
|
|
already = 1;
|
|
|
|
p = nil;
|
|
|
|
while(p = pcimatch(p, 0, 0)) {
|
|
|
|
/*
|
|
|
|
* Find Ohci controllers (Programming Interface = 0x10).
|
|
|
|
*/
|
|
|
|
if(p->ccrb != Pcibcserial || p->ccru != Pciscusb ||
|
|
|
|
p->ccrp != 0x10)
|
|
|
|
continue;
|
|
|
|
mem = p->mem[0].bar & ~0x0F;
|
|
|
|
dprint("ohci: %x/%x port 0x%lux size 0x%x irq %d\n",
|
|
|
|
p->vid, p->did, mem, p->mem[0].size, p->intl);
|
|
|
|
if(mem == 0){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: failed to map registers\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-04-19 23:33:14 +02:00
|
|
|
ctlr = jehanne_malloc(sizeof(Ctlr));
|
2016-11-25 17:18:40 +01:00
|
|
|
if(ctlr == nil){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: no memory\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
ctlr->pcidev = p;
|
|
|
|
ctlr->ohci = vmap(mem, p->mem[0].size);
|
|
|
|
dprint("scanpci: ctlr %#p, ohci %#p\n", ctlr, ctlr->ohci);
|
|
|
|
pcisetbme(p);
|
|
|
|
pcisetpms(p, 0);
|
|
|
|
for(i = 0; i < Nhcis; i++)
|
|
|
|
if(ctlrs[i] == nil){
|
|
|
|
ctlrs[i] = ctlr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(i == Nhcis)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: bug: no more controllers\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
usbdebug(Hci* _, int d)
|
|
|
|
{
|
|
|
|
debug = d;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* build the periodic scheduling tree:
|
|
|
|
* framesize must be a multiple of the tree size
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
mkqhtree(Ctlr *ctlr)
|
|
|
|
{
|
|
|
|
int i, n, d, o, leaf0, depth;
|
|
|
|
Ed **tree;
|
|
|
|
Qtree *qt;
|
|
|
|
|
|
|
|
depth = flog2(32);
|
|
|
|
n = (1 << (depth+1)) - 1;
|
2017-04-19 23:33:14 +02:00
|
|
|
qt = jehanne_mallocz(sizeof(*qt), 1);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(qt == nil)
|
|
|
|
panic("ohci: can't allocate scheduling tree");
|
|
|
|
qt->nel = n;
|
|
|
|
qt->depth = depth;
|
2017-04-19 23:33:14 +02:00
|
|
|
qt->bw = jehanne_mallocz(n * sizeof(qt->bw), 1);
|
|
|
|
qt->root = tree = jehanne_mallocz(n * sizeof(Ed *), 1);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(qt->bw == nil || qt->root == nil)
|
|
|
|
panic("ohci: can't allocate scheduling tree");
|
|
|
|
for(i = 0; i < n; i++){
|
|
|
|
if((tree[i] = edalloc()) == nil)
|
|
|
|
panic("ohci: mkqhtree");
|
|
|
|
tree[i]->ctrl = (8 << Edmpsshift); /* not needed */
|
|
|
|
tree[i]->ctrl |= Edskip;
|
|
|
|
|
|
|
|
if(i > 0)
|
|
|
|
edlinked(tree[i], tree[(i-1)/2]);
|
|
|
|
else
|
|
|
|
edlinked(tree[i], nil);
|
|
|
|
}
|
|
|
|
ctlr->ntree = i;
|
|
|
|
dprint("ohci: tree: %d endpoints allocated\n", i);
|
|
|
|
|
|
|
|
/* distribute leaves evenly round the frame list */
|
|
|
|
leaf0 = n / 2;
|
|
|
|
for(i = 0; i < 32; i++){
|
|
|
|
o = 0;
|
|
|
|
for(d = 0; d < depth; d++){
|
|
|
|
o <<= 1;
|
|
|
|
if(i & (1 << d))
|
|
|
|
o |= 1;
|
|
|
|
}
|
|
|
|
if(leaf0 + o >= n){
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("leaf0=%d o=%d i=%d n=%d\n", leaf0, o, i, n);
|
2016-11-25 17:18:40 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
ctlr->hcca->intrtable[i] = ptr2pa(tree[leaf0 + o]);
|
|
|
|
}
|
|
|
|
ctlr->tree = qt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ohcimeminit(Ctlr *ctlr)
|
|
|
|
{
|
|
|
|
Hcca *hcca;
|
|
|
|
|
|
|
|
edfree(edalloc()); /* allocate pools now */
|
|
|
|
tdfree(tdalloc());
|
|
|
|
|
2017-04-19 23:33:14 +02:00
|
|
|
hcca = jehanne_mallocalign(sizeof(Hcca), 256, 0, 0);
|
2016-11-25 17:18:40 +01:00
|
|
|
if(hcca == nil)
|
|
|
|
panic("ohci: no memory for Hcca");
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_memset(hcca, 0, sizeof(*hcca));
|
2016-11-25 17:18:40 +01:00
|
|
|
ctlr->hcca = hcca;
|
|
|
|
|
|
|
|
mkqhtree(ctlr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ohcireset(Ctlr *ctlr)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
dprint("ohci %#p reset\n", ctlr->ohci);
|
|
|
|
|
|
|
|
if(ctlr->ohci->control & Cir){
|
|
|
|
dprint("ohci: smm active, taking over\n");
|
|
|
|
ctlr->ohci->cmdsts |= Socr; /* take ownership */
|
|
|
|
for(i = 0; i<100; i++){
|
|
|
|
if((ctlr->ohci->control & Cir) == 0)
|
|
|
|
break;
|
|
|
|
delay(1);
|
|
|
|
}
|
|
|
|
if(i == 100)
|
2017-04-19 23:33:14 +02:00
|
|
|
jehanne_print("ohci: smm takeover timed out\n");
|
2016-11-25 17:18:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* usually enter here in reset, wait till its through,
|
|
|
|
* then do our own so we are on known timing conditions.
|
|
|
|
* Is this needed?
|
|
|
|
*/
|
|
|
|
delay(100);
|
|
|
|
ctlr->ohci->control = 0;
|
|
|
|
delay(100);
|
|
|
|
|
|
|
|
/* legacy support register: turn off lunacy mode */
|
|
|
|
pcicfgw16(ctlr->pcidev, 0xc0, 0x2000);
|
|
|
|
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
shutdown(Hci *hp)
|
|
|
|
{
|
|
|
|
Ctlr *ctlr;
|
|
|
|
|
|
|
|
ctlr = hp->aux;
|
|
|
|
|
|
|
|
ilock(&ctlr->l);
|
|
|
|
ctlr->ohci->intrdisable = Mie;
|
|
|
|
ctlr->ohci->intrenable = 0;
|
|
|
|
ctlr->ohci->control = 0;
|
|
|
|
delay(100);
|
|
|
|
iunlock(&ctlr->l);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
reset(Hci *hp)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
Ctlr *ctlr;
|
|
|
|
Pcidev *p;
|
|
|
|
static Lock resetlck;
|
|
|
|
|
|
|
|
if(getconf("*nousbohci"))
|
|
|
|
return -1;
|
|
|
|
ilock(&resetlck);
|
|
|
|
scanpci();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Any adapter matches if no hp->port is supplied,
|
|
|
|
* otherwise the ports must match.
|
|
|
|
*/
|
|
|
|
ctlr = nil;
|
|
|
|
for(i = 0; i < Nhcis && ctlrs[i] != nil; i++){
|
|
|
|
ctlr = ctlrs[i];
|
|
|
|
if(ctlr->active == 0)
|
|
|
|
if(hp->port == 0 || hp->port == (uintptr_t)ctlr->ohci){
|
|
|
|
ctlr->active = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
iunlock(&resetlck);
|
|
|
|
if(ctlrs[i] == nil || i == Nhcis)
|
|
|
|
return -1;
|
|
|
|
if(ctlr->ohci->control == ~0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
p = ctlr->pcidev;
|
|
|
|
hp->aux = ctlr;
|
|
|
|
hp->port = (uintptr_t)ctlr->ohci;
|
|
|
|
hp->irq = p->intl;
|
|
|
|
hp->tbdf = p->tbdf;
|
|
|
|
ctlr->nports = hp->nports = ctlr->ohci->rhdesca & 0xff;
|
|
|
|
|
|
|
|
ohcireset(ctlr);
|
|
|
|
ohcimeminit(ctlr);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Linkage to the generic HCI driver.
|
|
|
|
*/
|
|
|
|
hp->init = init;
|
|
|
|
hp->dump = dump;
|
|
|
|
hp->interrupt = interrupt;
|
|
|
|
hp->epopen = epopen;
|
|
|
|
hp->epclose = epclose;
|
|
|
|
hp->epread = epread;
|
|
|
|
hp->epwrite = epwrite;
|
|
|
|
hp->seprintep = seprintep;
|
|
|
|
hp->portenable = portenable;
|
|
|
|
hp->portreset = portreset;
|
|
|
|
hp->portstatus = portstatus;
|
|
|
|
hp->shutdown = shutdown;
|
|
|
|
hp->debug = usbdebug;
|
|
|
|
hp->type = "ohci";
|
|
|
|
|
|
|
|
/*
|
|
|
|
* IRQ2 doesn't really exist, it's used to gang the interrupt
|
|
|
|
* controllers together. A device set to IRQ2 will appear on
|
|
|
|
* the second interrupt controller as IRQ9.
|
|
|
|
*/
|
|
|
|
if(hp->irq == 2)
|
|
|
|
hp->irq = 9;
|
|
|
|
intrenable(hp->irq, hp->interrupt, hp, hp->tbdf, hp->type);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
usbohcilink(void)
|
|
|
|
{
|
|
|
|
addhcitype("ohci", reset);
|
|
|
|
}
|