'Device Driver/Character Device'에 해당하는 글 1건

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
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,