* Verify checksum of downloaded rust target riscv32imac-unknown-xous_*.zip
* Pin base image to a sha256 digest
* Quote build arg to prevent shell injection attacks
* Run cargo --locked to make sure builds follow Cargo.lock
* Use --proto '=https' for curl to only allow HTTPS protocol to prevent downgrade attacks
* Create non-root user to run cargo commands and build artifacts
Turns out we *can* check this at run-time. I thought originally
once the chip was sealed these became hidden. I was wrong.
So, this means we can actually test to confirm that production
was done correctly and in fact the CM7 and debug ports are sealed
off.
this adds that test.
The info region is a region of RRAM that is used to configure
the chip - boot security, what CPUs can run, access control
lists, etc. There's no secrets in here, but it's very helpful
to inspect for audits and correctness of configuration, so
a routine is added to print it in boot1.
It is not clear if I can get the factory to burn the serial number
(wafer lot, ID, x/y location) into the location I want it to be.
So, I am allowing this to be updated by the firmware on boot,
thus necessitating the permissions to be writeable for the serial
number field.
Furthermore, if there is any error in initializing the chip's
identity in the factory, right now the chip goes into a mode
where it has crashed and can't run diagnostic code. Write errors
in this stage are thus turned into .ok() instead of .unwrap()
because we want to allow the chip to go through the whole flow
and then diagnose any issues later on.
The prior implementation tried to use the UDMA streaming mode
to get Rx data out of the UART.
The problem is that with streaming mode, it's hard to tell how much
data is actually available. This is because the thing that sets up
the stream sets "some number of bytes" as the fetch depth of the
DMA Rx, but all you can read is "number of bytes remaining" out
of the DMA interface.
So when 0 bytes are remaining, you can fetch some data, but you don't
know *how much* to fetch. And if someone else fetched partial data
before you, you don't know how much partial data was fetched. This
could be resolved with some gnarly, highly concurrent state that
gets incremented on every fetch.
Or, we could use poll mode. There's a reason i think it's in the
block, because the Rx DMA mode is more or less not usable. Poll
mode you read a config register, see if there's valid data, and if
so, when you read the data register, it's pulled out of a FIFO.
The FIFO is in hardware and it's 4-deep. As long as you don't
need more than a depth of 4, you're rock solid.
This is a lot easier than the UDMA Rx mode, so I've just gone ahead
and deprecated the Rx mode. I could only ever get the Rx mode
to work to receive a single character *anyways*, so despite its
potential to receive thousands of bytes without CPU intervention,
it was only ever used as a 1-deep buffer because of the inability
to know exactly how much was in the buffer. Thus, the poll mode
is actually an improvement over the DMA mode, in practice.
More importantly, it means the Rx link is much more stable in
GDB kernel mode, if the baud rate is cranked down to 115200 (at
1M baud, the kernel still can't respond quite fast enough to
reliably drain it).
this is a first take at the poke_memory primitive, which is necessary
for GDB to patch in debug breakpoints.
I think the memory patching is *working*, but...stuff like single
stepping is still not working. This might be just me using GDB
wrong, tho. Need someone who actually uses GDB/is more familiar
with it to poke around a bit more.
- dabao now includes debug-proc by default
- gdb-stub now passes to both kernel and user programs, so that
uart switching can be achieved
- print-panics activated in the kernel, so we can see the panic
messages. They had disappeared for a while, and didn't notice.
this is gated behind the feature "debug-proc" because this could
leak attacker-useful information, and also it adds a primitive that
causes the kernel to write the debug information into any page
that the userspace already has access to. There are some checks
to ensure that the page is actually in RAM (so you can't use this
to trigger weird peripheral states, etc.) so hypothetically this
should just cause corruption in the userspace process if it is
abused, but still - more attack surface, more problems.
however, the feature is useful enough and the risk is small enough
that I think I'm going to leave it enabled for the foreseeable future.
It's definitely helpful to be able to see how much free memory
you have, and other resource allocation trackers like connection
limits and interrupt handler allocations.
a number of warnings cropped up in the latest Rust, which
have been cleaned up.
In addition, a definition for a PlatformSpecific syscall has been
added. This is a catch-all for meta-calls that can be experimented
with on a specific platform without having to update the xous API.
Xous API updates necessitate about a hundred files to get their versions
bumped to absorb the new API, and so this is considered to be
a fairly major task.
debug shell is actually not reachable in bao1x targets because
the DUART is Tx-only. thus this code is just dead weight in the
kernel for bao1x platforms.
since the swap update is going to require republishing the
Xous-rs crate, I'm adding a bunch of more descriptive error codes
that I wish Xous could have, and documented the cases in which
they would be used.
This makes it a bit easier to propagate errors up the stack on
more of the calls, at the expense of a more complicated error
type.
A particular edge case was finally reproduced & fixed. The edge
case occurs when a swap is triggered while handling an interrupt,
and an OOM happens during the context switch to/from the interrupt.
The background on this is that the hardware drivers for the swapper,
as well as much of the logical heavy lifting on e.g. what pages to
swap out, live in a userspace process known as xous-swapper.
The userspace process has to run free of interrupts - when you're
OOM'd or trying to swap, interrupts should not happen. The way to
do this in Xous is to run that code inside an interurpt context,
as interrupts are not interruptable.
Thus, the xous swapper userspace consists of two halves: a regular
userspace stub that provides some amount of control/debug over
the swapper, and a handler that gets run in a "pseudo-interrupt"
context.
Like all other interrupts, the handler "borrows" the stack of the currently
running thread - interrupt contexts don't have an explicit stack, they
just grow on top of the current stack of the current thread in the
current process at the time of the interrupt.
This is broken because when you have a swap event inside an interrupt
context, there are conditions where you might not have a stack. What
was in fact happening is that the stack pointer was being orphaned
as being the "exception stack" - which is a temporary location,
only accessible by the kernel, used for storing all the registers
going into and out of exceptions. Since no stack pointer is set up,
the moment the userspace starts to run and spill its registers to
stack, the system trips a "nested swap" event, which leads to a panic
because this is not allowed.
The fix to this is to devote two hard-wired pages, mapped in the
loader, to be the swapper's private stack area. Instead of behaving
like an interrupt and borrowing the caller's stack, the swapper instead
goes back to its hard-wired stack to do all the processing.
This should also improve the performance of swap handling in some
cases because we can now guarantee the stack is always in-memory
and doesn't need to be swapped in during swap processing.
One complication is that the "reserved memory" that the swapper
puts aside in case it *does* need to allocate extra pages during
handling can't be freed at the top of the OOM routine. Thus if
the swapper ends up using new pages, we should expect yet another
crash.
However, in practice, this doesn't seem to happen, because the
cases in which we hit needing a new page are typically related
to cases early on in boot where superpages/new page table entries
are being allocated, or if we're growing the heap that passes
data back and forth between swap and userspace. Because this call
triggers in such a specific condition, I hypothesize that actually
in practice it should generally be able to run without allocations.
One case where maybe it could hit an allocation is if the swapper
hit, for the first time, in an interrupt handler that was swapped
out, and never hit before, and the interrupt handler needed a
superpage to be allocated, thus triggering an allocation request
for a new PTE or two. I think this is probably pretty unlikely, but
if we do hit it, the fix would be to, for the purposes of targets
with "swap" enabled, to mark unmap_memory and map_memory to be
legal syscalls in interrupt contexts. These are normally illegal
because interrupts should always return - and these calls could
potentially fail or block. However, I think in this case it's
a trade-off between "we should be failing if we run out of memory"
and that failure being a potentially incorrect behavior that is guarded
against by a runtime check to discourage such calls.