root/src/system/boot/platform/bios_ia32/serial.cpp
/*
 * Copyright 2004-2008, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "serial.h"

#include <boot/platform.h>
#include <arch/cpu.h>
#include <boot/stage2.h>

#include <string.h>


//#define ENABLE_SERIAL
        // define this to always enable serial output


enum serial_register_offsets {
        SERIAL_TRANSMIT_BUFFER          = 0,
        SERIAL_RECEIVE_BUFFER           = 0,
        SERIAL_DIVISOR_LATCH_LOW        = 0,
        SERIAL_DIVISOR_LATCH_HIGH       = 1,
        SERIAL_FIFO_CONTROL                     = 2,
        SERIAL_LINE_CONTROL                     = 3,
        SERIAL_MODEM_CONTROL            = 4,
        SERIAL_LINE_STATUS                      = 5,
        SERIAL_MODEM_STATUS                     = 6,
};

static const uint32 kSerialBaudRate = 115200;

static int32 sSerialEnabled = 0;
static uint16 sSerialBasePort = 0x3f8;


static void
serial_putc(char c)
{
        // wait until the transmitter empty bit is set
        int32 timeout = 256 * 1024;
        while ((in8(sSerialBasePort + SERIAL_LINE_STATUS) & 0x20) == 0) {
                if (--timeout == 0) {
                        sSerialEnabled = 0;
                        return;
                }
                asm volatile ("pause");
        }

        out8(c, sSerialBasePort + SERIAL_TRANSMIT_BUFFER);
}


extern "C" void
serial_puts(const char* string, size_t size)
{
        if (sSerialEnabled <= 0)
                return;

        while (size-- != 0) {
                char c = string[0];

                if (c == '\n') {
                        serial_putc('\r');
                        serial_putc('\n');
                } else if (c != '\r')
                        serial_putc(c);

                string++;
        }
}


extern "C" void
serial_disable(void)
{
#ifdef ENABLE_SERIAL
        sSerialEnabled = 0;
#else
        sSerialEnabled--;
#endif
}


extern "C" void
serial_enable(void)
{
        sSerialEnabled++;
}


extern "C" void
serial_init(void)
{
        // copy the base ports of the optional 4 serial ports to the kernel args
        // 0x0000:0x0400 is the location of that information in the BIOS data
        // segment
        uint16* ports = (uint16*)0x400;
        memcpy(gKernelArgs.platform_args.serial_base_ports, ports,
                sizeof(uint16) * MAX_SERIAL_PORTS);

        // only use the port if we could find one, else use the standard port
        if (gKernelArgs.platform_args.serial_base_ports[0] != 0)
                sSerialBasePort = gKernelArgs.platform_args.serial_base_ports[0];

        uint16 divisor = uint16(115200 / kSerialBaudRate);

        out8(0x80, sSerialBasePort + SERIAL_LINE_CONTROL);
                // set divisor latch access bit
        out8(divisor & 0xf, sSerialBasePort + SERIAL_DIVISOR_LATCH_LOW);
        out8(divisor >> 8, sSerialBasePort + SERIAL_DIVISOR_LATCH_HIGH);
        out8(3, sSerialBasePort + SERIAL_LINE_CONTROL);
                // 8N1

#ifdef ENABLE_SERIAL
        serial_enable();
#endif
}