119 lines
3.7 KiB
C
119 lines
3.7 KiB
C
// HAL serial output endpoint. This file owns polling COM1 writes and subscribes serial to HAL text streams.
|
|
|
|
#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>
|
|
|
|
#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_SPIN_LIMIT 1000000u
|
|
|
|
static volatile long gSerialWriteLock;
|
|
static volatile long 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 without allowing broken or removed hardware to stall the machine forever.
|
|
static bool HalSerialWaitForTransmit(void) {
|
|
for (uint32_t spin = 0; spin < HAL_SERIAL_TX_SPIN_LIMIT; 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 (_InterlockedCompareExchange(&gSerialWriteLock, 1, 0) != 0)
|
|
_mm_pause();
|
|
}
|
|
|
|
// Releases the normal serial writer lock.
|
|
static void HalSerialReleaseWriteLock(void) {
|
|
_InterlockedExchange(&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 ||
|
|
_InterlockedCompareExchange(&gSerialPresent, 0, 0) == 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)
|
|
_InterlockedExchange(&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;
|
|
}
|