임베디드 시스템에서 실시간으로 이벤트를 처리하는 기술은 인터럽트입니다. UART 통신은 인터럽트를 활용하여 데이터를 주고받는 역할을 수행합니다. 인터럽트 처리 과정과 UART 드라이버의 작동 원리 그리고 리눅스 커널에서의 구현 사례를 알아보도록 하겠습니다.

고속 디바이스(이더넷)
저속 디바이스(키보드, I2C, UART)
리눅스 커널은 장단점을 고려하여 저속 디바이스에는 인터럽트를 사용하고 고속 디바이스에는 인터럽트와 폴링을 함께 사용하는 하이브리드 방식을 채택
동적 전환(rdc/r6040.c)
static int r6040_poll(struct napi_struct *napi, int budget)
{
struct r6040_private *priv =
container_of(napi, struct r6040_private, napi);
struct net_device *dev = priv->dev;
void __iomem *ioaddr = priv->base;
int work_done;
r6040_tx(dev);
work_done = r6040_rx(dev, budget);
if (work_done < budget) {
napi_complete_done(napi, work_done);
/* Enable RX/TX interrupt */
iowrite16(ioread16(ioaddr + MIER) | RX_INTS | TX_INTS,
ioaddr + MIER);
}
return work_done;
}
IRQ 번호를 할당받으며 OS는 번호를 통해 어떤 디바이스에서 인터럽트가 발생했는지 식별
IRQ 번호는 하드웨어 플랫폼에서 정의
라즈베리 파이 4의 인터럽트 맵을 살펴보면 주변 장치에 할당된 IRQ 번호를 확인
인터럽트를 사용하기 위해서는 먼저 OS에 해당 인터럽트를 등록
리눅스 커널에서는 request_threaded_irq 함수를 사용하여 인터럽트를 등록
int request_threaded_irq(unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long irqflags,
const char *devname,
void *dev_id);

PL011 UART 컨트롤러는 다양한 레지스터를 통해 제어

// amba-pl011.c
// ...
static irqreturn_t pl011_int(int irq, void *dev_id)
{
struct uart_amba_port *uap = dev_id;
unsigned long flags;
unsigned int status, pass_counter = AMBA_ISR_PASS_LIMIT;
int handled = 0;
spin_lock_irqsave(&uap->port.lock, flags);
status = pl011_read(uap, REG_RIS) & uap->im;
do {
if (status & (UART011_RTIS | UART011_RXIS)) {
if (pl011_dma_rx_running(uap))
pl011_rx_irq(uap);
else
pl011_rx_chars(uap);
}
// ...
if (pass_counter-- == 0)
break;
status = pl011_read(uap, REG_RIS) & uap->im;
} while (status & (UART011_TXIS | UART011_RTIS | UART011_RXIS));
spin_unlock_irqrestore(&uap->port.lock, flags);
return IRQ_RETVAL(handled);
}
// ...
static void pl011_rx_chars(struct uart_amba_port *uap)
{
struct tty_port *port = &uap->port.state->port;
unsigned int status, ch, flag, fifotaken;
while (!pl011_tx_stopped(uap) &&
(fifotaken = pl011_read_raw_fifo(uap, &ch, &status, &flag))) {
uap->port.icount.rx++;
uart_handle_sysrq_char(&uap->port, ch);
if (uart_handle_break(&uap->port))
continue;
tty_insert_flip_char(port, ch, flag);
}
spin_unlock(&uap->port.lock);
tty_flip_buffer_push(port);
spin_lock(&uap->port.lock);
}
// ...
static int pl011_startup(struct uart_port *port)
{
struct uart_amba_port *uap =
container_of(port, struct uart_amba_port, port);
unsigned int cr;
int retval;
retval = pl011_hwnit(port);
if (retval)
goto clk_dis;
retval = pl011_allocate_irq(uap);
if (retval)
goto clk_dis;
// ...
return 0;
clk_dis:
clk_disable_unprepare(uap->clk);
return retval;
}
static int pl011_allocate_irq(struct uart_amba_port *uap)
{
int ret;
pl011_write(uap, 0, REG_IMSC);
ret = request_irq(uap->port.irq, pl011_int, IRQF_SHARED,
"uart-pl011", uap);
if (ret)
dev_err(uap->port.dev,
"Failed to register AMBA-PL011 interrupt\n");
return ret;
}
static int pl011_probe(struct amba_device *dev, const struct amba_id *id)
{
// ...
ret = pl011_setup_port(dev, &uap->port, &res, portnr);
if (ret)
goto unregister_region;
pl011_setup_fifos(uap);
uap->port.dev = &dev->dev;
uap->port.irq = dev->irq[0];
uap->port.ops = &amba_pl011_pops;
// ...
pl011_register_port(uap);
return ret;
}
static struct amba_driver pl011_driver = {
// ...
.probe = pl011_probe,
// ...
};
static int __init pl011_init(void)
{
printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
return amba_driver_register(&pl011_driver);
}
// ...
module_init(pl011_init);
pl011_startup()
pl011_rx_chars()
pl011_int()
pl011_probe()
pl011_init()
initcall은 빌드인(builtin) 드라이버의 초기화 순서를 정의하는 메커니즘
// init/main.c
// ...
#define core_initcall(fn) __define_initcall(fn, 1)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define device_initcall(fn) __define_initcall(fn, 6)
#define late_initcall(fn) __define_initcall(fn, 7)
인터럽트 처리 과정, UART 드라이버의 작동 원리 그리고 리눅스 커널에서의 구현 사례를 자세히 살펴보았습니다. 인터럽트는 임베디드 시스템에서 발생하는 이벤트를 신속하고 효율적으로 처리하는 기술이며 UART는 인터럽트를 활용하여 데이터를 주고받는 역할을 수행합니다.