<kernel v4.0>
Interrupts -11- (IC Driver for rpi2)
Device Tree로 hierarchy irq chip 구성
설명에 사용된 샘플 보드는 다음과 같다.
- 64비트 rock960
- GIC v3 인터럽트 컨트롤러
- rock 사의 rk3399칩셋은 GIC v3를 사용한다.
- 커널은 v5.4를 기준으로 설명하였다.
- 32비트 rpi2
- BCM2836 전용 인터럽트 컨트롤러
- 커널은 v4.4를 기준으로 설명하였다.
다음 그림은 rpi2에서 설정되는 5개의 irq_chip을 보여준다.
3 개의 인터럽트 컨트롤러
- gpio pinctrl 디바이스
- gpio를 사용하는 pinctrl 디바이스 드라이버에 구현되어 있다.
- 인터럽트 컨트롤러 디바이스 드라이버가 아니므로 IRQCHIP_DECLARE()를 사용하지 않았으므로 __chip_id_of_table 섹션에 인터럽트 컨트롤러 엔트리가 등록되지 않는다.
- 인터럽트 초기화 루틴 irqchip_init()에서 초기화 함수를 호출하지 않는다.
- 인터럽트 컨트롤러 디바이스 드라이버가 아니므로 IRQCHIP_DECLARE()를 사용하지 않았으므로 __chip_id_of_table 섹션에 인터럽트 컨트롤러 엔트리가 등록되지 않는다.
- DT 스크립트에 “gpio-controller;” 및 “interrupt-controller;” 두 가지 기능의 컨트롤러가 동작한다.
- 메인 기능이 gpio를 사용하는 pinctrl 디바이스이다.
- 추가 기능으로 gpio에서 인터럽트 라인 컨트롤 기능을 수행할 수 있다.
- gpio pin 들을 인터럽트 라인으로 사용할 수 있어서 이들에 대한 set/clear 등의 마스킹 동작을 위해 irq_chip이 구현되어 있다.
- gpio를 사용하는 pinctrl 디바이스 드라이버에 구현되어 있다.
- GPU 인터럽트 컨트롤러
- 0 ~ 2번 뱅크를 입력으로 받아들이며 각각 최대 32개의 인터럽트 라인을 가지고 있다.
- rpi에서도 사용하는 인터럽트 컨트롤러
- LOCAL 인터럽트 컨트롤러
- rpi2에서 SMP core 간 IPI 등의 소프트 인터럽트 처리 등을 위해 별도로 추가되었다.
- 3 개의 irq_chip으로 구성
- gpu 인터럽트를 처리하기 위한 irq_chip
- 4개의 timer 인터럽트를 처리하기 위한 irq_chip
- 1개의 pmu 인터럽트를 처리하기 위한 irq_chip
DT용 인터럽트 컨트롤러 초기화 – rpi2
부모 컨트롤러 Part
bcm2836_arm_irqchip_l1_intc_of_init()
drivers/irqchip/irq-bcm2836.c
static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node, struct device_node *parent) { intc.base = of_iomap(node, 0); if (!intc.base) { panic("%s: unable to map local interrupt registers\n", node->full_name); } bcm2835_init_local_timer_frequency(); intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1, &bcm2836_arm_irqchip_intc_ops, NULL); if (!intc.domain) panic("%s: unable to create IRQ domain\n", node->full_name); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPNSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTHPIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTVIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_GPU_FAST, &bcm2836_arm_irqchip_gpu); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_PMU_FAST, &bcm2836_arm_irqchip_pmu); bcm2836_arm_irqchip_smp_init(); set_handle_irq(bcm2836_arm_irqchip_handle_irq); return 0; }
bcm2836 로컬 인터럽트에 해당하는 1개의 irq domain 과 3 개의 irq chip을 구성하고 사용하는 인터럽트들을 매핑한다.
- 코드 라인 4~8에서 인수로 전달받은 인터럽트 컨트롤러 노드의 base 레지스터를 IO 매핑하여 가상주소를 알아온다. 만일 매핑이 실패하면 panic 처리를 한다.
- rpi2: 0xf400_0000
- 코드 라인 10~14에서 10개의 irq로 리니어 domain을 추가한다. domain의 변환 함수가 ops에 등록되어 운용된다. 만일 irq domain이 추가되지 않는 경우 panic 처리를 한다.
- .xlate = irq_domain_xlate_onecell
- 코드 라인 18~25에서 4개의 타이머 인터럽트를 timer용 irq chip에 구성하고 매핑한다.
- 코드 라인 26~27에서 1개의 gpu 인터럽트를 gpu용 irq chip에 구성하고 매핑한다.
- 코드 라인 28~29에서 1개의 pmu 인터럽트를 pmu용 irq chip에 구성하고 매핑한다.
- 코드 라인 31에서 cpu on/off 등 상태 변경에 따른 notify를 받기 위해 연동하고, ipi 호출을 위해 연동함수를 등록한다.
- 코드 라인 33에서 irq exception 처리 루틴에서 호출하도록 irq 핸들러 함수를 전역 handle_arch_irq에 등록한다.
자식 컨트롤러 Part
bcm2836_armctrl_of_init()
drivers/irqchip/irq-bcm2835.c
static int __init bcm2836_armctrl_of_init(struct device_node *node, struct device_node *parent) { return armctrl_of_init(node, parent, true); }
처리할 인터럽트 컨트롤러에 대한 디바이스 노드와 상위 인터럽트 컨트롤러에 대한 디바이스 노드 정보를 갖고 armctrl_of_init() 함수를 호출한다.
armctrl_of_init()
drivers/irqchip/irq-bcm2835.c
static int __init armctrl_of_init(struct device_node *node, struct device_node *parent, bool is_2836) { void __iomem *base; int irq, b, i; base = of_iomap(node, 0); if (!base) panic("%s: unable to map IC registers\n", node->full_name); intc.base = base; intc.domain = irq_domain_add_linear(node, NUMBER_IRQS * 2, &armctrl_ops, NULL); if (!intc.domain) panic("%s: unable to create IRQ domain\n", node->full_name); for (b = 0; b < NR_BANKS; b++) { intc.pending[b] = base + reg_pending[b]; intc.enable[b] = base + reg_enable[b]; intc.disable[b] = base + reg_disable[b]; for (i = 0; i < bank_irqs[b]; i++) { irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i)); BUG_ON(irq <= 0); irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq); irq_set_probe(irq); } } if (is_2836) { int parent_irq = irq_of_parse_and_map(node, 0); if (!parent_irq) { panic("%s: unable to get parent interrupt.\n", node->full_name); } irq_set_chained_handler(parent_irq, bcm2836_chained_handle_irq); } else { set_handle_irq(bcm2835_handle_irq); } if (is_2836) { intc.local_regmap = syscon_regmap_lookup_by_compatible("brcm,bcm2836-arm-local"); if (IS_ERR(intc.local_regmap)) { pr_err("Failed to get local register map. FIQ is disabled for cpus > 1\n"); intc.local_regmap = NULL; } } /* Make a duplicate irq range which is used to enable FIQ */ for (b = 0; b < NR_BANKS; b++) { for (i = 0; i < bank_irqs[b]; i++) { irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i) + NUMBER_IRQS); BUG_ON(irq <= 0); irq_set_chip(irq, &armctrl_chip); irq_set_probe(irq); } } init_FIQ(FIQ_START); return 0; }
bcm2836 gpu 인터럽트에 해당하는 irq domain 과 irq chip을 구성하고 사용하는 인터럽트들을 매핑한다.
- 코드 라인 8~13에서 인수로 전달받은 인터럽트 컨트롤러 노드의 base 레지스터를 IO 매핑하여 가상주소를 알아온다. 만일 매핑이 실패하면 panic 처리를 한다.
- rpi2: 0xf300_b200
- 코드 라인 14~17에서 3개 뱅크(각 32개 irq)가 연결되는 96개 irq + 96개 fiq에 대응하도록 irq domain을 구성하고 매핑한다.
- irq domain의 ops
- .xlate =armctrl_xlate
- irq domain의 ops
- 코드 라인 19~22에서 각 뱅크의 pending 레지스터와 enable 및 disable을 레지스터의 가상 주소를 대입한다.
- 코드 라인 24~25에서 각 뱅크에서 처리할 hw 인터럽트 수 만큼을 irq domain과 irq 디스크립터에 매핑한다.
- 3 개의 각 뱅크에서 매핑할 hwirq 들
- bank#0
- hwirq=0~7까지 8개
- irq=22~29
- bank#1
- hwirq=32~63까지 32개
- irq=32~63
- bank#2
- hwirq=64~95까지 32개
- irq=62~93
- bank#0
- 3 개의 각 뱅크에서 매핑할 hwirq 들
- 코드 라인 27~30에서 “ARMCTRL-level” 이라는 이름의 irq chip을 설정하고 핸들러로 handle_level_irq()를 사용한다.
- 코드 라인 33~40에서 이 드라이버 코드는 bcm2835(rpi)와 혼용하여 사용하므로 bcm2836(rpi2)인 경우 부모 인터럽트 컨트롤의 hw irq를 알아와서 이 인터럽트 핸들러로 bcm2836_chained_handle_irq() 함수를 대입한다. 만일 Device Tree 스크립트에서 parent irq 번호를 알아오지 못한 경우 panic 처리한다.
- 모든 gpu 인터럽트들 중 하나라도 인터럽트가 발생하면 부모 인터럽트 컨트롤러의 8번 hwirq도 인터럽트가 발생한다.
- 코드 라인 41~43에서 bcm2835(rpi)에서 호출한 경우 핸들러로 bcm2835_handle_irq() 함수를 대입한다.
- 코드 라인 45~52에서 Device Tree에서 System Control Register 정보를 읽어서 base 주소를 대입한다. 못 읽어오는 경우 메시지를 출력한다. 이 때 2 번째 cpu 부터 fiq 인터럽트를 수신할 수 없게된다.
- rpi2: 물리 주소=0x4000_0000, size=0x100
- 코드 라인 55~58에서 각 뱅크에서 처리할 hw 인터럽트 수 만큼을 irq domain과 irq 디스크립터에 매핑한다. 이 인터럽트들은 fiq에 해당한다.
- 3 개의 각 뱅크에서 매핑할 hwirq 들
- bank#0
- hwirq=96~103까지 8개
- irq=94~101
- bank#1
- hwirq=128~159까지 32개
- irq=102~133
- bank#2
- hwirq=160~191까지 32개
- irq=134~165
- bank#0
- 3 개의 각 뱅크에서 매핑할 hwirq 들
- 코드 라인 60~62에서 “ARMCTRL-level” 이라는 이름의 irq chip을 설정하고 probe를 설정한다.
- 코드 라인 64에서 default FIQ 인터럽트 처리기를 백업하기 위해 fiq 벡터 값과 fiq 모드에서 사용하는 레지스터들 일부(r8 ~ r12, sp, lr)를 백업해둔다.
- fiq 사용 시 멀티플 레지스터 로드 및 저장 시 깨질 수 있기에 복원을 위해 초기 핸들러를 백업해 둔다.
GPIO 컨트롤러 Part
인터럽트 컨트롤러에서 초기화하지 않고 GPIO의 pinctrl 드라이버에서 초기화된다. 초기화 과정의 소스는 여기서 설명하지 않고 아래 그림만 참고한다.
IRQ exception 처리 후 컨트롤러가 설정한 핸들러 함수 호출
부모 컨트롤러 Part
bcm2836_arm_irqchip_handle_irq()
static void __exception_irq_entry bcm2836_arm_irqchip_handle_irq(struct pt_regs *regs) { int cpu = smp_processor_id(); u32 stat; stat = readl_relaxed(intc.base + LOCAL_IRQ_PENDING0 + 4 * cpu); if (stat & BIT(LOCAL_IRQ_MAILBOX0)) { #ifdef CONFIG_SMP void __iomem *mailbox0 = (intc.base + LOCAL_MAILBOX0_CLR0 + 16 * cpu); u32 mbox_val = readl(mailbox0); u32 ipi = ffs(mbox_val) - 1; writel(1 << ipi, mailbox0); handle_IPI(ipi, regs); #endif } else if (stat) { u32 hwirq = ffs(stat) - 1; handle_IRQ(irq_linear_revmap(intc.domain, hwirq), regs); } }
mailbox0 레지스터를 읽어 pending irq가 있는 경우 IPI 핸들러를 호출하고 그렇지 않은 경우 IRQ 핸들러를 호출한다.
- rpi2: 부모 인터럽트 컨틀롤러에서 발생한 irq들은 4개의 timer와 pmu 인터럽트이다.
- 코드 라인 7~8에서 현재 cpu에 대한 local pending 레지스터를 읽어서 bit4를 통해 mailbox가 수신되었는지 확인한다. 만일 수신된 경우
- 코드 라인 9~17에서 수신된 mailbox에서 가장 먼저(msb) 처리할 IPI 번호를 읽어 그 비트를 클리어하고 handle_IPI() 함수를 호출하여 IPI 처리를 수행하게 한다.
- 코드 라인 18~22에서 hwirq -> irq 번호로 reversemap을 사용하여 transalation하여 얻은 번호로 handle_irq()를 호출한다.
자식 컨트롤러 Part
bcm2836_chained_handle_irq()
drivers/irqchip/irq-bcm2835.c
static void bcm2836_chained_handle_irq(struct irq_desc *desc) { u32 hwirq; while ((hwirq = get_next_armctrl_hwirq()) != ~0) generic_handle_irq(irq_linear_revmap(intc.domain, hwirq)); }
gpu pending 레지스터 값을 읽어서 hwirq 값을 irq로 변환한 후 generic_handle_irq() 함수를 호출한다.
- 변환한 irq에 등록한 핸들러 함수를 호출한다.
- rpi2: handle_level_irq()
get_next_armctrl_hwirq()
drivers/irqchip/irq-bcm2835.c
static u32 get_next_armctrl_hwirq(void) { u32 stat = readl_relaxed(intc.pending[0]) & BANK0_VALID_MASK; if (stat == 0) return ~0; else if (stat & BANK0_HWIRQ_MASK) return MAKE_HWIRQ(0, ffs(stat & BANK0_HWIRQ_MASK) - 1); else if (stat & SHORTCUT1_MASK) return armctrl_translate_shortcut(1, stat & SHORTCUT1_MASK); else if (stat & SHORTCUT2_MASK) return armctrl_translate_shortcut(2, stat & SHORTCUT2_MASK); else if (stat & BANK1_HWIRQ) return armctrl_translate_bank(1); else if (stat & BANK2_HWIRQ) return armctrl_translate_bank(2); else BUG(); }
gpu pending 레지스터 값을 읽어서 hwirq로 변환한다.
- 코드 라인 3에서 로컬 pending 레지스터 값을 읽어서 관련 비트들만 마스크한다.
- timer 4개, mailbox 4개, gpu 매핑 1개, pmu 매핑 1개, shortcut 11개 비트들
- 코드 라인 5~6에서 pending 인터럽트가 없는 경우 ~0(호출 함수에서 루프의 끝을 의미)을 반환한다.
- 코드 라인 7~8에서 local 인터럽트 8개(bit7:0) 중 하나에 속한 경우 0번 뱅크에 해당하는 hwirq로 변환하여 반환한다.
- 코드 라인 9~10에서 shortcut1에 해당하는 인터럽트가 발생한 경우 1번 뱅크에 해당하는 hwirq로 변환하여 반환한다.
- bit10 ~ bit14 -> <7, 9, 10, 18, 19> + 32(bank1)
- 코드 라인 11~12에서 shortcut2에 해당하는 인터럽트가 발생한 경우 2번 뱅크에 해당하는 hwirq로 변환하여 반환한다.
- bit15 ~ bit20 -> <21, 22, 23, 24, 25, 30> + 64(bank2)
- 코드 라인 13~14에서 bank1에 해당하는 인터럽트인 경우 gpu#1 pending 레지스터를 읽어 가장 우측 비트(lsb)가 1로 설정된 인터럽트에 해당하는 hwirq로 변환하여 반환한다.
- bit0 ~ bit31 -> 30 ~ 61
- 코드 라인 15~16에서 bank2에 해당하는 인터럽트인 경우 gpu#2 pending 레지스터를 읽어 가장 우측 비트(lsb)가 1로 설정된 인터럽트에 해당하는 hwirq로 변환하여 반환한다.
- bit0 ~ bit31 -> 62 ~ 93
armctrl_translate_shortcut()
drivers/irqchip/irq-bcm2835.c
static u32 armctrl_translate_shortcut(int bank, u32 stat) { return MAKE_HWIRQ(bank, shortcuts[ffs(stat >> SHORTCUT_SHIFT) - 1]); }
shortcut에 해당하는 인터럽트가 발생한 경우 해당 뱅크에 해당하는 hwirq로 변환하여 반환한다.
- bit10 ~ bit14 -> <7, 9, 10, 18, 19> + 32(bank1)
- bit15 ~ bit20 -> <21, 22, 23, 24, 25, 30> + 64(bank2)
armctrl_translate_bank()
drivers/irqchip/irq-bcm2835.c
/* * Handle each interrupt across the entire interrupt controller. This reads the * status register before handling each interrupt, which is necessary given that * handle_IRQ may briefly re-enable interrupts for soft IRQ handling. */ static u32 armctrl_translate_bank(int bank) { u32 stat = readl_relaxed(intc.pending[bank]); return MAKE_HWIRQ(bank, ffs(stat) - 1); }
gpu#1 또는 gpu#2 pending 레지스터에서 읽은 stat 값에서 가장 우측 비트에 해당하는 hwirq를 반환한다.
- bit0 ~ bit31 -> 30 ~ 61
- bit0 ~ bit31 -> 62 ~ 93
drivers/irqchip/irq-bcm2835.c
/* Put the bank and irq (32 bits) into the hwirq */ #define MAKE_HWIRQ(b, n) ((b << 5) | (n)) #define HWIRQ_BANK(i) (i >> 5) #define HWIRQ_BIT(i) BIT(i & 0x1f)
irq chip의 콜백 함수 – rpi2
ARMCTRL-level용
armctrl_chip
drivers/irqchip/irq-bcm2835.c
static struct irq_chip armctrl_chip = { .name = "ARMCTRL-level", .irq_mask = armctrl_mask_irq, .irq_unmask = armctrl_unmask_irq };
bcm2835 인터럽트용 irq_chip 콜백 함수들
armctrl_mask_irq()
drivers/irqchip/irq-bcm2835.c
static void armctrl_mask_irq(struct irq_data *d) { if (d->hwirq >= NUMBER_IRQS) writel_relaxed(REG_FIQ_DISABLE, intc.base + REG_FIQ_CONTROL); else writel_relaxed(HWIRQ_BIT(d->hwirq), intc.disable[HWIRQ_BANK(d->hwirq)]); }
요청한 인터럽트(hwirq) 라인을 마스크하여 인터럽트 진입을 허용하지 않게 한다.
- 코드 라인 3~4에서 hwirq가 NUMBER_IRQS(96) 이상인 경우 FIQ 전체에 대한 인터럽트 마스크를 수행한다.
- 코드 라인 5~7에서 요청 IRQ에 대한 마스크를 수행한다.
- 1개 뱅크당 32개의 인터럽트 라인을 제어할 수 있다.
armctrl_unmask_irq()
drivers/irqchip/irq-bcm2835.c
static void armctrl_unmask_irq(struct irq_data *d) { if (d->hwirq >= NUMBER_IRQS) { if (num_online_cpus() > 1) { unsigned int data; int ret; if (!intc.local_regmap) { pr_err("FIQ is disabled due to missing regmap\n"); return; } ret = regmap_read(intc.local_regmap, ARM_LOCAL_GPU_INT_ROUTING, &data); if (ret) { pr_err("Failed to read int routing %d\n", ret); return; } data &= ~0xc; data |= (1 << 2); regmap_write(intc.local_regmap, ARM_LOCAL_GPU_INT_ROUTING, data); } writel_relaxed(REG_FIQ_ENABLE | hwirq_to_fiq(d->hwirq), intc.base + REG_FIQ_CONTROL); } else { writel_relaxed(HWIRQ_BIT(d->hwirq), intc.enable[HWIRQ_BANK(d->hwirq)]); } }
요청한 인터럽트(hwirq) 라인을 언마스크하여 인터럽트 진입을 허용하게 한다.
- 코드 라인 3에서 hwirq가 NUMBER_IRQS(96) 이상인 경우
- 코드 라인 4~24에서 online cpu가 1개를 초과하는 경우 2개 이상인 경우 bcm2836에 있는 local routing 레지스터를 설정할 수 있고, 여기에서 irq cpu 라우팅을 fiq cpu 라우팅으로 변경한다.
- local routing 레지스터 값은 하위 3비트만 유효하다.
- 최하위 비트 2개는 cpu core 번호
- bit2는 0=irq, 1=fiq
- 이 조작으로 영향을 받는 인터럽트는 core 전용이 아닌 공통 인터럽트들로 아래와 같다.
- GPU IRQ
- Local Timer
- AXI error
- 미 사용 15개의 local peripheral 인터럽트들
- 이 조작으로 영향을 받지 않는 인터럽트들은 core 인터럽트로 아래와 같다.
- 4개의 코어 타이머 인터럽트들 x 4 cpus
- 1개의 pm 인터럽트 x 4 cpus
- 4개의 mailbox 인터럽트들 x 4 cpus
- local routing 레지스터 값은 하위 3비트만 유효하다.
- 코드 라인 26~27에서 FIQ Control 레지스터의 8번 비트를 켜고 bit0~bit7의 irq(fiq) source를 지정한다.
- FIQ Control 레지스터: 버스(VC) 주소=0x7e00_b20c, ARM 물리 주소=0x3f00_b20c, ARM 가상 주소=0xf300_b20c
- 1개의 irq 소스를 fiq로 라우팅할 수 있다.
- rpi2: 고석 처리가 요구되는 usb를 fiq로 라우팅하여 사용한다.
- 코드 라인 28~31에서 요청 IRQ를 enable을 시킨다.
- 3개의 뱅크에서 각 뱅크당 최대 32개의 인터럽트 라인을 제어할 수 있다.
Argument 파싱 for DT
armctrl_xlate() – RPI2
arch/arm/mach-bcm2709/armctrl.c
/* from drivers/irqchip/irq-bcm2835.c */ static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr, const u32 *intspec, unsigned int intsize, unsigned long *out_hwirq, unsigned int *out_type) { if (WARN_ON(intsize != 2)) return -EINVAL; if (WARN_ON(intspec[0] >= NR_BANKS)) return -EINVAL; if (WARN_ON(intspec[1] >= IRQS_PER_BANK)) return -EINVAL; if (WARN_ON(intspec[0] == 0 && intspec[1] >= NR_IRQS_BANK0)) return -EINVAL; if (WARN_ON(intspec[0] == 3 && intspec[1] > 3 && intspec[1] != 5 && intspec[1] != 9)) return -EINVAL; if (intspec[0] == 0) *out_hwirq = ARM_IRQ0_BASE + intspec[1]; else if (intspec[0] == 1) *out_hwirq = ARM_IRQ1_BASE + intspec[1]; else if (intspec[0] == 2) *out_hwirq = ARM_IRQ2_BASE + intspec[1]; else *out_hwirq = ARM_IRQ_LOCAL_BASE + intspec[1]; /* reverse remap_irqs[] */ switch (*out_hwirq) { case INTERRUPT_VC_JPEG: *out_hwirq = INTERRUPT_JPEG; break; case INTERRUPT_VC_USB: *out_hwirq = INTERRUPT_USB; break; case INTERRUPT_VC_3D: *out_hwirq = INTERRUPT_3D; break; case INTERRUPT_VC_DMA2: *out_hwirq = INTERRUPT_DMA2; break; case INTERRUPT_VC_DMA3: *out_hwirq = INTERRUPT_DMA3; break; case INTERRUPT_VC_I2C: *out_hwirq = INTERRUPT_I2C; break; case INTERRUPT_VC_SPI: *out_hwirq = INTERRUPT_SPI; break; case INTERRUPT_VC_I2SPCM: *out_hwirq = INTERRUPT_I2SPCM; break; case INTERRUPT_VC_SDIO: *out_hwirq = INTERRUPT_SDIO; break; case INTERRUPT_VC_UART: *out_hwirq = INTERRUPT_UART; break; case INTERRUPT_VC_ARASANSDIO: *out_hwirq = INTERRUPT_ARASANSDIO; break; } *out_type = IRQ_TYPE_NONE; return 0; }
DT 스크립트에 설정한 argumnet 값으로 hwirq 번호를 알아온다.
- 인터럽트를 사용하는 디바이스는 “interrupts = { bank, irq }” 속성 값을 전달한다.
- bcm2708(rpi) 및 bcm2709(rpi2)에서는 2 개의 인수를 받는데 처음 것은 bank 이고, 두 번째 것은 각 뱅크에 해당하는 개별 인터럽트 번호(0~31번 )이다.
- rpi2 예) out_hwirq = 0~85, 96~99, 101, 105
- 코드 라인 6~7에서 DT 스크립트에서 설정한 “interrupts = { bank, irq }” 형태로 인수가 2개가 아닌 경우 -EINVAL 에러를 반환한다.
- 코드 라인 9~10에서 bank 값이 4 이상인 경우 -EINVAL 에러를 반환한다.
- rpi: 0~2까지 가능
- rpi2: 0~3까지 가능
- 코드 라인 12~13에서 irq 값이 bank당 최대 값 수(32) 이상인 경우 -EINVAL 에러를 반환한다.
- rpi & rpi2: bank당 0~31까지 가능
- 코드 라인 15~16에서 0번 bank를 사용하는 irq 값이 bank #0 최대 인터럽트 수 이상인 경우 -EIVAL 에러를 반환한다.
- rpi & rpi2: bank당 0~20까지 가능
- 코드 라인 18~19에서 3번 bank의 경우 irq 번호가 0~3, 5 및 9번이 아닌 경우 -EINVAL 에러를 반환한다.
- 0~3: core timer, 5=mail box #1, 9=pmu
- 코드 라인 21~22에서 bank 0번의 경우 64번 부터 hw 인터럽트가 시작된다.
- 코드 라인 23~24에서 bank 1번의 경우 0번 부터 hw 인터럽트가 시작된다.
- 코드 라인 25~26에서 bank 2번의 경우 32번 부터 hw 인터럽트가 시작된다.
- 코드 라인 27~28에서 bank 3번의 경우 96번 부터 hw 인터럽트가 시작된다.
- 코드 라인 41~65에서 Video Core에서 사용하는 인터럽트들(#0 ~ #64) 중 11개가 공유되어 Local ARM에 라우팅(#74~#84) 된다.
- 예) 7(vc_usb) -> 74(arm_usb)
- 코드 라인 67~68에서 에러가 없는 경우 out_type에 IRQ_TYPE_NONE을 대입한후 0을 반환한다.
GPIO pinctrl-bcm2835용
rpi 및 rpi2에는 2개의 뱅크에 나뉘어 총 54개의 gpio 라인이 제공되고 이를 28개, 18개, 8개로 나누어 81~83번의 hw 인터럽트를 발생시킨다.
bcm2835_gpio_irq_chip
drivers/pinctrl/bcm/pinctrl-bcm2835.c
static struct irq_chip bcm2835_gpio_irq_chip = { .name = MODULE_NAME, .irq_enable = bcm2835_gpio_irq_enable, .irq_disable = bcm2835_gpio_irq_disable, .irq_set_type = bcm2835_gpio_irq_set_type, };
bcm2835의 gpio 인터럽트용 irq_chip 콜백 함수들
bcm2835_gpio_irq_enable()
drivers/pinctrl/bcm/pinctrl-bcm2835.c
static void bcm2835_gpio_irq_enable(struct irq_data *data) { struct bcm2835_pinctrl *pc = irq_data_get_irq_chip_data(data); unsigned gpio = irqd_to_hwirq(data); unsigned offset = GPIO_REG_SHIFT(gpio); unsigned bank = GPIO_REG_OFFSET(gpio); unsigned long flags; spin_lock_irqsave(&pc->irq_lock[bank], flags); set_bit(offset, &pc->enabled_irq_map[bank]); bcm2835_gpio_irq_config(pc, gpio, true); spin_unlock_irqrestore(&pc->irq_lock[bank], flags); }
gpio irq를 enable 한다. (hwirq=0~53)
- 코드 라인 3에서 irq_chip->chip_data에 저장해둔 pc(pin control)를 알아온다.
- 코드 라인 4에서 gpio hwirq 번호를 알아온다.
- 코드 라인 5~6에서 hwirq에 대한 bank와 offset을 구한다.
- 예) hwirq=53인 경우 bank=1, offset=21
- 코드 라인 10에서 hwirq에 해당하는 enable 비트를 설정한다.
- 코드 라인 11에서 hwirq의 트리거 타입에 따른 gpio 레지스터를 조작하여 enable 설정을 한다.
bcm2835_gpio_irq_disable()
drivers/pinctrl/bcm/pinctrl-bcm2835.c
static void bcm2835_gpio_irq_disable(struct irq_data *data) { struct bcm2835_pinctrl *pc = irq_data_get_irq_chip_data(data); unsigned gpio = irqd_to_hwirq(data); unsigned offset = GPIO_REG_SHIFT(gpio); unsigned bank = GPIO_REG_OFFSET(gpio); unsigned long flags; spin_lock_irqsave(&pc->irq_lock[bank], flags); bcm2835_gpio_irq_config(pc, gpio, false); /* Clear events that were latched prior to clearing event sources */ bcm2835_gpio_set_bit(pc, GPEDS0, gpio); clear_bit(offset, &pc->enabled_irq_map[bank]); spin_unlock_irqrestore(&pc->irq_lock[bank], flags); }
gpio irq를 disable 한다. (hwirq=0~53)
- 코드 라인 3에서 irq_chip->chip_data에 저장해둔 pc(pin control)를 알아온다.
- 코드 라인 4에서 hwirq 번호를 알아온다.
- rpi2: 81 ~ 83
- 코드 라인 5~6에서 hwirq에 대한 bank와 offset을 구한다.
- 예) hwirq=81인 경우 bank=2, offset=17
- 코드 라인 10에서 hwirq의 트리거 타입에 따른 gpio 레지스터를 조작하여 disable 설정을 한다.
- 코드 라인 12에서 2 개의 GPEDS(GPIO Event Detect Status) Register 중 요청 hwirq에 해당하는 비트를 1로 기록하여 이벤트 발생 비트를 클리어한다.
- 코드 라인 13에서 hwirq에 해당하는 enable 비트를 클리어한다.
bcm2835_gpio_irq_set_type()
drivers/pinctrl/bcm/pinctrl-bcm2835.c
static int bcm2835_gpio_irq_set_type(struct irq_data *data, unsigned int type) { struct bcm2835_pinctrl *pc = irq_data_get_irq_chip_data(data); unsigned gpio = irqd_to_hwirq(data); unsigned offset = GPIO_REG_SHIFT(gpio); unsigned bank = GPIO_REG_OFFSET(gpio); unsigned long flags; int ret; spin_lock_irqsave(&pc->irq_lock[bank], flags); if (test_bit(offset, &pc->enabled_irq_map[bank])) ret = __bcm2835_gpio_irq_set_type_enabled(pc, gpio, type); else ret = __bcm2835_gpio_irq_set_type_disabled(pc, gpio, type); spin_unlock_irqrestore(&pc->irq_lock[bank], flags); return ret; }
gpio irq에 해당하는 트리거 타입을 설정한다. (hwirq=0~53)
- 타입 설정 전에 요청 irq에 대해 disable 한 후 트리거 타입을 바꾼다. 그런 후 enable 비트가 설정된 경우 해당 irq에 대해 enable 시킨다.
bcm2836-gpu용
bcm2836_arm_irqchip_gpu
drivers/irqchip/irq-bcm2836.c
static struct irq_chip bcm2836_arm_irqchip_gpu = { .name = "bcm2836-gpu", .irq_mask = bcm2836_arm_irqchip_mask_gpu_irq, .irq_unmask = bcm2836_arm_irqchip_unmask_gpu_irq, };
bcm2836의 GPU 인터럽트용 irq_chip 콜백 함수들
bcm2836_arm_irqchip_mask_gpu_irq()
drivers/irqchip/irq-bcm2836.c
static void bcm2836_arm_irqchip_mask_gpu_irq(struct irq_data *d) { }
1개 gpu 인터럽트의 mask 처리 요청에 대해 아무것도 수행하지 않는다.
bcm2836_arm_irqchip_unmask_gpu_irq()
drivers/irqchip/irq-bcm2836.c
static void bcm2836_arm_irqchip_unmask_gpu_irq(struct irq_data *d) { }
1개 gpu 인터럽트의 unmask 처리 요청에 대해 아무것도 수행하지 않는다.
bcm2836-pmu용
bcm2836_arm_irqchip_pmu
drivers/irqchip/irq-bcm2836.c
static struct irq_chip bcm2836_arm_irqchip_pmu = { .name = "bcm2836-pmu", .irq_mask = bcm2836_arm_irqchip_mask_pmu_irq, .irq_unmask = bcm2836_arm_irqchip_unmask_pmu_irq, };
bcm2836의 pmu 인터럽트용 irq_chip 콜백 함수들
bcm2836_arm_irqchip_mask_pmu_irq()
drivers/irqchip/irq-bcm2836.c
static void bcm2836_arm_irqchip_mask_pmu_irq(struct irq_data *d) { writel(1 << smp_processor_id(), intc.base + LOCAL_PM_ROUTING_CLR); }
1개 pmu 인터럽트의 mask 처리 요청을 수행한다.
bcm2836_arm_irqchip_unmask_pmu_irq()
drivers/irqchip/irq-bcm2836.c
static void bcm2836_arm_irqchip_unmask_pmu_irq(struct irq_data *d) { writel(1 << smp_processor_id(), intc.base + LOCAL_PM_ROUTING_SET); }
1개 pmu 인터럽트의 unmask 처리 요청을 수행한다.
bcm2836-timer용
bcm2836_arm_irqchip_timer
drivers/irqchip/irq-bcm2836.c
static struct irq_chip bcm2836_arm_irqchip_timer = { .name = "bcm2836-timer", .irq_mask = bcm2836_arm_irqchip_mask_timer_irq, .irq_unmask = bcm2836_arm_irqchip_unmask_timer_irq, };
bcm2836의 각 코어에 구성된 4개 local 타이머용 irq_chip 콜백 함수들
bcm2836_arm_irqchip_mask_timer_irq()
drivers/irqchip/irq-bcm2836.c
static void bcm2836_arm_irqchip_mask_timer_irq(struct irq_data *d) { bcm2836_arm_irqchip_mask_per_cpu_irq(LOCAL_TIMER_INT_CONTROL0, d->hwirq - LOCAL_IRQ_CNTPSIRQ, smp_processor_id()); }
4개의 local timer 인터럽트는 cpu별로 동작하는 per-cpu 타입인데 이 중 현재 cpu에 요청한 타이머 인터럽트(irq_data->hwirq(0~3))를 마스크 처리한다.
- cpu별 LOCAL_TIMER_INT_CONTROL 레지스터
- 0x4000_0040 (#0번 core) ~ 0x4000_004c(#3번 core)
- 4 개 timer 인터럽트 중 요청한 인터럽트(0 ~ 3번 중 하나)를 마스크한다.
bcm2836_arm_irqchip_mask_per_cpu_irq()
drivers/irqchip/irq-bcm2836.c
static void bcm2836_arm_irqchip_mask_per_cpu_irq(unsigned int reg_offset, unsigned int bit, int cpu) { void __iomem *reg = intc.base + reg_offset + 4 * cpu; writel(readl(reg) & ~BIT(bit), reg); }
bcm2836 local 인터럽트 컨트롤러에서 per-cpu 인터럽트에 대한 마스크를 수행한다.
- rpi2:
- intc.base는 0x4000_0000
- reg_offset는 0x40
bcm2836_arm_irqchip_unmask_timer_irq()
drivers/irqchip/irq-bcm2836.c
static void bcm2836_arm_irqchip_unmask_timer_irq(struct irq_data *d) { bcm2836_arm_irqchip_unmask_per_cpu_irq(LOCAL_TIMER_INT_CONTROL0, d->hwirq - LOCAL_IRQ_CNTPSIRQ, smp_processor_id()); }
4개의 local timer 인터럽트는 cpu별로 동작하는 per-cpu 타입인데 이 중 현재 cpu에 요청한 타이머 인터럽트(irq_data->hwirq(0~3))를 언마스크 처리한다.
- cpu별 LOCAL_TIMER_INT_CONTROL 레지스터
- 0x4000_0040 (#0번 core) ~ 0x4000_004c(#3번 core)
- 4 개 timer 인터럽트 중 요청한 인터럽트(0 ~ 3번 중 하나)를 클리어한다.
bcm2836_arm_irqchip_unmask_per_cpu_irq()
drivers/irqchip/irq-bcm2836.c
static void bcm2836_arm_irqchip_unmask_per_cpu_irq(unsigned int reg_offset, unsigned int bit, int cpu) { void __iomem *reg = intc.base + reg_offset + 4 * cpu; writel(readl(reg) | BIT(bit), reg); }
bcm2836 local 인터럽트 컨트롤러에서 per-cpu 인터럽트에 대한 언마스크를 수행한다.
머신 specific 인터럽트 컨트롤러 초기화
디바이스 트리를 사용하지 않는 경우 다음 코드들이 적용된다.
- 참고로 ARM32 시스템에서 최근 v4.4 이상 커널들과 모든 ARM64 시스템은 모두 디바이스 트리를 사용하는 방식을 사용한다.
bcm2708_init_irq() & bcm2709_init_irq()
arch/arm/mach-bcm2708/bcm2708.c
void __init bcm2708_init_irq(void) { armctrl_init(__io_address(ARMCTRL_IC_BASE), 0, 0, 0); }
인터럽트 컨트롤러의 base 가상 주소를 인수로 인터럽트 컨트롤러를 초기화한다.
arch/arm/mach-bcm2709/bcm2709.c
void __init bcm2709_init_irq(void) { armctrl_init(__io_address(ARMCTRL_IC_BASE), 0, 0, 0); }
인터럽트 컨트롤러의 base 가상 주소를 인수로 인터럽트 컨트롤러를 초기화한다.
rpi vs rpi2 ARM 인터럽트 컨트롤러 주소
BCM2708을 사용하는 rpi와 BCM2709를 사용하는 rpi2는 내부에서 사용하는 디바이스들의 구성은 거의 유사하나 peripheral base 물리 주소가 다르고 이에 따른 가상 주소도 다르다.
- peripheral base 물리 주소 -> 가상 주소
- rpi: 0x2000_0000 -> 0xf200_0000
- rpi2: 0x3f00_0000 -> 0xf300_0000
rpi 물리 주소
#define BCM2708_PERI_BASE 0x20000000 #define ARM_BASE (BCM2708_PERI_BASE + 0xB000) /* BCM2708 ARM control block */ #define ARMCTRL_IC_BASE (ARM_BASE + 0x200) /* ARM interrupt controller */
- rpi의 인터럽트 컨트롤러 물리 주소 = 0x2000_b200
- rpi의 인터럽트 컨트롤러 가상 주소 = 0xf200_b200
rpi2 물리 주소
#define BCM2708_PERI_BASE 0x3F000000 #define ARM_BASE (BCM2708_PERI_BASE + 0xB000) /* BCM2708 ARM control block */ #define ARMCTRL_IC_BASE (ARM_BASE + 0x200) /* ARM interrupt controller */
- rpi2의 인터럽트 컨트롤러 물리 주소 = 0x3f00_b200
- rpi2의 인터럽트 컨트롤러 가상 주소 = 0xf300_b200
armctrl_init() – bcm2709 용
arch/arm/mach-bcm2709/armctrl.c
/** * armctrl_init - initialise a vectored interrupt controller * @base: iomem base address * @irq_start: starting interrupt number, must be muliple of 32 * @armctrl_sources: bitmask of interrupt sources to allow * @resume_sources: bitmask of interrupt sources to allow for resume */
int __init armctrl_init(void __iomem * base, unsigned int irq_start, u32 armctrl_sources, u32 resume_sources) { unsigned int irq; for (irq = 0; irq < BCM2708_ALLOC_IRQS; irq++) { unsigned int data = irq; if (irq >= INTERRUPT_JPEG && irq <= INTERRUPT_ARASANSDIO) data = remap_irqs[irq - INTERRUPT_JPEG]; if (irq >= IRQ_ARM_LOCAL_CNTPSIRQ && irq <= IRQ_ARM_LOCAL_TIMER) { irq_set_percpu_devid(irq); irq_set_chip_and_handler(irq, &armctrl_chip, handle_percpu_devid_irq); set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); } else { irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq); set_irq_flags(irq, IRQF_VALID | IRQF_PROBE | IRQF_DISABLED); } irq_set_chip_data(irq, (void *)data); } armctrl_pm_register(base, irq_start, resume_sources); init_FIQ(FIQ_START); armctrl_dt_init(); return 0; }
bcm2709용 인터럽트 컨트롤러를 초기화하고 각 인터럽트에 대한 핸들러들을 준비한다.
- 코드 라인 6에서 인터럽트 컨트롤러에 할당된 irq 수 만큼 루프를 돈다.
- rpi: SPARE_ALLOC_IRQS=394
- rpi2: SPARE_ALLOC_IRQS=480
- 코드 라인 8~9에서 GPU용 리눅스 irq를 hwirq로 변환한다.
- irq 번호가 INTERRUPT_JPEG(74) ~ INTERRUPT_ARASANSDIO(84) 번까지의 irq는 GPU(VC)가 ARM용으로 routing 한 리눅스 irq 번호이므로 기존 GPU(VC)용 hwirq 번호를 알아낸 후 data에 대입한다.
- GPU(VC) hardware irq 중 11개의 irq는 ARM에서 같이 공유하여 사용하기 위해 ARM pending 레지스터로 라우팅(변환)하여 사용한다.
- remap_irqs[]
- INTERRUPT_VC_JPEG(7)
- INTERRUPT_VC_USB(9)
- (..생략..)
- INTERRUPT_VC_ARASANSDIO(62) 까지 총 11개가 등록되어 있다.
- 코드 라인 10~13에서 irq 번호가 IRQ_ARM_LOCAL_CNTPSIRQ(96) ~ IRQ_ARM_LOCAL_TIMER(107)번 사이의 local 인터럽트인 경우 core별로 전용 사용되므로 per-cpu 디바이스를 취급하는 핸들러를 설정한다.
- 코드 라인 14~17에서 그 외의 irq 번호인 경우 일반 핸들러를 설정한다.
- 코드 라인 18에서 hwirq 번호로 chip_data를 구성한다.
- 코드 라인 21에서 PM(Power Management)를 위해 armctrl_info 구조체에 인터럽트 정보를 대입해둔다.
- 코드 라인 22에서 FIQ 인터럽트 핸들러를 준비한다.
- 코드 라인 23에서 Device Tree 스크립트를 통해 해당 인터럽트 컨트롤러 정보를 읽어 irq_domain을 구성한다.
- 코드 라인 24에서 성공 값 0을 반환한다.
다음 그림은 armctrl_init() 함수의 처리 과정을 보여준다.
Machine용 IRQ Handler (Kernel v4.0 for rpi2)
arch_irq_handler_default 매크로(for RPI2)
arch/arm/mach-bcm2709/include/mach/entry-macro.S
/* * Interrupt handling. Preserves r7, r8, r9 */
.macro arch_irq_handler_default 1: get_irqnr_and_base r0, r2, r6, lr .endm
IPI(Inter Process Interrupt) 처리
arch/arm/mach-bcm2709/include/mach/entry-macro.S
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp /* get core number */ mrc p15, 0, \base, c0, c0, 5 ubfx \base, \base, #0, #2 /* get core's local interrupt controller */ ldr \irqstat, = __io_address(ARM_LOCAL_IRQ_PENDING0) @ local interrupt source add \irqstat, \irqstat, \base, lsl #2 ldr \tmp, [\irqstat] #ifdef CONFIG_SMP /* test for mailbox0 (IPI) interrupt */ tst \tmp, #0x10 beq 1030f /* get core's mailbox interrupt control */ ldr \irqstat, = __io_address(ARM_LOCAL_MAILBOX0_CLR0) @ mbox_clr add \irqstat, \irqstat, \base, lsl #4 ldr \tmp, [\irqstat] clz \tmp, \tmp rsb \irqnr, \tmp, #31 mov \tmp, #1 lsl \tmp, \irqnr str \tmp, [\irqstat] @ clear interrupt source dsb mov r1, sp adr lr, BSYM(1b) b do_IPI #endif
pending 레지스터를 읽어 IPI 처리 요청이 있는 경우 mailbox0를 읽어 인터럽트 소스를 알아와서 모든 설정된 IPI 처리를 한다. 처리 순서는 높은 IPI 번호부터 처리한다. (irqnr=r0, irqstat=r2, base=r6, tmp=lr)
- 코드 라인 4~10에서 cpu 번호를 구한 후 그 cpu에 해당하는 pending 레지스터 값을 읽어와서 \tmp에 대입한다.
- 예) cpu #3에 대한 레지스터 가상 주소: ARM_LOCAL_IRQ_PENDING0(0xf400_0060) + cpu(3) * 4 = 0xf400_006c
- 코드 라인 13~14에서 IPI(mailbox0) 인터럽트 요청이 아닌 경우 1030 레이블로 이동한다.
- pending 레지스터의 0x10 비트: mailbox0
- 코드 라인 17~19에서 cpu에 해당하는 mailbox0 값을 읽어와서 \tmp에 대입한다.
- 코드 라인 20~21에서 가장 좌측에 설정된 인터럽트 비트를 \irqnr에 대입한다. (IPI는 역순으로 우선 처리된다.)
- 코드 라인 22~24에서 클리어하고자 하는 IPI 인터럽트 비트만 설정하여 읽어왔던 mailbox0에 저장한다.
- clz: 좌측부터 0으로 시작하는 비트 수
- mailbox는 str 명령을 통하여 값이 저장되는 것이 아니라 값에 해당하는 인터럽트 소스들을 클리어 한다.
- 코드 라인 26~28에서 두 번째 인수 r1에 스택에 있는 pt_regs를 대입하고, 복귀 주소로 arch_irq_handler_default 매크로 안에 있는 get_irqnr_and_base 호출 위치를 설정한 다음 IPI 처리를 위해 do_IPI() 함수를 호출한다.
- IPI 처리가 다 완료될 때 까지 루프를 돈다.
- 참고: do_IPI() | 문c
ARM(GPU) 및 ARM 인터럽트 처리
1030: /* check gpu interrupt */ tst \tmp, #0x100 beq 1040f ldr \base, =IO_ADDRESS(ARMCTRL_IC_BASE) /* get masked status */ ldr \irqstat, [\base, #(ARM_IRQ_PEND0 - ARMCTRL_IC_BASE)] mov \irqnr, #(ARM_IRQ0_BASE + 31) and \tmp, \irqstat, #0x300 @ save bits 8 and 9 /* clear bits 8 and 9, and test */ bics \irqstat, \irqstat, #0x300 bne 1010f tst \tmp, #0x100 ldrne \irqstat, [\base, #(ARM_IRQ_PEND1 - ARMCTRL_IC_BASE)] movne \irqnr, #(ARM_IRQ1_BASE + 31) @ Mask out the interrupts also present in PEND0 - see SW-5809 bicne \irqstat, #((1<<7) | (1<<9) | (1<<10)) bicne \irqstat, #((1<<18) | (1<<19)) bne 1010f tst \tmp, #0x200 ldrne \irqstat, [\base, #(ARM_IRQ_PEND2 - ARMCTRL_IC_BASE)] movne \irqnr, #(ARM_IRQ2_BASE + 31) @ Mask out the interrupts also present in PEND0 - see SW-5809 bicne \irqstat, #((1<<21) | (1<<22) | (1<<23) | (1<<24) | (1<<25)) bicne \irqstat, #((1<<30)) beq 1020f 1010: @ For non-zero x, LSB(x) = 31 - CLZ(x^(x-1)) sub \tmp, \irqstat, #1 eor \irqstat, \irqstat, \tmp clz \tmp, \irqstat sub \irqnr, \tmp b 1050f
pending #0 레지스터를 읽어 ARM(GPU) #1, ARM(GPU)#2, ARM #0 인터럽트 순서대로 발생한 인터럽트 소스의 ISR을 수행한다.
- 예) ARM(GPU) #1에 배치된 arm dma #0 인터럽트가 요청된 경우
- pending 0 레지스터 값=0x100, pending 1 레지스터 값=0x1_0000 (bit16), 최종 인터럽트 번호 \irqnr=16
- 예) ARM(GPU) #2에 배치한 arm gpio 인터럽트가 요청된 경우
- pending 0 레지스터 값=0x200, pending 2 레지스터 값=0x10_0000 (bit20), 최종 인터럽트 번호 \irqnr=52
- 예) ARM #0에 배치한 arm uart 인터럽트가 요청된 경우
- pending 0 레지스터 값=0x8_0000 (bit19), 최종 인터럽트 번호 \irqnr=83
- 코드 라인 3~4에서 ARM(GPU) 인터럽트 처리 요청이 없으면 1040 레이블로 이동한다.
- 코드 라인 6~8에서 pending #0 레지스터 값을 읽어 /irqstat에 저장한다.
- ARM 인터럽트 컨트롤러 가상 주소를 구해 \base에 저장한다.
- =IO_ADDRESS(0x3f00_0000(기본 주소) + 0xb000(peri offset) + 0x200(인터럽트 컨트롤러 offset) = 0xf300_b200
- ARM 인터럽트 컨트롤러 가상 주소를 구해 \base에 저장한다.
- 코드 라인 9에서 ARM #0이 담당하는 IRQ 끝 번호 값(95)을 \irqnr에 대입한다.
- 코드 라인 10~13에서 pending #0 레지스터에서 읽었던 /irqstat에서 ARM #1(GPU #1) 또는 ARM #2(GPU #2)에 해당하는 비트를 클리어한다. 클리어 전에도 설정된 적 없으면 ARM #0 인터럽트를 처리하기 위해 1010 레이블로 이동한다.
- 코드 라인 15~21에서 ARM #1(GPU #1)에 해당하는 경우 pending #1 레지스터 값을 읽어 /irqstat에 저장하되 VideoCore에서 사용하는 인터럽트에 해당하는 7, 9, 10, 18, 19번 비트들을 제거한다. IRQ #1(GPU #1)의 인터럽트 끝 번호(31)를 \irqnr에 대입한 후 해당 인터럽트 수행을 위해 1010 레이블로 이동한다.
- 코드 라인 23~29에서 ARM #2(GPU #2)에 해당하는 경우 pending #2 레지스터 값을 읽어 /irqstat에 저장하되 VideoCore에서 사용하는 인터럽트에 해당하는21~25, 30번 비트들을 제거한다. IRQ #2(GPU #2)의 인터럽트 끝 번호(63)를 \irqnr에 대입하고 해당 인터럽트 수행을 위해 계속 아래 1010 레이블로 진행한다.
- 코드 라인 32~36에서 처리할 인터럽트 끝 번호(irq0=95, irq1=31, ir12=63, local irq=127)에서 \irqstat의 첫 인터럽트 비트에 해당하는 번호를 뺀 후 해당 인터럽트 수행을 위해 1050 레이블로 이동한다.
Local Interrupt 처리
1040: cmp \tmp, #0 beq 1020f /* handle local (e.g. timer) interrupts */ @ For non-zero x, LSB(x) = 31 - CLZ(x^(x-1)) mov \irqnr, #(ARM_IRQ_LOCAL_BASE + 31) sub \irqstat, \tmp, #1 eor \irqstat, \irqstat, \tmp clz \tmp, \irqstat sub \irqnr, \tmp 1050: mov r1, sp @ @ routine called with r0 = irq number, r1 = struct pt_regs * @ adr lr, BSYM(1b) b asm_do_IRQ 1020: @ EQ will be set if no irqs pending .endm
local ARM 인터럽트 처리요청이 있는 경우 발생한 인터럽트 소스의 ISR을 수행한다.
- 예) Local ARM 인터럽트에 배치한 arm timer 인터럽트가 요청된 경우
- local interrupt controller 레지스터 값=0x8, 최종 인터럽트 번호 \irqnr=99
- 코드 라인 2~3에서 pending 레지스터 값이 0인 경우 더이상 처리할 인터럽트가 없으므로 종료한다.
- 코드 라인 7~11에서 Local ARM이 처리하는 끝 번호(127) 값을 \irqnr에 대입한다. 끝 번호(127)에서 /tmp 인터럽트 비트들 중 가장 마지막 비트에 해당하는 번호를 뺀다
- 코드 라인 13~18에서 r1 레지스터에 pt_regs 주소를 대입하고, lr에 이 매크로(get_irqnr_and_base)를 호출하는 곳을 담고 ISR을 호출한다.
FIQ 초기화
init_FIQ()
arch/arm/kernel/fiq.c
void __init init_FIQ(int start) { unsigned offset = FIQ_OFFSET; dfl_fiq_insn = *(unsigned long *)(0xffff0000 + offset); get_fiq_regs(&dfl_fiq_regs); fiq_start = start; }
default FIQ 인터럽트 처리기를 백업하기 위해 fiq 벡터 값과 fiq 모드에서 사용하는 레지스터들 일부(r8 ~ r12, sp, lr)를 백업해둔다.
- fiq가 필요한 owner task에서 핸들러를 준비하여 요청/해제등을 수행 시 파괴된 레지스터와 fiq 벡터들을 복원시 사용하기 위해 백업해둔다.
- STM 및 LDM 수행은 보통 atomic 하게 수행되지만 cpu가 low interrupt latency 모드를 사용하는 경우 STM과 LDM 명령이 atomic하게 이루어지지 않아 STM 도중에 fiq exception 되는 경우 완전하게 스택에 기록하지 않는 문제가 발생하고 fiq 인터럽트 서비스 루틴 수행 후 다시 원래 모드 루틴으로 복귀할 때 LDM으로 읽은 값(최종 값이 보통 pc)에 가베지가 로드되어 문제가 발생할 수 있다. 이런 경우를 위해 fiq 모드에서 사용하였던 default 인터럽트 핸들러용 레지스터들을 백업해두고 이를 복원하는 용도로 사용한다.
- fiq는 irq에 비해 약 10 ~ 20여배 latency가 작다. (10 ~ 20 ns vs 수 백 ns)
- 참고: [ARM] 3256/1: Make the function-returning ldm’s use sp as the base register
- rpi2 예)
- “usb_fiq” 라는 이름의 usb 드라이버에서 fiq를 사용한다.
- hcd_init_fiq() – drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c
- 코드 라인 3~4에서 fiq에 해당하는 exception 벡터에 기록된 명령을 읽어와서 전역 변수 dfl_fiq_insn에 저장한다.
- 코드 라인 5에서 fiq 모드로 진입한 후 레지스터 백업 장소 dfl_fiq_regs->r8 위치에 레지스터 r8 ~ r12, sp, lr을 저장하고 다시 svc 모드로 돌아온다.
- 코드 라인 6에서 fiq_start에 hw irq 시작 번호를 대입한다.
- rpi: 85
- rpi2: 128
ENTRY(__get_fiq_regs) mov r2, #PSR_I_BIT | PSR_F_BIT | FIQ_MODE mrs r1, cpsr msr cpsr_c, r2 @ select FIQ mode mov r0, r0 @ avoid hazard prior to ARMv4 stmia r0!, {r8 - r12} str sp, [r0], #4 str lr, [r0] msr cpsr_c, r1 @ return to SVC mode mov r0, r0 @ avoid hazard prior to ARMv4 ret lr ENDPROC(__get_fiq_regs)
FIQ 모드로 진입한 후 인수 r0 레지스터가 가리키는 주소에 현재 레지스터 r8 ~ r12, sp, lr을 저장하고 다시 SVC 모드로 돌아온다.
Device Tree에서 인터럽트 컨트롤러 노드 정보를 읽어 irq domain 생성
armctrl_dt_init()
arch/arm/mach-bcm2709/armctrl.c
void __init armctrl_dt_init(void) { struct device_node *np; struct irq_domain *domain; np = of_find_compatible_node(NULL, NULL, "brcm,bcm2708-armctrl-ic"); if (!np) return; domain = irq_domain_add_legacy(np, BCM2708_ALLOC_IRQS, IRQ_ARMCTRL_START, 0, &armctrl_ops, NULL); WARN_ON(!domain); }
디바이스 트리 스크립트에 설정된 인터럽트 컨트롤러 명이 “brcm,bcm2708-armctrl-ic”인 경우 irq_domain을 추가하고 legacy 방법으로 domain을 최대 할당 인터럽트 수 만큼 지정한다.
- 최대 할당 인터럽트 수
- rpi: 394
- rpi2: 480
참고
- Interrupts -1- (Interrupt Controller) | 문c
- Interrupts -2- (irq chip) | 문c
- Interrupts -3- (irq domain) | 문c
- Interrupts -4- (Top-Half & Bottom-Half) | 문c
- Interrupts -5- (Softirq) | 문c – 현재 글
- Interrupts -6- (IPI Cross-call) | 문c
- Interrupts -7- (Workqueue 1) | 문c
- Interrupts -8- (Workqueue 2) | 문c
- Interrupts -9- (GIC v3 Driver) | 문c
- Interrupts -10- (irq partition) | 문c
- Interrupts -11- (RPI2 IC Driver) | 문c – 현재 글
- Interrupts -12- (irq desc) | 문c