Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
GhettOHCI - perhaps the world's smallest and stupidest OHCI stack.
/*
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
You can’t perform that action at this time.