arc-lkm/internal/uart/virtual_uart.c
AuxXxilium d288da5003 init: lkm
Signed-off-by: AuxXxilium <info@auxxxilium.tech>
2024-05-05 01:31:48 +02:00

1032 lines
45 KiB
C
Executable File

/**
* A true National Semiconductors 16550A software emulator
*
* WHAT IS THIS?
* -------------
* In short this provides a feature-complete emulation of the now-famous 16550A chip present in IBM/PC compatibles
* since ~1987. This emulator was prepared to work with the Linux 8250 driver and fool it into believing it talks with a
* real chip. Moreover, the code isn't hacking around any private parts of the kernel but rather fully emulates
* registries and their behaviors according to the chip's data sheet.
* The emulation layer supports standard 8250-compliant feature (in essence UART) set with addition of two 16 bytes
* TX/RX FIFOs with configurable threshold as well as timer or virtual IRQ model. The code should be pretty
* straight-forward to read but it contains MANY quirks. All of them however are heavily documented throughout the file.
*
* DEALING WITH OPEN PORTS
* -----------------------
* While using this module you should know that there's a one important quirk: since we're in the Linux kernel we can
* do anything with the port even if it's open. It's a blessing and a curse. Even if the physical ttyS1 port is open and
* you add a virtual ttyS1 all of the sudden all applications will talk to your virtual port. This is great as you don't
* have to restart them but it's also bad while debugging as you may get input you don't expect. To see what's using the
* port execute "ls -l /proc/[0-9]<asterisk>/fd/<asterisk> 2>&1 |grep /dev/ttyS1 2>&1 | grep ttyS" (replace <asterisk>)
* as lsof is not available in pre-boot.
* A note however: the /dev/ttyS node WILL be recreated, so if you do replce a port which was opened you can expect to
* see "/dev/ttyS1 (deleted)" in the ls output from above. It is not an issue as we're "taking over" the port anyway so
* both the /dev/ttyS1 as well as the old fd are pointing to the same place kernel-wise.
*
* LIMITATIONS
* -----------
* - For obvious reasons (as we are not working with a real hw) the DMA portion of the chip is not emulated
* - On most system the maximum number of UARTs emulated is 4 (driver's limitation, see CONFIG_SERIAL_8250_NR_UARTS)
* - FIFOs, true to the original 16550A, are limited to 16 bytes each. In theory, if needed, they can be enlarge up to
* even 256 bytes each with chip model change (as 8250 driver actually tests how big a FIFO is on setup)
* - FIFO mode is always enabled. There are some not-fully-accurate pieces which don't handle non-FIFO operation. There
* is (at least to our knowledge) no reason to use it adn kernel always asks for FIFO to save CPU anyway.
*
* USAGE
* -----
* See header file docs.
*
* INTERNALS
* ---------
* - To DISABLE vIRQ and fall back to a timer (offered by 8250) define VUART_USE_TIMER_FALLBACK - this will cause the
* Linux driver to poll every so often for new data. This is fine if the port is opened-written to-closed but not
* when apps keep it long open (as the APIC timer will constantly fire for nothing)
* - To see detailed logs of registries being accessed and modified and what not define VUART_DEBUG_LOG - you will get
* all the info you need for debugging. However keep in mind setting this along with VUART_USE_TIMER_FALLBACK will be
* pretty catastrophic as you will be flooded with messages about IIR being read as long as the port stays open in
* the userland. This consciously does not use kernel's dynamic debug facilities are some (e.g. 918+) kernels are
* compiled without it.
* - To change name of the vIRQ thread define VUART_THREAD_FMT which gets a real port IRQ # and ttyS# as its params.
* - UART_BUG_SWAPPED (defined in uart_defs.h) is used to detect swapped ports and make sure numbers used here are real
* ttyS* values and not swapped bs (as 8250 matches ports by iobase and not line#)
*
* References:
* - https://github.com/clearlinux/kvmtool/blob/b5891a4337eb6744c8ac22cc02df3257961ae23e/hw/serial.c (inspiration)
* - https://www.ti.com/lit/ug/sprugp1/sprugp1.pdf (everything you need to know abt UART, referred in code as "Ti doc")
* - http://caro.su/msx/ocm_de1/16550.pdf (useful and short UART know-how with a good registry table in Table 2, p. 9)
* - https://www.linuxjournal.com/article/8144 (handling threading in kernel)
*/
//Here are some flags which can be used to modify the behavior of VirtualUART. They're checked by other header files.
//Keep in mind you may need to set the debug in vuart_virtual_irq separatedly (or in common.h)
//#define VUART_DEBUG_LOG
//#define VUART_USE_TIMER_FALLBACK
#include "virtual_uart.h"
#include "vuart_internal.h"
#include "../../common.h" //can set VUART_DEBUG_LOG and others
#include "../../debug/debug_vuart.h" //it will provide normal or nooped versions of macros; CHECKS VUART_DEBUG_LOG
#include "../../config/uart_defs.h" //COM defs & struct uart_port
#include "../../internal/intercept_driver_register.h" //is_driver_registered, watch_driver_register, unwatch_driver_register
#include "vuart_virtual_irq.h" //vIRQ handling & shimming; CHECKS VUART_USE_TIMER_FALLBACK
#include <linux/serial_8250.h> //serial8250_unregister_port, uart_8250_port
#include <linux/serial_reg.h> //UART_* consts
#include <linux/spinlock.h> //locking devices (vdev->lock)
#include <linux/kfifo.h> //kfifo_*
/************************************************* Static definitions *************************************************/
/*
* According to https://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming bits 6 and 7 must be set to
* consider FIFO as enabled-and-working (bit 7 only designates "FIFO enabled, but not functioning" (?)
*/
#define UART_IIR_FIFOEN 0xc0
#define UART_IIR_FIFEN_B6 0x40
#define UART_IIR_FIFEN_B7 0x80
#define UART_DRIVER_NAME "serial8250" //see drivers/tty/serial/8250/8250_core.c in "serial8250_isa_driver"
/**
* Static definition of all possible UARTs in the system supported by 8250 driver
* These definitions are exactly the same as in arch/x86/include/asm/serial.h
*/
static struct serial8250_16550A_vdev ttySs[] = {
//we're crying too... the issue is normally operate on port lines (=ttyS#) but during port registration ports the driver
// performs matching based on its internal iobase mapping, so we can ask for the port to be line=0 but if the driver
// finds a port with iobase specified under line=1 it will just register is as line=1 instead of line=0. This causes all
// sorts of problems as during reads we expect the vdev line to match what we actually registered. To fix it and make it
// independent of all fucking swapping and reswapping we will have to emit events from uart_swapper and other nonsense
// ...this is ridiculous. So we take a sane assumptions:
// - if the kernel is broken we accommodate for that assuming no un-swapping will be done afterwards
// - if the kernel is broken and swap fix is disabled by debug flag we handle the swapping
// - if something is borked we don't offer a detection comparing lines because before we get a response from the driver
// registering the port it will call our read function and break everything
// TODO: this whole code should switch to relying on iobases instead o lines. This way when we do reads or writes we
// don't care if something is swapped - we call for registration on line 0, we lookup what's the expected iobase
// for that ttyS and we register for it. If the driver decides to use a different line# we shouldn't care.
#if defined(UART_BUG_SWAPPED) && defined(DBG_DISABLE_UART_SWAP_FIX)
[0] = { .line = 0, .iobase = STD_COM2_IOBASE, .irq = STD_COM2_IRQ, .baud = STD_COMX_BAUD }, //COM1 aka ttyS1
[1] = { .line = 1, .iobase = STD_COM1_IOBASE, .irq = STD_COM1_IRQ, .baud = STD_COMX_BAUD }, //COM2 aka ttyS0
#else
[0] = { .line = 0, .iobase = STD_COM1_IOBASE, .irq = STD_COM1_IRQ, .baud = STD_COMX_BAUD }, //COM1 aka ttyS0
[1] = { .line = 1, .iobase = STD_COM2_IOBASE, .irq = STD_COM2_IRQ, .baud = STD_COMX_BAUD }, //COM2 aka ttyS1
#endif
[2] = { .line = 2, .iobase = STD_COM3_IOBASE, .irq = STD_COM3_IRQ, .baud = STD_COMX_BAUD }, //COM3 aka ttyS2
[3] = { .line = 3, .iobase = STD_COM4_IOBASE, .irq = STD_COM4_IRQ, .baud = STD_COMX_BAUD }, //COM4 aka ttyS3
};
//Internal type for callbacks; see vuart_set_tx_callback() for details
struct flush_callback {
vuart_callback_t *fn;
void *buffer;
int threshold;
};
//Storage for all TX callbacks, see vuart_set_tx_callback()
static struct flush_callback *flush_cbs[SERIAL8250_LAST_ISA_LINE] = { NULL };
static volatile bool kernel_driver_ready = false; //Whether the 8250 UART driver is ready
/**************************************** Internal helper function-like macros ****************************************/
//Get vDEV from line/ttyS number (created for consistency)
#define get_line_vdev(line) (&ttySs[(line)])
//8250 driver doesn't give access to the real uart_port upon adding but does it on first read/write
#define capture_uart_port(vdev, port) if (unlikely(!(vdev)->up)) (vdev)->up = port;
//Some functions should warn use out of courtesy that we're running in a stupid environment
#if defined(UART_BUG_SWAPPED) && defined(DBG_DISABLE_UART_SWAP_FIX)
#define warn_bug_swapped(line) \
if ((line) < 2) { \
pr_loc_inf( \
"Requested ttyS%d vUART - this kernel has UART SWAP => modifying what physically is ttyS%d (io=0x%x)", \
line, !line, get_line_vdev(line)->iobase); \
}
#else
#define warn_bug_swapped(line) //noop
#endif
#define for_each_vdev() for (int line=0; line < ARRAY_SIZE(ttySs); ++line)
//Before v3.13 the kfifo_put() accepted a pointer, since then it accepts a value
//ffs... https://github.com/torvalds/linux/commit/498d319bb512992ef0784c278fa03679f2f5649d
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)
#define kfifo_put_val(fifo, val) kfifo_put(fifo, &val)
#else
#define kfifo_put_val(fifo, val) kfifo_put(fifo, val)
#endif
/****************************************** Internal chip emulation functions ******************************************/
/**
* Updates state of the IIR register
*
* In the physical world when the UART chip is connected to the CPU there's an interrupt line which goes high when
* the chip detects any of the conditions which are interrupt-worthy. Whether something is interrupt-worthy is
* determined by the driver (i.e. Linux kernel) and set in the IER. When an interrupt is generated the kernel gets only
* the information "something happened on IRQ 4" (which means SOMETHING happened on COM1 *or* COM 3). To determine
* what it is (or maybe there multiple things even!) the kernel reads IIR which gives the REASON why a given interrupt
* happened (and thus also indirectly specifies which channel/chip generated the interrupt).
*
* This code below is written based on the Table 3-6 in Ti doc - it summarizes IIR state and how it should change. It
* should be called AFTER everything else modified registers. In general if you changed some other registries you should
* call this function. You usually want to do it once upon returning control to an outside caller (after making all
* all changes).
*
* Regardless of whether vIRQ is enabled or not this register MUST be updated.
*/
static void update_interrupts_state(struct serial8250_16550A_vdev *vdev)
{
uart_prdbg("Recomputing IIR state");
//Order of these if/elseifs is CRUCIAL - interrupts have priorities and they're masked
u8 new_iir_int_state = 0;
if ((vdev->ier & UART_IER_RLSI) &&
unlikely((vdev->lsr & UART_LSR_OE) || (vdev->lsr & UART_LSR_PE) || (vdev->lsr & UART_LSR_FE) ||
(vdev->lsr & UART_LSR_BI))) {
//Kernel enabled OE/PE/FE/BI interrupts and there's one of them
uart_prdbg("IIR: setting RLS (errors) interrupt");
new_iir_int_state |= UART_IIR_RLSI;
} else if ((vdev->ier & UART_IER_RDI) && (vdev->lsr & UART_LSR_DR)) {
//We don't distinguish between FIFO and non-FIFO mode and just set interrupt if there's some data to be read
//We also don't support the receiver time-out (kernel should pick up the data in time as it's a virtual port)
uart_prdbg("IIR: setting RD (data-ready) interrupt");
new_iir_int_state |= UART_IIR_RDI;
} else if ((vdev->ier & UART_IER_THRI) && ((vdev->lsr & UART_LSR_TEMT) || kfifo_is_empty(vdev->tx_fifo))) {
//When THR is empty or FIFO is empty (for us it's the same thing) kernel wants to know about that
uart_prdbg("IIR: setting THR (transmitter empty) interrupt");
new_iir_int_state |= UART_IIR_THRI;
}
//If any interrupts are triggered (or not) we need to set IPEND accordingly
if (new_iir_int_state) {
new_iir_int_state &= ~UART_IIR_NO_INT; //since there were some interrupts we clear IPEND (=interrupts pending)
vuart_virq_wake_up(vdev);
} else {
new_iir_int_state |= UART_IIR_NO_INT; //since there were no interrupts we set IPEND (=no interrupts pending)
}
//IIR (despite its name) also contains FIFO status along interrupts
vdev->iir = new_iir_int_state;
if (likely(vdev->fcr & UART_FCR_ENABLE_FIFO))
vdev->iir |= UART_IIR_FIFOEN;
dump_iir(vdev);
uart_prdbg("Finished IIR state");
}
/**
* Put registries into the "chip reset" state as described by the datasheet (see Tables 3-* in Ti doc)
* You should NOT modify these values under any circumstances as they're meant to represent the real chip RESET state
*/
static void reset_device(struct serial8250_16550A_vdev *vdev)
{
uart_prdbg("Resetting virtual chip @ ttyS%d", vdev->line);
lock_vuart_oppr(vdev);
//Upon reset both FIFOs must be erased
if (vdev->tx_fifo)
kfifo_reset(vdev->tx_fifo);
if (vdev->rx_fifo)
kfifo_reset(vdev->rx_fifo);
//Registries for when DLAB=0
vdev->rhr = 0x00; //no data in receiving channel
vdev->thr = 0x00; //no data in transmission channel
vdev->ier = 0x00; //no interrupts enabled
vdev->iir = UART_IIR_NO_INT; //no pending interrupts, FIFO not active
vdev->fcr = 0x00; //FIFO disabled (which invalidates other FIFO properties in FCR), DMA disabled
vdev->lcr = 0x00; //non-DLAB mode, errors cleared, 1 STOP bit, 5 bit words (not that it matters for virtual port)
vdev->mcr = UART_MCR_OUT2; //autoflow disabled, loop mode disabled, OUT2 enabled as global interrupt
vdev->lsr = UART_LSR_TEMT | UART_LSR_THRE; //transmitter empty & idle, all errors cleared, break not requested
vdev->msr = 0x00; //all flow control flags not triggered
vdev->scr = 0x00; //empty scratchpad
//Additional registries when DLAB=1
vdev->dll = 0x00; //undefined divisor LSB latch
vdev->dlm = 0x00; //undefined divisor MSB latch
unlock_vuart_oppr(vdev);
uart_prdbg("Virtual chip @ ttyS%d reset done", vdev->line);
}
/**
* Allocate/create FIFOs on the device if they don't exist (and if they do you shouldn't call this function)
*
* @todo it should free on errors
*/
static int alloc_fifos(struct serial8250_16550A_vdev *vdev)
{
if (unlikely(vdev->rx_fifo)) { //this shouldn't happen on non-initialized port
pr_loc_bug("RX FIFO @ %d already alloc'd", vdev->line);
return -EINVAL;
}
if (unlikely(vdev->tx_fifo)) { //this shouldn't happen on non-initialized port
pr_loc_bug("TX FIFO @ %d already alloc'd", vdev->line);
return -EINVAL;
}
kzalloc_or_exit_int(vdev->rx_fifo, sizeof(struct kfifo));
kzalloc_or_exit_int(vdev->tx_fifo, sizeof(struct kfifo));
if (unlikely(kfifo_alloc(vdev->rx_fifo, VUART_FIFO_LEN, GFP_KERNEL) != 0)) {
pr_loc_crt("kfifo_alloc for RX FIFO elements @ %d failed", vdev->line);
return -EFAULT;
}
if (unlikely(kfifo_alloc(vdev->tx_fifo, VUART_FIFO_LEN, GFP_KERNEL) != 0)) {
pr_loc_crt("kfifo_alloc for TX FIFO elements @ %d failed", vdev->line);
return -EFAULT;
}
return 0;
}
/**
* Reverses what alloc_fifos() did
*/
static int free_fifos(struct serial8250_16550A_vdev *vdev)
{
//This should be called when the vIRQ thread is killed so nothing call the IRQ handler without FIFOs
if (unlikely(!vdev->rx_fifo || !vdev->tx_fifo)) { //this shouldn't happen on initialized port
pr_loc_bug("RX and/or TX FIFO @ %d are not alloc'd (nothing to free)", vdev->line);
return -EINVAL;
}
kfifo_free(vdev->rx_fifo);
kfifo_free(vdev->tx_fifo);
return 0;
}
/**
* Deposits the TX queue contents into callbacks set using vuart_set_tx_callback() and clears the FIFO itself
* If no callbacks were defined it will simply clear.
*
* This function does NOT recalculate IIRs (see update_interrupts_state()) and assumes you have vdev lock.
*/
static void flush_tx_fifo(struct serial8250_16550A_vdev *vdev, vuart_flush_reason reason)
{
uart_prdbg("Flushing TX FIFO now! reason=%d", reason);
if (likely(flush_cbs[vdev->line])) {
unsigned int flushed_bytes = 0;
flushed_bytes = kfifo_out(vdev->tx_fifo, flush_cbs[vdev->line]->buffer, VUART_FIFO_LEN);
flush_cbs[vdev->line]->fn(vdev->line, flush_cbs[vdev->line]->buffer, flushed_bytes, reason);
} else {
uart_prdbg("No callback for TX FIFO @ %d - discarding", vdev->line);
kfifo_reset(vdev->tx_fifo);
}
vdev->lsr |= UART_LSR_TEMT | UART_LSR_THRE; //nothing should be in the buffer
}
/**
* Pulls a character/byte from RX FIFO and places it into RHR for the driver to read it
* - It updates all registers according to the specs
* - It assumes you have vdev lock
* - It does NOT recalculate IIRs (see update_interrupts_state())
* - It will produce an error if you try to do the transfer while FIFO is empty but it will not crash. You should check
* UART_LSR_DR before calling this function.
*
* @return character which was read
*/
static unsigned char transfer_char_fifo_rhr(struct serial8250_16550A_vdev *vdev)
{
//Before this function is called UART_LSR_DR should be verified - it wasn't or it was wrong if this exploded
if(unlikely(kfifo_get(vdev->rx_fifo, &vdev->rhr) == 0))
pr_loc_bug("Attempted to %s with empty FIFO - that shouldn't happen if the DR flag was checked", __FUNCTION__);
if (kfifo_is_empty(vdev->rx_fifo))
vdev->lsr &= ~UART_LSR_DR;
//See descriptions of these fields in Table 3-12 from TI doc - these flags are cleared on character read
vdev->lsr &= ~UART_LSR_BI;
vdev->lsr &= ~UART_LSR_FE;
vdev->lsr &= ~UART_LSR_PE;
vdev->lsr &= ~UART_LSR_OE; //by definition, we cannot have overrun if a character was just read
return vdev->rhr;
}
/**
* An alternative to transfer_char_fifo_rhr() when FIFOs aren't used for transfers (e.g. in MSR TEST/LOOP mode)
*
* This function does NOT recalculate IIRs (see update_interrupts_state()) and assumes you have vdev lock.
*/
static void handle_receive_char(struct serial8250_16550A_vdev *vdev, unsigned char value)
{
//@todo this only handles overruns in FIFO mode and does not do that in non-FIFO; it behaves correctly but it
// doesn't report OEs in non-FIFO
vdev->rhr = value; //RHR is always populated with the value no matter the FIFO or non-FIFO mode
//Put value in FIFO, it will indicate with return of 0 if it was full before attempted put (overrun/overflow)
if (kfifo_put_val(vdev->rx_fifo, value) == 0) {
vdev->lsr |= UART_LSR_OE; //set overrun flag as FIFO detected that
//During TEST/LOOP mode many overflows are caused on purpose - we don't want to hear about them really
if (unlikely(!(vdev->mcr & UART_MCR_LOOP)))
pr_loc_wrn("RX FIFO overflow detected @ ttyS%d", vdev->line);
} else {
vdev->lsr &= ~UART_LSR_OE; //no overrun condition - clear OE flag just in case
}
vdev->lsr |= UART_LSR_DR; //receiver has something for the kernel to pickup
}
/**
* Called when kernel sent something to the device and it has to be put into TX FIFO & THR
*
* This function does NOT recalculate IIRs (see update_interrupts_state()) and assumes you have vdev lock.
*
* CAUTION: order of these "ifs" for flushes here is crucial: we make a guarantee to the reason parameter that if both
* VUART_FLUSH_THRESHOLD and VUART_FLUSH_FULL are true (i.e. callback was set with threshold == VUART_FIFO_LEN) we
* will prioritize threshold trigger (as a user-specified event takes precedence over internal event of FIFO full)
* If the threshold specified by the callback setter was met flush the FIFO
*/
static void handle_transmit_char(struct serial8250_16550A_vdev *vdev, unsigned char value)
{
//@todo this only handle non-FIFO properly: doesn't detect OE, and doesn't reset THRE
vdev->thr = value; //THR is always populated with the value no matter the FIFO or non-FIFO mode
vdev->lsr &= ~UART_LSR_THRE;
int fifo_len = kfifo_len(vdev->tx_fifo);
uart_prdbg("%s got new char ascii=%c hex=%02x on ttyS%d (FIFO#=%d)", __FUNCTION__, value, value, vdev->line,
fifo_len);
//FIFO is full - try to flush it; if we got here it means the threshold is for sure >VUART_FIFO_LEN as this is
// checked after we put data into the FIFO (to make sure we trigger THRESHOLD event and not FULL)
//The reason why we check this at the beginning of new char and not after adding to FIFO is that if the transmitting
// party sends exactly VUART_FIFO_LEN bytes and then ends the transmission we don't want to flush with FULL but with
// IDLE to give a better sense of what's going on to the caller. FULL implies "we got too much data, there may be
// more coming" while IDLE implies that the unit of transmission ended.
if (unlikely(fifo_len == VUART_FIFO_LEN))
flush_tx_fifo(vdev, VUART_FLUSH_FULL);
//Put value in FIFO, it will indicate with return of 0 if it was full before attempted put (overrun/overflow)
//This, if we are correct, cannot happen if the flush_tx_fifo() is functioning correctly as we try to flush above
int fifo_add = kfifo_put_val(vdev->tx_fifo, value);
fifo_len += fifo_add; //we can call kfifo_ API for this but why if we have both pieces of info anyway? ;)
if (unlikely(fifo_add == 0)) {
vdev->lsr |= UART_LSR_OE; //set overrun flag as FIFO detected that
pr_loc_wrn("TX FIFO overflow detected");
} else {
vdev->lsr &= ~UART_LSR_OE; //no overrun condition - clear OE flag just in case
}
vdev->lsr &= ~UART_LSR_TEMT; //transmitter buffers are no longer empty
//@todo THRE should be reset immediately in non-FIFO mode (i.e. at the same time as TEMT)
//This is to prevent kernel from freaking out about "blackhole" UART (see https://unix.stackexchange.com/a/387650)
if (fifo_len >= VUART_FIFO_LEN / 2)
vdev->lsr &= ~UART_LSR_THRE;
if (likely(flush_cbs[vdev->line]) && fifo_len >= flush_cbs[vdev->line]->threshold)
flush_tx_fifo(vdev, VUART_FLUSH_THRESHOLD);
}
/**
* The main READ routing passed to the 8250 driver. It should be as fast as possible and MUST be multithread-safe
*
* Device ==responding-to==> kernel; aka "do you have something for me?"
* This function is used to read data and registers.
*
* @param offset This is really the register value. It's named "offset" in accordance with Linux nomenclature which
* makes sense for physical chips (as this is a memory offset from chip's memory base)
*/
static unsigned int serial_remote_read(struct uart_port *port, int offset)
{
uart_prdbg("Serial READ for line=%d/%d", port->line, ttySs[port->line].line);
struct serial8250_16550A_vdev *vdev = get_line_vdev(port->line);
lock_vuart(vdev);
capture_uart_port(vdev, port);
unsigned int out;
switch (offset) {
case UART_RX:
//if DLAB is enabled DLL registry is desired; otherwise we should send THR
//See Table 2 in the chip manual. DLAB controls access to address 000, 001, and 101. When DLAB=1 these
//addrs respond with DLL, DLM, and PSD respectively, when DLAB=0 they respond with RHR/THR, IER/DLM, and LSR
if (vdev->lcr & UART_LCR_DLAB) {
out = vdev->dll;
reg_read("DLL");
} else if (vdev->lsr & UART_LSR_BI) { //chip wants a break?
out = 0;
vdev->lsr &= ~UART_LSR_BI; //clear the break for the next cycle; see BI in Table 3-12 from TI doc
uart_prdbg("LSR indicated break request, cleared");
dump_lsr(vdev);
} else if(vdev->lsr & UART_LSR_DR) { //Did we receive anything?
out = transfer_char_fifo_rhr(vdev);
dump_lsr(vdev);
uart_prdbg("Providing RHR registry (val=%x DLAB=0 LSR_DR=1)", out);
} else {
out = 0;
//Such read isn't invalid. However, it is done e.g. in the init sequence as a workaround for some
// physical chips bugs in the past or to clear the RHR before other operations (even if LSR DR=0)
uart_prdbg("Nothing in RHR (DLAB=0; LSR_DR=0) - noop");
dump_lsr(vdev);
}
break;
case UART_IER:
if (vdev->lcr & UART_LCR_DLAB) {
out = vdev->dlm;
reg_read("DLM");
} else {
out = vdev->ier;
reg_read_dump(vdev, ier, "IER");
}
break;
case UART_IIR:
out = vdev->iir;
reg_read_dump(vdev, iir, "IIR/ISR");
break;
//case UART_FCR not present - write only register
case UART_LCR:
out = vdev->lcr;
reg_read_dump(vdev, lcr, "LCR");
break;
case UART_MCR:
out = vdev->mcr;
reg_read_dump(vdev, mcr, "MCR");
break;
case UART_LSR:
out = vdev->lsr;
reg_read_dump(vdev, lsr, "LSR");
vdev->lsr &= ~UART_LSR_OE; //See "OE" Table 3-12 or Table 3-6 - it needs to be cleared on LSR read
break;
case UART_MSR:
out = vdev->msr;
reg_read_dump(vdev, msr, "MSR");
//See table 3-13 in Ti doc; MSR is masked with values from MCR when MCR indicates test/loop mode
if (unlikely(vdev->mcr & UART_MCR_LOOP)) {
if (vdev->mcr & UART_MCR_RTS) out |= UART_MSR_CTS; else out &= ~UART_MSR_CTS;
if (vdev->mcr & UART_MCR_DTR) out |= UART_MSR_DSR; else out &= ~UART_MSR_DSR;
if (vdev->mcr & UART_MCR_OUT1) out |= UART_MSR_RI; else out &= ~UART_MSR_RI;
if (vdev->mcr & UART_MCR_OUT2) out |= UART_MSR_DCD; else out &= ~UART_MSR_DCD;
uart_prdbg("[!] Masked real MSR values to: CTS=%d | DSR=%d | RI=%d | DCD=%d",
out&UART_MSR_CTS?1:0, out&UART_MSR_DSR?1:0, out&UART_MSR_RI?1:0, out&UART_MSR_DCD?1:0);
}
break;
case UART_SCR:
out = vdev->scr;
reg_read("SCR/SPR");
break;
default:
pr_loc_bug("Unknown registry %x read attempt on ttyS%d", offset, vdev->line);
out = 0;
break;
}
update_interrupts_state(vdev);
unlock_vuart(vdev);
return out;
}
/**
* The main WRITE routing passed to the 8250 driver. It should be as fast as possible and MUST be multithread-safe
*
* Kernel => device, aka "I have something FOR YOU, send it along"
* This function is also used to write registers.
*
* @param offset This is really the register value. It's named "offset" in accordance with Linux nomenclature which
* makes sense for physical chips (as this is a memory offset from chip's memory base)
*/
static void serial_remote_write(struct uart_port *port, int offset, int value)
{
//uart_prdbg("Serial WRITE for line=%d/%d", port->line, ttySs[port->line].line);
struct serial8250_16550A_vdev *vdev = get_line_vdev(port->line);
lock_vuart(vdev);
capture_uart_port(vdev, port);
switch (offset) {
case UART_TX:
//See "case UART_RX" for explanation
if (vdev->lcr & UART_LCR_DLAB) { //DLAB overrides everything
vdev->dll = value;
reg_write("DLL");
} else if (vdev->mcr & UART_MCR_LOOP) { //are we in the reflection/loop mode? (=> fake TX->RX connection)
uart_prdbg("Loopback enabled, writing %x meant for THR to RHR directly", value);
handle_receive_char(vdev, (unsigned char)value); //loopback emulates receiving char on RX
dump_mcr(vdev);
dump_lsr(vdev);
} else { //just pickup the data from kernel
handle_transmit_char(vdev, (unsigned char)value);
reg_write("THR");
dump_lsr(vdev);
}
break;
case UART_IER:
if (vdev->lcr & UART_LCR_DLAB) {
vdev->dlm = value;
reg_write("DLM");
break;
}
/*
* This is a little shortcut to deliver data to the callback even if the threshold wasn't met. This is
* done since kernel DISABLES THR empty interrupts when it finishes writing (which makes sense - otherwise
* it will be flooded with interrupts all the time as nothing gets written to THR). This means that
* kernel wrote everything what was there to write and [presumably] nothing else is coming anytime soon
* So in short: if THReINT was enabled and it JUST got disabled flush the FIFO if it isn't empty
*/
if ((vdev->ier & UART_IER_THRI) && !(value & UART_IER_THRI) && !kfifo_is_empty(vdev->tx_fifo)) {
uart_prdbg("Kernel driver disabled THRe interrupt and fifo isn't empty - triggering IDLE flush");
flush_tx_fifo(vdev, VUART_FLUSH_IDLE);
}
vdev->ier = value & 0x0f; //we're not letting kernel set DMA registers since we don't support DMA
reg_write_dump(vdev, ier, "IER");
break;
//case UART_IIR not present - read only register
case UART_FCR:
//FIFO registers are guarded by the FIFOEN - if it's not set only FIFOEN can be modified, see p27 of Ti doc
if (!(vdev->fcr & UART_FCR_ENABLE_FIFO) && !(value & UART_FCR_ENABLE_FIFO))
value &= UART_FCR_ENABLE_FIFO;
vdev->fcr = value;
reg_write_dump(vdev, fcr, "FCR");
//If the new FCR value called for flush of TX and/or RX do that right away
if (vdev->fcr & UART_FCR_CLEAR_XMIT) {
kfifo_reset(vdev->tx_fifo);
vdev->lsr |= UART_LSR_TEMT | UART_LSR_THRE;
uart_prdbg("TX FIFO flushed on FCR request");
dump_lsr(vdev);
}
if (vdev->fcr & UART_FCR_CLEAR_RCVR) {
kfifo_reset(vdev->rx_fifo);
vdev->lsr &= ~UART_LSR_DR;
uart_prdbg("RX FIFO flushed on FCR request");
dump_lsr(vdev);
}
break;
case UART_LCR:
vdev->lcr = value;
reg_write_dump(vdev, lcr, "LCR");
break;
case UART_MCR:
vdev->mcr = value;
reg_write_dump(vdev, mcr, "MCR");
break;
case UART_LSR:
vdev->lsr = value;
pr_loc_bug("Bogus LSR write attempt on ttyS%d - why?", vdev->line);
dump_lsr(vdev);
break;
case UART_MSR:
vdev->msr = value;
pr_loc_bug("Bogus MSR write attempt on ttyS%d - why?", vdev->line);
dump_msr(vdev);
break;
case UART_SCR:
vdev->scr = value;
reg_write("SCR");
break;
default:
pr_loc_bug("Unknown registry %x write attempt on ttyS%d with %x", offset, vdev->line, value);
break;
}
update_interrupts_state(vdev);
unlock_vuart(vdev);
}
/************************************************** vUART Glue Layer **************************************************/
static driver_watcher_instance *driver_watcher = NULL;
static int update_serial8250_isa_port(struct serial8250_16550A_vdev *vdev);
static int restore_serial8250_isa_port(struct serial8250_16550A_vdev *vdev);
/**
* Initializes/allocates what's needed in a fresh vdev structure (or one which was previously freed)
*/
static int initialize_ttyS(struct serial8250_16550A_vdev *vdev)
{
int out;
pr_loc_dbg("Initializing ttyS%d vUART", vdev->line);
if (unlikely(vdev->initialized)) {
pr_loc_bug("ttyS%d is already initialized", vdev->line);
return -EBUSY;
}
reset_device(vdev); //Puts device in a known RESET state as defined by the real chip docs
if ((out = alloc_fifos(vdev) != 0))
return out;
kmalloc_or_exit_int(vdev->lock, sizeof(spinlock_t));
spin_lock_init(vdev->lock);
//virq_* stuff is allocated/freed by enable_/disable_interrupts()
vdev->initialized = true;
pr_loc_dbg("Initialized ttyS%d vUART", vdev->line);
return 0;
}
/**
* Deinitializes/frees what was previously built by initialize_ttyS()
*/
static int deinitialize_ttyS(struct serial8250_16550A_vdev *vdev)
{
int out;
pr_loc_dbg("Deinitializing ttyS%d vUART", vdev->line);
if (unlikely(!vdev->initialized)) {
pr_loc_bug("ttyS%d is not initialized", vdev->line);
return -ENODEV;
}
if ((out = free_fifos(vdev) != 0))
return out;
kfree(vdev->lock);
vdev->initialized = false;
pr_loc_dbg("Deinitialized ttyS%d vUART", vdev->line);
return 0;
}
/**
* Watches for the serial8250 driver to load in order to register ports which were added before the driver loaded
*/
static driver_watch_notify_result serial8250_ready_watcher(struct device_driver *drv, driver_watch_notify_state event)
{
if (unlikely(event != DWATCH_STATE_LIVE))
return DWATCH_NOTIFY_CONTINUE;
pr_loc_dbg("%s driver loaded - adding queued ports", UART_DRIVER_NAME);
kernel_driver_ready = true;
int out;
for_each_vdev() {
//non-initialized ports are these which were never added as vUARTs
if (!ttySs[line].initialized || ttySs[line].registered)
continue;
pr_loc_dbg("Processing enqueued port %d", line);
if ((out = update_serial8250_isa_port(&ttySs[line])) != 0) {
//This is critical as ports were promised to be registered to other parts of the application but we cannot
// fulfill that promise now
pr_loc_crt("Failed to process port %d - error=%d", line, out);
}
}
pr_loc_dbg("Finished processing enqueued ports");
return DWATCH_NOTIFY_DONE;
}
/**
* Checks the current serial8250 status
*
* @return 0 if not loaded, 1 if loaded, -E on error
*/
static int probe_driver(void)
{
if (kernel_driver_ready)
return 1; //we've already checked the state and confirmed as ready before
int driver_ready_tristate = is_driver_registered(UART_DRIVER_NAME, NULL);
if (driver_ready_tristate < 0) {
pr_loc_err("Failed to check %s driver state - error=%d", UART_DRIVER_NAME, driver_ready_tristate);
return -EIO;
}
if (driver_ready_tristate == 1)
kernel_driver_ready = true;
return driver_ready_tristate;
}
/**
* Attempt to watch for the serial8250 driver readiness (if needed)
*
* @return 0 if driver is not loaded and a watcher has been set up,
* 1 if driver is already loaded (and nothing needs to be done),
* -E on error
*/
static int try_wait_for_serial8250_driver(void)
{
int driver_ready_tristate = probe_driver();
if (driver_ready_tristate != 0)
return driver_ready_tristate; //if the driver is ready (=1) or an error occurred (-E) we don't do anything here
pr_loc_inf("%s driver is not ready - the port addition will be delayed until the driver loads", UART_DRIVER_NAME);
driver_watcher = watch_driver_register(UART_DRIVER_NAME, serial8250_ready_watcher, DWATCH_STATE_LIVE);
if (IS_ERR(driver_watcher)) {
pr_loc_err("Failed to register driver watcher - no ports can be registered till the driver loads");
return PTR_ERR(driver_watcher);
}
return 0;
}
/**
* Disable the driver watcher if it was set up
*
* @return 0 on success, -E on error
*/
static int try_leave_serial8250_driver(void)
{
if (!driver_watcher) //we're only concerned about watching the driver
return 0;
for_each_vdev() {
if (ttySs[line].initialized && !ttySs[line].registered) {
pr_loc_dbg("Cannot leave %s driver yet - port %d is still awaiting registration", UART_DRIVER_NAME, line);
return 0;
}
}
int out = unwatch_driver_register(driver_watcher);
driver_watcher = NULL;
if (out != 0)
pr_loc_err("Failed to unwatch driver (error=%d)", out);
return out;
}
/**
* Asks the Linux 8250 driver to UPDATE properties of a given serial device which matches line & iobase
*
* The reason why this function is called update_ rather than add_ is that we're NOT adding anything new to the driver.
* Rather we're registering a port which is already there (as vUART only deals with COM1-4, i.e. legacy IBM/PC ports)
* and matches our spec.
*/
static int update_serial8250_isa_port(struct serial8250_16550A_vdev *vdev)
{
int out;
pr_loc_dbg("Registering ttyS%d (io=0x%x) in the driver", vdev->line, vdev->iobase);
if (unlikely(vdev->registered)) {
pr_loc_bug("Port ttyS%d (io=0x%x) is already registered in the driver", vdev->line, vdev->iobase);
return -EEXIST;
}
int driver_ready_tristate = try_wait_for_serial8250_driver();
if (driver_ready_tristate == 0) {
pr_loc_wrn("The %s driver is not ready - vUART port ttyS%d (io=0x%x) will be activated later", UART_DRIVER_NAME,
vdev->line, vdev->iobase);
return 0;
}
if (driver_ready_tristate < 0) {
pr_loc_err("%s failed due to underlining driver error", __FUNCTION__);
return driver_ready_tristate;
}
struct uart_8250_port *up;
kzalloc_or_exit_int(up, sizeof(struct uart_8250_port));
struct uart_port *port = &up->port;
port->line = vdev->line;
port->iobase = vdev->iobase;
port->uartclk = vdev->baud * 16;
port->flags = STD_COMX_FLAGS;
//This is a silly workaround to let the kernel know "we don't REALLY support IRQ"
//While the code do support IRQs handling we weren't able to find a smart way to "simulate" IRQ 3-4 (which are
// normally HW interrupts). However, the 8250 driver will emulate them for us using APIC
port->irq = (vuart_virq_supported()) ? vdev->irq : SERIAL8250_SOFT_IRQ;
port->irqflags = 0;
port->hub6 = 0;
port->membase = 0;
port->iotype = 0;
port->regshift = 0;
port->serial_in = serial_remote_read;
port->serial_out = serial_remote_write;
port->type = PORT_16550A;
up->cur_iotype = 0xFF;
//DO NOT EVEN THINK about assigning "port" top vdev->port!!! serial8250_register_8250_port() uses our passed port to
// match internally reserved (during boot) port structure. Our structure misses a lot of stuff like handlers and so
//YOU CANNOT ASSIGN IT HERE!
//This is the most explosion-prone section so logs are useful
uart_prdbg("Calling serial8250_register_8250_port to register port");
if ((out = serial8250_register_8250_port(up)) < 0) { //it returns port # on success or -E on error
pr_loc_err("Failed to register ttyS%d - driver failure (error=%d)", vdev->line, out);
goto out_free;
}
pr_loc_dbg("ttyS%d registered with driver (line=%d)", vdev->line, out);
out = 0; //serial8250_register_8250_port return serial port line # or -E code
vdev->registered = true;
out_free:
kfree(up);
return out;
}
/**
* Restores original UART in 8250 driver
*/
static int restore_serial8250_isa_port(struct serial8250_16550A_vdev *vdev)
{
int out;
pr_loc_dbg("Unregistering ttyS%d (io=0x%x) from the driver", vdev->line, vdev->iobase);
if (unlikely(!vdev->registered)) {
pr_loc_dbg("Port ttyS%d (io=0x%x) is not registered in the driver - nothing to restore", vdev->line,
vdev->iobase);
return 0;
}
if (unlikely(!kernel_driver_ready)) {
pr_loc_wrn("Port ttyS%d (io=0x%x) cannot be restored - kernel driver not ready", vdev->line, vdev->iobase);
return 0; //not an error as technically the port is NOT in the driver
}
struct uart_8250_port *up;
kzalloc_or_exit_int(up, sizeof(struct uart_8250_port));
struct uart_port *port = &up->port;
port->line = vdev->line;
up->cur_iotype = 0xFF;
port->iobase = vdev->iobase;
port->uartclk = vdev->baud * 16;
port->irq = vdev->irq; //set a REAL IRQ
port->flags = STD_COMX_FLAGS;
up->port = *port;
//This is the most explosion-prone section so logs are useful
//This may sound counter-intuitive but we don't want to REMOVE the port, we want to just re-register it with
//all default callbacks.
pr_loc_dbg("Calling serial8250_register_8250_port to restore port");
if ((out = serial8250_register_8250_port(up)) < 0) { //it returns port # on success or -E on error
pr_loc_err("Failed to restore ttyS%d - driver failure (error=%d)", vdev->line, out);
goto out_free;
}
pr_loc_dbg("ttyS%d finished unregistraton from driver (line=%d)", vdev->line, out);
out = 0; //serial8250_register_8250_port return serial port line # or -E code
vdev->registered = false;
out = try_leave_serial8250_driver();
out_free:
kfree(up);
return out;
}
int vuart_set_tx_callback(int line, vuart_callback_t *cb, char *buffer, int threshold)
{
validate_isa_line(line);
struct serial8250_16550A_vdev *vdev = get_line_vdev(line);
if (!cb) {
pr_loc_dbg("Removing TX callback for ttyS%d (line=%d)", line, vdev->line);
if (unlikely(!flush_cbs[line])) {
pr_loc_dbg("Nothing to do - no TX callback set");
return 0;
}
//We don't really need to lock for that
kfree(flush_cbs[line]);
flush_cbs[line] = NULL;
pr_loc_dbg("Removed TX callback for ttyS%d (line=%d)", line, vdev->line);
return 0;
}
pr_loc_dbg("Setting TX callback for for ttyS%d (line=%d)", line, vdev->line);
line = vdev->line; //this looks to make no sense BUT it does when serials are swapped
if (likely(!flush_cbs[line])) { //if there was already a cb there we don't need to reserve memory
kmalloc_or_exit_int(flush_cbs[line], sizeof(struct flush_callback));
}
//This can technically be called during serial port operation so we need to get a lock before we change these or
// we risk sending a buffer to a wrong function. That lock may not exist when device is not added yet.
lock_vuart_oppr(vdev);
flush_cbs[line]->fn = cb;
flush_cbs[line]->buffer = buffer;
flush_cbs[line]->threshold = threshold;
unlock_vuart_oppr(vdev);
pr_loc_dbg("Added TX callback for ttyS%d (line=%d)", line, vdev->line);
return 0;
}
int vuart_inject_rx(int line, const char *buffer, int length)
{
validate_isa_line(line);
if (unlikely(length > VUART_FIFO_LEN)) {
pr_loc_bug("Attempted to inject buffer of %d bytes - it's larger than FIFO size (%d bytes)", length, VUART_FIFO_LEN);
return -E2BIG;
}
struct serial8250_16550A_vdev *vdev = get_line_vdev(line);
if (unlikely(!vdev->initialized)) {
pr_loc_bug("Cannot inject data into non-initialized or non-registered device");
return -ENXIO;
}
if (unlikely(!vdev->registered)) {
pr_loc_wrn("Cannot inject data into unregistered device"); //...as it will be removed by the driver on reg
return 0;
}
//No space to put data - not an error per-sen as this can be re-run again
if ((vdev->lsr & UART_LSR_DR) && unlikely(kfifo_is_full(vdev->rx_fifo) || unlikely(vdev->mcr & UART_MCR_LOOP)))
return 0;
int put_bytes = kfifo_in(vdev->rx_fifo, buffer, VUART_FIFO_LEN);
if (likely(put_bytes > 0))
vdev->lsr |= UART_LSR_DR;
uart_prdbg("Injected %d bytes into ttyS%d RX", put_bytes, line);
update_interrupts_state(vdev);
return put_bytes;
}
int vuart_add_device(int line)
{
pr_loc_dbg("Adding vUART ttyS%d", line);
validate_isa_line(line);
warn_bug_swapped(line);
int out;
struct serial8250_16550A_vdev *vdev = get_line_vdev(line);
if ((out = initialize_ttyS(vdev)) != 0)
return out;
if ((out = update_serial8250_isa_port(vdev)) != 0)
goto error_deinit;
if ((out = vuart_enable_interrupts(vdev)) != 0)
goto error_restore;
pr_loc_inf("Added vUART at ttyS%d", line);
return 0;
error_restore:
restore_serial8250_isa_port(vdev);
error_deinit:
deinitialize_ttyS(vdev);
return out;
}
int vuart_remove_device(int line)
{
pr_loc_dbg("Removing vUART ttyS%d", line);
validate_isa_line(line);
warn_bug_swapped(line);
int out;
struct serial8250_16550A_vdev *vdev = get_line_vdev(line);
if ((out = vuart_disable_interrupts(vdev)) != 0 || (out = deinitialize_ttyS(vdev)) != 0 ||
(out = restore_serial8250_isa_port(vdev)) != 0 || (out = vuart_set_tx_callback(line, NULL, NULL, 0)) != 0)
return out;
pr_loc_inf("Removed vUART & restored original UART at ttyS%d", line);
return 0;
}