sylveos

Toy Operating System
Log | Files | Refs

mini-uart.zig (13617B)


      1 const std = @import("std");
      2 
      3 const StackRingBuffer = @import("shared").StackRingBuffer;
      4 
      5 const interrupts = @import("../interrupts.zig");
      6 const mem = @import("../mem.zig");
      7 
      8 const clock = @import("./clock.zig");
      9 const gpio = @import("./gpio.zig");
     10 
     11 pub const Error = error{ AlreadyInitialized, NotInitialized, InvalidReadLimit, Timeout } || gpio.Error;
     12 
     13 // Page 8: Auxiliary peripherals Register Map
     14 const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0021_5000;
     15 const AUX_ENABLES: usize = BASE_ADDRESS + 0x0004;
     16 const AUX_MU_IO_REG: usize = BASE_ADDRESS + 0x0040;
     17 const AUX_MU_IER_REG: usize = BASE_ADDRESS + 0x0044;
     18 const AUX_MU_IIR_REG: usize = BASE_ADDRESS + 0x0048;
     19 const AUX_MU_LCR_REG: usize = BASE_ADDRESS + 0x004C;
     20 const AUX_MU_MCR_REG: usize = BASE_ADDRESS + 0x0050;
     21 const AUX_MU_LSR_REG: usize = BASE_ADDRESS + 0x0054;
     22 const AUX_MU_MSR_REG: usize = BASE_ADDRESS + 0x0058;
     23 const AUX_MU_SCRATCH: usize = BASE_ADDRESS + 0x005C;
     24 const AUX_MU_CNTL_REG: usize = BASE_ADDRESS + 0x0060;
     25 const AUX_MU_STAT_REG: usize = BASE_ADDRESS + 0x0064;
     26 const AUX_MU_BAUD_REG: usize = BASE_ADDRESS + 0x0068;
     27 
     28 const CLOCK_FREQ = 250_000_000;
     29 
     30 const TxPin = enum(u8) {
     31     Gpio14 = 14,
     32     Gpio32 = 32,
     33     Gpio36 = 36,
     34 };
     35 const RxPin = enum(u8) {
     36     Gpio15 = 15,
     37     Gpio33 = 33,
     38     Gpio37 = 37,
     39 };
     40 
     41 var initialized = false;
     42 var rx_interrupts_enabled = false;
     43 var tx_interrupts_enabled = false;
     44 var use_tx_interrupts = false;
     45 
     46 pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
     47     if (initialized) return Error.AlreadyInitialized;
     48 
     49     // Set GPIO pins first (as specified by manual)
     50     // Page 102 specifies which alt mode
     51     const tx_fn: gpio.FunctionSelect = switch (tx_pin) {
     52         .Gpio14 => .alt_fn_5,
     53         .Gpio32 => .alt_fn_3,
     54         .Gpio36 => .alt_fn_2,
     55     };
     56     const rx_fn: gpio.FunctionSelect = switch (rx_pin) {
     57         .Gpio15 => .alt_fn_5,
     58         .Gpio33 => .alt_fn_3,
     59         .Gpio37 => .alt_fn_2,
     60     };
     61 
     62     // The error case is technically unreachable due to hardcoding gpio pin values
     63     // But at the same time this function body may change with time so it's important
     64     // to keep the Error
     65     try gpio.fn_sel(@intFromEnum(tx_pin), tx_fn);
     66     try gpio.fn_sel(@intFromEnum(rx_pin), rx_fn);
     67 
     68     try gpio.set_pull(@intFromEnum(tx_pin), .Off);
     69     try gpio.set_pull(@intFromEnum(rx_pin), .Off);
     70 
     71     // AUX_ENABLES needs bit 0 set to 1
     72     // Page 9: AUXENB Register
     73     mem.put_u32(@ptrFromInt(AUX_ENABLES), 1);
     74 
     75     // Disable interrupts
     76     // Page 12: AUX_MU_IER_REG Register
     77     // When bit 0/1 is 0, interrupts are disabled
     78     mem.put_u32(@ptrFromInt(AUX_MU_IER_REG), 0);
     79 
     80     // Disable RX/TX during configuration
     81     // Page 17: AUX_MU_CNTL_REG Register
     82     // When bit 0/1 is 0, UART receiver/transmitter is disabled
     83     mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0);
     84 
     85     // Set data size to 8 bits
     86     // Page 14: AUX_MU_LCR_REG Register
     87     // When bit 0 is 1, UART is in 8-bit mode, else 7-bit
     88     // Errata: "bit 1 must be set for 8 bit mode"
     89     mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 0b11);
     90 
     91     // Put RTS high (indicate request to send)
     92     // Page 14: AUX_MU_MCR_REG Register
     93     // When bit 1 is 0, RTS line is high, else low
     94     mem.put_u32(@ptrFromInt(AUX_MU_MCR_REG), 0);
     95 
     96     // Clear FIFO
     97     // Page 13: AUX_MU_IER_REG Register
     98     // When bit 1/2 is 1, receive/transmit will be cleared
     99     mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0b110);
    100 
    101     // Set baud rate
    102     // Page 11: 2.2.1 Mini UART Implementation Details
    103     // The baudrate formula is given as (system_clock_freq)/(8 * (baudrate_reg + 1))
    104     mem.put_u32(@ptrFromInt(AUX_MU_BAUD_REG), CLOCK_FREQ / (8 * baud) - 1);
    105 
    106     // Enable RX/TX again
    107     // Page 17: AUX_MU_CNTL_REG Register
    108     // When bit 0/1 is 1, UART receiver/transmitter is enabled
    109     mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0b11);
    110 
    111     mem.barrier(.Write);
    112 
    113     initialized = true;
    114 }
    115 
    116 pub fn set_rx_interrupts(state: bool) Error!void {
    117     if (state) {
    118         try enable_rx_interrupts();
    119     } else {
    120         try disable_rx_interrupts();
    121     }
    122 }
    123 
    124 fn interrupt_state(tx: bool, rx: bool) void { // Enable RX/TX Interrupts
    125     // Page 12: AUX_MU_IIR_REG Register
    126     // Bit 0 - RX Interrupt
    127     // Bit 1 - TX Interrupt
    128     // Errata: "Bits 1:0 are swapped. bit 0 is receive
    129     // interrupt and bit 1 is transmit."
    130     // Errata: "Bits 3:2 are marked as don't care, but
    131     // are actually required in order to receive interrupts."
    132     if (tx and rx) {
    133         mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b1111);
    134     } else if (tx and !rx) {
    135         mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b1110);
    136     } else if (!tx and rx) {
    137         mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0101);
    138     } else if (!tx and !rx) {
    139         mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0000);
    140     }
    141 }
    142 
    143 // Separate function to allow TX used during early boot
    144 fn enable_rx_interrupts() Error!void {
    145     if (!initialized) return Error.NotInitialized;
    146     if (rx_interrupts_enabled) return Error.AlreadyInitialized;
    147 
    148     mem.barrier(.Write);
    149 
    150     interrupts.set_exception_handler(.IRQ, uart_handler);
    151     interrupts.enable_peripheral_interrupt(.AuxInterrupt);
    152 
    153     interrupt_state(tx_interrupts_enabled, true);
    154 
    155     rx_interrupts_enabled = true;
    156 }
    157 
    158 fn disable_rx_interrupts() Error!void {
    159     if (!initialized) return Error.NotInitialized;
    160     if (!rx_interrupts_enabled) return Error.NotInitialized;
    161 
    162     mem.barrier(.Write);
    163 
    164     interrupt_state(tx_interrupts_enabled, false);
    165 
    166     rx_interrupts_enabled = false;
    167 }
    168 
    169 pub fn set_tx_interrupts(state: bool) void {
    170     use_tx_interrupts = state;
    171 
    172     if (state) {
    173         interrupts.set_exception_handler(.IRQ, uart_handler);
    174         interrupts.enable_peripheral_interrupt(.AuxInterrupt);
    175     } else {
    176         drain_write_queue();
    177     }
    178 }
    179 
    180 fn enable_tx_interrupt() !void {
    181     if (!initialized) return Error.NotInitialized;
    182 
    183     interrupt_state(true, rx_interrupts_enabled);
    184 
    185     tx_interrupts_enabled = true;
    186 }
    187 
    188 fn disable_tx_interrupt() !void {
    189     if (!initialized) return Error.NotInitialized;
    190 
    191     interrupt_state(false, rx_interrupts_enabled);
    192 
    193     tx_interrupts_enabled = false;
    194 }
    195 
    196 fn drain_write_queue() void {
    197     const cs = mem.enter_critical_section();
    198     defer cs.exit();
    199 
    200     while (tx_list.pop()) |b| {
    201         write_byte_sync(b);
    202     }
    203 }
    204 
    205 var tx_list: StackRingBuffer(u8, 128) = .init();
    206 
    207 var rx_list: StackRingBuffer(u8, 128) = .init();
    208 var rx_writer: ?*std.Io.Writer = null;
    209 var rx_writer_written: usize = 0;
    210 
    211 pub fn switch_rx_writer(rx_w: ?*std.Io.Writer) !void {
    212     const cs = mem.enter_critical_section();
    213     defer cs.exit();
    214 
    215     rx_writer_written = 0;
    216 
    217     if (rx_w) |w| {
    218         // Copy buffer into new writer
    219         while (rx_list.pop()) |b| {
    220             try w.writeByte(b);
    221             rx_writer_written += 1;
    222         }
    223     }
    224 
    225     rx_writer = rx_w;
    226 }
    227 
    228 pub fn get_rx_written() usize {
    229     const cs = mem.enter_critical_section();
    230     defer cs.exit();
    231 
    232     return rx_writer_written;
    233 }
    234 
    235 noinline fn uart_handler(_: interrupts.Registers, _: interrupts.ExceptionVector) void {
    236     mem.barrier(.Write);
    237     defer mem.barrier(.Write);
    238 
    239     if (!initialized) return;
    240     if (!interrupts.pending_peripheral_interrupt(.AuxInterrupt)) return;
    241 
    242     while (true) {
    243         const IIR = mem.get_u32(@ptrFromInt(AUX_MU_IIR_REG));
    244 
    245         // UART interrupt pending
    246         if ((IIR & 0b1) == 1) return;
    247 
    248         switch (IIR & 0b110) {
    249             // RX has byte
    250             0b100 => {
    251                 const b = read_byte_raw();
    252                 if (rx_writer) |w| {
    253                     rx_writer_written += 1;
    254                     w.writeByte(b) catch {};
    255                 } else {
    256                     rx_list.push(b) catch {};
    257                 }
    258             },
    259             // TX has space
    260             0b010 => {
    261                 if (tx_list.pop()) |byte| {
    262                     mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF);
    263                 } else {
    264                     disable_tx_interrupt() catch {};
    265                     return;
    266                 }
    267             },
    268             // Unknown
    269             else => break,
    270         }
    271     }
    272 }
    273 
    274 pub fn is_initialized() bool {
    275     return initialized;
    276 }
    277 
    278 const Status = packed struct(u32) {
    279     rx_has_symbol: bool,
    280     tx_has_space: bool,
    281     rx_is_idle: bool,
    282     tx_is_idle: bool,
    283     rx_overrun: bool,
    284     tx_full: bool,
    285     rts_status: bool,
    286     ctx_status: bool,
    287     tx_is_empty: bool,
    288     tx_done: bool,
    289     _reserved_10_15: u6,
    290     rx_fill_level: u4,
    291     _reserved_20_23: u4,
    292     tx_fill_level: u4,
    293     _reserved_28_31: u4,
    294 };
    295 
    296 pub fn status() Status {
    297     return @bitCast(mem.get_u32_barrier(@ptrFromInt(AUX_MU_STAT_REG)));
    298 }
    299 
    300 pub fn read_queue_length() usize {
    301     const cs = mem.enter_critical_section();
    302     defer cs.exit();
    303 
    304     return rx_list.length();
    305 }
    306 
    307 pub fn write_queue_length() usize {
    308     // Queuing disabled
    309     const cs = mem.enter_critical_section();
    310     defer cs.exit();
    311 
    312     return tx_list.length();
    313 }
    314 
    315 pub fn can_read() bool {
    316     // Check if FIFO has data
    317     // Page 15: AUX_MU_LSR_REG Register
    318     // Bit 1 is set when FIFO holds at least 1 byte
    319     return read_queue_length() > 0 or can_read_raw();
    320 }
    321 
    322 // // TODO; is this necessary
    323 inline fn can_read_raw() bool {
    324     return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x01) == 1;
    325 }
    326 
    327 pub fn read_byte_sync_timeout(timeout: ?u64) !u8 {
    328     var wait_until: u64 = std.math.maxInt(u64);
    329 
    330     if (timeout) |t| {
    331         wait_until = clock.current_count() + t;
    332     }
    333 
    334     while (true) {
    335         if (clock.current_count() > wait_until) {
    336             return Error.Timeout;
    337         }
    338 
    339         if (!can_read()) continue;
    340 
    341         if (read_queue_length() > 0) {
    342             return read_byte_async().?;
    343         } else {
    344             return read_byte_raw();
    345         }
    346 
    347         return;
    348     }
    349 }
    350 
    351 pub fn read_byte_sync() u8 {
    352     return read_byte_sync_timeout(null) catch unreachable;
    353 }
    354 
    355 pub fn read_byte_async() ?u8 {
    356     const cs = mem.enter_critical_section();
    357     defer cs.exit();
    358 
    359     return rx_list.pop();
    360 }
    361 
    362 inline fn read_byte_raw() u8 {
    363     return @truncate(mem.get_u32_barrier(@ptrFromInt(AUX_MU_IO_REG)));
    364 }
    365 
    366 pub fn can_write() bool {
    367     // Check if FIFO can accept data
    368     // Page 15: AUX_MU_LSR_REG Register
    369     // Bit 5 is set when FIFO can accept at least 1 byte
    370     return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20) != 0;
    371 }
    372 
    373 // pub fn write_queued() usize {
    374 //     return tx_list.items.len;
    375 // }
    376 
    377 pub fn write_byte_async(byte: u8) !void {
    378     const cs = mem.enter_critical_section();
    379     defer cs.exit();
    380 
    381     try tx_list.push(byte);
    382 
    383     // Let TX start draining
    384     enable_tx_interrupt() catch {};
    385 }
    386 
    387 pub fn write_byte_sync_timeout(byte: u8, timeout: ?u64) !void {
    388     var wait_until: u64 = std.math.maxInt(u64);
    389 
    390     if (timeout) |t| {
    391         wait_until = clock.current_count() + t;
    392     }
    393 
    394     while (true) {
    395         if (clock.current_count() > wait_until) {
    396             return Error.Timeout;
    397         }
    398 
    399         if (!can_write()) continue;
    400 
    401         write_byte_raw(byte);
    402 
    403         return;
    404     }
    405 }
    406 
    407 pub fn write_byte_sync(byte: u8) void {
    408     write_byte_sync_timeout(byte, null) catch {};
    409 }
    410 
    411 inline fn write_byte_raw(byte: u8) void {
    412     mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF);
    413 }
    414 
    415 pub fn write_byte(byte: u8) void {
    416     if (use_tx_interrupts) {
    417         write_byte_async(byte) catch {
    418             write_byte_sync(byte);
    419         };
    420     } else {
    421         write_byte_sync(byte);
    422     }
    423 }
    424 
    425 pub fn write_slice(bytes: []const u8) void {
    426     for (bytes) |b| {
    427         write_byte(b);
    428     }
    429 }
    430 
    431 pub fn write_slice_sync(bytes: []const u8) void {
    432     for (bytes) |b| {
    433         write_byte_sync(b);
    434     }
    435 }
    436 
    437 pub fn flush() void {
    438     // Loop until there's nothing left in TX queue
    439     while (true) {
    440         const s = status();
    441 
    442         if (tx_interrupts_enabled == false and write_queue_length() > 0) {
    443             drain_write_queue();
    444         }
    445 
    446         if (s.tx_is_empty and s.tx_is_idle and write_queue_length() == 0) break;
    447     }
    448 }
    449 
    450 fn writer_drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
    451     if (!initialized) return std.Io.Writer.Error.WriteFailed;
    452 
    453     // We don't care about getting the "parent"
    454     _ = io_w;
    455     _ = splat;
    456 
    457     write_slice(data[0]);
    458 
    459     return data[0].len;
    460 }
    461 
    462 fn writer_flush(io_w: *std.Io.Writer) !void {
    463     if (!initialized) return std.Io.Writer.Error.WriteFailed;
    464 
    465     // We don't care about getting the "parent"
    466     _ = io_w;
    467 
    468     flush();
    469 }
    470 
    471 pub fn print(comptime fmt: []const u8, args: anytype) void {
    472     writer.print(fmt, args) catch {};
    473 }
    474 
    475 const writer_vtable: std.Io.Writer.VTable = .{ .drain = writer_drain, .flush = writer_flush };
    476 pub var writer = std.Io.Writer{ .buffer = undefined, .vtable = &writer_vtable };
    477 
    478 fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usize {
    479     // We don't care about getting the "parent"
    480     _ = io_r;
    481 
    482     if (limit.toInt()) |max| {
    483         if (max > rx_list.items.len and rx_interrupts_enabled) {
    484             // Use direct writes when there's a large buffer
    485             try switch_rx_writer(io_w);
    486 
    487             while (get_rx_written() < max) {}
    488 
    489             try switch_rx_writer(null);
    490         } else {
    491             // Small buffers should just read blocking
    492             for (0..max) |_| {
    493                 try io_w.writeByte(read_byte_sync());
    494             }
    495         }
    496 
    497         return max;
    498     } else {
    499         return std.Io.Reader.StreamError.ReadFailed;
    500     }
    501 }
    502 
    503 const reader_vtable: std.Io.Reader.VTable = .{ .stream = stream };
    504 pub var reader = std.Io.Reader{ .buffer = undefined, .seek = 0, .end = 0, .vtable = &reader_vtable };