/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   Debug serial driver
 * @license GPL-3.0-only
 */
 
#include <kernel.h>
#include <io/serial/serial.h>
#include <sched/spinlock.h>

extern struct init_status init;

extern int panic_count;
struct spinlock serial_lock = {0};

/*
 * outb - Writes a byte to a CPU port
 * @port: CPU port to write to
 * @data: Byte to write
 *
 * Writes a single byte to the serial interface.
 */
void outb(int port, unsigned char data)
{
    __asm__ __volatile__("outb %%al, %%dx" :: "a" (data),"d" (port));
}

/*
 * inb - Gets a byte in through a CPU port
 * @port: The CPU port to get a byte from
 *
 * Return:
 * <data> - byte got from port
 */
unsigned char inb(int port)
{
    unsigned char data = 0;
    __asm__ __volatile__("inb %%dx, %%al" : "=a" (data) : "d" (port));
    return data;
}

/*
 * serial_init - Initializes serial interface
 * 
 * Return:
 * %-EIO - Input/output error
 * %0    - Success
 */
int serial_init()
{
    outb(PORT + 1, 0x00);      // Disable all interrupts
    outb(PORT + 3, 0x80);    // Enable DLAB (set baud rate divisor)
    outb(PORT + 0, 0x03);    // Set divisor to 3 (lo byte) 38400 baud
    outb(PORT + 1, 0x00);    //                  (hi byte)
    outb(PORT + 3, 0x03);    // 8 bits, no parity, one stop bit
    outb(PORT + 2, 0xC7);    // Enable FIFO, clear them, with 14-byte threshold
    outb(PORT + 4, 0x0B);    // IRQs enabled, RTS/DSR set
    outb(PORT + 4, 0x1E);    // Set in loopback mode, test the serial chip
    outb(PORT + 0, 0xAE);    // Test serial chip (send byte 0xAE and check if serial returns same byte)

    if (inb(PORT) != 0xAE) {
        return -EIO;
    }

    // Set normal operation mode
    outb(PORT + 4, 0x0F);

    init.serial = true;
    DEBUG("*** Welcome to PepperOS! (built @ %s %s) ***", __DATE__, __TIME__);
    return 0;
}

/*
 * is_transmit_empty - Check if the serial transmit register is empty
 *
 * Return: Non-zero if the transmit register is empty and a new
 * byte can be written to the serial port, 0 otherwise.
 */
static int is_transmit_empty()
{
    return inb(PORT + 5) & 0x20;
}

/*
 * skputc - Serial kernel putchar
 * @c: character to write
 *
 * Writes a single character to the serial interface.
 */
void skputc(char c)
{
    if (panic_count == 0) {
        spinlock_acquire(&serial_lock);
        while (!is_transmit_empty()); // wait for free spot
        outb(PORT, c);
        spinlock_release(&serial_lock);
    } else {
        while (!is_transmit_empty());
        outb(PORT, c);
    }
}

/*
 * skputs - Serial kernel puts
 * @str: Message to write
 *
 * Writes a non-formatted string to serial output.
 */
void skputs(const char* str)
{
    unsigned int i=0;
    while (str[i]) {
        skputc(str[i]);
        i++;
    }
}