Created
April 24, 2018 16:48
-
-
Save marcan/4ce7e13321a03d85a73b10840f532892 to your computer and use it in GitHub Desktop.
GhettOHCI - perhaps the world's smallest and stupidest OHCI stack.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
mini - a Free Software replacement for the Nintendo/BroadOn IOS. | |
ghettohci - debug over FT232 over OHCI | |
Copyright (C) 2012 Hector Martin "marcan" <marcan@marcansoft.com> | |
# This code is licensed to you under the terms of the GNU GPL, version 2; | |
# see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt | |
*/ | |
#include "types.h" | |
#include "utils.h" | |
#include "debug.h" | |
#include "memory.h" | |
#include "string.h" | |
#include "hollywood.h" | |
#include "ohci.h" | |
#define dprintf dsprintf | |
#define HC_REVISION 0x0 | |
#define HC_CONTROL 0x4 | |
#define HC_COMMANDSTATUS 0x8 | |
#define HC_INTERRUPTSTATUS 0xc | |
#define HC_INTERRUPTENABLE 0x10 | |
#define HC_INTERRUPTDISABLE 0x14 | |
#define HC_HCCA 0x18 | |
#define HC_PERIODCURRENTED 0x1c | |
#define HC_CONTROLHEADED 0x20 | |
#define HC_CONTROLCURRENTED 0x24 | |
#define HC_BULKHEADED 0x28 | |
#define HC_BULKCURRENTED 0x2c | |
#define HC_DONEHEAD 0x30 | |
#define HC_FMINTERVAL 0x34 | |
#define HC_FMREMAINING 0x38 | |
#define HC_FMNUMBER 0x3c | |
#define HC_PERIODICSTART 0x40 | |
#define HC_LSTHRESHOLD 0x44 | |
#define HC_RHDESCRIPTORA 0x48 | |
#define HC_RHDESCRIPTORB 0x4c | |
#define HC_RHSTATUS 0x50 | |
#define HC_RHPORTSTATUS 0x54 | |
#define CLE 0x10 | |
#define BLE 0x20 | |
#define HCFS_OPERATIONAL 0x80 | |
#define HCR 0x01 | |
#define CLF 0x02 | |
#define BLF 0x04 | |
#define CCS 0x0001 | |
#define PES 0x0002 | |
#define PRS 0x0010 | |
#define PPS 0x0100 | |
#define LSDA 0x0200 | |
#define HCCA_FRAMENUMBER 0x20 | |
#define HCCA_DONEHEAD 0x21 | |
#define H 1 | |
#define C 2 | |
static inline u32 reg_read32(u32 reg) | |
{ | |
return read32(OHCI0_REG_BASE + reg); | |
} | |
static inline void reg_write32(u32 reg, u32 val) | |
{ | |
write32(OHCI0_REG_BASE + reg, val); | |
} | |
static inline u32 swab32(u32 val) | |
{ | |
return (val>>24)|((val>>8)&0x0000ff00)|((val<<8)&0x00ff0000)|(val<<24); | |
} | |
static inline u16 swab16(u16 val) | |
{ | |
return (val>>8)|(val<<8); | |
} | |
#ifdef NO_MMU | |
#define UNCACHED_BASE 0 | |
#else | |
#define UNCACHED_BASE 0xc0000000 | |
#endif | |
static inline u32 mem_read32(void *mem) | |
{ | |
return swab32(read32(UNCACHED_BASE | (u32)mem)); | |
} | |
static inline void mem_write32(void *mem, u32 val) | |
{ | |
write32(UNCACHED_BASE | (u32)mem, swab32(val)); | |
} | |
void uberswab(void *addr, u32 len) | |
{ | |
u32 *p = addr; | |
if (len & 3) { | |
dprintf("uberswab: not a multiple of 32 bits\n"); | |
} | |
len >>= 2; | |
dc_flushrange(addr, len); | |
while (len--) { | |
mem_write32(p, read32(UNCACHED_BASE | (u32)p)); | |
p++; | |
} | |
} | |
#define read32 fail32 | |
#define write32 fail32 | |
struct ed { | |
u32 flags; | |
u32 tail; | |
u32 head; | |
u32 nexted; | |
} ALIGNED(32); | |
ct_assert(sizeof(struct ed) == 32); | |
struct td { | |
u32 flags; | |
u32 cbp; | |
u32 next; | |
u32 be; | |
} ALIGNED(32); | |
ct_assert(sizeof(struct td) == 32); | |
// Yes, I am totally hardcoding the descriptors right here. | |
// Who needs .text when you have .data? | |
u8 bulk_buf[256] ALIGNED(32) MEM2_BSS; | |
u8 setup_buf[32] ALIGNED(32) MEM2_BSS; | |
u8 ctl_buf[64] ALIGNED(32) MEM2_BSS; | |
struct ed ctl_ed MEM2_DATA ALIGNED(32) = { | |
0x00080000, | |
0, 0, 0 | |
}; | |
struct ed in_ed MEM2_DATA ALIGNED(32) = { | |
0x00401081, | |
0, 0, 0 | |
}; | |
struct ed out_ed MEM2_DATA ALIGNED(32) = { | |
0x00400901, | |
0, 0, 0 | |
}; | |
#define ALLOW_SHORT (1<<18) | |
#define SETUP (0<<19) | |
#define OUT (1<<19) | |
#define IN (2<<19) | |
#define DT_ED (0<<24) | |
#define DT_DATA0 (2<<24) | |
#define DT_DATA1 (3<<24) | |
struct td bulk_td MEM2_DATA ALIGNED(32) = { | |
0, 0, 0, 0 | |
}; | |
struct td setup_td MEM2_DATA ALIGNED(32) = { | |
0, 0, 0, 7 + (u32)setup_buf | |
}; | |
struct td data_td MEM2_DATA ALIGNED(32) = { | |
0, 0, 0, 0 | |
}; | |
struct td status_td MEM2_DATA ALIGNED(32) = { | |
0, 0, 0, 0 | |
}; | |
static u32 hcca[64] MEM2_BSS ALIGNED(256); | |
static int dev_port = -1; | |
static int initialized = 0; | |
static void dump(void) | |
{ | |
int i; | |
dprintf("HC register dump:\n"); | |
for (i = HC_REVISION; i <= (HC_RHPORTSTATUS+1); i+=4) { | |
dprintf("%02x = %08x\n", i, reg_read32(i)); | |
} | |
} | |
static int waitsof(void) | |
{ | |
int i = 1000; | |
u32 val = reg_read32(HC_FMNUMBER); | |
while (i--) { | |
if (reg_read32(HC_FMNUMBER) != val) | |
return 1; | |
udelay(10); | |
} | |
dprintf("waitsof() failed\n"); | |
dump(); | |
return 0; | |
} | |
int ctl_xfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, void *data) | |
{ | |
dprintf("CTL: %02x %02x %04x %04x %04x %p\n", bmRequestType, bRequest, wValue, wIndex, wLength, data); | |
if (wLength > 64) { | |
dprintf("ctl xfer too long\n"); | |
wLength = 64; | |
} | |
setup_buf[0] = bmRequestType; | |
setup_buf[1] = bRequest; | |
*(u16*)&setup_buf[2] = swab16(wValue); | |
*(u16*)&setup_buf[4] = swab16(wIndex); | |
*(u16*)&setup_buf[6] = swab16(wLength); | |
dc_flushrange(setup_buf, 8); | |
mem_write32(&setup_td.flags, SETUP | DT_DATA0); | |
mem_write32(&setup_td.cbp, (u32)setup_buf); | |
if (wLength) { | |
mem_write32(&setup_td.next, (u32)&data_td); | |
mem_write32(&data_td.cbp, (u32)&ctl_buf); | |
mem_write32(&data_td.be, wLength - 1 + (u32)ctl_buf); | |
mem_write32(&data_td.next, (u32)&status_td); | |
if (bmRequestType & 0x80) { | |
mem_write32(&data_td.flags, IN | DT_DATA1 | ALLOW_SHORT); | |
mem_write32(&status_td.flags, OUT | DT_DATA1 | ALLOW_SHORT); | |
dc_invalidaterange(ctl_buf, wLength); | |
} else { | |
mem_write32(&data_td.flags, OUT | DT_DATA1); | |
mem_write32(&status_td.flags, IN | DT_DATA1 | ALLOW_SHORT); | |
memcpy(ctl_buf, data, wLength); | |
dc_flushrange(ctl_buf, wLength); | |
} | |
} else { | |
mem_write32(&setup_td.next, (u32)&status_td); | |
mem_write32(&status_td.flags, IN | DT_DATA1 | ALLOW_SHORT); | |
} | |
mem_write32(&status_td.next, 0); | |
mem_write32(&ctl_ed.tail, 0); | |
mem_write32(&ctl_ed.head, (u32)&setup_td); | |
ahb_flush_from(AHB_1); | |
ahb_flush_to(AHB_OHCI0); | |
/*dprintf("TDs: %p %p %p\n", &setup_td, &data_td, &status_td ); | |
hexdump((void*)(0xc0000000 | (u32)&setup_td), 16); | |
hexdump((void*)(0xc0000000 | (u32)&data_td), 16); | |
hexdump((void*)(0xc0000000 | (u32)&status_td), 16); | |
hexdump((void*)(0xc0000000 | (u32)&ctl_ed), 16);*/ | |
reg_write32(HC_CONTROLHEADED, (u32)&ctl_ed); | |
reg_write32(HC_INTERRUPTSTATUS, 0x2); | |
reg_write32(HC_COMMANDSTATUS, CLF); | |
reg_write32(HC_CONTROL, CLE | HCFS_OPERATIONAL); | |
int i = 10; | |
while (1) { | |
ahb_flush_from(AHB_OHCI0); | |
ahb_flush_to(AHB_STARLET); | |
u32 v = mem_read32(&ctl_ed.head); | |
if (v & H) { | |
dprintf("Xfer error, EP halted. Setup CC=%d\n", mem_read32(&setup_td.flags) >> 28); | |
return -1; | |
} | |
//dprintf("%08x %08x %08x %08x %08x %08x \n", v, mem_read32(&ctl_ed.flags), mem_read32(&setup_td.flags), mem_read32(&data_td.flags), mem_read32(&status_td.flags), reg_read32(HC_INTERRUPTSTATUS)); | |
if ((v & 0xfffffff0) == 0) | |
break; | |
if (!i-- || !waitsof()) { | |
dprintf("Xfer timed out\n"); | |
dump(); | |
return -1; | |
} | |
} | |
reg_write32(HC_CONTROL, HCFS_OPERATIONAL); | |
int xfered = wLength; | |
u32 cbp = mem_read32(&data_td.cbp); | |
if (cbp) { | |
xfered = cbp - (u32)ctl_buf; | |
dprintf("Short ctl xfer: %d/%d\n", xfered, wLength); | |
} | |
if (xfered && bmRequestType & 0x80) { | |
memcpy(data, ctl_buf, xfered); | |
} | |
return xfered; | |
} | |
int bulk_xfer(int is_write, void *data, int len) | |
{ | |
if (len > 256) { | |
dprintf("data xfer too long\n"); | |
len = 256; | |
} | |
if (!len && !is_write) | |
return 0; | |
mem_write32(&bulk_td.cbp, (u32)bulk_buf); | |
mem_write32(&bulk_td.be, len - 1 + (u32)bulk_buf); | |
mem_write32(&bulk_td.next, 0); | |
struct ed *ep_desc; | |
if (is_write) { | |
mem_write32(&bulk_td.flags, OUT | DT_ED); | |
memcpy(bulk_buf, data, len); | |
dc_flushrange(bulk_buf, len); | |
ep_desc = &out_ed; | |
} else { | |
mem_write32(&bulk_td.flags, IN | DT_ED | ALLOW_SHORT); | |
dc_invalidaterange(bulk_buf, len); | |
ep_desc = &in_ed; | |
} | |
mem_write32(&ep_desc->tail, 0); | |
u32 c = mem_read32(&ep_desc->head) & C; | |
mem_write32(&ep_desc->head, c|(u32)&bulk_td); | |
ahb_flush_from(AHB_1); | |
ahb_flush_to(AHB_OHCI0); | |
reg_write32(HC_BULKHEADED, (u32)ep_desc); | |
reg_write32(HC_INTERRUPTSTATUS, 0x2); | |
reg_write32(HC_COMMANDSTATUS, BLF); | |
reg_write32(HC_CONTROL, BLE | HCFS_OPERATIONAL); | |
int i = 50; | |
while (1) { | |
ahb_flush_from(AHB_OHCI0); | |
ahb_flush_to(AHB_STARLET); | |
u32 v = mem_read32(&ep_desc->head); | |
if (v & H) { | |
dprintf("Xfer error, EP halted. Bulk CC=%d\n", mem_read32(&bulk_td.flags) >> 28); | |
return -1; | |
} | |
if ((v & 0xfffffff0) == 0) | |
break; | |
// reads should never block (modem status is always returned), while writes | |
// should reasonably complete in 50 frames unless something's bork | |
if (!i-- || !waitsof()) { | |
dprintf("Xfer timed out\n"); | |
dump(); | |
return -1; | |
} | |
} | |
reg_write32(HC_CONTROL, HCFS_OPERATIONAL); | |
ahb_flush_from(AHB_OHCI0); | |
ahb_flush_to(AHB_STARLET); | |
u32 cbp = mem_read32(&bulk_td.cbp); | |
if (cbp) { | |
len = cbp - (u32)bulk_buf; | |
} | |
if (len && !is_write) { | |
memcpy(data, bulk_buf, len); | |
} | |
return len; | |
} | |
#define SET_ADDRESS 5 | |
#define GET_DESCRIPTOR 6 | |
#define SET_CONFIGURATION 9 | |
int ftdi_init(void) | |
{ | |
u16 dev[8]; | |
if (ctl_xfer(0x80, GET_DESCRIPTOR, 0x100, 0, 16, dev) < 0) | |
return -1; | |
u16 vid = swab16(dev[4]); | |
u16 pid = swab16(dev[5]); | |
dprintf("VID=%04x, PID=%04x\n", vid, pid); | |
if (vid != 0x0403 || pid != 0x6001) { | |
dprintf("Wrong device\n"); | |
return -1; | |
} | |
if (ctl_xfer(0, SET_ADDRESS, 1, 0, 0, NULL) < 0) | |
return -1; | |
mem_write32(&ctl_ed.flags, mem_read32(&ctl_ed.flags) | 1); // new addr | |
if (ctl_xfer(0, SET_CONFIGURATION, 1, 0, 0, NULL) < 0) | |
return -1; | |
if (ctl_xfer(0x40, 0, 0, 0, 0, NULL) < 0) | |
return -1; | |
// 230400 baud | |
if (ctl_xfer(0x40, 3, 0xd, 0, 0, NULL) < 0) | |
return -1; | |
dprintf("FTDI configured\n"); | |
return 0; | |
} | |
int ftdi_write(const char *buf, int len) | |
{ | |
if (!initialized) | |
return -1; | |
int res; | |
int done = 0; | |
while (len) { | |
int tlen = len; | |
if (tlen > 256) | |
tlen = 256; | |
res = bulk_xfer(1, (void*)buf, tlen); | |
if (res != tlen) { | |
dprintf("Bulk OUT transfer failed: %d/%d\n", res, tlen); | |
if (res > 0) | |
done += res; | |
return done; | |
} | |
done += tlen; | |
buf += tlen; | |
len -= tlen; | |
} | |
return done; | |
} | |
static u8 rbuf[64]; | |
static u8 *rp = NULL; | |
static u8 rlen = 0; | |
int ftdi_read(char *buf, int len) | |
{ | |
if (!initialized) | |
return -1; | |
int res; | |
int done = 0; | |
while (len) { | |
int tlen = len; | |
if (rlen && rp) { | |
if (tlen > rlen) | |
tlen = rlen; | |
memcpy(buf, rp, tlen); | |
rlen -= tlen; | |
len -= tlen; | |
rp += tlen; | |
done += tlen; | |
buf += tlen; | |
} else { | |
res = bulk_xfer(0, rbuf, 64); | |
if (res < 2) { | |
dprintf("Bulk IN transfer failed: %d\n", res); | |
return done; | |
} | |
if (res > 64) { | |
dprintf("Bulk IN transfer wtf: %d\n", res); | |
} | |
rp = rbuf+2; | |
rlen = res - 2; | |
} | |
} | |
return done; | |
} | |
int ohci_initialize(void) | |
{ | |
int i; | |
// first, swab our memory structures | |
// this also flushes them | |
uberswab(&ctl_ed, sizeof(ctl_ed)); | |
uberswab(&setup_td, sizeof(setup_td)); | |
uberswab(&data_td, sizeof(data_td)); | |
uberswab(&status_td, sizeof(status_td)); | |
uberswab(&in_ed, sizeof(in_ed)); | |
uberswab(&out_ed, sizeof(out_ed)); | |
uberswab(&bulk_td, sizeof(bulk_td)); | |
dprintf("OHCI revision: 0x%x\n", reg_read32(HC_REVISION)); | |
// reset | |
reg_write32(HC_COMMANDSTATUS, HCR); | |
udelay(100); | |
if (reg_read32(HC_COMMANDSTATUS) & HCR) { | |
dprintf("OHCI reset failed\n"); | |
return -1; | |
} | |
dprintf("OHCI reset complete\n"); | |
reg_write32(HC_CONTROL, 0); | |
// power up ports | |
int ports = reg_read32(HC_RHDESCRIPTORA) & 0xff; | |
dprintf("%d root hub ports\n", ports); | |
for (i = 0; i < ports; i++) { | |
reg_write32(HC_RHPORTSTATUS + 4*i, PPS); | |
} | |
udelay(50000); | |
// set HCCA pointer | |
memset(hcca, 0, sizeof(hcca)); | |
dc_flushrange(hcca, sizeof(hcca)); | |
reg_write32(HC_HCCA, (u32)hcca); | |
dprintf("HCCA at %p 0x%x\n", hcca, reg_read32(HC_HCCA)); | |
// set frame interval and largest data packet | |
reg_write32(HC_FMINTERVAL, 0x27782edf); | |
// go into operational mode | |
reg_write32(HC_CONTROL, HCFS_OPERATIONAL); | |
if (!waitsof()) { | |
dprintf("USB is made of fail\n"); | |
return -1; | |
} | |
dprintf("USB is operational\n"); | |
// look for devices | |
dev_port = -1; | |
for (i = 0; i < ports; i++) { | |
u32 status = reg_read32(HC_RHPORTSTATUS + 4*i); | |
dprintf("Port %d: %08x\n", i, status); | |
if (status & CCS) { | |
dprintf(" Device connnected\n"); | |
if (status & LSDA) { | |
dprintf(" Low speed\n"); | |
} else { | |
if (dev_port == -1) | |
dev_port = i; | |
} | |
} | |
} | |
if (dev_port == -1) { | |
dprintf("No full speed devices connected. You want hotplugging? HA, SUCKS TO BE YOU!\n"); | |
return -1; | |
} | |
// clear change flags | |
reg_write32(HC_RHPORTSTATUS + 4*dev_port, 0xffff0000); | |
// reset and enable the port | |
reg_write32(HC_RHPORTSTATUS + 4*dev_port, PRS); | |
// wait for some event | |
while(!(reg_read32(HC_RHPORTSTATUS + 4*dev_port) & 0xffff0000)); | |
// did it work? | |
if (!(reg_read32(HC_RHPORTSTATUS + 4*dev_port) & PES)) { | |
dprintf("Could not enable device\n"); | |
return -1; | |
} | |
dprintf("Device is reset\n"); | |
if (ftdi_init() < 0) | |
return -1; | |
initialized = 1; | |
return 0; | |
} | |
void ohci_shutdown(void) | |
{ | |
// meh | |
reg_write32(HC_COMMANDSTATUS, HCR); | |
initialized = 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment