Objectives

  • 주변장치와의 소통
  • Interrupt Handler 구현
  • Synchronizing interrupts with Process Context

Intro

Background Information

I/O Ports

  • Definition: set of I/O addresses
  • can be mapped to physical memory addresses communicate directly with the device through instructions
  • port is differentiated by the number of bits: 8, 16, 32 bit ports
  • Types
    • Control registers: receive device commands
    • Status registers: contains device’s internal status information
    • Input registers: data is taken from the device
    • Output registers: data is written to transmit it to the device
  • Example
    • Parallel Port has eight ports (each port: 8bits).
    • Data Log @base: 0x378
      • both entry & exit log
    • Status Register @base+1: 0x379
    • Control Register @base+2: 0x37a

IRQ (Interrupt ReQuest)

  • I/O ports or Special memory areas can be insufficient to control the device

  • Polling Inefficiency: interrogating the device status repeatedly

  • Objective: 특정 event가 일어났을 때, hardware가 event가 일어났음을 알려줌

  • To make use..

    • Interrupt Handlers must be implemented
    • Interrupts must be requested before use and released after use.
    • Device Drivers must
      • share an interrupt or
      • synchronize with interrupts
  • Accessing shared resources

    • between an interrupt routine

Accessing the hardware

1. Request Access to I/O Ports

// Shorthand
#define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name), 0)
#define release_region(start,n) __release_region(&ioport_resource, (start), (n))

Example

  • COM1
    • Base Address: 0x3F8
    • has 8 ports

Requesting access

#include <linux/ioport.h>
 
#define MY_BASEPORT 0x3F8
#define MY_NR_PORTS 8
 
if (!request_region(MY_BASEPORT, MY_NR_PORTS, "com1")) {
     /* handle error */
     return -ENODEV;
}

Releasing access

release_region(MY_BASEPORT, MY_NR_PORTS);

List Port Requests

$ sudo cat /proc/ioports
0000-001f : dma1
0020-0021 : pic1
0040-005f : timer
0060-006f : keyboard
0070-0077 : rtc
0080-008f : dma page reg
00a0-00a1 : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : ide1
01f0-01f7 : ide0
0376-0376 : ide1
0378-037a : parport0
037b-037f : parport0
03c0-03df : vga+
03f6-03f6 : ide0
03f8-03ff : serial
...

2. Accessing I/O Ports (Kernel Space)

asm/io.h

signaturecomments
unsigned inb(int port)reads one byte (8 bits) from port
void outb(unsigned char byte, int port)writes one byte (8 bits) to port
unsigned inw(int port)reads two bytes (16-bit) ports
void outw(unsigned short word, int port)writes two bytes (16-bits) to port
unsigned inl (int port)reads four bytes (32-bits) from port
void outl(unsigned long word, int port)writes four bytes (32-bits) to port
  • To insert delay (in case I/O operations transferring data too fast which occurs problems), insert _p such as inb_p, outb_p, etc.

Example

#include <asm/io.h>
#define MY_BASEPORT 0x3F8
 
unsigned char value = 0xFF;
outb(value, MY_BASEPORT);
value = inb(MY_BASEPORT);

3. Accessing I/O ports (User Space)

sys/io.h

Example

#include <sys/io.h>
#define MY_BASEPORT 0x3F8
 
// Get & Release permission for the first 3 ports of the serial port
 
/* 1 to get permission */
if (ioperm(MY_BASEPORT, 3, 1)) {
     /* handle error */
}
 
/* 0 to release permission */
if (ioperm(MY_BASEPORT, 3, 0)) {
     /* handle error */
}

Interrupt Handling

Requesting an interrupt

request_irq & free_irq

#include <linux/interrupt.h>
 
typedef irqreturn_t (*irq_handler_t)(int, void *);
 
int request_irq(unsigned int irq_no, irq_handler_t handler,
                unsigned long flags, const char *dev_name, void *dev_id);
 
void free_irq(unsigned int irq_no, void *dev_id);

handler function is executed in interrupt context..

  • we can’t call blocking APIs(mutex_lock() or msleep())
  • avoid doing a lot of work & use deferred work if needed
  • read the device register to get the status of the device and acknowledge the interrupt
  • operations that most of the time can be performed with non-blocking calls

request_threaded_irq

device에서 interrupt 발생해도 non-blocking mode에서 device register 읽을 수 없는 상황 존재..

이런 경우에 work-in-process action (ex. work queue, kernel thread)를 이용하여 device register에 접근해야 함.

request_threaded_irq 이용하면 process-phase 혹은 interrupt context phase에서 interrupt handling 을 수행할 수 있음.

  • handler: interrupt context에서 수행할 function
  • thread_fn: process context에서 수행할 function
#include <linux/interrupt.h>
 
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
                         irq_handler_t thread_fn,
                         unsigned long flags, const char *name, void *dev);

flags

  • IRQF_SHARED: interrupt can be shared with other devices.
    • if not set, if there’s already a handler associated with the requested interrupt, the request for interrupt will fail
    • all the associated interrupt handlers will be executed until the device that genet
  • IRQF_ONESHOT:

Reference

Implementing an interrupt handler

Interrupt Handler Function Signature

irqreturn_t (*handler)(int irq_no, void *dev_id);

Reference

Locking

  • interrupt handler들은 interrupt context에서 실행되기 때문에 다음과 같은 실행 가능한 동작들이 제한된다.
    • user space memory 접근 불가
    • blocking function 호출 불가
  • spinlock 이용한 synchronization 까다롭고 deadlock 만들 수 있음 (spinlock used is already acquired by a process that has been interrupted by the running handler)
  • 하지만, spinlock 이용하는 경우 있음…
    • interrupt handler - process context 사이에서 데이터 공유
    • interrupt handler - bottom-half handler 사이에서 데이터 공유
    • 이런 경우에는 interrupt를 끄고 spinlock을 써야됨..
  • Disabling Interrupts
    • processor level에서 모든 interrupt 끄기.. faster & preferred
    • interrupt controller level에서 특정 interrupt 끄기

Disabling All Interrupts at Processor level

#include <linux/spinlock.h>
 
void spin_lock_irqsave (spinlock_t * lock, unsigned long flags);
void spin_unlock_irqrestore (spinlock_t * lock, unsigned long flags);
 
void spin_lock_irq (spinlock_t * lock);
void spin_unlock_irq (spinlock_t * lock);
  • read_lock_irqsave()

  • read_unlock_irqrestore()

  • read_lock_irq()

  • read_unlock_irq

  • write_lock_irqsave()

  • write_unlock_irqrestore()

  • write_lock_irq()

  • write_unlock_irq()

Disabling Particular Interrupt at Interrupt Controller level

  • disable_irq()
  • disable_irq_nosync(): async version
  • enable_irq()

Interrupt Statistics