blob: 5b23410588c3141071f606737e18c0ef083fc6da [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "usbdpi.h"
#ifdef __linux__
#include <pty.h>
#elif __APPLE__
#include <util.h>
#endif
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "verilator_sim_ctrl.h"
static const char *st_states[] = {"ST_IDLE 0", "ST_SEND 1", "ST_GET 2",
"ST_SYNC 3", "ST_EOP 4", "ST_EOP0 5"};
static const char *hs_states[] = {
"HS_STARTFRAME 0", "HS_WAITACK 1", "HS_SET_DATASTAGE 2", "HS_DS_RXDATA 3",
"HS_DS_SENDACK 4", "HS_DONEDADR 5", "HS_REQDATA 6", "HS_WAITDATA 7",
"HS_SENDACK 8", "HS_WAIT_PKT 9", "HS_ACKIFDATA 10", "HS_SENDHI 11",
"HS_EMPTYDATA 12", "HS_WAITACK2 13", "HS_NEXTFRAME 14"};
extern VerilatorSimCtrl *simctrl;
static void finish(void) {
if (!simctrl) {
return;
}
simctrl->RequestStop();
}
void *usbdpi_create(const char *name, int loglevel) {
int i;
struct usbdpi_ctx *ctx =
(struct usbdpi_ctx *)calloc(1, sizeof(struct usbdpi_ctx));
assert(ctx);
ctx->tick = 0;
ctx->frame = 0;
ctx->framepend = 0;
ctx->lastframe = 0;
ctx->inframe = 4;
ctx->state = ST_IDLE;
ctx->driving = 0;
ctx->hostSt = HS_NEXTFRAME;
ctx->loglevel = loglevel;
ctx->mon = monitor_usb_init();
char cwd[PATH_MAX];
char *cwd_rv;
cwd_rv = getcwd(cwd, sizeof(cwd));
assert(cwd_rv != NULL);
int rv;
rv = snprintf(ctx->fifo_pathname, PATH_MAX, "%s/%s", cwd, name);
assert(rv <= PATH_MAX && rv > 0);
rv = mkfifo(ctx->fifo_pathname, 0644); // writes are not supported currently
if (rv != 0) {
fprintf(stderr, "USB: Unable to create FIFO at %s: %s\n",
ctx->fifo_pathname, strerror(errno));
return NULL;
}
ctx->fifo_fd = open(ctx->fifo_pathname, O_RDWR);
if (ctx->fifo_fd < 0) {
fprintf(stderr, "USB: Unable to open FIFO at %s: %s\n", ctx->fifo_pathname,
strerror(errno));
return NULL;
}
printf(
"\n"
"USB: FIFO pipe created at %s. Run\n"
"$ cat %s\n"
"to observe the output.\n",
ctx->fifo_pathname, ctx->fifo_pathname);
return (void *)ctx;
}
const char *decode_usb[] = {"SE0", "0-K", "1-J", "SE1"};
void usbdpi_device_to_host(void *ctx_void, svBitVecVal *d2p_data) {
struct usbdpi_ctx *ctx = (struct usbdpi_ctx *)ctx_void;
assert(ctx);
int d2p = d2p_data[0];
int dp, dn;
int n;
char obuf[MAX_OBUF];
char raw_str[D2P_BITS + 1];
for (int i = 0; i < 5; i++) {
raw_str[5 - i - 1] = !!(d2p & (1 << i)) + '0';
}
raw_str[D2P_BITS] = 0;
if (d2p & (D2P_DP_EN | D2P_DN_EN)) {
if (ctx->state == ST_SEND) {
printf("USB: %4x %8d error state %s hs %s and device drives\n",
ctx->frame, ctx->tick, st_states[ctx->state],
hs_states[ctx->hostSt]);
}
ctx->state = ST_GET;
} else {
if (ctx->state == ST_GET) {
ctx->state = ST_IDLE;
}
}
dp = (((d2p & D2P_DP_EN) && (d2p & D2P_DP)) ||
(!(d2p & D2P_DP_EN) && (d2p & D2P_PU)))
? 1
: 0;
dn = ((d2p & D2P_DN_EN) && (d2p & D2P_DN)) ? 1 : 0;
if (ctx->loglevel & LOG_BIT) {
n = snprintf(obuf, MAX_OBUF, "%4x %8d %s %s %s\n", ctx->frame, ctx->tick,
raw_str, (d2p & D2P_PU) ? "PU" : " ",
(ctx->state == ST_GET) ? decode_usb[dp << 1 | dn] : "ZZ ");
ssize_t written = write(ctx->fifo_fd, obuf, n);
assert(written == n);
}
}
// Note: start points to the PID which is not in the CRC
void add_crc16(uint8_t *dp, int start, int pos) {
uint32_t crc = CRC16(dp + start + 1, pos - start - 1);
dp[pos] = crc & 0xff;
dp[pos + 1] = crc >> 8;
}
// Set device address (with null data stage)
void setDeviceAddress(struct usbdpi_ctx *ctx) {
switch (ctx->hostSt) {
case HS_STARTFRAME:
ctx->state = ST_SYNC;
ctx->bytes = 14;
ctx->datastart = 3;
ctx->byte = 0;
ctx->bit = 1;
// Setup PID and data to set device 2
ctx->data[0] = USB_PID_SETUP;
ctx->data[1] = 0;
ctx->data[2] = 0 | CRC5(0, 11) << 3;
ctx->data[3] = USB_PID_DATA0;
ctx->data[4] = 0; // h2d, std, device
ctx->data[5] = 5; // set address
ctx->data[6] = 2; // device address
ctx->data[7] = 0;
ctx->data[8] = 0; // wIndex = 0
ctx->data[9] = 0;
ctx->data[10] = 0; // wLength = 0
ctx->data[11] = 0;
add_crc16(ctx->data, ctx->datastart, 12);
// ctx->data[12] = 0xEB; // pre-computed CRC16
// ctx->data[13] = 0x16;
ctx->hostSt = HS_WAITACK;
break;
case HS_WAITACK:
ctx->wait = ctx->tick_bits + 532; // HACK
ctx->hostSt = HS_SET_DATASTAGE;
break;
case HS_SET_DATASTAGE:
if (ctx->tick_bits == ctx->wait) {
ctx->state = ST_SYNC;
ctx->bytes = 3;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_IN;
ctx->data[1] = 0;
ctx->data[2] = 0 | CRC5(0, 11) << 3;
ctx->hostSt = HS_DS_RXDATA;
}
break;
case HS_DS_RXDATA:
ctx->wait = ctx->tick_bits + 48; // HACK -- 2 bytes
ctx->hostSt = HS_DS_SENDACK;
break;
case HS_DS_SENDACK:
if (ctx->tick_bits == ctx->wait) {
ctx->state = ST_SYNC;
ctx->bytes = 1;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_ACK;
ctx->hostSt = HS_NEXTFRAME;
}
break;
default:
break;
}
}
// Get Descriptor
void readDescriptor(struct usbdpi_ctx *ctx) {
switch (ctx->hostSt) {
case HS_STARTFRAME:
ctx->state = ST_SYNC;
ctx->bytes = 14;
ctx->datastart = 3;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_SETUP;
ctx->data[1] = 2;
ctx->data[2] = 0 | CRC5(2, 11) << 3;
ctx->data[3] = USB_PID_DATA0;
ctx->data[4] = 0x80; // d2h, std, device
ctx->data[5] = 6; // get descr
ctx->data[6] = 0; // index 0
ctx->data[7] = 1; // type device
ctx->data[8] = 0; // wIndex = 0
ctx->data[9] = 0;
ctx->data[10] = 0x12; // wLength = 18
ctx->data[11] = 0;
add_crc16(ctx->data, ctx->datastart, 12);
// ctx->data[12] = 0xE0; // pre-computed CRC32
// ctx->data[13] = 0xF4;
ctx->hostSt = HS_WAITACK;
break;
case HS_WAITACK:
ctx->wait = ctx->tick_bits + 532; // HACK
ctx->hostSt = HS_REQDATA;
break;
case HS_REQDATA:
if (ctx->tick_bits == ctx->wait) {
ctx->state = ST_SYNC;
ctx->bytes = 3;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_IN;
ctx->data[1] = 2;
ctx->data[2] = 0 | CRC5(2, 11) << 3;
ctx->hostSt = HS_WAITDATA;
}
break;
case HS_WAITDATA:
ctx->wait = ctx->tick_bits + 200; // HACK
ctx->hostSt = HS_SENDACK;
break;
case HS_SENDACK:
if (ctx->tick_bits == ctx->wait) {
ctx->state = ST_SYNC;
ctx->bytes = 1;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_ACK;
ctx->hostSt = HS_NEXTFRAME;
}
break;
default:
break;
}
}
// Get Baud
void readBaud(struct usbdpi_ctx *ctx) {
switch (ctx->hostSt) {
case HS_STARTFRAME:
ctx->state = ST_SYNC;
ctx->bytes = 14;
ctx->datastart = 3;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_SETUP;
ctx->data[1] = 0x82;
ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
ctx->data[3] = USB_PID_DATA0;
ctx->data[4] = 0xc2; // d2h, vendor, endpoint
ctx->data[5] = 2; // get baud
ctx->data[6] = 0; // index 0
ctx->data[7] = 0; // type device
ctx->data[8] = 0; // wIndex = 0
ctx->data[9] = 0;
ctx->data[10] = 0x2; // wLength = 2
ctx->data[11] = 0;
add_crc16(ctx->data, ctx->datastart, 12);
// ctx->data[12] = 0x10; // pre-computed CRC32
// ctx->data[13] = 0xDD;
ctx->hostSt = HS_WAITACK;
break;
case HS_WAITACK:
ctx->wait = ctx->tick_bits + 32; // HACK
ctx->hostSt = HS_REQDATA;
break;
case HS_REQDATA:
if (ctx->tick_bits == ctx->wait) {
ctx->state = ST_SYNC;
ctx->bytes = 3;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_IN;
ctx->data[1] = 0x82;
ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
ctx->hostSt = HS_WAITDATA;
}
break;
case HS_WAITDATA:
ctx->wait = ctx->tick_bits + 200; // HACK
ctx->hostSt = HS_SENDACK;
break;
case HS_SENDACK:
if (ctx->tick_bits == ctx->wait) {
ctx->state = ST_SYNC;
ctx->bytes = 1;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_ACK;
ctx->hostSt = HS_EMPTYDATA;
}
break;
case HS_EMPTYDATA:
ctx->state = ST_SYNC;
ctx->bytes = 6;
ctx->datastart = 3;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_OUT;
ctx->data[1] = 0x82;
ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
ctx->data[3] = USB_PID_DATA1;
ctx->data[4] = 0x0; // pre-computed CRC32
ctx->data[5] = 0x0;
ctx->hostSt = HS_WAITACK2;
break;
case HS_WAITACK2:
ctx->wait = ctx->tick_bits + 32; // HACK
ctx->hostSt = HS_NEXTFRAME;
break;
default:
break;
}
}
// Set Baud
void setBaud(struct usbdpi_ctx *ctx) {
switch (ctx->hostSt) {
case HS_STARTFRAME:
ctx->state = ST_SYNC;
ctx->bytes = 14;
ctx->datastart = 3;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_SETUP;
ctx->data[1] = 0x82;
ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
ctx->data[3] = USB_PID_DATA0;
ctx->data[4] = 0x42; // h2d, vendor, endpoint
ctx->data[5] = 3; // set baud
ctx->data[6] = 96; // index 0
ctx->data[7] = 0; // type device
ctx->data[8] = 0; // wIndex = 0
ctx->data[9] = 0;
ctx->data[10] = 0; // wLength = 0
ctx->data[11] = 0;
add_crc16(ctx->data, ctx->datastart, 12);
// ctx->data[12] = 0x00; // pre-computed CRC32
// ctx->data[13] = 0xBD;
ctx->hostSt = HS_WAITACK;
break;
case HS_WAITACK:
ctx->wait = ctx->tick_bits + 32; // HACK
ctx->hostSt = HS_REQDATA;
break;
case HS_REQDATA:
if (ctx->tick_bits == ctx->wait) {
ctx->state = ST_SYNC;
ctx->bytes = 3;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_IN;
ctx->data[1] = 0x82;
ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
ctx->hostSt = HS_WAITDATA;
}
break;
case HS_WAITDATA:
ctx->wait = ctx->tick_bits + 40; // HACK
ctx->hostSt = HS_SENDACK;
break;
case HS_SENDACK:
if (ctx->tick_bits >= ctx->wait) {
ctx->state = ST_SYNC;
ctx->bytes = 1;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_ACK;
ctx->hostSt = HS_NEXTFRAME;
}
break;
default:
break;
}
}
// Request IN. Get back NAK or DATA0/DATA1.
// sendHi -> also send OUT packet
// nakData -> send NAK instead of ACK if there is data
void pollRX(struct usbdpi_ctx *ctx, int sendHi, int nakData) {
switch (ctx->hostSt) {
case HS_STARTFRAME:
ctx->state = ST_SYNC;
ctx->bytes = 3;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_IN;
ctx->data[1] = 0x82;
ctx->data[2] = 0x00 | CRC5(0x0082, 11) << 3;
ctx->hostSt = HS_WAIT_PKT;
ctx->lastrxpid = 0;
break;
case HS_WAIT_PKT:
if ((ctx->lastrxpid) && (ctx->lastrxpid != USB_PID_IN)) {
ctx->wait = ctx->tick_bits + 32; // Expect to be busy then
ctx->hostSt = HS_ACKIFDATA;
}
break;
case HS_ACKIFDATA:
if (ctx->tick_bits >= ctx->wait) {
if (ctx->lastrxpid != USB_PID_NAK) {
// device sent data so ACK it
// TODO check DATA0 vs DATA1
ctx->state = ST_SYNC;
ctx->bytes = 1;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = nakData ? USB_PID_NAK : USB_PID_ACK;
}
if (sendHi) {
ctx->hostSt = HS_SENDHI;
} else {
ctx->hostSt = HS_NEXTFRAME;
ctx->inframe = ctx->frame;
}
}
break;
case HS_SENDHI:
ctx->state = ST_SYNC;
ctx->bytes = 9;
ctx->datastart = 3;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_OUT;
ctx->data[1] = 0x82;
ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
ctx->data[3] = USB_PID_DATA0;
ctx->data[4] = 0x48;
ctx->data[5] = 0x69;
ctx->data[6] = 0x21;
add_crc16(ctx->data, ctx->datastart, 7);
// ctx->data[7] = 0xE0; // pre-computed CRC16
// ctx->data[8] = 0x61;
ctx->inframe = ctx->frame;
ctx->hostSt = HS_NEXTFRAME; // Device will ACK
break;
default:
break;
}
}
char usbdpi_host_to_device(void *ctx_void, svBitVecVal *d2p_data) {
struct usbdpi_ctx *ctx = (struct usbdpi_ctx *)ctx_void;
assert(ctx);
int d2p = d2p_data[0];
uint32_t last_driving = ctx->driving;
int force_stat = 0;
int dat;
if (ctx->tick == 0) {
for (int i = 7; i > 0; i--) {
printf("Sleep %d...\n", i);
sleep(1);
}
}
ctx->tick++;
ctx->tick_bits = ctx->tick >> 2;
if (ctx->tick & 3) {
return ctx->driving;
}
monitor_usb(ctx->mon, ctx->fifo_fd, ctx->loglevel, ctx->tick,
(ctx->state != ST_IDLE) && (ctx->state != ST_GET), ctx->driving,
d2p, &(ctx->lastrxpid));
if (ctx->tick_bits == SENSE_AT) {
ctx->driving |= P2D_SENSE;
}
if ((d2p & D2P_PU) == 0) {
ctx->recovery_time = ctx->tick + 4 * 48;
return ctx->driving;
}
if (ctx->tick < ctx->recovery_time) {
ctx->lastframe = ctx->tick_bits;
return ctx->driving;
}
if ((ctx->tick_bits - ctx->lastframe) >= FRAME_INTERVAL) {
if (ctx->state != ST_IDLE) {
if (ctx->framepend == 0) {
printf("USB: %4x %8d error state %d at frame %d time\n", ctx->frame,
ctx->tick, ctx->state, ctx->frame + 1);
}
ctx->framepend = 1;
} else {
if (ctx->framepend == 1) {
printf("USB: %4x %8d can send frame %d SOF\n", ctx->frame, ctx->tick,
ctx->frame + 1);
}
ctx->framepend = 0;
ctx->frame++;
ctx->lastframe = ctx->tick_bits;
ctx->state = ST_SYNC;
ctx->bytes = 3;
ctx->datastart = -1;
ctx->byte = 0;
ctx->bit = 1;
ctx->data[0] = USB_PID_SOF;
ctx->data[1] = ctx->frame & 0xff;
ctx->data[2] = (ctx->frame & 0x700) >> 8 | CRC5(ctx->frame & 0x7ff, 11)
<< 3;
printf("USB: %8d frame 0x%x CRC5 0x%x\n", ctx->tick, ctx->frame,
CRC5(ctx->frame, 11));
if (ctx->hostSt == HS_NEXTFRAME) {
ctx->hostSt = HS_STARTFRAME;
}
}
}
switch (ctx->state) {
case ST_IDLE:
if ((simctrl && simctrl->TracingEnabled() && (ctx->frame == 20)) ||
(ctx->frame > 2000))
finish();
switch (ctx->frame) {
case 1:
setDeviceAddress(ctx);
break;
case 2:
readDescriptor(ctx);
break;
// These should be at 3 and 4 but the read needs the host
// not to be sending (until skip fifo is implemented in in_pe engine)
// so for now push later when things are quiet (could also adjust
// hello_world to not use the uart until frame 4)
case 10:
readBaud(ctx);
break;
case 15:
setBaud(ctx);
break;
default:
if (ctx->frame > ctx->inframe) {
pollRX(ctx, (ctx->frame == 9) || (ctx->frame == 14),
((ctx->frame == 16) || (ctx->frame == 9)));
}
}
break;
case ST_SYNC:
dat = ((USB_SYNC & ctx->bit)) ? P2D_DP : P2D_DN;
ctx->driving = (ctx->driving & P2D_SENSE) | dat;
force_stat = 1;
ctx->bit <<= 1;
if (ctx->bit == 0x100) {
ctx->bit = 1;
ctx->linebits = 0;
ctx->state = ST_SEND;
}
break;
case ST_SEND:
if ((ctx->linebits & 0x3f) == 0x3f) { // sent 6 ones
// bit stuff and force a transition
ctx->driving ^= (P2D_DP | P2D_DN);
force_stat = 1;
ctx->linebits = (ctx->linebits << 1);
} else if (ctx->byte >= ctx->bytes) {
ctx->state = ST_EOP;
ctx->driving = ctx->driving & P2D_SENSE; // SE0
ctx->bit = 1;
force_stat = 1;
} else {
int nextbit;
nextbit = (ctx->data[ctx->byte] & ctx->bit) ? 1 : 0;
if (nextbit == 0) {
ctx->driving ^= (P2D_DP | P2D_DN);
}
ctx->linebits = (ctx->linebits << 1) | nextbit;
force_stat = 1;
ctx->bit <<= 1;
if (ctx->bit == 0x100) {
ctx->bit = 1;
ctx->byte++;
if (ctx->byte == ctx->datastart) {
ctx->state = ST_EOP0;
}
}
}
break;
case ST_EOP0:
ctx->driving = ctx->driving & P2D_SENSE; // SE0
ctx->state = ST_EOP;
break;
case ST_EOP: // SE0 SE0 J
if (ctx->bit == 4) {
ctx->driving = (ctx->driving & P2D_SENSE) | P2D_DP; // J
}
if (ctx->bit == 8) {
ctx->driving = (d2p & D2P_PU) ? (ctx->driving & P2D_SENSE) | P2D_DP
: // Z + pullup
ctx->driving & P2D_SENSE; // z without pullup = SE0
if (ctx->byte == ctx->datastart) {
ctx->bit = 1;
ctx->state = ST_SYNC;
} else {
ctx->state = ST_IDLE;
}
}
ctx->bit <<= 1;
break;
}
if ((ctx->loglevel & LOG_BIT) &&
(force_stat || (ctx->driving != last_driving))) {
int n;
char obuf[MAX_OBUF];
n = snprintf(
obuf, MAX_OBUF, "%4x %8d %s %s\n", ctx->frame, ctx->tick,
ctx->driving & P2D_SENSE ? "VBUS" : " ",
(ctx->state != ST_IDLE) ? decode_usb[(ctx->driving >> 1) & 3] : "ZZ ");
ssize_t written = write(ctx->fifo_fd, obuf, n);
assert(written == n);
}
return ctx->driving;
}
void usbdpi_close(void *ctx_void) {
struct usbdpi_ctx *ctx = (struct usbdpi_ctx *)ctx_void;
if (!ctx) {
return;
}
int rv;
rv = close(ctx->fifo_fd);
if (rv != 0) {
printf("USB: Failed to close FIFO: %s\n", strerror(errno));
}
rv = unlink(ctx->fifo_pathname);
if (rv != 0) {
printf("USB: Failed to unlink FIFO file at %s: %s\n", ctx->fifo_pathname,
strerror(errno));
}
free(ctx);
}