Files

139 lines
4.4 KiB
C

// HAL serial output endpoint. This file owns polling COM1 writes and subscribes serial to HAL text streams.
#include <ntlite/atomic.h>
#include <stdint.h>
#include <stdbool.h>
#include <intrin.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_output.h>
#include <kernel/hal/hal_processor.h>
#include <kernel/hal/hal_timer.h>
#define HAL_COM1_BASE 0x03F8u
#define HAL_COM1_DATA_PORT (HAL_COM1_BASE + 0u)
#define HAL_COM1_STATUS_PORT (HAL_COM1_BASE + 5u)
#define HAL_COM1_SCRATCH_PORT (HAL_COM1_BASE + 7u)
#define HAL_COM1_TX_READY 0x20u
#define HAL_SERIAL_TX_TIMEOUT_US 2000u
#define HAL_SERIAL_TIMER_SPIN_CAP 65536u
#define HAL_SERIAL_EARLY_SPINS 4096u
static volatile int32_t gSerialWriteLock;
static volatile int32_t gSerialPresent;
// Probes the 16550 scratch register without relying on line-status values from an absent port.
static bool HalSerialProbeCom1(void) {
uint8_t original = __inbyte(HAL_COM1_SCRATCH_PORT);
__outbyte(HAL_COM1_SCRATCH_PORT, 0x5Au);
uint8_t first = __inbyte(HAL_COM1_SCRATCH_PORT);
__outbyte(HAL_COM1_SCRATCH_PORT, 0xA5u);
uint8_t second = __inbyte(HAL_COM1_SCRATCH_PORT);
__outbyte(HAL_COM1_SCRATCH_PORT, original);
return first == 0x5Au && second == 0xA5u;
}
// Waits for COM1 transmit readiness with a real microsecond deadline once the HAL timer is live.
static bool HalSerialWaitForTransmit(void) {
uint64_t start = HalTimerQueryTicks();
if (start != 0) {
uint64_t deadline = start > UINT64_MAX - HAL_SERIAL_TX_TIMEOUT_US
? UINT64_MAX
: start + HAL_SERIAL_TX_TIMEOUT_US;
for (uint32_t spin = 0; spin < HAL_SERIAL_TIMER_SPIN_CAP; ++spin) {
if ((__inbyte(HAL_COM1_STATUS_PORT) & HAL_COM1_TX_READY) != 0)
return true;
if (HalTimerQueryTicks() >= deadline)
return false;
_mm_pause();
}
return false;
}
// Serial is registered before timer calibration, so keep the early path small and finite.
for (uint32_t spin = 0; spin < HAL_SERIAL_EARLY_SPINS; ++spin) {
if ((__inbyte(HAL_COM1_STATUS_PORT) & HAL_COM1_TX_READY) != 0)
return true;
_mm_pause();
}
return false;
}
// Writes one byte to COM1 when the device remains responsive.
static bool HalSerialWriteByte(uint8_t value) {
if (!HalSerialWaitForTransmit())
return false;
__outbyte(HAL_COM1_DATA_PORT, value);
return true;
}
// Acquires the normal serial writer lock.
static void HalSerialAcquireWriteLock(void) {
while (NtlAtomicCompareExchange32(&gSerialWriteLock, 1, 0) != 0)
_mm_pause();
}
// Releases the normal serial writer lock.
static void HalSerialReleaseWriteLock(void) {
NtlAtomicExchange32(&gSerialWriteLock, 0);
}
// Writes one output record through polling COM1.
static void HalSerialWriteRecord(void* context, const HAL_OUTPUT_RECORD* record) {
(void)context;
if (!record || !record->Buffer ||
NtlAtomicLoad32(&gSerialPresent) == 0)
return;
bool emergency = (record->Flags & HAL_OUTPUT_WRITE_EMERGENCY) != 0;
uintptr_t interruptFlags = 0;
if (!emergency) {
interruptFlags = HalDisableInterrupts();
HalSerialAcquireWriteLock();
}
bool responsive = true;
for (size_t i = 0; i < record->Length && responsive; i++) {
char c = record->Buffer[i];
if (c == '\n')
responsive = HalSerialWriteByte('\r');
if (responsive)
responsive = HalSerialWriteByte((uint8_t)c);
}
if (!responsive)
NtlAtomicExchange32(&gSerialPresent, 0);
if (!emergency) {
HalSerialReleaseWriteLock();
HalRestoreInterrupts(interruptFlags);
}
}
// Registers the polling serial endpoint only when a 16550-compatible COM1 device is present.
void HalSerialInitialize(HAL_OUTPUT_ROUTER* output) {
if (!output || !output->RegisterEndpoint)
return;
gSerialWriteLock = 0;
gSerialPresent = 0;
if (!HalSerialProbeCom1())
return;
gSerialPresent = 1;
HAL_OUTPUT_ENDPOINT endpoint;
endpoint.Name = "serial0";
endpoint.StreamMask = HAL_OUTPUT_STREAM_ALL;
endpoint.Capabilities = HAL_OUTPUT_ENDPOINT_PANIC_SAFE;
endpoint.Write = HalSerialWriteRecord;
endpoint.Context = 0;
uint32_t endpointId = HAL_OUTPUT_ENDPOINT_INVALID;
if (KSUCCESS(output->RegisterEndpoint(&endpoint, &endpointId)))
output->SerialEndpointId = endpointId;
else
gSerialPresent = 0;
}