UART 가 System에서 동작하기 위해서는 Register, TX,RX FIFO, ISR 등 여러 요소들이 필요하다. 이번 글에서는 각각의 요소들을 살펴보겠다.
1. UART에서 사용하는 주요 Register 및 용도
Register Name | Description |
Transmitter Holding Register (THR) | 전송할 Char저장 |
Receiver Buffer Register (RBR) | 전송 받은 char 저장 |
Interrupt Enable Register (IER) | UART가 trigger 할 Interrupt 종류 설정 |
FIFO Control Register (FCR) | FIFO RCV TX Set/Clear, Interrupt Trigger level 설정 |
Interrupt Identification Register (IIR) | Trigger된 Interrupt의 종류 저장(RX, TX, Timeout, RLS) |
Line Status Register (LSR) | frame, parity, overrun error 정보 저장. DR, THR 상태 저장 |
FIFO Enable인 경우, FIFO에 Data full 상황에서 데이터를 읽기 위해서는 FIFO Size 만큼 RBR을 읽으면 되고, FIFO Data Empty 상황에서 데이터를 보낼 경우에는 THR에 FIFO Size 만큼 쓰면 된다. 즉 RX FIFO의 첫 Element가 RBR이 되고, TX FIFO 의 마지막 Element가 THR이라고 생각하면 된다.
2. UART Interrupt 가 발생하는 상황
* THRE(Transmitter Holding Register Empty) Interrupt : THR이 비었으니 전송할 데이터를 보내라고 요청
* RD(Received Data Available) Interrupt: RBR에 값이 있으니 데이터를 읽으라고 요청.
(FIFO Enable인 경우 설정한 Trigger level에 따라 Interrupt 발생.)
* Timeout interrupt: RBR 이 비어있지 않은 상황에서 일정시간동안 데이터를 받지 못하거나 CPU가 데이터를 읽어가지 않았을 때 발생.
3. Serial Interrupt Service Routine (linux/drivers/serial/8250.c)
UART에서 발생한 모든 Interrupt는 serial8250_interrupt() 에서 처리된다.
static irqreturn_t serial8250_interrupt(int irq, void *dev_id)
{
struct irq_info *i = dev_id;
struct list_head *l, *end = NULL;
int pass_counter = 0, handled = 0;
DEBUG_INTR("serial8250_interrupt(%d)...", irq);
spin_lock(&i->lock);
l = i->head;
do {
struct uart_8250_port *up;
unsigned int iir;
up = list_entry(l, struct uart_8250_port, list);
iir = serial_in(up, UART_IIR);
if (!(iir & UART_IIR_NO_INT)) { //Pending된 Interrupt 가 있는 경우 (rx, tx, timeout 모두)
serial8250_handle_port(up); //RX 또는 TX에 따라 Transmit or Receive 수행
handled = 1;
end = NULL;
} else if (up->port.iotype == UPIO_DWAPB &&
(iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
/* The DesignWare APB UART has an Busy Detect (0x07)
* interrupt meaning an LCR write attempt occured while the
* UART was busy. The interrupt must be cleared by reading
* the UART status register (USR) and the LCR re-written. */
unsigned int status;
status = *(volatile u32 *)up->port.private_data;
serial_out(up, UART_LCR, up->lcr);
handled = 1;
end = NULL;
} else if (end == NULL)
end = l;
l = l->next;
if (l == i->head && pass_counter++ > PASS_LIMIT)
{ //isr 의 수행 시간 제한
/* If we hit this, we're dead. */
printk(KERN_ERR "serial8250: too much work for "
"irq%d\n", irq);
break;
}
} while (l != end);
spin_unlock(&i->lock);
DEBUG_INTR("end.\n");
return IRQ_RETVAL(handled);
}
WRITTEN BY
- RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.