#include #include #include #define COM_PORT 0x3F8 #define REG_DATA 0 #define REG_IER 1 #define REG_FCR 2 #define REG_LCR 3 #define REG_MCR 4 #define REG_LSR 5 #define LSR_THR_EMPTY 0x20 /* * serial output lock. * * the 16550 has a single transmit holding register and no way to read back what * is still queued, so two CPUs writing at once interleave mid-line and produce * garbled output. that is not cosmetic here: the AP bring-up log is the only * evidence available when a core fails to start, so a scrambled "ap: 3 online" * is indistinguishable from a core that never came up. * * a test-and-set flag is enough rather than a queue: output is strictly * synchronous (we spin until the holding register drains), so the critical * section is short and bounded by a few dozen I/O cycles. * * the wait is a plain spin, never a halt. an AP that arrived here with * interrupts off would deadlock against a `hlt`, and the BSP must stay able to * report the stall. */ static volatile uint8_t serial_lock; static void serial_lock_acquire(void) { while (atomic_test_and_set_u8(&serial_lock) != 0) { /* another CPU owns the port; keep retrying */ } } static void serial_lock_release(void) { /* * plain store rather than a locked release. the xchg in the acquire path is * a full barrier, so everything written before this store is already visible * to the next acquirer's locked read of the flag. */ serial_lock = 0; } static uint8_t serial_read(uint16_t reg) { return inb((uint16_t)(COM_PORT + reg)); } static void serial_write(uint16_t reg, uint8_t value) { outb((uint16_t)(COM_PORT + reg), value); } static void serial_write_byte(uint8_t byte) { while ((serial_read(REG_LSR) & LSR_THR_EMPTY) == 0) { /* wait for the transmit holding register to drain */ } serial_write(REG_DATA, byte); } void serial_init(void) { serial_write(REG_IER, 0x00); /* no interrupts */ serial_write(REG_LCR, 0x80); /* enable DLAB */ serial_write(REG_DATA, 0x03); /* divisor low (38400 baud) */ serial_write(REG_IER, 0x00); /* divisor high */ serial_write(REG_LCR, 0x03); /* 8 bits, no parity, one stop bit */ serial_write(REG_FCR, 0xC7); /* enable + clear FIFOs, 14-byte threshold */ serial_write(REG_MCR, 0x0B); /* IRQs enabled, RTS/DSR set */ } void serial_putc(char c) { serial_lock_acquire(); if (c == '\n') { serial_write_byte((uint8_t)'\r'); } serial_write_byte((uint8_t)c); serial_lock_release(); } /* * the lock is taken once per string rather than per character: a 40-character * line then costs one atomic instead of forty, and the output stays atomic * across the whole line, which is what the log reader needs. */ void serial_puts(const char *s) { serial_lock_acquire(); while (*s != '\0') { if (*s == '\n') { serial_write_byte((uint8_t)'\r'); } serial_write_byte((uint8_t)*s); s++; } serial_lock_release(); } void serial_put_hex(uint64_t value) { int shift; serial_puts("0x"); for (shift = 60; shift >= 0; shift -= 4) { uint8_t nibble = (uint8_t)((value >> (unsigned)shift) & 0xF); char c = (nibble < 10) ? (char)('0' + nibble) : (char)('a' + (nibble - 10)); serial_putc(c); } }