furix
cute os
arch/x86_64/serial.c
3251 bytes raw
1#include <serial.h>
2
3#include <io.h>
4#include <types.h>
5
6#define COM_PORT 0x3F8
7
8#define REG_DATA 0
9#define REG_IER 1
10#define REG_FCR 2
11#define REG_LCR 3
12#define REG_MCR 4
13#define REG_LSR 5
14
15#define LSR_THR_EMPTY 0x20
16
17/*
18 * serial output lock.
19 *
20 * the 16550 has a single transmit holding register and no way to read back what
21 * is still queued, so two CPUs writing at once interleave mid-line and produce
22 * garbled output. that is not cosmetic here: the AP bring-up log is the only
23 * evidence available when a core fails to start, so a scrambled "ap: 3 online"
24 * is indistinguishable from a core that never came up.
25 *
26 * a test-and-set flag is enough rather than a queue: output is strictly
27 * synchronous (we spin until the holding register drains), so the critical
28 * section is short and bounded by a few dozen I/O cycles.
29 *
30 * the wait is a plain spin, never a halt. an AP that arrived here with
31 * interrupts off would deadlock against a `hlt`, and the BSP must stay able to
32 * report the stall.
33 */
34static volatile uint8_t serial_lock;
35
36static void serial_lock_acquire(void) {
37 while (atomic_test_and_set_u8(&serial_lock) != 0) {
38 /* another CPU owns the port; keep retrying */
39 }
40}
41
42static void serial_lock_release(void) {
43 /*
44 * plain store rather than a locked release. the xchg in the acquire path is
45 * a full barrier, so everything written before this store is already visible
46 * to the next acquirer's locked read of the flag.
47 */
48 serial_lock = 0;
49}
50
51static uint8_t serial_read(uint16_t reg) {
52 return inb((uint16_t)(COM_PORT + reg));
53}
54
55static void serial_write(uint16_t reg, uint8_t value) {
56 outb((uint16_t)(COM_PORT + reg), value);
57}
58
59static void serial_write_byte(uint8_t byte) {
60 while ((serial_read(REG_LSR) & LSR_THR_EMPTY) == 0) {
61 /* wait for the transmit holding register to drain */
62 }
63 serial_write(REG_DATA, byte);
64}
65
66void serial_init(void) {
67 serial_write(REG_IER, 0x00); /* no interrupts */
68 serial_write(REG_LCR, 0x80); /* enable DLAB */
69 serial_write(REG_DATA, 0x03); /* divisor low (38400 baud) */
70 serial_write(REG_IER, 0x00); /* divisor high */
71 serial_write(REG_LCR, 0x03); /* 8 bits, no parity, one stop bit */
72 serial_write(REG_FCR, 0xC7); /* enable + clear FIFOs, 14-byte threshold */
73 serial_write(REG_MCR, 0x0B); /* IRQs enabled, RTS/DSR set */
74}
75
76void serial_putc(char c) {
77 serial_lock_acquire();
78
79 if (c == '\n') {
80 serial_write_byte((uint8_t)'\r');
81 }
82 serial_write_byte((uint8_t)c);
83
84 serial_lock_release();
85}
86
87/*
88 * the lock is taken once per string rather than per character: a 40-character
89 * line then costs one atomic instead of forty, and the output stays atomic
90 * across the whole line, which is what the log reader needs.
91 */
92void serial_puts(const char *s) {
93 serial_lock_acquire();
94
95 while (*s != '\0') {
96 if (*s == '\n') {
97 serial_write_byte((uint8_t)'\r');
98 }
99 serial_write_byte((uint8_t)*s);
100 s++;
101 }
102
103 serial_lock_release();
104}
105
106void serial_put_hex(uint64_t value) {
107 int shift;
108
109 serial_puts("0x");
110 for (shift = 60; shift >= 0; shift -= 4) {
111 uint8_t nibble = (uint8_t)((value >> (unsigned)shift) & 0xF);
112 char c = (nibble < 10) ? (char)('0' + nibble) : (char)('a' + (nibble - 10));
113 serial_putc(c);
114 }
115}