aboutsummaryrefslogtreecommitdiff
path: root/02-usart/src/kern
diff options
context:
space:
mode:
Diffstat (limited to '02-usart/src/kern')
-rw-r--r--02-usart/src/kern/delay.c9
-rw-r--r--02-usart/src/kern/dma/dma_manager.c312
-rw-r--r--02-usart/src/kern/lib.c56
-rw-r--r--02-usart/src/kern/main.c117
-rw-r--r--02-usart/src/kern/mem.c206
-rw-r--r--02-usart/src/kern/spin.c49
-rw-r--r--02-usart/src/kern/stdlibrepl.c13
-rw-r--r--02-usart/src/kern/string.c9
-rw-r--r--02-usart/src/kern/vector.c0
9 files changed, 771 insertions, 0 deletions
diff --git a/02-usart/src/kern/delay.c b/02-usart/src/kern/delay.c
new file mode 100644
index 0000000..28ef710
--- /dev/null
+++ b/02-usart/src/kern/delay.c
@@ -0,0 +1,9 @@
+#include "kern/delay.h"
+
+void delay(uint32_t delay)
+{
+ while (delay--) {
+ /* needed to keep the compiler from optimizing away the loop. */
+ asm volatile("");
+ }
+}
diff --git a/02-usart/src/kern/dma/dma_manager.c b/02-usart/src/kern/dma/dma_manager.c
new file mode 100644
index 0000000..4336496
--- /dev/null
+++ b/02-usart/src/kern/dma/dma_manager.c
@@ -0,0 +1,312 @@
+#include "kern/dma/dma_manager.h"
+#include "arch/stm32l4xxx/peripherals/dma.h"
+#include "arch/stm32l4xxx/peripherals/usart.h"
+#include "arch/stm32l4xxx/peripherals/rcc.h"
+
+
+/* Bitmask of DMA2 channels in use. */
+static uint8_t dma_inuse[2];
+
+static inline dma_t* get_dma(int dma)
+{
+ if (dma) {
+ return &DMA2;
+ } else {
+ return &DMA1;
+ }
+}
+
+static dma_t* get_raw_dma(dma_channel_t chan)
+{
+ return get_dma(chan.dma);
+}
+
+static dma_channel_config_t* get_raw_channel_config(dma_channel_t chan)
+{
+ dma_t* dma = get_raw_dma(chan);
+ return &dma->channel_config[chan.chan];
+}
+
+static uint32_t get_periph_location(dma_peripheral_t operipheral)
+{
+#define CASE(p, n) case p: return ptr2reg(n);
+ switch (operipheral) {
+ CASE(DMA1_PERIPH_USART1_RX, &USART1.rd_r)
+ CASE(DMA1_PERIPH_USART1_TX, &USART1.td_r)
+ CASE(DMA1_PERIPH_USART2_RX, &USART2.rd_r)
+ CASE(DMA1_PERIPH_USART2_TX, &USART2.td_r)
+
+ default:
+ return 0;
+ };
+#undef CASE
+}
+
+static dma_channel_t allocate_dma_channel(
+ dma_peripheral_t operipheral, int* modesel)
+{
+ dma_peripheral_t peripheral = operipheral & 0xff;
+ int dmasel = peripheral >= DMA2_DMA1_SWITCH__;
+ if (dmasel) {
+ peripheral -= DMA2_DMA1_SWITCH__;
+ }
+ int chan = peripheral % DMA_N_CHANNELS;
+
+ *modesel = peripheral / 7;
+ return (dma_channel_t) {
+ .dma = dmasel,
+ .chan = chan
+ };
+}
+
+/*
+ * Atomically reserves the DMA channel so other calls
+ * cannot erroneously reserve the same DMA channel.
+ *
+ * Returns 0 if this function was unable to reserve
+ * the channel.
+ */
+static int try_reserve_dma_channel(
+ dma_channel_t chan)
+{
+ int in_use = __sync_fetch_and_or(
+ &dma_inuse[chan.dma], 1 << chan.chan);
+
+ return !(in_use & (1 << chan.chan));
+}
+
+ // int in_use = __sync_fetch_and_or(&dma_inuse[dmasel], 1 << chan);
+void release_dma_channel(dma_channel_t chan)
+{
+ dma_channel_config_t* config = get_raw_channel_config(chan);
+ regset(config->cc_r, dma_cc_en, 0); /* Disable the register. */
+ dma_inuse[chan.dma] &= ~(1 << chan.chan); /* Release the DMA. */
+
+ if (!dma_inuse[chan.dma]) {
+ /* Power-down the DMA if not in use. */
+ if (chan.dma) {
+ regset(RCC.ahb1en_r, rcc_dma2en, 0);
+ } else {
+ regset(RCC.ahb1en_r, rcc_dma1en, 0);
+ }
+ }
+}
+
+void configure_dma_channel(
+ dma_channel_t chan,
+ dma_peripheral_t operipheral,
+ dma_opts_t* opts,
+ dma_dir_t dir,
+ int selmode,
+ bool mem2mem,
+ int* error_out)
+{
+ if (chan.dma) {
+ regset(RCC.ahb1en_r, rcc_dma2en, 1);
+ } else {
+ regset(RCC.ahb1en_r, rcc_dma1en, 1);
+ }
+
+ dma_t* dma = get_raw_dma(chan);
+ regset(dma->csel_r, 0xF << (4 * chan.chan), selmode);
+ dma_channel_config_t* config =
+ &dma->channel_config[chan.chan];
+
+ uint32_t reg = 0;
+
+ regset(reg, dma_cc_dir, dir);
+ regset(reg, dma_cc_tcie, opts->transfer_complete_interrupt_enable);
+ regset(reg, dma_cc_htie, opts->half_transfer_interrupt_enable);
+ regset(reg, dma_cc_teie, opts->transfer_error_interrupt_enable);
+ regset(reg, dma_cc_circ, opts->circular_mode);
+ regset(reg, dma_cc_pinc, opts->peripheral_increment);
+ regset(reg, dma_cc_minc, opts->memory_increment);
+ regset(reg, dma_cc_psize, opts->peripheral_block_size);
+ regset(reg, dma_cc_msize, opts->memory_block_size);
+ regset(reg, dma_cc_pl, opts->priority);
+ regset(reg, dma_cc_mem2mem, mem2mem);
+
+ config->cc_r = reg;
+ config->cpa_r = get_periph_location(operipheral);
+
+ *error_out = 0;
+}
+
+dma_mem2mem_channel_t select_dma_channel_mem2mem(
+ int channel,
+ dma_opts_t* opts,
+ int* error_out)
+{
+
+#define WRAP(c) ((dma_mem2mem_channel_t) { .c_ = c })
+ // TODO this should probably be in a critical section.
+ dma_channel_t chan;
+ if (channel == -1) {
+ chan.dma = 1;
+ if ((dma_inuse[chan.dma] & 0x7F) == 0x7F) {
+ chan.dma = 0;
+ }
+
+ if ((dma_inuse[chan.dma] & 0x7F) == 0x7F) {
+ *error_out = DMA_ERROR_CHANNEL_IN_USE;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ uint8_t t = ~(dma_inuse[chan.dma] << 1);
+ chan.chan = 6 - (__builtin_clz(t) - 24);
+ } else {
+ if (channel < 7) {
+ chan.dma = 0;
+ chan.chan = channel;
+ } else {
+ chan.dma = 0;
+ chan.chan = channel - 7;
+ }
+ }
+
+ if (!try_reserve_dma_channel(chan)) {
+ *error_out = DMA_ERROR_CHANNEL_IN_USE;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ int ec = 0;
+ configure_dma_channel(
+ chan,
+ -1 /* No peripheral */,
+ opts,
+ READ_FROM_PERIPHERAL,
+ /* selmode = */ 0x8,
+ /* mem2mem = */ true,
+ &ec);
+
+ if (ec) {
+ *error_out = ec;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ *error_out = 0;
+ return WRAP(chan);
+#undef WRAP
+}
+
+dma_mem2p_channel_t select_dma_channel_mem2p(
+ dma_peripheral_t peripheral,
+ dma_opts_t* opts_in,
+ int* error_out)
+{
+#define WRAP(c) ((dma_mem2p_channel_t) { .c_ = c })
+ *error_out = 0;
+
+ int modesel;
+ dma_channel_t ret =
+ allocate_dma_channel(peripheral, &modesel);
+
+ if (!try_reserve_dma_channel(ret)) {
+ *error_out = DMA_ERROR_CHANNEL_IN_USE;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ configure_dma_channel(
+ ret,
+ peripheral,
+ opts_in,
+ READ_FROM_MEMORY,
+ modesel,
+ /* mem2mem = */ false,
+ error_out);
+
+ if (*error_out) {
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ *error_out = 0;
+ return WRAP(ret);
+#undef WRAP
+}
+
+dma_p2mem_channel_t select_dma_channel_p2mem(
+ dma_peripheral_t peripheral,
+ dma_opts_t* opts_in,
+ int* error_out)
+{
+#define WRAP(c) ((dma_p2mem_channel_t) { .c_ = c })
+ *error_out = 0;
+
+ int modesel;
+ dma_channel_t ret =
+ allocate_dma_channel(peripheral, &modesel);
+
+ if (!try_reserve_dma_channel(ret)) {
+ *error_out = DMA_ERROR_CHANNEL_IN_USE;
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ configure_dma_channel(
+ ret,
+ peripheral,
+ opts_in,
+ READ_FROM_PERIPHERAL,
+ modesel,
+ /* mem2mem = */ false,
+ error_out);
+
+ if (*error_out) {
+ return WRAP(DMA_CHAN_ERROR);
+ }
+
+ *error_out = 0;
+ return WRAP(ret);
+#undef WRAP
+}
+
+
+void dma_mem2p_initiate_transfer(
+ dma_mem2p_channel_t chan, const void* from_loc, uint16_t nblocks)
+{
+ dma_channel_config_t* config = get_raw_channel_config(chan.c_);
+ config->cma_r = ptr2reg(from_loc);
+ config->cndt_r = nblocks;
+
+ regset(config->cc_r, dma_cc_en, 1);
+}
+
+void dma_mem2mem_initiate_transfer(
+ dma_mem2mem_channel_t chan,
+ void* to_loc,
+ const void* from_loc,
+ uint16_t nblocks)
+{
+ dma_channel_config_t* config = get_raw_channel_config(chan.c_);
+ config->cma_r = ptr2reg(to_loc);
+ config->cpa_r = ptr2reg(from_loc);
+ config->cndt_r = nblocks;
+
+ regset(config->cc_r, dma_cc_en, 1);
+}
+
+void dma_p2mem_initiate_transfer(
+ dma_p2mem_channel_t chan, void* to_loc, uint16_t nblocks)
+{
+ dma_channel_config_t* config = get_raw_channel_config(chan.c_);
+
+ config->cma_r = ptr2reg(to_loc);
+ config->cndt_r = nblocks;
+
+ regset(config->cc_r, dma_cc_en, 1);
+}
+
+interrupt_t dma_channel_get_interrupt(dma_channel_t chan)
+{
+ if (chan.dma == 0) {
+ return IRQ_DMA1_CHANNEL1_IRQ + chan.chan;
+ } else {
+ switch (chan.chan) {
+ case 5:
+ return IRQ_DMA1_CHANNEL6_IRQ;
+ case 6:
+ return IRQ_DMA1_CHANNEL7_IRQ;
+ default:
+ return IRQ_DMA2_CHANNEL1_IRQ + chan.chan;
+ }
+ }
+}
diff --git a/02-usart/src/kern/lib.c b/02-usart/src/kern/lib.c
new file mode 100644
index 0000000..88188cc
--- /dev/null
+++ b/02-usart/src/kern/lib.c
@@ -0,0 +1,56 @@
+#include "kern/lib.h"
+
+#define nybble_to_hex(n) \
+ ((n) < 10 ? 0x30 + (n) : ('A' + ((n) - 10)))
+
+void hexify(uint32_t v, char* into)
+{
+ into += 8;
+
+ *(into--) = 0;
+
+ *(into--) = nybble_to_hex(v & 0x0F);
+ v >>= 4;
+ *(into--) = nybble_to_hex(v & 0x0F);
+ v >>= 4;
+ *(into--) = nybble_to_hex(v & 0x0F);
+ v >>= 4;
+ *(into--) = nybble_to_hex(v & 0x0F);
+ v >>= 4;
+
+ *(into--) = nybble_to_hex(v & 0x0F);
+ v >>= 4;
+ *(into--) = nybble_to_hex(v & 0x0F);
+ v >>= 4;
+ *(into--) = nybble_to_hex(v & 0x0F);
+ v >>= 4;
+ *into = nybble_to_hex(v & 0x0F);
+ v >>= 4;
+}
+
+void decimalify(int v, char* into)
+{
+ int c = 0;
+ int i;
+
+ if (v == 0) {
+ *(into ++) = '0';
+ *into = 0;
+ return;
+ } else {
+ while (v > 0) {
+ *(into ++) = 0x30 + (v % 10);
+ v /= 10;
+ ++ c;
+ }
+ }
+ *into = 0;
+
+ into -= c;
+ for (i = 0; i < c / 2; ++ i) {
+ char tmp = into[i];
+ into[i] = into[c - i - 1];
+ into[c - i - 1] = tmp;
+ }
+
+}
diff --git a/02-usart/src/kern/main.c b/02-usart/src/kern/main.c
new file mode 100644
index 0000000..0e0c89c
--- /dev/null
+++ b/02-usart/src/kern/main.c
@@ -0,0 +1,117 @@
+
+#include "arch.h"
+
+#include "arch/stm32l4xxx/peripherals/clock.h"
+#include "arch/stm32l4xxx/peripherals/dma.h"
+#include "arch/stm32l4xxx/peripherals/gpio.h"
+#include "arch/stm32l4xxx/peripherals/system.h"
+#include "arch/stm32l4xxx/peripherals/usart.h"
+#include "arch/stm32l4xxx/peripherals/nvic.h"
+#include "arch/stm32l4xxx/peripherals/irq.h"
+
+#include "kern/dma/dma_manager.h"
+
+#include "kern/delay.h"
+#include "kern/mem.h"
+#include "kern/spin.h"
+#include "kern/string.h"
+
+/** Overrides the default systick irq handler. */
+void on_systick()
+{
+ static int is_on = 0;
+
+ __IO gpio_port_t* port_b = enable_gpio(GPIO_PORT_B);
+ gpio_output_pin_t pin3 = set_gpio_pin_output(port_b, PIN_3);
+
+ if (is_on) {
+ pin_off(pin3);
+ } else {
+ pin_on(pin3);
+ }
+
+ is_on = ! is_on;
+}
+
+void setup_usart2(uint32_t baud_rate)
+{
+ __IO gpio_port_t* port_a = enable_gpio(GPIO_PORT_A);
+ enable_hsi(&RCC, true);
+
+ set_usart2_clock_src(&RCC, USART_CLK_SRC_HSI16);
+ set_usart2_clock_enabled(&RCC, USART_CLK_SRC_HSI16);
+
+ set_gpio_pin_mode(port_a, PIN_2, MODE_ALTERNATE);
+ set_gpio_pin_mode(port_a, PIN_15, MODE_ALTERNATE);
+ set_gpio_alternate_function(port_a, PIN_2, AFN_7);
+ set_gpio_alternate_function(port_a, PIN_15, AFN_3);
+
+ /* De-assert reset of USART2 */
+ regset(RCC.apb1rst1_r, rcc_usart2rst, 0);
+
+ USART2.c_r1 = 0;
+ USART2.c_r2 = 0;
+ USART2.c_r3 = 0;
+
+ usart_set_divisor(&USART2, 16000000 / baud_rate);
+}
+
+#ifdef ARCH_STM32L4
+
+/* Main function. This gets executed from the interrupt vector defined above. */
+int main()
+{
+ /* Enable a higher clock frequency. */
+ set_system_clock_MHz(80);
+
+ setup_usart2(115200);
+ regset(USART2.c_r1, usart_txeie, 1);
+ regset(USART2.c_r1, usart_rxneie, 1);
+ usart_enable_dma(&USART2, USART_ENABLE_TX);
+ usart_set_enabled(&USART2, USART_ENABLE_TX | USART_ENABLE_RX);
+
+
+ dma_opts_t opts = DEFAULT_DMA_OPTS;
+ opts.transfer_complete_interrupt_enable = 1;
+ int ec = 0;
+ dma_mem2p_channel_t dma_chan =
+ select_dma_channel_mem2p(DMA1_PERIPH_USART2_TX, &opts, &ec);
+ enable_interrupt(dma_channel_get_interrupt(dma_chan.c_));
+
+ if (ec) {
+ usart_printf(&USART2, "Select DMA channel failed :( %d\n", ec);
+ for (;;);
+ }
+
+ // const char* thing = "Good Thing This Works!";
+
+ char* str = halloc(128);
+ kstrcpy(str, "Hello, Heap!");
+
+ usart_printf(&USART2, "DATA_SEGMENT_START %p\n", &DATA_SEGMENT_START);
+ usart_printf(&USART2, "DATA_SEGMENT_STOP: %p\n", &DATA_SEGMENT_STOP);
+ usart_printf(&USART2, "str at: %p\n", str);
+ usart_printf(&USART2, "str: %s\n", str);
+ // usart_printf(&USART2, "%s\n", thing);
+ // regset(USART2.ic_r, usart_tccf, 1);
+ // dma_mem2p_initiate_transfer(dma_chan, thing, strlen(thing));
+
+ __IO gpio_port_t* port_b = enable_gpio(GPIO_PORT_B);
+ gpio_output_pin_t pin3 = set_gpio_pin_output(port_b, PIN_3);
+ pin_on(pin3);
+
+ // usart_printf(&USART2, "Start Configuring Countdown!\n");
+
+ /* Set the countdown to start from 1,000,0000. */
+ SCB.strv_r = 10000000;
+
+ /* Enable interrupts. */
+ regset(SCB.stcs_r, scb_tickint, 1);
+
+ /* Start the systick. */
+ regset(SCB.stcs_r, scb_enable, 1);
+
+ // usart_printf(&USART2, "Start Countdown Started!\n");
+}
+
+#endif
diff --git a/02-usart/src/kern/mem.c b/02-usart/src/kern/mem.c
new file mode 100644
index 0000000..79bcabf
--- /dev/null
+++ b/02-usart/src/kern/mem.c
@@ -0,0 +1,206 @@
+#include "arch.h"
+#include "kern/mem.h"
+#include "kern/common.h"
+
+#ifdef ARCH_STM32L4
+/* Provide a definition for memset() when not provided for the
+ * microcontroller. */
+void* memset(void* dest, int c, size_t n)
+{
+ uint8_t c8 = (uint8_t) c;
+ uint8_t* dest8 = (uint8_t*) dest;
+ uint8_t* to = dest8 + n;
+
+ while(dest8 < to) {
+ *(dest8 ++) = c8;
+ }
+
+ return dest;
+}
+
+#else
+
+void* memset(void* dest, int c, size_t n);
+
+#endif
+
+typedef uint16_t halloc_off_t;
+
+// The sizes will count the number of WORDS allocated.
+// Since there's a max size of 16k, only 12 bits will be
+// needed for this.
+typedef struct HALLOC_NODE {
+ union {
+ uint32_t header;
+ struct {
+ /* Is this memory block currently in use (hasn't been hfree'd) */
+ bool used:1;
+ /* Number of words allocated. Does not include the header. */
+ uint16_t size:15;
+ /* The location of the previous block (in WORDS from offest) */
+ halloc_off_t prev;
+ } PACKED;
+ };
+
+ uint8_t mem[]; /* The memory to use. */
+} halloc_node_t;
+
+halloc_node_t* halloc_start;
+
+#define halloc_node_next(cur) \
+ ((halloc_node_t*)(((uint8_t*)(cur)) + (((cur)->size + 1) * 4)))
+
+#define halloc_node_prev(cur) halloc_node_at_off(cur->prev)
+
+#define halloc_node_at_off(offset) \
+ ((halloc_node_t*)(((uint8_t*) halloc_start) + (offset) * 4))
+
+#define halloc_node_get_off(node) \
+ (((uint32_t)(((uint8_t*)(node)) - ((uint8_t*)(halloc_start)))) / 4)
+
+#define size_for(n) \
+ (((n) / 4) + ((n) % 4 != 0))
+
+void* halloc(size_t size)
+{
+ if (!halloc_start) {
+ halloc_start = (halloc_node_t*) DATA_SEGMENT_STOP_ADDR;
+ memset(halloc_start, 0, sizeof(halloc_node_t));
+ halloc_start->size = (MAX_HEAP_SIZE / 4) - 1;
+ }
+
+ size_t realsz = size_for(size); /* Clip the size to the nearest word. */
+ halloc_off_t offset = 0;
+ while (offset < (MAX_HEAP_SIZE / 4)) {
+ halloc_node_t* cur = halloc_node_at_off(offset);
+
+ if (!cur->used && (cur->size >= realsz)) {
+ cur->used = true;
+ size_t orig_size = cur->size;
+ cur->size = realsz;
+
+ if (orig_size > realsz) {
+ /* This halloc node needs to split into two blocks. */
+ halloc_node_t* next = halloc_node_next(cur);
+ next->used = 0;
+ next->size = orig_size - realsz - sizeof(halloc_node_t) / 4;
+ next->prev = offset;
+
+ halloc_node_t* nextnext = halloc_node_next(next);
+ if (halloc_node_get_off(nextnext) < (MAX_HEAP_SIZE / 4)) {
+ nextnext->prev = halloc_node_get_off(next);
+ }
+ }
+
+ return (void*) cur->mem;
+ }
+
+ offset += (sizeof(halloc_node_t) / 4) + cur->size;
+ }
+
+ return NULL;
+}
+
+/* Joins this node with the previous and next nodes if they're free. */
+static void coalesce(halloc_node_t* cur)
+{
+ if (cur->used) return;
+
+ halloc_node_t* next = halloc_node_next(cur);
+ if (!next->used) {
+ cur->size += next->size + sizeof(halloc_node_t) / 4;
+ }
+
+ if (cur != halloc_start) {
+ coalesce(halloc_node_prev(cur));
+ }
+}
+
+void hfree(void* mem)
+{
+ /* TODO this should be a critical section. */
+ if (!mem) {
+ /* Consistent with C, freeing a NULL pointer does nothing. */
+ return;
+ }
+
+ halloc_node_t* header = ((halloc_node_t*) mem) - 1;
+
+ if (!header->used) {
+#ifdef FOR_TESTING
+ assert(header->used);
+#endif
+ return; // TODO make this fail on ARM.
+ }
+
+ header->used = 0;
+ coalesce(header);
+}
+
+#ifdef FOR_TESTING
+
+#include <stdio.h>
+
+void* debug_halloc_get_next_ptr(void* ptr)
+{
+ halloc_node_t* node = ptr - sizeof(halloc_node_t);
+ halloc_node_t* next = halloc_node_next(node);
+
+ return next->mem;
+}
+
+void* debug_halloc_get_prev_ptr(void* ptr)
+{
+ halloc_node_t* node = ptr - sizeof(halloc_node_t);
+ halloc_node_t* prev = halloc_node_prev(node);
+
+ return prev->mem;
+}
+
+/* Tests that we can walk up and down the allocated blocks and that they
+ * are properly aligned. */
+int debug_halloc_assert_consistency(char* error, size_t len)
+{
+ halloc_node_t* cur = halloc_node_at_off(0);
+ size_t total_size = 0;
+ size_t loop_check = 0;
+
+ while(1) {
+ total_size += cur->size + 1;
+
+ halloc_node_t* next = halloc_node_next(cur);
+ if ((uint8_t*) next == ((uint8_t*)&DATA_SEGMENT_STOP) + MAX_HEAP_SIZE) {
+ break;
+ } else if ((uint8_t*) next > (uint8_t*)DATA_SEGMENT_STOP_ADDR + MAX_HEAP_SIZE){
+ snprintf(
+ error, len, "Next node points is out of bounds. %p vs max of %p\n",
+ next,
+ (void*)(DATA_SEGMENT_STOP_ADDR + MAX_HEAP_SIZE));
+ return 1;
+ }
+ cur = next;
+ }
+
+ if (total_size * 4 != MAX_HEAP_SIZE) {
+ snprintf(
+ error, len, "Total recorded size is inconsistent. %lu vs %lu\n",
+ total_size * 4, MAX_HEAP_SIZE);
+ return 1;
+ }
+
+ while (loop_check < 10000) {
+ halloc_node_t* prev = halloc_node_prev(cur);
+
+ if (prev == halloc_start) {
+ return 0;
+ }
+
+ cur = prev;
+ ++ loop_check;
+ }
+
+ snprintf(error, len, "Loop check failed.\n");
+ return 1;
+}
+
+#endif
diff --git a/02-usart/src/kern/spin.c b/02-usart/src/kern/spin.c
new file mode 100644
index 0000000..7c4f6eb
--- /dev/null
+++ b/02-usart/src/kern/spin.c
@@ -0,0 +1,49 @@
+#include "kern/spin.h"
+#include "kern/delay.h"
+#include "arch/stm32l4xxx/peripherals/gpio.h"
+
+#define SHORT_DELAY 200000
+#define LONG_DELAY (SHORT_DELAY * 2)
+
+static void flash_bit(
+ uint32_t base, gpio_output_pin_t out_pin,
+ uint8_t bit /* 0 => 0, non-zero => 1 */)
+{
+ pin_on(out_pin);
+ if (bit) {
+ delay(base * 2);
+ } else {
+ delay(base);
+ }
+ pin_off(out_pin);
+ delay(base);
+}
+
+void spin(uint32_t base, uint8_t c)
+{
+ uint8_t code;
+ __IO gpio_port_t* port_b = enable_gpio(GPIO_PORT_B);
+ gpio_output_pin_t pin3 = set_gpio_pin_output(port_b, PIN_3);
+
+ for (;;) {
+ code = c;
+ flash_bit(base, pin3, code & 0x80);
+ code <<= 1;
+ flash_bit(base, pin3, code & 0x80);
+ code <<= 1;
+ flash_bit(base, pin3, code & 0x80);
+ code <<= 1;
+ flash_bit(base, pin3, code & 0x80);
+
+ code <<= 1;
+ flash_bit(base, pin3, code & 0x80);
+ code <<= 1;
+ flash_bit(base, pin3, code & 0x80);
+ code <<= 1;
+ flash_bit(base, pin3, code & 0x80);
+ code <<= 1;
+ flash_bit(base, pin3, code & 0x80);
+
+ delay(base * 4);
+ }
+}
diff --git a/02-usart/src/kern/stdlibrepl.c b/02-usart/src/kern/stdlibrepl.c
new file mode 100644
index 0000000..2d9d839
--- /dev/null
+++ b/02-usart/src/kern/stdlibrepl.c
@@ -0,0 +1,13 @@
+/*
+ * Replacement for common stdlib functions that don't exist
+ * on the ARM bare-metal compilation environment.
+ */
+
+#include <stddef.h>
+
+size_t strlen(char* ch)
+{
+ size_t ret = 0;
+ while(*(ch ++) != 0) ++ ret;
+ return ret;
+}
diff --git a/02-usart/src/kern/string.c b/02-usart/src/kern/string.c
new file mode 100644
index 0000000..4afa228
--- /dev/null
+++ b/02-usart/src/kern/string.c
@@ -0,0 +1,9 @@
+#include "kern/string.h"
+
+void kstrcpy(char* into, const char* from)
+{
+ while(*from) {
+ *(into ++) = *(from ++);
+ }
+ *into = 0;
+}
diff --git a/02-usart/src/kern/vector.c b/02-usart/src/kern/vector.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/02-usart/src/kern/vector.c