Intel Galileo bare metal UART

2019-03-27 14:54发布

问题:

I want to program a little "hello world" bare metal application on the Intel Galileo board. Using UEFI to print out text (to UART-1) works well, of course, but I want to access the UART "manually", without any help from UEFI.

In QEMU my code works well:

.h file

#define COM1_PORT (0x03F8)
#define UART_PORT (COM1_PORT)

enum uart_port_offs_t
{   //          DLAB RW
    THR = 0, //   0   W  Transmitter Holding Buffer
    RBR = 0, //   0  R   Receiver Buffer
    DLL = 0, //   1  RW  Divisor Latch Low Byte
    IER = 1, //   0  RW  Interrupt Enable Register
    DLH = 1, //   1  RW  Divisor Latch High Byte
    IIR = 2, //   -  R   Interrupt Identification Register
    FCR = 2, //   -  RW  FIFO Control Register
    LCR = 3, //   -  RW  Line Control Register
    MCR = 4, //   -  RW  Modem Control Register
    LSR = 5, //   -  R   Line Status Register
    MSR = 6, //   -  R   Modem Status Register
    SR  = 7, //   -  RW  Scratch Register
};

.c file

void uart_init(void)
{
    outb(UART_PORT + IER, 0x00); // Disable all interrupts

    outb(UART_PORT + LCR, LCR_DLAB);
    outb(UART_PORT + DLL, BAUD_LL); // Set divisor (lo byte)
    outb(UART_PORT + DLH, BAUD_HL); //             (hi byte)
    outb(UART_PORT + LCR, LCR_WORD_BITS_8 | LCR_PAR_NONE | LCR_STOP_BITS_1);
    outb(UART_PORT + FCR, FCR_ENABLE | FCR_CLR_RECV | FCR_CLR_SEND | FCR_TRIGGER_16);
    outb(UART_PORT + MCR, MCR_DSR | MCR_RTS | MCR_AUX2);
}

ssize_t uart_write(const char *buf, size_t len)
{
    size_t written = 0;
    while (written < len) {
        while (!is_output_empty()) {
            asm volatile ("pause");
        }
        outb(UART_PORT + THR, buf[written]);
        ++written;
    }
    return written;
}

main

SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Exiting EFI boot services ...\r\n");
SystemTable->BootServices->ExitBootServices(ImageHandle, map_key);

uart_init();

while (1) {
    const char s[] = "UART\r\n";
    uart_write(s, sizeof (s) - 1);
}

The specs did not help me very much. I guess that the UARTs on the Intel Galileo board don't use/emulate the normal/legacy COM ports 3F8h, 2F8h, 3E8h, or 2E8h.

Can anyone tell me what I am doing wrong, or even post a minimal bare metal hello world example?

回答1:

I assume you are aiming at the serial port that is the "audio-like" connector on the Intel Galileo board.

Here are a few resources that should help:

  • Galileo schematic
  • Intel Quark SoC X1000 datasheet
  • Intel Galileo IO Mapping
  • Sergey's blog entry about Configuring the Serial Port for Galileo
  • Intel Quark Board Support Package downloads, including
    • Board Support Package Sources (currently ver.1.0.0)
    • Intel Quark SoC X1000 UEFI Firmware Writer’s Guide

Things to note about this UART:

  • This serial port comes out of the QUARK chip as UART1 (see the schematics).
  • There are a few GPIOs that you may need to manipulate (see Sergey's blog for doing this in Linux):
    • gpio4: This GPIO controls level shifter for UART signals and some other signals connected to Quark SoC, such as SPI and fast I/O. Writing '1' to this GPIO enables level shifter.
    • gpio40: This GPIO controls multiplexer for pin 0. Writing '0' to this GPIO connects pin 0 to UART's RxD (receive data) signal.
    • gpio41: This GPIO controls multiplexer for pin 1. Writing '0' to this GPIO connects pin 1 to UART's TxD (transmit data) signal.
  • Check the chapter 18 (High Speed UART) in the Quark datasheet for what to put in the UART registers:
    • Registers DLH, DLL specify the baud rate
    • Decide whether you want the DMA mode (chapter 18.3.1), the FIFO-interrupt mode (chapter 18.3.2), or the FIFO-polling mode (chapter 18.3.3). The latter is simpler but less effective, IMHO. The former requires you to configure DMA properly as well.

Since there is quite a bit to read for chapter 18 (~67 pages of useful information), I'm not going to retype all that here, please read the datasheet and configure the registers accordingly.

General notes:

  • For bare-metal approach first make sure that your boot procedure is correct, configuring all the clocking options, GPIO default modes and values, timers if any, etc. For Boot checklist read chapter 4.12 in X1000 UEFI Firmware Writer’s Guide (~18 things to do to boot this chip). After that I'd verify it with a simple "LED blinking" application on a GPIO.

  • Tinkering with 3F8h and similar ports is not going to help on "bare metal" of this SoC. You need to deal with the registers directly, or find and use appropriate library or framework (maybe UEFI BIOS?).

  • Programming sources for the particular platform should be a good read for examples. For example, in Board Support Package Sources for Intel Quark the archive Quark_EDKII_v1.0.0.tar.gz is the UEFI source code for Quark/Galileo. It there, the Serial.c and Serial.h files might just be what you are looking for:

    Quark_EDKII_v1.0.0/QuarkSocPkg/QuarkSouthCluster/Uart/Dxe/Serial.*