01 · QUESTION
무엇을 확인할 것인가
MSI message의 address/data, PCI requester identity, Linux IRQ와 target CPU가 어떤 domain hierarchy로 연결되는가?
PCI MSI core가 vector를 할당하면 hierarchical irqdomain이 device ID와 event/vector를 parent controller hwirq로 변환하고 compose_msi_msg가 device table에 쓸 address/data를 만든다. affinity 변경은 message 또는 remapping table을 갱신한다.
device MSI-X table programming, interrupt-remapper/ITS/IMSIC table update, CPU vector allocation과 irq_desc activation을 구분한다. masked 상태에서 route를 바꾸고 in-flight interrupt를 drain해야 한다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | GICv3 ITS device/event translation과 LPI | 잘못된 DeviceID alias, collection affinity 또는 table cache invalidation 누락은 MSI가 엉뚱한 CPU로 가거나 사라지는 원인이 된다. | BDF/DeviceID, EventID, LPI INTID, collection target, ITS command queue read/write pointer와 pending table을 본다. |
| x86-64 | MSI address/data의 APIC vector 또는 interrupt-remapping IRTE | xAPIC destination width 한계, stale IRTE, source validation 실패 또는 vector migration race가 DMA-remapping fault와 lost interrupt를 만든다. | BDF, MSI address/data, vector, destination APIC ID, IRTE index/source-id, remapping fault와 vector cleanup state를 확인한다. |
| RISC-V | IMSIC per-hart interrupt file과 AIA MSI domain | hart/guest index 계산, MSI page address 또는 EIID width가 틀리면 다른 interrupt file이 pending되거나 write가 fault난다. | hart index, guest index, MSI page PA, EIID, EIP/EIE, parent APLIC source와 irqdomain mapping을 기록한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- GICv3 ITS device/event translation과 LPI
- state
- PCI requester ID를 DeviceID로, MSI data의 event를 EventID로 사용해 ITS가 collection/target redistributor의 LPI로 변환한다. command queue의 MAPD, MAPTI, MOVI와 SYNC가 device/collection table 상태를 관리한다.
- checkpoint
- BDF/DeviceID, EventID, LPI INTID, collection target, ITS command queue read/write pointer와 pending table을 본다.
x86-64
- mechanism
- MSI address/data의 APIC vector 또는 interrupt-remapping IRTE
- state
- remapping이 없으면 message에 destination APIC ID와 vector를 직접 encode한다. VT-d/AMD remapping이 켜지면 message는 IRTE index를 가리키고 IOMMU가 requester 검증, destination과 vector를 선택한다.
- checkpoint
- BDF, MSI address/data, vector, destination APIC ID, IRTE index/source-id, remapping fault와 vector cleanup state를 확인한다.
RISC-V
- mechanism
- IMSIC per-hart interrupt file과 AIA MSI domain
- state
- MSI write가 target hart/guest interrupt-file address와 EIID data를 사용한다. IMSIC domain이 EIID를 Linux IRQ에 연결하고 APLIC가 wired source를 MSI로 변환해 같은 interrupt file에 전달할 수도 있다.
- checkpoint
- hart index, guest index, MSI page PA, EIID, EIP/EIE, parent APLIC source와 irqdomain mapping을 기록한다.
04 · SOURCE
Linux 6.18.37 원본 코드와 줄별 설명
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
arm64 · Linux 6.18.37
GICv3 ITS device/event translation과 LPI
PCI requester ID를 DeviceID로, MSI data의 event를 EventID로 사용해 ITS가 collection/target redistributor의 LPI로 변환한다. command queue의 MAPD, MAPTI, MOVI와 SYNC가 device/collection table 상태를 관리한다.
원본 코드: drivers/irqchip/irq-gic-v3-its.c:3677-3757
3677 fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
3678 } else {
3679 return -EINVAL;
3680 }
3681
3682 return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
3683}
3684
3685static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
3686 unsigned int nr_irqs, void *args)
3687{
3688 msi_alloc_info_t *info = args;
3689 struct its_device *its_dev = info->scratchpad[0].ptr;
3690 struct its_node *its = its_dev->its;
3691 struct irq_data *irqd;
3692 irq_hw_number_t hwirq;
3693 int err;
3694 int i;
3695
3696 err = its_alloc_device_irq(its_dev, nr_irqs, &hwirq);
3697 if (err)
3698 return err;
3699
3700 err = iommu_dma_prepare_msi(info->desc, its->get_msi_base(its_dev));
3701 if (err)
3702 return err;
3703
3704 for (i = 0; i < nr_irqs; i++) {
3705 err = its_irq_gic_domain_alloc(domain, virq + i, hwirq + i);
3706 if (err)
3707 return err;
3708
3709 irq_domain_set_hwirq_and_chip(domain, virq + i,
3710 hwirq + i, &its_irq_chip, its_dev);
3711 irqd = irq_get_irq_data(virq + i);
3712 irqd_set_single_target(irqd);
3713 irqd_set_affinity_on_activate(irqd);
3714 irqd_set_resend_when_in_progress(irqd);
3715 pr_debug("ID:%d pID:%d vID:%d\n",
3716 (int)(hwirq + i - its_dev->event_map.lpi_base),
3717 (int)(hwirq + i), virq + i);
3718 }
3719
3720 return 0;
3721}
3722
3723static int its_irq_domain_activate(struct irq_domain *domain,
3724 struct irq_data *d, bool reserve)
3725{
3726 struct its_device *its_dev = irq_data_get_irq_chip_data(d);
3727 u32 event = its_get_event_id(d);
3728 int cpu;
3729
3730 cpu = its_select_cpu(d, cpu_online_mask);
3731 if (cpu < 0 || cpu >= nr_cpu_ids)
3732 return -EINVAL;
3733
3734 its_inc_lpi_count(d, cpu);
3735 its_dev->event_map.col_map[event] = cpu;
3736 irq_data_update_effective_affinity(d, cpumask_of(cpu));
3737
3738 /* Map the GIC IRQ and event to the device */
3739 its_send_mapti(its_dev, d->hwirq, event);
3740 return 0;
3741}
3742
3743static void its_irq_domain_deactivate(struct irq_domain *domain,
3744 struct irq_data *d)
3745{
3746 struct its_device *its_dev = irq_data_get_irq_chip_data(d);
3747 u32 event = its_get_event_id(d);
3748
3749 its_dec_lpi_count(d, its_dev->event_map.col_map[event]);
3750 /* Stop the delivery of interrupts */
3751 its_send_discard(its_dev, event);
3752}
3753
3754static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq,
3755 unsigned int nr_irqs)
3756{
3757 struct irq_data *d = irq_domain_get_irq_data(domain, virq);라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 81개 줄에 각각 설명을 붙였습니다.
fwspec.param[1] = IRQ_TYPE_EDGE_RISING;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
} else {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return -EINVAL;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned int nr_irqs, void *args)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
msi_alloc_info_t *info = args;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
struct its_device *its_dev = info->scratchpad[0].ptr;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
struct its_node *its = its_dev->its;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
struct irq_data *irqd;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
irq_hw_number_t hwirq;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
int err;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
int i;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
err = its_alloc_device_irq(its_dev, nr_irqs, &hwirq);device event 공간에서 연속 hwirq를 확보해 LPI mapping 준비를 한다.
if (err)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return err;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
err = iommu_dma_prepare_msi(info->desc, its->get_msi_base(its_dev));helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (err)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return err;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (i = 0; i < nr_irqs; i++) {range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
err = its_irq_gic_domain_alloc(domain, virq + i, hwirq + i);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (err)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return err;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
irq_domain_set_hwirq_and_chip(domain, virq + i,Linux virq에 ITS hwirq와 irq_chip callback을 연결한다.
hwirq + i, &its_irq_chip, its_dev);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
irqd = irq_get_irq_data(virq + i);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
irqd_set_single_target(irqd);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
irqd_set_affinity_on_activate(irqd);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
irqd_set_resend_when_in_progress(irqd);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
pr_debug("ID:%d pID:%d vID:%d\n",이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(int)(hwirq + i - its_dev->event_map.lpi_base),이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(int)(hwirq + i), virq + i);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return 0;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static int its_irq_domain_activate(struct irq_domain *domain,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct irq_data *d, bool reserve)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct its_device *its_dev = irq_data_get_irq_chip_data(d);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
u32 event = its_get_event_id(d);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
int cpu;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
cpu = its_select_cpu(d, cpu_online_mask);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (cpu < 0 || cpu >= nr_cpu_ids)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -EINVAL;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
its_inc_lpi_count(d, cpu);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
its_dev->event_map.col_map[event] = cpu;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
irq_data_update_effective_affinity(d, cpumask_of(cpu));helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Map the GIC IRQ and event to the device */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
its_send_mapti(its_dev, d->hwirq, event);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return 0;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void its_irq_domain_deactivate(struct irq_domain *domain,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct irq_data *d)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct its_device *its_dev = irq_data_get_irq_chip_data(d);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
u32 event = its_get_event_id(d);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
its_dec_lpi_count(d, its_dev->event_map.col_map[event]);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
/* Stop the delivery of interrupts */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
its_send_discard(its_dev, event);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned int nr_irqs)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct irq_data *d = irq_domain_get_irq_data(domain, virq);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
x86-64 · Linux 6.18.37
MSI address/data의 APIC vector 또는 interrupt-remapping IRTE
remapping이 없으면 message에 destination APIC ID와 vector를 직접 encode한다. VT-d/AMD remapping이 켜지면 message는 IRTE index를 가리키고 IOMMU가 requester 검증, destination과 vector를 선택한다.
원본 코드: arch/x86/kernel/apic/msi.c:258-304
258
259static const struct msi_parent_ops x86_vector_msi_parent_ops = {
260 .supported_flags = X86_VECTOR_MSI_FLAGS_SUPPORTED,
261 .init_dev_msi_info = x86_init_dev_msi_info,
262};
263
264struct irq_domain * __init native_create_pci_msi_domain(void)
265{
266 if (apic_is_disabled)
267 return NULL;
268
269 x86_vector_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
270 x86_vector_domain->msi_parent_ops = &x86_vector_msi_parent_ops;
271 return x86_vector_domain;
272}
273
274void __init x86_create_pci_msi_domain(void)
275{
276 x86_pci_msi_default_domain = x86_init.irqs.create_pci_msi_domain();
277}
278
279/* Keep around for hyperV */
280int pci_msi_prepare(struct irq_domain *domain, struct device *dev, int nvec,
281 msi_alloc_info_t *arg)
282{
283 init_irq_alloc_info(arg, NULL);
284
285 if (to_pci_dev(dev)->msix_enabled)
286 arg->type = X86_IRQ_ALLOC_TYPE_PCI_MSIX;
287 else
288 arg->type = X86_IRQ_ALLOC_TYPE_PCI_MSI;
289 return 0;
290}
291EXPORT_SYMBOL_GPL(pci_msi_prepare);
292
293#ifdef CONFIG_DMAR_TABLE
294/*
295 * The Intel IOMMU (ab)uses the high bits of the MSI address to contain the
296 * high bits of the destination APIC ID. This can't be done in the general
297 * case for MSIs as it would be targeting real memory above 4GiB not the
298 * APIC.
299 */
300static void dmar_msi_compose_msg(struct irq_data *data, struct msi_msg *msg)
301{
302 __irq_msi_compose_msg(irqd_cfg(data), msg, true);
303}
304 라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 47개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static const struct msi_parent_ops x86_vector_msi_parent_ops = {계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.supported_flags = X86_VECTOR_MSI_FLAGS_SUPPORTED,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.init_dev_msi_info = x86_init_dev_msi_info,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
};C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
struct irq_domain * __init native_create_pci_msi_domain(void)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (apic_is_disabled)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return NULL;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
x86_vector_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;최종 local APIC vector와 target CPU를 관리하는 parent IRQ domain이다.
x86_vector_domain->msi_parent_ops = &x86_vector_msi_parent_ops;최종 local APIC vector와 target CPU를 관리하는 parent IRQ domain이다.
return x86_vector_domain;최종 local APIC vector와 target CPU를 관리하는 parent IRQ domain이다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void __init x86_create_pci_msi_domain(void)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
x86_pci_msi_default_domain = x86_init.irqs.create_pci_msi_domain();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Keep around for hyperV */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
int pci_msi_prepare(struct irq_domain *domain, struct device *dev, int nvec,이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
msi_alloc_info_t *arg)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
init_irq_alloc_info(arg, NULL);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (to_pci_dev(dev)->msix_enabled)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
arg->type = X86_IRQ_ALLOC_TYPE_PCI_MSIX;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
arg->type = X86_IRQ_ALLOC_TYPE_PCI_MSI;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
return 0;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
EXPORT_SYMBOL_GPL(pci_msi_prepare);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_DMAR_TABLEKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The Intel IOMMU (ab)uses the high bits of the MSI address to contain theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* high bits of the destination APIC ID. This can't be done in the generalLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* case for MSIs as it would be targeting real memory above 4GiB not theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* APIC.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
static void dmar_msi_compose_msg(struct irq_data *data, struct msi_msg *msg)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
__irq_msi_compose_msg(irqd_cfg(data), msg, true);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
RISC-V · Linux 6.18.37
IMSIC per-hart interrupt file과 AIA MSI domain
MSI write가 target hart/guest interrupt-file address와 EIID data를 사용한다. IMSIC domain이 EIID를 Linux IRQ에 연결하고 APLIC가 wired source를 MSI로 변환해 같은 interrupt file에 전달할 수도 있다.
원본 코드: drivers/irqchip/irq-riscv-imsic-platform.c:217-283
217 .irq_force_complete_move = imsic_irq_force_complete_move,
218#endif
219 .irq_retrigger = imsic_irq_retrigger,
220 .irq_ack = imsic_irq_ack,
221 .irq_compose_msi_msg = imsic_irq_compose_msg,
222 .flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND,
223};
224
225static int imsic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
226 unsigned int nr_irqs, void *args)
227{
228 struct imsic_vector *vec;
229
230 /* Multi-MSI is not supported yet. */
231 if (nr_irqs > 1)
232 return -EOPNOTSUPP;
233
234 vec = imsic_vector_alloc(virq, cpu_online_mask);
235 if (!vec)
236 return -ENOSPC;
237
238 irq_domain_set_info(domain, virq, virq, &imsic_irq_base_chip, vec,
239 handle_edge_irq, NULL, NULL);
240 irq_set_noprobe(virq);
241 irq_set_affinity(virq, cpu_online_mask);
242 irq_data_update_effective_affinity(irq_get_irq_data(virq), cpumask_of(vec->cpu));
243
244 return 0;
245}
246
247static void imsic_irq_domain_free(struct irq_domain *domain, unsigned int virq,
248 unsigned int nr_irqs)
249{
250 struct irq_data *d = irq_domain_get_irq_data(domain, virq);
251
252 imsic_vector_free(irq_data_get_irq_chip_data(d));
253 irq_domain_free_irqs_parent(domain, virq, nr_irqs);
254}
255
256#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
257static void imsic_irq_debug_show(struct seq_file *m, struct irq_domain *d,
258 struct irq_data *irqd, int ind)
259{
260 if (!irqd) {
261 imsic_vector_debug_show_summary(m, ind);
262 return;
263 }
264
265 imsic_vector_debug_show(m, irq_data_get_irq_chip_data(irqd), ind);
266}
267#endif
268
269static const struct irq_domain_ops imsic_base_domain_ops = {
270 .alloc = imsic_irq_domain_alloc,
271 .free = imsic_irq_domain_free,
272 .select = msi_lib_irq_domain_select,
273#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
274 .debug_show = imsic_irq_debug_show,
275#endif
276};
277
278static bool imsic_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
279 struct irq_domain *real_parent, struct msi_domain_info *info)
280{
281 if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
282 return false;
283 라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 67개 줄에 각각 설명을 붙였습니다.
.irq_force_complete_move = imsic_irq_force_complete_move,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
.irq_retrigger = imsic_irq_retrigger,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.irq_ack = imsic_irq_ack,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.irq_compose_msi_msg = imsic_irq_compose_msg,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
};C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static int imsic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned int nr_irqs, void *args)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct imsic_vector *vec;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Multi-MSI is not supported yet. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (nr_irqs > 1)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -EOPNOTSUPP;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
vec = imsic_vector_alloc(virq, cpu_online_mask);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (!vec)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -ENOSPC;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
irq_domain_set_info(domain, virq, virq, &imsic_irq_base_chip, vec,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
handle_edge_irq, NULL, NULL);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
irq_set_noprobe(virq);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
irq_set_affinity(virq, cpu_online_mask);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
irq_data_update_effective_affinity(irq_get_irq_data(virq), cpumask_of(vec->cpu));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return 0;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void imsic_irq_domain_free(struct irq_domain *domain, unsigned int virq,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned int nr_irqs)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct irq_data *d = irq_domain_get_irq_data(domain, virq);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
imsic_vector_free(irq_data_get_irq_chip_data(d));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
irq_domain_free_irqs_parent(domain, virq, nr_irqs);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_GENERIC_IRQ_DEBUGFSKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
static void imsic_irq_debug_show(struct seq_file *m, struct irq_domain *d,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct irq_data *irqd, int ind)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (!irqd) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
imsic_vector_debug_show_summary(m, ind);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
imsic_vector_debug_show(m, irq_data_get_irq_chip_data(irqd), ind);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static const struct irq_domain_ops imsic_base_domain_ops = {계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.alloc = imsic_irq_domain_alloc,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.free = imsic_irq_domain_free,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.select = msi_lib_irq_domain_select,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
#ifdef CONFIG_GENERIC_IRQ_DEBUGFSKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
.debug_show = imsic_irq_debug_show,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
};C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static bool imsic_init_dev_msi_info(struct device *dev, struct irq_domain *domain,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct irq_domain *real_parent, struct msi_domain_info *info)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
05 · WORKED EXAMPLE
숫자로 검산하기
MSI-X vector를 CPU2에서 CPU7로 이동
device MSI-X table entry 하나가 RX queue 3에 연결되어 있고 interrupt remapping이 활성화됐다고 가정한다.
- maskqueue vector를 mask하고 handler와 in-flight IRQ가 끝날 때까지 synchronize한다.
- allocate/update새 target CPU7의 vector/LPI/EIID를 확보하거나 remapping entry의 destination을 갱신한다.
- flushITS SYNC, IRTE cache invalidation 또는 IMSIC route update completion을 기다린다.
- unmask새 message/entry가 device에 보이는 것을 확인한 뒤 vector를 unmask하고 이전 target resource를 해제한다.
결론affinity 변경은 cpumask 한 줄 수정이 아니라 device, remapper와 CPU controller 세 상태의 원자적 교체다.
06 · DEEP DIVE
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
PCI MSI core가 vector를 할당하면 hierarchical irqdomain이 device ID와 event/vector를 parent controller hwirq로 변환하고 compose_msi_msg가 device table에 쓸 address/data를 만든다. affinity 변경은 message 또는 remapping table을 갱신한다.
device MSI-X table programming, interrupt-remapper/ITS/IMSIC table update, CPU vector allocation과 irq_desc activation을 구분한다. masked 상태에서 route를 바꾸고 in-flight interrupt를 drain해야 한다.
arm64: GICv3 ITS device/event translation과 LPI
PCI requester ID를 DeviceID로, MSI data의 event를 EventID로 사용해 ITS가 collection/target redistributor의 LPI로 변환한다. command queue의 MAPD, MAPTI, MOVI와 SYNC가 device/collection table 상태를 관리한다.
ITS command queue write와 doorbell, table memory visibility, SYNC completion 뒤에만 device MSI를 unmask한다. 디버깅할 때는 BDF/DeviceID, EventID, LPI INTID, collection target, ITS command queue read/write pointer와 pending table을 본다.
x86-64: MSI address/data의 APIC vector 또는 interrupt-remapping IRTE
remapping이 없으면 message에 destination APIC ID와 vector를 직접 encode한다. VT-d/AMD remapping이 켜지면 message는 IRTE index를 가리키고 IOMMU가 requester 검증, destination과 vector를 선택한다.
vector allocator가 per-CPU vector_irq를 설치하고 IRTE/message update를 완료한 뒤 device를 unmask해야 한다. 디버깅할 때는 BDF, MSI address/data, vector, destination APIC ID, IRTE index/source-id, remapping fault와 vector cleanup state를 확인한다.
RISC-V: IMSIC per-hart interrupt file과 AIA MSI domain
MSI write가 target hart/guest interrupt-file address와 EIID data를 사용한다. IMSIC domain이 EIID를 Linux IRQ에 연결하고 APLIC가 wired source를 MSI로 변환해 같은 interrupt file에 전달할 수도 있다.
EIID allocation, target file update와 pending bit drain 뒤 affinity를 전환한다. guest file은 virtualization identity와 별도 lifetime을 가진다. 디버깅할 때는 hart index, guest index, MSI page PA, EIID, EIP/EIE, parent APLIC source와 irqdomain mapping을 기록한다.
객체 수명과 소유권을 먼저 고정한다
MSI descriptor, vector와 remapping entry는 device가 bus mastering과 MSI를 중지하고 synchronize_irq가 끝날 때까지 유지된다. vector 조기 재사용은 다른 device handler를 실행시킨다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
MSI write의 PCIe fabric 지연, translation table cache miss, target interrupt controller delivery, CPU irq-off와 handler 시간이 합쳐진다. affinity가 NUMA/cluster 경계를 넘으면 data와 IRQ locality가 달라진다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | 잘못된 DeviceID alias, collection affinity 또는 table cache invalidation 누락은 MSI가 엉뚱한 CPU로 가거나 사라지는 원인이 된다. | BDF/DeviceID, EventID, LPI INTID, collection target, ITS command queue read/write pointer와 pending table을 본다. |
| x86-64 | xAPIC destination width 한계, stale IRTE, source validation 실패 또는 vector migration race가 DMA-remapping fault와 lost interrupt를 만든다. | BDF, MSI address/data, vector, destination APIC ID, IRTE index/source-id, remapping fault와 vector cleanup state를 확인한다. |
| RISC-V | hart/guest index 계산, MSI page address 또는 EIID width가 틀리면 다른 interrupt file이 pending되거나 write가 fault난다. | hart index, guest index, MSI page PA, EIID, EIP/EIE, parent APLIC source와 irqdomain mapping을 기록한다. |
08 · LAB
재현과 계측 절차
- MSI-X table과 kernel irqdomain mapping을 동시에 dump해 BDF에서 CPU vector까지 역추적한다.
- IRQ affinity를 부하 중 이동시키며 lost/duplicate sequence number와 route-update latency를 측정한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64drivers/irqchip/irq-gic-v3-its.c:3677-3757
- x86-64arch/x86/kernel/apic/msi.c:258-304
- RISC-Vdrivers/irqchip/irq-riscv-imsic-platform.c:217-283
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.