← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 16/21

PCIe MSI/MSI-X와 interrupt remapping

device가 특정 memory address에 data를 쓰는 MSI transaction이 GIC ITS, x86 APIC remapping, RISC-V IMSIC interrupt file로 변환되는 과정을 비교합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 195 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

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해야 한다.

지연 시간 관점MSI write의 PCIe fabric 지연, translation table cache miss, target interrupt controller delivery, CPU irq-off와 handler 시간이 합쳐진다. affinity가 NUMA/cluster 경계를 넘으면 data와 IRQ locality가 달라진다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64GICv3 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-64MSI address/data의 APIC vector 또는 interrupt-remapping IRTExAPIC 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-VIMSIC per-hart interrupt file과 AIA MSI domainhart/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

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

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을 기록한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractPCI MSI core가 vector를 할당하면 hierarchical irqdomain이 device ID와 event/vector를 parent controller hwirq로 변환하고 compose_msi_msg가 device table에 쓸 address/data를 만든다. affinity 변경은 message 또는 remapping table을 갱신한다.
arm64GICv3 ITS device/event translation과 LPIITS command queue write와 doorbell, table memory visibility, SYNC completion 뒤에만 device MSI를 unmask한다.
x86-64MSI address/data의 APIC vector 또는 interrupt-remapping IRTEvector allocator가 per-CPU vector_irq를 설치하고 IRTE/message update를 완료한 뒤 device를 unmask해야 한다.
RISC-VIMSIC per-hart interrupt file과 AIA MSI domainEIID allocation, target file update와 pending bit drain 뒤 affinity를 전환한다. guest file은 virtualization identity와 별도 lifetime을 가진다.
lifetime boundaryMSI descriptor, vector와 remapping entry는 device가 bus mastering과 MSI를 중지하고 synchronize_irq가 끝날 때까지 유지된다. vector 조기 재사용은 다른 device handler를 실행시킨다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비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-64state 준비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-Vstate 준비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을 기록한다.

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개 줄에 각각 설명을 붙였습니다.

L3677 fwspec.param[1] = IRQ_TYPE_EDGE_RISING;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L3678 } else {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3679 return -EINVAL;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L3680 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3681(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3682 return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L3683}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3684(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3685static 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를 분리해 해석한다.

L3686 unsigned int nr_irqs, void *args)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3687{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3688 msi_alloc_info_t *info = args;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L3689 struct its_device *its_dev = info->scratchpad[0].ptr;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L3690 struct its_node *its = its_dev->its;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L3691 struct irq_data *irqd;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L3692 irq_hw_number_t hwirq;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L3693 int err;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L3694 int i;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L3695(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3696 err = its_alloc_device_irq(its_dev, nr_irqs, &hwirq);

device event 공간에서 연속 hwirq를 확보해 LPI mapping 준비를 한다.

L3697 if (err)

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L3698 return err;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L3699(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3700 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를 연결해 본다.

L3701 if (err)

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L3702 return err;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L3703(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3704 for (i = 0; i < nr_irqs; i++) {

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L3705 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를 연결해 본다.

L3706 if (err)

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L3707 return err;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L3708(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3709 irq_domain_set_hwirq_and_chip(domain, virq + i,

Linux virq에 ITS hwirq와 irq_chip callback을 연결한다.

L3710 hwirq + i, &its_irq_chip, its_dev);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3711 irqd = irq_get_irq_data(virq + i);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3712 irqd_set_single_target(irqd);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3713 irqd_set_affinity_on_activate(irqd);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3714 irqd_set_resend_when_in_progress(irqd);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3715 pr_debug("ID:%d pID:%d vID:%d\n",

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3716 (int)(hwirq + i - its_dev->event_map.lpi_base),

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3717 (int)(hwirq + i), virq + i);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3718 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3719(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3720 return 0;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L3721}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3722(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3723static int its_irq_domain_activate(struct irq_domain *domain,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3724 struct irq_data *d, bool reserve)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3725{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3726 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를 연결해 본다.

L3727 u32 event = its_get_event_id(d);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3728 int cpu;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L3729(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3730 cpu = its_select_cpu(d, cpu_online_mask);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3731 if (cpu < 0 || cpu >= nr_cpu_ids)

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L3732 return -EINVAL;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L3733(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3734 its_inc_lpi_count(d, cpu);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3735 its_dev->event_map.col_map[event] = cpu;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L3736 irq_data_update_effective_affinity(d, cpumask_of(cpu));

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3737(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3738 /* Map the GIC IRQ and event to the device */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L3739 its_send_mapti(its_dev, d->hwirq, event);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3740 return 0;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L3741}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3742(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3743static void its_irq_domain_deactivate(struct irq_domain *domain,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3744 struct irq_data *d)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3745{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3746 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를 연결해 본다.

L3747 u32 event = its_get_event_id(d);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3748(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3749 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를 연결해 본다.

L3750 /* Stop the delivery of interrupts */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L3751 its_send_discard(its_dev, event);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L3752}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3753(blank)

빈 줄은 arm64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L3754static 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를 분리해 해석한다.

L3755 unsigned int nr_irqs)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L3756{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L3757 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개 줄에 각각 설명을 붙였습니다.

L258(blank)

빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L259static const struct msi_parent_ops x86_vector_msi_parent_ops = {

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L260 .supported_flags = X86_VECTOR_MSI_FLAGS_SUPPORTED,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L261 .init_dev_msi_info = x86_init_dev_msi_info,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L262};

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L263(blank)

빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L264struct irq_domain * __init native_create_pci_msi_domain(void)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L265{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L266 if (apic_is_disabled)

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L267 return NULL;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L268(blank)

빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L269 x86_vector_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;

최종 local APIC vector와 target CPU를 관리하는 parent IRQ domain이다.

L270 x86_vector_domain->msi_parent_ops = &x86_vector_msi_parent_ops;

최종 local APIC vector와 target CPU를 관리하는 parent IRQ domain이다.

L271 return x86_vector_domain;

최종 local APIC vector와 target CPU를 관리하는 parent IRQ domain이다.

L272}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L273(blank)

빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L274void __init x86_create_pci_msi_domain(void)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L275{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L276 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를 연결해 본다.

L277}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L278(blank)

빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L279/* Keep around for hyperV */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L280int 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를 분리해 해석한다.

L281 msi_alloc_info_t *arg)

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L282{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L283 init_irq_alloc_info(arg, NULL);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L284(blank)

빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L285 if (to_pci_dev(dev)->msix_enabled)

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L286 arg->type = X86_IRQ_ALLOC_TYPE_PCI_MSIX;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L287 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L288 arg->type = X86_IRQ_ALLOC_TYPE_PCI_MSI;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L289 return 0;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L290}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L291EXPORT_SYMBOL_GPL(pci_msi_prepare);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L292(blank)

빈 줄은 x86-64 PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L293#ifdef CONFIG_DMAR_TABLE

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L294/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L295 * The Intel IOMMU (ab)uses the high bits of the MSI address to contain the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L296 * high bits of the destination APIC ID. This can't be done in the general

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L297 * case for MSIs as it would be targeting real memory above 4GiB not the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L298 * APIC.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L299 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L300static void dmar_msi_compose_msg(struct irq_data *data, struct msi_msg *msg)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L301{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L302 __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를 연결해 본다.

L303}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L304(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개 줄에 각각 설명을 붙였습니다.

L217 .irq_force_complete_move = imsic_irq_force_complete_move,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L218#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L219 .irq_retrigger = imsic_irq_retrigger,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L220 .irq_ack = imsic_irq_ack,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L221 .irq_compose_msi_msg = imsic_irq_compose_msg,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L222 .flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L223};

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L224(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L225static 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를 분리해 해석한다.

L226 unsigned int nr_irqs, void *args)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L227{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L228 struct imsic_vector *vec;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L229(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L230 /* Multi-MSI is not supported yet. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L231 if (nr_irqs > 1)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L232 return -EOPNOTSUPP;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L233(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L234 vec = imsic_vector_alloc(virq, cpu_online_mask);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L235 if (!vec)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L236 return -ENOSPC;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L237(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L238 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를 분리해 해석한다.

L239 handle_edge_irq, NULL, NULL);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L240 irq_set_noprobe(virq);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L241 irq_set_affinity(virq, cpu_online_mask);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L242 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를 연결해 본다.

L243(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L244 return 0;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L245}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L246(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L247static 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를 분리해 해석한다.

L248 unsigned int nr_irqs)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L249{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L250 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를 연결해 본다.

L251(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L252 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를 연결해 본다.

L253 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를 연결해 본다.

L254}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L255(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L256#ifdef CONFIG_GENERIC_IRQ_DEBUGFS

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L257static 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를 분리해 해석한다.

L258 struct irq_data *irqd, int ind)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L259{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L260 if (!irqd) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L261 imsic_vector_debug_show_summary(m, ind);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L262 return;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L263 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L264(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L265 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를 연결해 본다.

L266}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L267#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L268(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L269static const struct irq_domain_ops imsic_base_domain_ops = {

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L270 .alloc = imsic_irq_domain_alloc,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L271 .free = imsic_irq_domain_free,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L272 .select = msi_lib_irq_domain_select,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L273#ifdef CONFIG_GENERIC_IRQ_DEBUGFS

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L274 .debug_show = imsic_irq_debug_show,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L275#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L276};

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L277(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L278static 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를 분리해 해석한다.

L279 struct irq_domain *real_parent, struct msi_domain_info *info)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. PCIe MSI/MSI-X와 interrupt remapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L280{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L281 if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L282 return false;

이 함수가 PCIe MSI/MSI-X와 interrupt remapping 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L283(blank)

빈 줄은 RISC-V PCIe MSI/MSI-X와 interrupt remapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

MSI-X vector를 CPU2에서 CPU7로 이동

device MSI-X table entry 하나가 RX queue 3에 연결되어 있고 interrupt remapping이 활성화됐다고 가정한다.

  1. maskqueue vector를 mask하고 handler와 in-flight IRQ가 끝날 때까지 synchronize한다.
  2. allocate/update새 target CPU7의 vector/LPI/EIID를 확보하거나 remapping entry의 destination을 갱신한다.
  3. flushITS SYNC, IRTE cache invalidation 또는 IMSIC route update completion을 기다린다.
  4. unmask새 message/entry가 device에 보이는 것을 확인한 뒤 vector를 unmask하고 이전 target resource를 해제한다.

결론affinity 변경은 cpumask 한 줄 수정이 아니라 device, remapper와 CPU controller 세 상태의 원자적 교체다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 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해야 한다.

02

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을 본다.

03

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를 확인한다.

04

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을 기록한다.

05

객체 수명과 소유권을 먼저 고정한다

MSI descriptor, vector와 remapping entry는 device가 bus mastering과 MSI를 중지하고 synchronize_irq가 끝날 때까지 유지된다. vector 조기 재사용은 다른 device handler를 실행시킨다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

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-64xAPIC 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-Vhart/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

재현과 계측 절차

  1. MSI-X table과 kernel irqdomain mapping을 동시에 dump해 BDF에서 CPU vector까지 역추적한다.
  2. IRQ affinity를 부하 중 이동시키며 lost/duplicate sequence number와 route-update latency를 측정한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.