lay down some initial data structures to talk to the UDC

This commit is contained in:
bunnie 2022-04-27 02:41:07 +08:00
parent a7684343f2
commit 467254fa81
4 changed files with 225 additions and 71 deletions

View file

@ -16,6 +16,7 @@ num-derive = {version = "0.3.3", default-features = false}
num-traits = {version = "0.2.14", default-features = false}
susres = {path = "../susres"}
keyboard = {path = "../keyboard"}
bitfield = "0.13.2"
[target.'cfg(not(any(windows,unix)))'.dependencies]
utralib = { path = "../../utralib"}

View file

@ -0,0 +1,13 @@
pub struct UsbTest {
}
impl UsbTest {
pub fn new() -> UsbTest {
UsbTest {
}
}
pub fn suspend(&self) {
}
pub fn resume(&self) {
}
}

198
services/usb-test/src/hw.rs Normal file
View file

@ -0,0 +1,198 @@
use utralib::generated::*;
use crate::*;
use bitfield::bitfield;
use core::ops::{Deref, DerefMut};
bitfield! {
pub struct UdcInterrupts(u32);
impl Debug;
pub endpoint, clear_endpoint: 15, 0;
pub reset, clear_reset: 16;
pub ep0_setup, clear_ep0_setup: 17;
pub suspend, clear_suspend: 18;
pub resume, clear_resume: 19;
pub disconnect, clear_disconnect: 20;
}
bitfield! {
pub struct UdcHalt(u32);
impl Debug;
pub endpointid, set_endpointid: 3, 0;
pub enable_req, set_enable_req: 4;
pub enable_ack, set_enable_ack: 5; // question: can we make this ... read-only?
}
bitfield! {
pub struct UdcConfig(u32);
impl Debug;
// this has an odd form: you must write `1` to these respective bits like "radio buttons" to affect pullups and interrupts
pub pullup_on, set_pullup_on: 0;
pub pullup_off, set_pullup_off: 1;
pub enable_ints, set_enable_ints: 2;
pub disable_ints, set_disable_ints: 3;
}
bitfield! {
pub struct UdcRamsize(u32);
impl Debug;
pub ramsize, dnu_ramsize: 3, 0;
}
bitfield! {
pub struct UdcEpStatus(u32);
impl Debug;
pub enable, set_enable: 0;
pub force_stall, set_force_stall: 1;
pub force_nack, set_force_nack: 2;
// selects DATA0/DATA1; 0 => DATA0. Also set by the controller automatically
pub data_phase, set_data_phase: 3;
// specifies the offset of the endpoint's descriptor in RAM. 0 => empty, otherwise multply by 16 to get the address
pub head_offset, set_head_offset: 15, 4;
pub isochronous, set_isochronous: 16;
pub max_packet_size, set_max_packet_size: 31, 22;
}
bitfield! {
pub struct UdcDescriptor0(u32);
impl Debug;
// current progress of the transfer, in bytes
pub offset, set_offset: 15, 0;
// 0xF -> in progress, 0x0 -> success
pub code, set_code: 19, 16;
}
bitfield! {
pub struct UdcDescriptor1(u32);
impl Debug;
// offset of the next descriptor in RAM. 0 => none, otherwise multiply by 16 to get the address in bytes
pub next_offset, set_next_offset: 15, 4;
// length of the data field in bytes
pub length, set_length: 31, 16;
}
bitfield! {
pub struct UdcDescriptor2(u32);
impl Debug;
// direction. 0 => OUT, 1 => IN
pub direction, set_direction: 16;
// if set, fires an interrupt when descriptor is completed
pub int_on_done, set_int_on_done: 17;
// From SpinalHDL docs directly: Normally, a descriptor completion only occurs when a USB transfer
// is smaller than the max_packet_size. But, if this field is set,
// then when the descriptor becomes full is also considered a completion event. (offset == length)
pub completion_on_full, set_completion_on_full: 18;
// forces dataphase to DATA1 when the descriptor is complete
pub data1_on_completion, set_data1_on_completion: 19;
}
/// This is located at 0xFF00 offset from the base of the memory region open for the UDC
#[repr(C)]
#[derive(Debug)]
pub struct SpinalUdcRegs {
/// current USB frame ID
frame: u32,
/// currently active address for tokens. cleared by USB reset
address: u32,
/// interrupt flags
interrupts: UdcInterrupts,
/// halt - use this to pause an endpoint to give the CPU a mutex on r/w access to its registers
halt: UdcHalt,
/// config
config: UdcConfig,
/// the ram starting at 0 has a size of 1 << ramsize. Only the lower 4 bits are valid, but the field takes up a u32
ramsize: UdcRamsize,
}
impl Deref for SpinalUdcRegs {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(self as *const SpinalUdcRegs as *const u8, core::mem::size_of::<SpinalUdcRegs>())
as &[u8]
}
}
}
impl DerefMut for SpinalUdcRegs {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
core::slice::from_raw_parts_mut(self as *mut SpinalUdcRegs as *mut u8, core::mem::size_of::<SpinalUdcRegs>())
as &mut [u8]
}
}
}
/// This is located at 0x0000-0x0047 inside the UDC region
#[repr(C)]
pub struct SpinalUdcMem {
endpoints: [UdcEpStatus; 16],
setup_data: [u8; 8],
}
/// This structure maps onto a variable length region anywhere inside the UDC region. It is always aligned to a 16-byte offset
#[repr(C)]
pub struct SpinalUdcDescriptorHeader {
d0: UdcDescriptor0,
d1: UdcDescriptor1,
d2: UdcDescriptor2,
}
/// This structure is a set of references to a UDC descriptor in RAM. It's tricky to
/// construct correctly, as it requires interpreting some bit fields returned by the
/// UDC to map where the header goes and then determine the length of the data. The
/// data slice's length cannot be known at compile time, because it varies with the size
/// of the USB packet. However, the data should always be located at an address immediately
/// following the header's location.
pub struct SpinalUdcDescriptor<'a> {
header: &'a SpinalUdcDescriptorHeader,
data: &'a [u8],
}
pub struct UsbTest {
pub(crate) conn: CID,
gpio_csr: utralib::CSR<u32>,
usb: xous::MemoryRange,
regs: &'static mut SpinalUdcRegs,
}
impl UsbTest {
pub fn new(sid: xous::SID) -> UsbTest {
let gpio_base = xous::syscall::map_memory(
xous::MemoryAddress::new(utra::gpio::HW_GPIO_BASE),
None,
4096,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't map GPIO CSR range");
// this particular core does not use CSRs for control - it uses directly memory mapped registers
let usb = xous::syscall::map_memory(
xous::MemoryAddress::new(utralib::HW_USBDEV_MEM),
None,
utralib::HW_USBDEV_MEM_LEN,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't map USB device memory range");
let mut usbtest = UsbTest {
gpio_csr: CSR::new(gpio_base.as_mut_ptr() as *mut u32),
conn: xous::connect(sid).unwrap(),
usb,
// Safety: the offset of the register bank is defined as 0xFF00 from the base of the
// usb memory area. Mapping SpinalUdcRegs here is safe assuming the structure has
// been correctly defined.
regs: unsafe {
(usb.as_mut_ptr().add(0xFF00) as *mut SpinalUdcRegs).as_mut().unwrap()
}
};
usbtest
}
pub fn print_regs(&self) {
log::info!("control regs: {:x?}", self.regs);
}
pub fn connect_device_core(&mut self, state: bool) {
if state {
log::info!("connecting USB device core");
self.gpio_csr.wfo(utra::gpio::USBSELECT_USBSELECT, 1);
} else {
log::info!("connecting USB debug core");
self.gpio_csr.wfo(utra::gpio::USBSELECT_USBSELECT, 0);
}
}
pub fn suspend(&mut self) {
}
pub fn resume(&mut self) {
}
}

View file

@ -10,82 +10,19 @@ use num_traits::*;
use xous::{CID, msg_scalar_unpack, Message, send_message};
#[cfg(any(target_os = "none", target_os = "xous"))]
mod implementation {
use utralib::generated::*;
use crate::*;
mod hw;
#[cfg(any(target_os = "none", target_os = "xous"))]
use hw::*;
pub struct UsbTest {
pub(crate) conn: CID,
gpio_csr: utralib::CSR<u32>,
usb: xous::MemoryRange,
}
impl UsbTest {
pub fn new(sid: xous::SID) -> UsbTest {
let gpio_base = xous::syscall::map_memory(
xous::MemoryAddress::new(utra::gpio::HW_GPIO_BASE),
None,
4096,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't map GPIO CSR range");
// this particular core does not use CSRs for control - it uses directly memory mapped registers
let usb = xous::syscall::map_memory(
xous::MemoryAddress::new(utralib::HW_USBDEV_MEM),
None,
utralib::HW_USBDEV_MEM_LEN,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't map USB device memory range");
let mut usbtest = UsbTest {
gpio_csr: CSR::new(gpio_base.as_mut_ptr() as *mut u32),
conn: xous::connect(sid).unwrap(),
usb,
};
usbtest
}
pub fn connect_device_core(&mut self, state: bool) {
if state {
log::info!("connecting USB device core");
self.gpio_csr.wfo(utra::gpio::USBSELECT_USBSELECT, 1);
} else {
log::info!("connecting USB debug core");
self.gpio_csr.wfo(utra::gpio::USBSELECT_USBSELECT, 0);
}
}
pub fn suspend(&mut self) {
}
pub fn resume(&mut self) {
}
}
}
// a stub to try to avoid breaking hosted mode for as long as possible.
#[cfg(not(any(target_os = "none", target_os = "xous")))]
mod implementation {
pub struct UsbTest {
}
impl UsbTest {
pub fn new() -> UsbTest {
UsbTest {
}
}
pub fn suspend(&self) {
}
pub fn resume(&self) {
}
}
}
mod hosted;
#[cfg(not(any(target_os = "none", target_os = "xous")))]
use hosted::*;
#[xous::xous_main]
fn xmain() -> ! {
use crate::implementation::UsbTest;
use crate::UsbTest;
log_server::init_wait().unwrap();
log::set_max_level(log::LevelFilter::Info);
@ -144,7 +81,7 @@ fn xmain() -> ! {
match args {
"1" => usbtest.connect_device_core(true),
"0" => usbtest.connect_device_core(false),
_ => log::info!("conn [1,0], got: {}", args),
_ => log::info!("usage: conn [1,0]; got: 'conn {}'", args),
}
}
_ => {
@ -159,6 +96,11 @@ fn xmain() -> ! {
}
"conn" => {
usbtest.connect_device_core(true);
log::info!("device core connected");
usbtest.print_regs();
}
"regs" => {
usbtest.print_regs();
}
_ => {
log::info!("unrecognized command");