blob: f368afbdad2ede23100e5c5b626d50e681d5cc5a [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 <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "usb_utils.h"
#include "usbdpi.h"
// USB monitor state
typedef enum { MS_IDLE = 0, MS_GET_PID, MS_GET_BYTES } usb_monitor_state_t;
// Current driver of the USB
typedef enum { M_NONE = 0, M_HOST, M_DEVICE } usbmon_driver_t;
#define SE0 0
#define DK 1
#define DJ 2
#define MON_BYTES_SIZE 1024
// Number of bytes in max output buffer line
#define MAX_OBUF 80
/**
* USB monitor context
*/
struct usb_monitor_ctx {
/**
* Log file
*/
FILE *file;
/**
* Monitor state, reflecting the current state of the USB
*/
usb_monitor_state_t state;
/**
* Current bus driver
*/
usbmon_driver_t driver;
int pu;
int line;
int rawbits;
int bits;
int needbits;
int sopAt;
uint8_t lastpid;
/**
* USB data callback
*/
usb_monitor_data_callback_t data_callback;
/**
* Context for data callback
*/
void *data_ctx;
/**
* Byte offset of the next byte to be collected in the data buffer
*/
uint8_t byte;
/**
* Buffer of collected bytes
*/
uint8_t bytes[MON_BYTES_SIZE + 2];
};
// Invoke the USB data callback function, if registered
static inline void data_callback(usb_monitor_ctx_t *mon,
usbmon_data_type_t type, uint8_t d) {
if (mon->data_callback) {
mon->data_callback(mon->data_ctx, type, d);
}
}
/**
* Create and initialize a USB monitor instance
*/
usb_monitor_ctx_t *usb_monitor_init(const char *filename,
usb_monitor_data_callback_t data_cb,
void *data_ctx) {
usb_monitor_ctx_t *mon =
(usb_monitor_ctx_t *)calloc(1, sizeof(usb_monitor_ctx_t));
assert(mon);
mon->state = MS_IDLE;
mon->driver = M_NONE;
mon->pu = 0;
mon->line = 0;
// Retain details of the device reception callback
mon->data_callback = data_cb;
mon->data_ctx = data_ctx;
mon->file = fopen(filename, "w");
if (!mon->file) {
fprintf(stderr, "USBDPI: Unable to open monitor file at %s: %s\n", filename,
strerror(errno));
free(mon);
return NULL;
}
// more useful for tail -f
setlinebuf(mon->file);
printf(
"\nUSBDPI: Monitor output file created at %s. Works well with tail:\n"
"$ tail -f %s\n",
filename, filename);
return mon;
}
/**
* Finalize a USB monitor
*/
void usb_monitor_fin(usb_monitor_ctx_t *mon) {
fclose(mon->file);
free(mon);
}
/**
* Append a formatted message to the USB monitor log file
*/
void usb_monitor_log(usb_monitor_ctx_t *ctx, const char *fmt, ...) {
char obuf[MAX_OBUF];
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(obuf, MAX_OBUF, fmt, ap);
va_end(ap);
size_t written = fwrite(obuf, sizeof(char), (size_t)n, ctx->file);
assert(written == (size_t)n);
}
#define DR_SIZE 128
static char dr[DR_SIZE];
char *pid_2data(int pid, unsigned char d0, unsigned char d1) {
int comp_crc = CRC5((d1 & 7) << 8 | d0, 11);
const char *crcok = (comp_crc == d1 >> 3) ? "OK" : "BAD";
switch (pid) {
case USB_PID_SOF:
snprintf(dr, DR_SIZE, "SOF %03x (CRC5 %02x %s)", (d1 & 7) << 8 | d0,
d1 >> 3, crcok);
break;
case USB_PID_SETUP:
case USB_PID_OUT:
case USB_PID_IN:
snprintf(dr, DR_SIZE, "%s %d.%d (CRC5 %02x %s)", decode_pid(pid),
d0 & 0x7f, (d1 & 7) << 1 | d0 >> 7, d1 >> 3, crcok);
break;
case USB_PID_DATA0:
case USB_PID_DATA1:
snprintf(dr, DR_SIZE, "%s %02x, %02x (%s)", decode_pid(pid), d0, d1,
(d0 | d1) ? "CRC16 BAD" : "NULL");
break;
// Validate and log other PIDs
default:
if (((pid >> 4) ^ 0xf) == (pid & 0xf)) {
snprintf(dr, DR_SIZE, "%s %02x, %02x (CRC5 %s)", decode_pid(pid), d0,
d1, crcok);
} else {
snprintf(dr, DR_SIZE, "BAD PID %s %02x, %02x (CRC5 %s)",
decode_pid(pid), d0, d1, crcok);
}
break;
}
return dr;
}
/**
* Per-cycle monitoring of the USB
*/
void usb_monitor(usb_monitor_ctx_t *mon, int loglevel, uint32_t tick_bits,
bool hdrive, uint32_t p2d, uint32_t d2p, uint8_t *lastpid) {
bool log = ((loglevel & 0x2) != 0);
bool compact = ((loglevel & 0x1) != 0);
assert(mon);
// Ascertain state of D+/D- pair; these may have been swapped in some use
// cases, but we can ascertain this by looking at the pull-up enables.
// The DUT is a full speed device so the pull up should be on D+
int dp, dn;
if ((d2p & D2P_DP_EN) || (d2p & D2P_DN_EN) || (d2p & D2P_D_EN)) {
if (hdrive) {
fprintf(mon->file, "mon: %8d: Bus clash\n", tick_bits);
}
if (d2p & D2P_TX_USE_D_SE0) {
// Single-ended mode uses D and SE0
if ((d2p & D2P_SE0) || !(d2p & D2P_D_EN)) {
// SE0 state, both D+ and D- are low
dp = 0;
dn = 0;
} else {
// Normal single-ended data transmission
dp = (d2p & D2P_D) ? 1 : 0;
dn = (d2p & D2P_D) ? 0 : 1;
}
} else {
dp = ((d2p & D2P_DP_EN) && (d2p & D2P_DP)) ? 1 : 0;
dn = ((d2p & D2P_DN_EN) && (d2p & D2P_DN)) ? 1 : 0;
}
mon->driver = M_DEVICE;
} else if (hdrive) {
if (d2p & D2P_RX_ENABLE) {
if (p2d & (P2D_DP | P2D_DN)) {
dp = (p2d & P2D_D) ? 1 : 0;
dn = (p2d & P2D_D) ? 0 : 1;
} else {
dp = 0;
dn = 0;
}
} else {
dp = (p2d & P2D_DP) ? 1 : 0;
dn = (p2d & P2D_DN) ? 1 : 0;
}
mon->driver = M_HOST;
} else {
if ((mon->driver != M_NONE) || (mon->pu != (d2p & D2P_PU))) {
if (log) {
if (d2p & D2P_PU) {
fprintf(mon->file, "mon: %8d: Idle, FS resistor (d2p 0x%x)\n",
tick_bits, d2p);
} else {
fprintf(mon->file, "mon: %8d: Idle, SE0\n", tick_bits);
}
}
mon->driver = M_NONE;
mon->pu = (d2p & D2P_PU);
}
mon->line = 0;
return;
}
// If the DN pullup is there then swap
if (d2p & D2P_DNPU) {
int tmp = dp;
dp = dn;
dn = tmp;
}
// Collect D+/D- state
mon->line = (mon->line << 2) | dp << 1 | dn;
// SYNC at start of packet
if (mon->state == MS_IDLE) {
if ((mon->line & 0xfff) == ((DK << 10) | (DJ << 8) | (DK << 6) | (DJ << 4) |
(DK << 2) | (DK << 0))) {
if (log) {
fprintf(mon->file, "mon: %8d: (%c) SOP\n", tick_bits,
mon->driver == M_HOST ? 'H' : 'D');
}
mon->sopAt = tick_bits;
mon->state = MS_GET_PID;
mon->needbits = 8;
data_callback(mon, UsbMon_DataType_Sync, 0U);
}
return;
}
// EOP detection, calculate and check the CRC16 on any data field
if ((mon->line & 0x3f) == ((SE0 << 4) | (SE0 << 2) | (DJ << 0))) {
if ((log || compact) && (mon->state == MS_GET_BYTES) && (mon->byte > 0)) {
uint32_t pkt_crc16, comp_crc16;
if (compact && mon->byte == 2) {
fprintf(mon->file, "mon: %8d -- %8d: (%c) SOP, PID %s, EOP\n",
mon->sopAt, tick_bits, mon->driver == M_HOST ? 'H' : 'D',
pid_2data(mon->lastpid, mon->bytes[0], mon->bytes[1]));
} else if (compact && mon->byte == 1) {
fprintf(mon->file, "mon: %8d -- %8d: (%c) SOP, PID %s %02x EOP\n",
mon->sopAt, tick_bits, mon->driver == M_HOST ? 'H' : 'D',
decode_pid(mon->lastpid), mon->bytes[0]);
} else {
if (compact) {
fprintf(mon->file, "mon: %8d -- %8d: (%c) SOP, PID %s, EOP\n",
mon->sopAt, tick_bits, mon->driver == M_HOST ? 'H' : 'D',
decode_pid(mon->lastpid));
}
fprintf(mon->file, "mon: %s:\n",
mon->driver == M_HOST ? "h->d" : "d->h");
comp_crc16 = CRC16(mon->bytes, mon->byte - 2);
pkt_crc16 =
mon->bytes[mon->byte - 2] | (mon->bytes[mon->byte - 1] << 8);
dump_bytes(mon->file, "mon: ", mon->bytes, mon->byte - 2, 0u);
// Display the received CRC16 value
fprintf(mon->file, "\nmon: (CRC16 %02x %02x",
mon->bytes[mon->byte - 2], mon->bytes[mon->byte - 1]);
if (comp_crc16 == pkt_crc16) {
fprintf(mon->file, "%s OK)\n",
(mon->byte == MON_BYTES_SIZE) ? "..." : "");
} else {
fprintf(mon->file,
"%s BAD)\nmon: CRC16 %04x BAD expected %04x\n",
(mon->byte == MON_BYTES_SIZE) ? "..." : "", pkt_crc16,
comp_crc16);
}
}
} else if (compact) {
fprintf(mon->file, "mon: %8d -- %8d: (%c) SOP, PID %s EOP\n", mon->sopAt,
tick_bits, mon->driver == M_HOST ? 'H' : 'D',
decode_pid(mon->lastpid));
}
if (log) {
fprintf(mon->file, "mon: %8d: (%c) EOP\n", tick_bits,
mon->driver == M_HOST ? 'H' : 'D');
}
mon->state = MS_IDLE;
data_callback(mon, UsbMon_DataType_EOP, 0U);
return;
}
int newbit = (((mon->line & 0xc) >> 2) == (mon->line & 0x3)) ? 1 : 0;
mon->rawbits = (mon->rawbits << 1) | newbit;
if ((mon->rawbits & 0x7e) == 0x7e) {
if (newbit == 1) {
fprintf(mon->file, "mon: %8d: (%c) Bitstuff error, got 1 after 0x%x\n",
tick_bits, mon->driver == M_HOST ? 'H' : 'D', mon->rawbits);
}
/* Ignore bit stuff bit */
return;
}
mon->bits = (mon->bits >> 1) | (newbit << 7);
mon->needbits--;
if (mon->needbits) {
return;
}
// Complete byte received
switch (mon->state) {
case MS_GET_PID: {
// Any byte for which the upper nibble is not the exact complement
// of the lower nibble is invalid
uint8_t pid = (uint8_t)mon->bits;
if (((pid ^ 0xf0) >> 4) ^ (pid & 0x0f)) {
if (log) {
fprintf(mon->file, "mon: %8d: (%c) BAD PID 0x%x\n", tick_bits,
mon->driver == M_HOST ? 'H' : 'D', pid);
}
} else {
*lastpid = pid;
mon->lastpid = pid;
if (log) {
fprintf(mon->file, "mon: %8d: (%c) PID %s (0x%x)\n", tick_bits,
mon->driver == M_HOST ? 'H' : 'D', decode_pid(pid), pid);
}
}
mon->state = MS_GET_BYTES;
mon->needbits = 8;
mon->byte = 0;
data_callback(mon, UsbMon_DataType_PID, pid);
} break;
case MS_GET_BYTES: {
uint8_t d = (uint8_t)mon->bits;
mon->bytes[mon->byte] = d;
mon->needbits = 8;
if (mon->byte < MON_BYTES_SIZE) {
mon->byte++;
}
data_callback(mon, UsbMon_DataType_Byte, d);
} break;
case MS_IDLE:
break;
default:
assert(!"Unknown/undefined USB monitor state");
break;
}
}
// Export some internal diagnostic state for visibility in waveforms
uint32_t usb_monitor_diags(usb_monitor_ctx_t *mon) {
// Show the PID most recently detected
uint32_t diags = mon->lastpid;
// Show the byte most recently received
if (mon->byte > 0 && mon->byte <= MON_BYTES_SIZE)
diags |= (mon->bytes[mon->byte - 1]) << 8;
// Show the down counting of bits required
diags |= (mon->needbits << 16);
// Monitor state number
diags |= mon->state << 20;
return diags;
}