Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 217. WinDbg - PCI 장치 열거 [링크 복사], [링크+제목 복사],
조회: 1106
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 
(연관된 글이 3개 있습니다.)

WinDbg - PCI 장치 열거

PC에 연결된 PCI Device 장치는 "Device Manager"를 통해 "View" / "Devices by connection" 메뉴를 통해 알 수 있습니다.

[그림: Hyper-V + Generation 1 VM 환경에서 본 PCI 장치]
pci_tree_1.png

위의 경우, "Intel(R) 82371AB/EB PCI Bus Master IDE Controller" 장치가 PCI 0번 버스에 Device == 7, Function == 1로 연결돼 있는 것을 보여줍니다.

혹시, 이론상 장착 가능한 PCI 장치의 수가 있을까요? 이에 대해 검색해 보면,

What limits the number of buses, devices and functions on a PCI bus?
; https://electronics.stackexchange.com/questions/393830/what-limits-the-number-of-buses-devices-and-functions-on-a-pci-bus

pci_tree_3.png

Bus Number로 8비트, Device Number로 5비트, Function Number로 3비트를 사용하므로, 각각 256, 32, 8개까지 지정할 수 있습니다. 정리해 보면, 1대의 PC가 256개의 Bus를 가질 수 있고, 개별 버스는 32개의 장치를, 개별 장치는 최대 8개의 Function을 내장하는 것이 가능합니다. 물론, 이것은 이론적으로 그렇다는 것이지 실제 규격은 Motherboard의 재량에 따라 달라집니다.

PCI 장치 열거는 WinDbg로도 !pcitree 명령어를 이용해 확인할 수 있는데요,

// Hyper-V + "Generation 1" 유형의 VM

4: kd> !pcitree
Bus 0x0 (FDO Ext ffff878f10a1ca20)
  (d=0,  f=0) 80867192 devext 0xffff878f10a174b0 devstack 0xffff878f10a17360 0600 Bridge/HOST to PCI
  (d=7,  f=0) 80867110 devext 0xffff878f10a394b0 devstack 0xffff878f10a39360 0601 Bridge/PCI to ISA
  (d=7,  f=1) 80867111 devext 0xffff878f10a3b4b0 devstack 0xffff878f10a3b360 0101 Mass Storage Controller/IDE
  (d=8,  f=0) 14145353 devext 0xffff878f10a3f4b0 devstack 0xffff878f10a3f360 0300 Display Controller/VGA
Total PCI Root busses processed = 1
Total PCI Segments processed = 1

Device Manager에서 확인했던 "Intel(R) 82371AB/EB PCI Bus Master IDE Controller" 장치가 !pcitree에서는 "Mass Storage Controller/IDE"라고 보입니다. 뿐만 아니라 !pci 명령어로도 장치 열거가 가능한데요,

4: kd> !pci
PCI Segment 0 Bus 0
00:0  8086:7192.03  Cmd[0006:.mb...]  Sts[0200:.....]  Intel Host Bridge
07:0  8086:7110.01  Cmd[0007:imb...]  Sts[0200:.....]  Intel ISA Bridge  SubID:1414:0000
07:1  8086:7111.01  Cmd[0005:i.b...]  Sts[0280:.....]  Intel IDE Controller
07:3  8086:7113.02  Cmd[0001:i.....]  Sts[0280:.....]  Intel Other Bridge
08:0  1414:5353.00  Cmd[011e:.mb..s]  Sts[0000:.....]  VGA Compatible Controller

// 각 장치의 출력에서 처음 "xx:y"는 "Device xx, Function y"를 의미합니다.

특이하게도 !pcitree와 Device Manager에는 보이지 않던 장치인 "Bus: 0, Device: 7, Function: 3"인 "Intel Other Bridge" 장치가 보입니다. 또한, !pci 명령어의 경우 특정 장치만을 골라 자세한 정보를 확인할 수도 있는데, 가령 bus == 0, device == 7, function == 3인 장치에 대한 정보는 이렇게 확인합니다.

// !pci [flags == 0x1FF] [bus] [device] [function]
// 0x1ff == 0b111111111 (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100)

0: kd> !pci 0x1FF 0 7 3

PCI Configuration Space (Segment:0000 Bus:00 Device:07 Function:03)
Common Header:
    00: VendorID       8086 Intel Corporation
    02: DeviceID       7113
    04: Command        0001 IOSpaceEn 
    06: Status         0280 FB2BCapable DEVSELTiming:1
    08: RevisionID     02
    09: ProgIF         00
    0a: SubClass       80 Other Bridge
    0b: BaseClass      06 Bridge Device
    0c: CacheLineSize  0000
    0d: LatencyTimer   00
    0e: HeaderType     00
    0f: BIST           00
    10: BAR0           00000000
    14: BAR1           00000000
    18: BAR2           00000000
    1c: BAR3           00000000
    20: BAR4           00000000
    24: BAR5           00000000
    28: CBCISPtr       00000000
    2c: SubSysVenID    0000
    2e: SubSysID       0000
    30: ROMBAR         00000000
    34: CapPtr         00
    3c: IntLine        00
    3d: IntPin         01
    3e: MinGnt         00
    3f: MaxLat         00
Device Private:
    40: 00000401 00000000 00000000 00000000
    50: 00000000 00000000 02000000 10000000
    60: 40000000 10c700e0 00000000 00000000
    70: 00000000 00000000 00000000 00000000
    80: 00000001 00000000 00000000 00000000
    90: 00000000 00000000 00000000 00000000
    a0: 00000000 00000000 00000000 00000000
    b0: 00000000 00000000 00000000 00000000
    c0: 00000000 00000000 00000000 00000000
    d0: 00000000 00000000 00000000 00000000
    e0: 00000000 00000000 00000000 00000000
    f0: 00000000 00000000 00000000 00000000

출력의 최상단을 보면 "PCI Configuration Space"라는 문자열이 나오는데요, 이는 PCI 장치가 제공하는 256 바이트 크기의 메모리 영역을 의미합니다. 즉, BIOS/OS가 부팅 시에 PCI 장치를 찾아 해당 장치로부터 저 정보를 읽어와 초기화를 진행하는 것입니다.

따라서, OS는 PCI 장치로부터 Vendor ID, Device ID, Revision ID, BaseClass, SubClass 등의 정보를 읽어올 수 있고 이를 통해 해당 장치를 다룰 수 있는 Device Driver를 로드할 수 있습니다.

참고로, 근래의 Hyper-V 환경에서는 Generation 2 유형의 VM을 주로 만들게 되는데요, 이런 VM에서는 PCI 장치가 아예 열거되지 않습니다.

8: kd> !pcitree
Total PCI Root busses processed = 0
Total PCI Segments processed = 1

0: kd> !pci 602 ffff ff
0: kd> 

왜냐하면, 어차피 소프트웨어로 구현된 장치들이기 때문에 PCI로 추상화시키지 않고 아예 Hyper-V에 맞게 최적화시킨 구조로 재작성했기 때문에,

pci_tree_2.png

보다시피, PCI 버스가 아닌 별도로 구현한 "Microsoft Hyper-V Virtual Machine Bus"로 돼 있어 PCI 장치가 아예 존재하지 않습니다.




PCI 장치의 Configuration Space는 256 바이트였지만, PCIe로 오면서 4,096 바이트까지 늘었습니다. 이러한 정보를 읽어오는 2가지 방법이 있는데요,

Introduction to PCIe
; https://astralvx.com/introduction-to-pcie/

  • Mechanism #1: Configuration Access Mechanism (CAM)
  • Mechanism #2: Enhanced Configuration Access Mechanism (ECAM)

1번 방법이 기존의 PCI 장치에 대해 0xcf8, 0xcfc I/O 포트를 이용해 256 바이트를 접근할 수 있는 방법이고 2번 방법이 PCIe 장치에 대해 운영체제가 메모리 맵핑된 주소 공간을 제공해 4,096 바이트까지 접근할 수 있는 방법입니다.

하지만, 하위 호환성을 유지하기 위해 PCIe 장치로 CAM 방식의 접근 방식을 통해 256 바이트를 읽어오는 것이 가능합니다.

재미 삼아 WinDbg를 이용해 약간의 실습을 해볼까요? ^^ 우선, 1번 CAM 방법은 I/O 포트를 이용해 접근해야 하는데 WinDbg에서는 IN, OUT 명령어를 사용할 수는 없으므로 실습이 안 됩니다. 대신 2번 방법은 가능한데요, WinDbg에서는 PCIe 장치의 Configuration Space를 메모리 매핑된 주소 공간을 !acpicache 명령어로 확인할 수 있기 때문입니다.

그런데 여기서도 한 가지 문제가 있는데요, Hyper-V의 Generation 1 VM 환경은 PCIe 규격까지는 지원하지 않는 것 같습니다. 어차피 가상이라 굳이 그렇게까지 맞춰 줄 필요는 없었던 것 같은데, 그래서 출력에 (PCIe의 ECAM에 대한 메모리 매핑 정보를 제공하는) MCFG 테이블이 없습니다.

// Hyper-V + "Generation 1" 유형의 VM

4: kd> !acpicache
Dumping cached ACPI tables...
  RSDT @(fffff7d100003018) Rev: 0x1 Len: 0x000040 TableID: MICROSFT
  FACP @(fffff7d10000c018) Rev: 0x2 Len: 0x000081 TableID: MICROSFT
  SRAT @(fffff7d10000d018) Rev: 0x2 Len: 0x000190 TableID: MICROSFT
  WAET @(fffff7d10000f018) Rev: 0x1 Len: 0x000028 TableID: MICROSFT
  APIC @(fffff7d100011018) Rev: 0x1 Len: 0x0000b2 TableID: MICROSFT
  SLIC @(ffff878f0f8e31b8) Rev: 0x1 Len: 0x000176 TableID: MICROSFT
  OEM0 @(ffff878f0f8e3358) Rev: 0x1 Len: 0x000064 TableID: MICROSFT
  OEMB @(ffff878f0f8e3688) Rev: 0x1 Len: 0x000064 TableID: MICROSFT
  DSDT @(ffff878f0fab1018) Rev: 0x1 Len: 0x003cd5 TableID: MSFTVM02

이는 Generation 2 VM 환경에서도 동일합니다.

// Hyper-V + "Generation 2" 유형의 VM (어차피 PCI 버스가 없으므로 굳이 MCFG 테이블을 제공하지 않는 듯!)

0: kd> !acpicache
Dumping cached ACPI tables...
  XSDT @(fffff7a140003018) Rev: 0x1 Len: 0x00005c TableID: MICROSFT
  FACP @(fffff7a14000c018) Rev: 0x6 Len: 0x000114 TableID: MICROSFT
  SRAT @(fffff7a14000d018) Rev: 0x2 Len: 0x0003b0 TableID: MICROSFT
  WAET @(fffff7a14000e018) Rev: 0x1 Len: 0x000028 TableID: MICROSFT
  APIC @(fffff7a140010018) Rev: 0x4 Len: 0x0000c8 TableID: MICROSFT
  OEM0 @(ffff9a0313747f38) Rev: 0x1 Len: 0x000064 TableID: MICROSFT
  TPM2 @(ffff9a03137484e8) Rev: 0x3 Len: 0x000034 TableID: VTPM    
  BGRT @(ffff9a0313748548) Rev: 0x1 Len: 0x000038 TableID: MICROSFT
  DSDT @(ffff9a031399f018) Rev: 0x2 Len: 0x01e191 TableID: DSDT01

그래서 이에 대한 실습을 하려면 직접 물리 PC를 이용해야 하는데요, 다음은 Surface Pro 6에서의 출력을 보여줍니다.

// Surface Pro 6 환경

lkd> !acpicache
Dumping cached ACPI tables...
  XSDT @(fffff785c0002018) Rev: 0x1 Len: 0x0000b4 TableID: MSFT    
  MCFG @(fffff785c0003018) Rev: 0x1 Len: 0x00003c TableID: MSFT    
  FACP @(fffff785c000f018) Rev: 0x5 Len: 0x00010c TableID: MSFT    
  APIC @(fffff785c0010018) Rev: 0x3 Len: 0x00012c TableID: MSFT    
  DMAR @(fffff785c0012018) Rev: 0x1 Len: 0x000088 TableID: MSFT    
  HPET @(fffff785c001b018) Rev: 0x1 Len: 0x000038 TableID: MSFT    
  FPDT @(ffffc58c446f2098) Rev: 0x1 Len: 0x000034 TableID: MSFT    
  WSMT @(ffffc58c446f2ed8) Rev: 0x1 Len: 0x000028 TableID: MSFT    
  MSDM @(ffffc58c446f2f28) Rev: 0x1 Len: 0x000055 TableID: 
  SSDT @(ffffc58c448e5018) Rev: 0x2 Len: 0x01a714 TableID: DptfTabl
  SSDT @(ffffc58c448ff758) Rev: 0x2 Len: 0x000574 TableID: Tpm2Tabl
  TPM2 @(ffffc58c448ffcf8) Rev: 0x3 Len: 0x000034 TableID: MSFT    
  SSDT @(ffffc58c44aee018) Rev: 0x2 Len: 0x0033bf TableID: SaSsdt 
  LPIT @(ffffc58c44af1638) Rev: 0x1 Len: 0x000094 TableID: MSFT    
  SSDT @(ffffc58c44af2018) Rev: 0x2 Len: 0x003461 TableID: RTD3_CA
  SSDT @(ffffc58c44af54a8) Rev: 0x2 Len: 0x0008f2 TableID: xh_ca000
  SSDT @(ffffc58c44af6018) Rev: 0x2 Len: 0x0017ae TableID: CpuSsdt
  NHLT @(ffffc58c44af78a8) Rev: 000 Len: 0x00002d TableID: MSFT    
  BGRT @(ffffc58c44af7968) Rev: 0x1 Len: 0x000038 TableID: MSFT    
  DSDT @(ffffc58c449c5018) Rev: 0x2 Len: 0x017fc2 TableID: MSFT   

보다시피 MCFG 테이블이 있고 이에 대해 더 자세한 정보를 확인하면,

lkd> !acpitable MCFG
HEADER - fffff785c0003018
  Signature:               MCFG
  Length:                  0x0000003c
  Revision:                0x01
  Checksum:                0xf9
  OEMID:                   MSFT  
  OEMTableID:              MSFT    
  OEMRevision:             0x00000001
  CreatorID:               MSFT
  CreatorRev:              0x0000005f
BODY - fffff785c000303c
fffff785`c000303c  00 00 00 00 00 00 00 00-00 00 00 e0 00 00 00 00  ................
fffff785`c000304c  00 00 00 ff 00 00 00 00                          ........

MCFG @(fffff785c0003018) 주소에는 헤더 정보가 있고,

// RSDT
// https://wiki.osdev.org/RSDT#Structure

struct ACPISDTHeader {
  char Signature[4];
  uint32_t Length; // 헤더 크기 (위의 경우, 0x0000003c)
  uint8_t Revision;
  uint8_t Checksum;
  char OEMID[6];
  char OEMTableID[8];
  uint32_t OEMRevision;
  uint32_t CreatorID;
  uint32_t CreatorRevision;
};

sizeof(ACPISDTHeader) == 36 (0x24)

ACPISDTHeader.Length (0x3c) 값에서 sizeof(ACPISDTHeader)를 뺀 0x18 (24) 바이트에 해당하는 BODY 정보가 "BODY - fffff785c000303c" 주소부터 이어집니다.

MCFG 테이블의 BODY 해석은 아래의 문서에서 찾을 수 있는데요,

Enhanced Configuration Mechanism
; https://wiki.osdev.org/PCI_Express#Enhanced_Configuration_Mechanism

따라서 다음과 같이 정리할 수 있습니다.

// MCFG 테이블의 BODY 정보 (24바이트)

lkd> dq /c1 fffff785c000303c L3
fffff785`c000303c  00000000`00000000 // Reserved
fffff785`c0003044  00000000`e0000000 // Base address of enhanced configuration mechanism
fffff785`c000304c  00000000`ff000000 // 0 ~ 2: PCI Segment Group Number
                                     // 3: Start PCI bus number decoded by this host bridge (따라서 0x0)
                                     // 4: End PCI bus number decoded by this host bridge (따라서 0xff)
                                     // 5 ~ 8: Reserved

즉, ECAM 정보가 매핑된 물리 주소는 00000000`e0000000이고, Bus Number로 0 ~ 0xff까지 담고 있으니까, (256개의 버스) * (버스 하나당 32개의 Device) * (Device 하나당 8개의 Function) * (1개의 ECAM 정보 크기 4,096 바이트)개가 매핑되어 있으니 총 256MB의 공간(00000000`e0000000 ~ 00000000`efffffff)이 ECAM으로 메모리 매핑된 것입니다.

그리고, 해당 공간에서 특정 PCI 장치의 Configuration Space에 해당하는 위치는 다음의 수식으로 구할 수 있습니다.

Physical_Address = MMIO_Starting_Physical_Address + ((Bus) << 20 | Device << 15 | Function << 12)

예를 들기 위해 장치 하나를 골라 볼까요? ^^

lkd> !pcitree
Bus 0x0 (FDO Ext ffffc58c479e21d0)
  (d=0,  f=0) 80865914 devext 0xffffc58c45ef04b0 devstack 0xffffc58c45ef0360 0600 Bridge/HOST to PCI
  (d=2,  f=0) 80865917 devext 0xffffc58c476e44b0 devstack 0xffffc58c476e4360 0300 Display Controller/VGA
  (d=4,  f=0) 80861903 devext 0xffffc58c472e44b0 devstack 0xffffc58c472e4360 1180 Unknown Base Class/Unknown Sub Class
  (d=5,  f=0) 80861919 devext 0xffffc58c472e64b0 devstack 0xffffc58c472e6360 0480 Multimedia Device/'Other'
  (d=13, f=0) 80869d35 devext 0xffffc58c472e84b0 devstack 0xffffc58c472e8360 0000 Pre PCI 2.0/Pre PCI 2.0 Non-VGA Device
  (d=14, f=0) 80869d2f devext 0xffffc58c479e44b0 devstack 0xffffc58c479e4360 0c03 Serial Bus Controller/USB
  (d=14, f=2) 80869d31 devext 0xffffc58c479e64b0 devstack 0xffffc58c479e6360 1180 Unknown Base Class/Unknown Sub Class
  (d=14, f=3) 80869d32 devext 0xffffc58c479e84b0 devstack 0xffffc58c479e8360 0480 Multimedia Device/'Other'
  (d=15, f=0) 80869d60 devext 0xffffc58c479ea4b0 devstack 0xffffc58c479ea360 1180 Unknown Base Class/Unknown Sub Class
  (d=15, f=1) 80869d61 devext 0xffffc58c479ec4b0 devstack 0xffffc58c479ec360 1180 Unknown Base Class/Unknown Sub Class
  (d=15, f=2) 80869d62 devext 0xffffc58c479ee4b0 devstack 0xffffc58c479ee360 1180 Unknown Base Class/Unknown Sub Class
  (d=15, f=3) 80869d63 devext 0xffffc58c479f04b0 devstack 0xffffc58c479f0360 1180 Unknown Base Class/Unknown Sub Class
  (d=16, f=0) 80869d3a devext 0xffffc58c45fe44b0 devstack 0xffffc58c45fe4360 0780 Simple Serial Communications Controller/'Other'
  (d=16, f=4) 80869d3e devext 0xffffc58c45fe64b0 devstack 0xffffc58c45fe6360 0780 Simple Serial Communications Controller/'Other'
  (d=1c, f=0) 80869d13 devext 0xffffc58c45fe84b0 devstack 0xffffc58c45fe8360 0604 Bridge/PCI to PCI
  Bus 0x1 (FDO Ext ffffc58c479f1190)
    (d=0,  f=0) 11ab2b38 devext 0xffffc58c470e21f0 devstack 0xffffc58c470e20a0 0200 Network Controller/Ethernet
  (d=1d, f=0) 80869d18 devext 0xffffc58c45fec4b0 devstack 0xffffc58c45fec360 0604 Bridge/PCI to PCI
  Bus 0x2 (FDO Ext ffffc58c471ea250)
    (d=0,  f=0) 144da806 devext 0xffffc58c470e51b0 devstack 0xffffc58c470e5060 0108 Mass Storage Controller/Unknown Sub Class
  (d=1e, f=0) 80869d27 devext 0xffffc58c45ff04b0 devstack 0xffffc58c45ff0360 1180 Unknown Base Class/Unknown Sub Class
  (d=1f, f=0) 80869d4e devext 0xffffc58c471e44b0 devstack 0xffffc58c471e4360 0601 Bridge/PCI to ISA
  (d=1f, f=2) 80869d21 devext 0xffffc58c471e64b0 devstack 0xffffc58c471e6360 0580 Memory Controller/'Other'
  (d=1f, f=3) 80869d71 devext 0xffffc58c472ef1b0 devstack 0xffffc58c472ef060 0403 Multimedia Device/Unknown Sub Class
Total PCI Root busses processed = 1
Total PCI Segments processed = 1

bus = 2, device = 0, function = 0에 해당하는 장치의 PCI Configuration Space 장치를 확인하려면,

Physical_Address = 00000000`e0000000 + ((2) << 20 | 0 << 15 | 0 << 12)
                 = 0xe0200000

이렇게 계산한 물리 주소를 덤프해 보면 됩니다.

// 아래의 출력은 MCFG 테이블이 있는 경우에만 실습이 가능합니다.
// 따라서 Hyper-V (Gen1, Gen2 상관없이) VM에서는 실습할 수 없습니다.

lkd> dd /p 0xe0200000 L64
00000000`e0200000  a806144d 00100406 01080200 00000000
00000000`e0200010  a1600004 00000000 00000000 00000000
00000000`e0200020  00000000 00000000 00000000 a801144d
00000000`e0200030  00000000 00000040 00000000 00000100
00000000`e0200040  00035001 00000008 00000000 00000000
00000000`e0200050  00867005 00000000 00000000 00000000
00000000`e0200060  00000000 00000000 00000000 00000000
00000000`e0200070  0002b010 17e88fc1 00102037 00477823
00000000`e0200080  10230142 00000000 00000000 00000000
00000000`e0200090  00000000 0000081f 00000400 0000000e
00000000`e02000a0  001e0003 00000000 00000000 00000000
00000000`e02000b0  80070011 00003000 00002000 00000000
00000000`e02000c0  00000000 00000000 00000000 00000000
00000000`e02000d0  00000003 00000000 00000000 00000000
00000000`e02000e0  00000000 00000000 00000000 00000000
00000000`e02000f0  00000000 00000000 00000000 00000000
00000000`e0200100  14820001 00000000 00000000 00062030
00000000`e0200110  00000000 00000000 000000a0 00000000
00000000`e0200120  00000000 00000000 00000000 00000000
00000000`e0200130  00000000 00000000 00000000 00000000
00000000`e0200140  00000000 00000000 15810003 00000000
00000000`e0200150  00000000 00000000 16810004 00000000
00000000`e0200160  00000000 00000001 18810019 00000000
00000000`e0200170  00000000 75007500 00000000 00000000
00000000`e0200180  00000000 00000000 19010018 10021002

e0200000의 첫 번째 2바이트가 Vendor ID, 그다음 2바이트가 Device ID이므로 a806144dd의 경우 (little endian임을 고려해) Vendor ID == 144d, Device ID == a806인 것으로 해석됩니다. 실제로 !pci 명령어를 통해 확인해 보면,

lkd> !pci 1 2 0 0
PCI Segment 0 Bus 0x2
00:0  144d:a806.00  Cmd[0406:.mb...]  Sts[0010:c....]  Class:1:8:2  SubID:144d:a801
      cf8:80020000  IntPin:1  IntLine:0  Rom:0  cis:0  cap:40
      MEM[0]:a1600004  

정확히 일치하는 것을 볼 수 있습니다. 일단 이 정도만 알아봐도 소프트웨어 개발자로서는 충분할 것 같습니다. ^^




혹시 Device Driver에서 PCI Configuration Space를 ECAM으로부터 읽어오는 방법이 있을까요?

Accessing PCI device configuration space
; https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/accessing-pci-device-configuration-space

Get ECAM base address from KMDF
; https://community.osr.com/t/get-ecam-base-address-from-kmdf/58296

BUS_INTERFACE_STANDARD::GetBusData and location (bus, slot, function)
; https://community.osr.com/t/bus-interface-standard-getbusdata-and-location-bus-slot-function/54670

과거에는 HalGetBusDataByOffset 함수를 사용할 수 있었던 것 같은데,

PCI_SLOT_NUMBER slot = { 0 };
slot.u.bits.DeviceNumber = dwSlot;
slot.u.bits.FunctionNumber = dwFunction;
data_len_ret = HalGetBusDataByOffset(PCIConfiguration, dwBus, slot.u.AsULONG, ulData, dwOffset, sizeof(ulData));

현재는 GUID_BUS_INTERFACE_STANDARD를 대상으로 IRP_MN_QUERY_INTERFACE를 사용하라고 합니다. 이렇게 얻은 ECAM 주소는 이전에 설명한 방법에 따라 bus/device/function을 계산해 덤프하면 될 것입니다.

혹은 버스 드라이버 측에 IRP_MN_READ_CONFIG 메시지를 보내,

Understanding PCI Configuration Space
; https://bsodtutorials.blogspot.com/2014/01/understanding-pci-configuration-space.html

보다 더 안전하게 조회하는 방법도 있습니다. 마지막으로, 약간 불안한 방법이라면, 아마도 "0xe0000000"라는 주소에서 느껴지듯이 Windows 운영체제는 고정적으로 메모리 매핑을 하는 것 같으므로 상황에 따라 그냥 하드 코딩해도 될 것입니다. ^^; 주로 그 주소로 나오긴 하지만, 간혹 테스트를 해보면 0xc0000000 또는 0xd0000000으로 나오는 경우도 있으니 사용 전 확인이 필요합니다.




참고로, !pci 명령어가 꽤나 복잡한 옵션을 제공하는데요,

!pci [Flags [Segment] [Bus [Device [Function [MinAddress MaxAddress]]]]]

첫 번째로 지정한 Flags 값에 따라 이후의 옵션 해석이 달라진다는 점에 주의해야 합니다. 가령, 도움말에서 Flags의 Bit 1을 보면,

Bit 1 (0x2)
Causes the display to include all buses in the range from bus 0 (zero) to the specified Bus.

라고 나오는데요, 따라서 flags에 bit 1이 켜져 있으면 Bus 옵션 값이 함께 지정된 것으로 간주합니다. 예를 들어, Hyper-V VM의 경우 Bus == 0에 모든 장치들이 연결된 것으로 구성하므로 그런 환경에서는 "!pci 2 0"이든 "!pci 2 ff"든 결과는 같습니다.

또 다른 예를 들어볼까요?

Bit 9 (0x200)
Causes the display to include segment information. When this bit is included, the Segment parameter must be included.

즉, bit 9가 켜져 있으면 segment도 지정한 것으로 간주하게 되는데요, 만약 이것이 Bit 1과 함께 켜져 있다면, Segment와 Bus가 모두 지정된 것으로 간주합니다.

// Flags에 따라 "!pci [flags] [segment] [bus]"로 해석

0: kd> !pci 0x202 0 0
PCI Segment 0 Bus 0
00:0  8086:7192.03  Cmd[0006:.mb...]  Sts[0200:.....]  Intel Host Bridge
07:0  8086:7110.01  Cmd[0007:imb...]  Sts[0200:.....]  Intel ISA Bridge  SubID:1414:0000
07:1  8086:7111.01  Cmd[0005:i.b...]  Sts[0280:.....]  Intel IDE Controller
07:3  8086:7113.02  Cmd[0001:i.....]  Sts[0280:.....]  Intel Other Bridge
08:0  1414:5353.00  Cmd[011e:.mb..s]  Sts[0000:.....]  VGA Compatible Controller

0: kd> !pci 0x202 1 0
1f  // 1번 segment에는 장치가 없으므로

대충 어떤 식인지 아시겠죠? ^^ 다른 예를 하나 더 들어 볼까요? 가령 MinAddress MaxAddress라고 나오는 옵션이 유효하려면 그것을 사용하라고 지시한 비트를 켜야 합니다. 이에 해당하는 것이 Bit 2니까,

Bit 2 (0x4)
Causes the display to include information in raw byte format. If MinAddress, MaxAddress, or flag bit 0x8 is set, this bit is automatically set as well.

이제 각각의 옵션 값은 다음과 같은 의미를 갖게 됩니다.

// !pci 0x4 [bus] [device] [function] [MinAddress] [MaxAddress]

0: kd> !pci 0x4 0 7 3 0 0x1f
PCI Segment 0 Bus 0
07:3  8086:7113.02  Cmd[0001:i.....]  Sts[0280:.....]  Intel Other Bridge
 00000000: 86 80 13 71 01 00 80 02-02 00 80 06 00 00 00 00   *...q............*
 00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   *................*

문서에 따르면 PCI segment 1개가 256개의 버스를 지원한다고 하니, 아마도 웬만한 PC에서는 거의 segment 옵션은 그냥 0으로 지정하면 될 듯합니다.




!pcitree 명령어에 대해 이런 식으로 오류가 발생하는 경우가 있습니다.

8: kd> !pcitree
Error retrieving address of PciFdoExtensionListHead

Symbol 파일을 다시 로드하면 정상 동작할 텐데요,

8: kd> .reload /f

만약 그래도 오류가 발생한다면 해당 환경이 Hyper-V의 Generation 2 VM일 경우가 있습니다. 또한, Generation 2 VM의 경우 PCI 장치가 없어 !pci 명령어를 수행하는 경우 다음과 같이 출력되는 것을 볼 수 있습니다.

0: kd> !pci 1
1f

아마도 !pci 명령어는 bus에 연결됐을 가능성이 있는 범위인 0 ~ 0x1f 개를 모두 확인하기 때문에 마지막에 1f가 출력되는 듯합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 2/12/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13883정성태2/12/2025189닷넷: 2317. C# - Memory Mapped I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13882정성태2/10/2025579스크립트: 70. 파이썬 - oracledb 패키지 연동 시 Thin / Thick 모드
13881정성태2/7/2025889닷넷: 2316. C# - Port I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13880정성태2/5/2025868오류 유형: 947. sshd - Failed to start OpenSSH server daemon.
13879정성태2/5/20251107오류 유형: 946. Ubuntu - N: Updating from such a repository can't be done securely, and is therefore disabled by default.
13878정성태2/3/2025987오류 유형: 945. Windows - 최대 절전 모드 시 DRIVER_POWER_STATE_FAILURE 발생 (pacer.sys)
13877정성태1/25/20251082닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI)파일 다운로드1
13876정성태1/25/20251274닷넷: 2314. C# - ProcessStartInfo 타입의 Arguments와 ArgumentList파일 다운로드1
13875정성태1/24/20251264스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
13874정성태1/24/20251211스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
13873정성태1/23/20251106디버깅 기술: 217. WinDbg - PCI 장치 열거
13872정성태1/23/20251089오류 유형: 944. WinDbg - 원격 커널 디버깅이 연결은 되지만 Break (Ctrl + Break) 키를 눌러도 멈추지 않는 현상
13871정성태1/22/20251201Windows: 278. Windows - 윈도우를 다른 모니터 화면으로 이동시키는 단축키 (Window + Shift + 화살표)
13870정성태1/18/20251274개발 환경 구성: 741. WinDbg - 네트워크 커널 디버깅이 가능한 NIC 카드 지원 확대
13869정성태1/18/20251329개발 환경 구성: 740. WinDbg - _NT_SYMBOL_PATH 환경 변수에 설정한 경로로 심벌 파일을 다운로드하지 않는 경우
13868정성태1/17/20251232Windows: 277. Hyper-V - Windows 11 VM의 Enhanced Session 모드로 로그인을 할 수 없는 문제
13867정성태1/17/20251396오류 유형: 943. Hyper-V에 Windows 11 설치 시 "This PC doesn't currently meet Windows 11 system requirements" 오류
13866정성태1/16/20251359개발 환경 구성: 739. Windows 10부터 바뀐 device driver 서명 방법
13865정성태1/15/20251476오류 유형: 942. C# - .NET Framework 4.5.2 이하의 버전에서 HttpWebRequest로 https 호출 시 "System.Net.WebException" 예외 발생
13864정성태1/15/20251427Linux: 114. eBPF를 위해 필요한 SELinux 보안 정책
13863정성태1/14/20251320Linux: 113. Linux - 프로세스를 위한 전용 SELinux 보안 문맥 지정
13862정성태1/13/20251282Linux: 112. Linux - 데몬을 위한 SELinux 보안 정책 설정
13861정성태1/11/20251792Windows: 276. 명령행에서 원격 서비스를 동기/비동기로 시작/중지
13860정성태1/10/20251740디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)
13859정성태1/9/20251850디버깅 기술: 215. Windbg - syscall 이후 실행되는 KiSystemCall64 함수 및 SSDT 디버깅
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...