-
Notifications
You must be signed in to change notification settings - Fork 85
Peripheral Access Conventions
- Make it clear what's going on
- Make it easy to inspect constants for correctness
- Base off of "Volatile" crate syntax
- Rely on Rust optimizer to turn otherwise complicated ops into the desired shift-and-masks
- Split into "base/offset", so the crate can be used for e.g. virtual memory mapped peripherals
To help things out, the following constants are generated:
-
{PERIPHERAL}_BASE
-- CSR-space physical base address of peripheral -
{PERIPHERAL}_CSR_LEN
-- length of CSR window -
{PERIPHERAL}_MEM
-- wishbone memory region associated with it -
{PERIPHERAL}_MEM_LEN
-- length of wishbone window -
{PERIPHERAL}_{REGISTER}
-- offset of register in peripheral block -
{PERIPHERAL}_{REGISTER}_{FIELD}
-- sub-field of register, has format [u32; 2] = [mask, offset]
For the register field, it's specified as a length-2 array, which is the mask
bitmask
(located at the actual position in the register, such that just AND-ing it will
mask out everything else but return a potentially shifted result), and offset
which is how much to shift the masked result, to return a value that is the field
but aligned to an unshifted position.
To access fields, extend the "pointer" class (is that possible? maybe by copying it or wrapping it) with the following methods:
- .r() -- simply returns the 32-bit value at the pointer
- .rm(mask) -- read with mask. Returns the bits at the specified mask position, shifted to the right
- .rw(value, mask) -- read then write with mask. Reads register then modifies bits at masked location. Returns itself.
- .ow(value, mask) -- overwrite. Don't read, just set all other bits to zero, except the value located in mask area.
- .ow(value) -- overwrite. Just jam
value
into the register, don't do anything fancy
soc.svd
gives us bit fields, but not memory regions or interrupts:
<peripheral>
<name>AUDIO</name>
<baseAddress>0xF0016000</baseAddress>
<groupName>AUDIO</groupName>
<registers>
<register>
<name>RX_CTL</name>
<description><![CDATA[Rx data path control]]></description>
<addressOffset>0x000c</addressOffset>
<resetValue>0x00</resetValue>
<size>32</size>
<access>read-write</access>
<fields>
<field>
<name>enable</name>
<msb>0</msb>
<bitRange>[0:0]</bitRange>
<lsb>0</lsb>
<description><![CDATA[Enable the receiving data]]></description>
</field>
<field>
<name>reset</name>
<msb>1</msb>
<bitRange>[1:1]</bitRange>
<lsb>1</lsb>
<description><![CDATA[Writing `1` resets the FIFO. Reset happens regardless of enable state.]]></description>
</field>
</fields>
</register>
</registers>
</peripheral>
csr.csv
gives us only names of whole registers and bases, but also gives us memory regions and interrupts:
csr_base,audio,0xf0016000,,
csr_register,audio_rx_ctl,0xf001600c,1,rw
constant,audio_interrupt,6,,
memory_region,audio,0xe0000000,4,io
proposed Rust-syntax accessors to be generated from a combination of the above two files:
// this goes in betrusted_pac.rs
const HW_AUDIO_BASE: u32 = 0xf001_6000;
const HW_AUDIO_MEM: u32 = 0xe000_0000;
const HW_AUDIO_CSR_LEN: u32 = 0x10; // do we need to round this up to a page? e.g. 0x1000
const HW_AUDIO_MEM_LEN: u32 = 0x1; // it's useful to know the actual length that's mapped if it's smaller than a page...
const HW_AUDIO_RX_CTL: u32 = 0x3; // 0xc/4
const HW_AUDIO_RX_CTL_ENABLE: [u32; 2] = [0b01; 0];
const HW_AUDIO_RX_CTL_RESET: [u32; 2] = [0b10; 1];
// set up the base pointers (note "unsafe" blocks omitted for clarity)
let audio_base = (HW_AUDIO_BASE as *mut u32) as *mut Volatile<u32>;
let audio_mem = (HW_AUDIO_MEM as *mut u32) as *mut Volatile<u32>;
// read RX_CTL
let rx_ctl_value = (*audio_base.add(HW_AUDIO_RX_CTL)).r();
let enable: bool = (*audio_base.add(HW_AUDIO_RX_CTL)).rm(HW_AUDIO_RX_CTL_ENABLE) != 0; // read based on mask
let reset: bool = (*audio_base.add(HW_AUDIO_RX_CTL)).rm(HW_AUDIO_RX_CTL_RESET) != 0;
(*audio_base.add(HW_AUDIO_RX_CTL)).ow(HW_AUDIO_RX_CTL_RESET); // sets the RESET bit, and also implicitly clears ENABLE
(*audio_base.add(HW_AUDIO_RX_CTL)).ow(0, HW_AUDIO_RX_CTL_RESET_MASK); // dynamic dispatch is possible in Rust, yes?
// these two proposals manipulates two bits
(*audio_base.add(HW_AUDIO_RX_CTL))
.rw(0, HW_AUDIO_RX_CTL_RESET)
.rw(1, HW_AUDIO_RX_CTL_ENABLE);
// actually same as above. The "False" can be dropped with no harm.
(*audio_base.add(HW_AUDIO_RX_CTL))
.rw(1, HW_AUDIO_RX_CTL_ENABLE);
// write and read to the audio memory window
(*audio_mem).write( audio_sample );
let mic_data: u16 = (*audio_mem).read() as u16;
// map to virtual memory
let audio_base_vmem = xous::syscall::map_memory(
xous::MemoryAddress::new(HW_AUDIO_BASE),
None,
HW_AUDIO_CSR_LEN, // do we need to round this up to a page?
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't map audio port");
// example of using vmem to access using the same constants/crate
let abase = (audio_base_vmem.as_ptr() as *mut u32) as *mut Voltaile<u32>;
(*abase.add(HW_AUDIO_RX_CTL)).rw(1, HW_AUDIO_RX_CTL_ENABLE);
// not addressed: interrupts??
<peripheral>
<name>UART</name>
<baseAddress>0xF0004000</baseAddress>
<groupName>UART</groupName>
<registers>
<register>
<name>RXTX</name>
<addressOffset>0x0000</addressOffset>
<resetValue>0x00</resetValue>
<size>32</size>
<access>read-write</access>
<fields>
<field>
<name>rxtx</name>
<msb>7</msb>
<bitRange>[7:0]</bitRange>
<lsb>0</lsb>
</field>
</fields>
</register>
</registers>
</peripheral>
csr_base,uart,0xf0004000,,
csr_register,uart_rxtx,0xf0004000,1,rw
const HW_UART_BASE: u32 = 0xf004_0000;
const HW_UART_RXTX: u32 = 0x0;
const HW_UART_RXTX_RXTX: [u32; 2] = [0b1111_1111, 0];
// example of the simplest type of register accessor
let uart_base = (HW_UART_BASE as *mut u32) as *mut Volatile<u32>;
let rx_char: u8 = (*uart_base.add(HW_UART_RXTX)).r() as u8;
// these are identical in this case because the register is simple
(*uart_base.add(HW_UART_RXTX)).ow(tx_char, HW_UART_RXTX_RXTX);
(*uart_base.add(HW_UART_RXTX)).ow(tx_char);
(*uart_base.add(HW_UART_RXTX))
.rw(tx_char, HW_UART_RXTX_RXTX); // this will read the register and throw away contents before replacing it
<peripheral>
<name>AUDIO</name>
<baseAddress>0xF0016000</baseAddress>
<groupName>AUDIO</groupName>
<registers>
<register>
<name>RX_STAT</name>
<description><![CDATA[Rx data path status]]></description>
<addressOffset>0x0010</addressOffset>
<resetValue>0x80000000</resetValue>
<size>32</size>
<access>read-only</access>
<fields>
<field>
<name>overflow</name>
<msb>0</msb>
<bitRange>[0:0]</bitRange>
<lsb>0</lsb>
<description><![CDATA[Rx overflow]]></description>
</field>
<field>
<name>underflow</name>
<msb>1</msb>
<bitRange>[1:1]</bitRange>
<lsb>1</lsb>
<description><![CDATA[Rx underflow]]></description>
</field>
<field>
<name>dataready</name>
<msb>2</msb>
<bitRange>[2:2]</bitRange>
<lsb>2</lsb>
<description><![CDATA[256 words of data loaded and ready to read]]></description>
</field>
<field>
<name>empty</name>
<msb>3</msb>
<bitRange>[3:3]</bitRange>
<lsb>3</lsb>
<description><![CDATA[No data available in FIFO to read]]></description>
</field>
<field>
<name>wrcount</name>
<msb>12</msb>
<bitRange>[12:4]</bitRange>
<lsb>4</lsb>
<description><![CDATA[Write count]]></description>
</field>
<field>
<name>rdcount</name>
<msb>21</msb>
<bitRange>[21:13]</bitRange>
<lsb>13</lsb>
<description><![CDATA[Read count]]></description>
</field>
<field>
<name>fifo_depth</name>
<msb>30</msb>
<bitRange>[30:22]</bitRange>
<lsb>22</lsb>
<description><![CDATA[FIFO depth as synthesized]]></description>
</field>
<field>
<name>concatenate_channels</name>
<msb>31</msb>
<bitRange>[31:31]</bitRange>
<lsb>31</lsb>
<description><![CDATA[Receive and send both channels atomically]]></description>
</field>
</fields>
</register>
</registers>
</peripheral>
csr_base,audio,0xf0016000,,
csr_register,audio_rx_stat,0xf0016010,1,ro
constant,audio_interrupt,6,,
memory_region,audio,0xe0000000,4,io
const HW_AUDIO_BASE: u32 = 0xf001_6000;
const HW_AUDIO_MEM: u32 = 0xe000_0000;
const HW_AUDIO_RX_STAT: u32 = 0x4; // 0x10/4
const HW_AUDIO_RX_STAT_EMPTY: [u32; 2] = [0b1000, 3];
const HW_AUDIO_RX_STAT_RDCOUNT: [u32; 2] = [0b11_1111_1110_0000_0000_0000, 13];
// set up the base pointers
let audio_base = (HW_AUDIO_BASE as *mut u32) as *mut Volatile<u32>;
let audio_mem = (HW_AUDIO_MEM as *mut u32) as *mut Volatile<u32>;
// read RX_STAT
let rx_stat = (*audio_base.add(HW_AUDIO_RX_STAT)).r(); // full 32 bit value
let empty: bool = (*audio_base.add(HW_AUDIO_RX_STAT)).rm(HW_AUDIO_RX_STAT_EMPTY) != 0;
// this will shift rdcount automatically
let rdcount: usize = (*audio_base.add(HW_AUDIO_RX_STAT)).rm(HW_AUDIO_RX_STAT_RDCOUNT);
// this overwrites the *entire* register with just WRCOUNT, and everything else as 0
(*audio_base.add(HW_AUDIO_RX_STAT)).ow(value, HW_AUDIO_RX_STAT_RDCOUNT);
// this will read the existing value and just update these two fields
(*audio_base.add(HW_AUDIO_RX_STAT))
.rw(value1, HW_AUDIO_RX_STAT_EMPTY)
.rw(value2, HW_AUDIO_RX_STAT_RDCOUNT);
// write and read to the audio memory window
(*audio_mem).write( audio_sample );
let mic_data: u16 = (*audio_mem).read() as u16;
// not addressed: interrupts??