| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <console.h> |
| #include <sys/io.h> |
| #include <sys/cpu.h> |
| #include <syslinux/config.h> |
| |
| #include "serial.h" |
| |
| enum { |
| THR = 0, |
| RBR = 0, |
| DLL = 0, |
| DLM = 1, |
| IER = 1, |
| IIR = 2, |
| FCR = 2, |
| LCR = 3, |
| MCR = 4, |
| LSR = 5, |
| MSR = 6, |
| SCR = 7, |
| }; |
| |
| |
| int serial_init(struct serial_if *sif, const char *argv[]) |
| { |
| const struct syslinux_serial_console_info *sci |
| = syslinux_serial_console_info(); |
| uint16_t port; |
| unsigned int divisor; |
| uint8_t dll, dlm, lcr; |
| |
| if (!argv[0]) { |
| if (sci->iobase) { |
| port = sci->iobase; |
| } else { |
| printf("No port number specified and not using serial console!\n"); |
| return -1; |
| } |
| } else { |
| port = strtoul(argv[0], NULL, 0); |
| if (port <= 3) { |
| uint16_t addr = ((uint16_t *)0x400)[port]; |
| if (!addr) { |
| printf("No serial port address found!\n"); |
| return -1; |
| } |
| printf("Serial port %u is at 0x%04x\n", port, addr); |
| port = addr; |
| } |
| } |
| |
| sif->port = port; |
| sif->console = false; |
| |
| divisor = 1; /* Default speed = 115200 bps */ |
| |
| /* Check to see if this is the same as the serial console */ |
| if (port == sci->iobase) { |
| /* Overlaying the console... */ |
| sif->console = true; |
| |
| /* Default to already configured speed */ |
| divisor = sci->divisor; |
| |
| /* Shut down I/O to the console for the time being */ |
| openconsole(&dev_null_r, &dev_null_w); |
| } |
| |
| if (argv[0] && argv[1]) |
| divisor = 115200/strtoul(argv[1], NULL, 0); |
| |
| cli(); /* Just in case... */ |
| |
| /* Save old register settings */ |
| sif->old.lcr = inb(port + LCR); |
| sif->old.mcr = inb(port + MCR); |
| sif->old.iir = inb(port + IIR); |
| |
| /* Set speed */ |
| outb(0x83, port + LCR); /* Enable divisor access */ |
| sif->old.dll = inb(port + DLL); |
| sif->old.dlm = inb(port + DLM); |
| outb(divisor, port + DLL); |
| outb(divisor >> 8, port + DLM); |
| (void)inb(port + IER); /* Synchronize */ |
| |
| dll = inb(port + DLL); |
| dlm = inb(port + DLM); |
| lcr = inb(port + LCR); |
| outb(0x03, port + LCR); /* Enable data access, n81 */ |
| (void)inb(port + IER); /* Synchronize */ |
| sif->old.ier = inb(port + IER); |
| |
| /* Disable interrupts */ |
| outb(0, port + IER); |
| |
| sti(); |
| |
| if (dll != (uint8_t)divisor || |
| dlm != (uint8_t)(divisor >> 8) || |
| lcr != 0x83) { |
| serial_cleanup(sif); |
| printf("No serial port detected!\n"); |
| return -1; /* This doesn't look like a serial port */ |
| } |
| |
| /* Enable 16550A FIFOs if available */ |
| outb(0x01, port + FCR); /* Enable FIFO */ |
| (void)inb(port + IER); /* Synchronize */ |
| if (inb(port + IIR) < 0xc0) |
| outb(0x00, port + FCR); /* Disable FIFOs if non-functional */ |
| (void)inb(port + IER); /* Synchronize */ |
| |
| return 0; |
| } |
| |
| void serial_write(struct serial_if *sif, const void *data, size_t n) |
| { |
| uint16_t port = sif->port; |
| const char *p = data; |
| uint8_t lsr; |
| |
| while (n--) { |
| do { |
| lsr = inb(port + LSR); |
| } while (!(lsr & 0x20)); |
| |
| outb(*p++, port + THR); |
| } |
| } |
| |
| void serial_read(struct serial_if *sif, void *data, size_t n) |
| { |
| uint16_t port = sif->port; |
| char *p = data; |
| uint8_t lsr; |
| |
| while (n--) { |
| do { |
| lsr = inb(port + LSR); |
| } while (!(lsr & 0x01)); |
| |
| *p++ = inb(port + RBR); |
| } |
| } |
| |
| void serial_cleanup(struct serial_if *sif) |
| { |
| uint16_t port = sif->port; |
| |
| outb(0x83, port + LCR); |
| (void)inb(port + IER); |
| outb(sif->old.dll, port + DLL); |
| outb(sif->old.dlm, port + DLM); |
| (void)inb(port + IER); |
| outb(sif->old.lcr & 0x7f, port + LCR); |
| (void)inb(port + IER); |
| outb(sif->old.mcr, port + MCR); |
| outb(sif->old.ier, port + IER); |
| if (sif->old.iir < 0xc0) |
| outb(0x00, port + FCR); /* Disable FIFOs */ |
| |
| /* Re-enable console messages, if we shut them down */ |
| if (sif->console) |
| openconsole(&dev_null_r, &dev_stdcon_w); |
| } |