PCI Express 장치인 Intel I211 Ethernet Controller는 시스템 부팅 시 BIOS에 의해 자동으로 구성되며, 해당 장치의 BAR(Base Address Register)를 통해 메모리 매핑된 I/O 영역을 확인할 수 있다. I211의 경우 주로 BAR0을 사용하여 제어 레지스터 공간에 접근하며, 이 영역은 물리적 메모리 주소로 매핑되어 CPU에서 직접 읽고 쓸 수 있다.
PCI 장치의 구성 공간은 표준적인 방식으로 접근 가능하다. 예를 들어, RU(Ring 0 Utility) 환경에서는 특정 버스(Bus), 디바이스(Dev), 기능(Function) 조합에 해당하는 장치의 구성 레지스터를 F6 키로 열어볼 수 있으며, 원하는 오프셋(예: 0x10)에 위치한 BAR 값을 확인하면 된다. 이 값은 일반적으로 0x80000000 | (bus << 16) | (dev << 11) | (fun << 8) | offset 형태의 비트 연산으로 계산되는 가상 주소 기반의 접근 구조를 따른다.
리눅스 환경(Ubuntu 등)에서는 다음 명령어를 통해 PCIe 장치의 메모리 매핑 정보를 쉽게 조회할 수 있다:
lspci -vv
출력 결과에서 대상 I211 장치 항목을 찾고, "Memory at"으로 시작하는 필드를 통해 BAR0의 실제 매핑 주소를 확인할 수 있다.
I211 제어 및 상태 레지스터 직접 접근
Linux 커널 외부에서 사용자 공간 프로그램을 통해 I211의 하드웨어 레지스터에 직접 접근하기 위해 /dev/mem을 사용하여 물리 메모리를 매핑하고, mmap을 통해 제어 영역에 접근하는 예제 코드를 제공한다. 아래는 장치의 제어 레지스터, 상태 레지스터, MAC 주소 등을 읽는 C 프로그램이다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdint.h>
#include <string.h>
// I211 BAR0 기본 주소와 크기 정의
#define I211_BASE_PHYS_ADDR 0xF7500000
#define I211_REGION_SIZE (128 * 1024)
// 주요 레지스터 오프셋 재정의
#define REG_CTRL_OFFSET 0x0000
#define REG_STATUS_OFFSET 0x0008
#define REG_EEPROM_READ 0x0014
#define REG_INTERRUPT_CAUSE 0x00C0
#define REG_MAC_ADDR_LOW 0x5400
#define REG_MAC_ADDR_HIGH 0x5404
// 32비트 레지스터 읽기 함수
static uint32_t read_register(volatile unsigned char* base, size_t offset) {
return *(volatile uint32_t*)(base + offset);
}
// 32비트 레지스터 쓰기 함수
static void write_register(volatile unsigned char* base, size_t offset, uint32_t value) {
*(volatile uint32_t*)(base + offset) = value;
}
int main() {
int mem_fd;
volatile unsigned char* mapped_base;
// 물리 메모리 장치 열기
mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd < 0) {
perror("Failed to open /dev/mem");
return EXIT_FAILURE;
}
// BAR0 영역 매핑
mapped_base = (volatile unsigned char*)mmap(
NULL,
I211_REGION_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED,
mem_fd,
I211_BASE_PHYS_ADDR
);
if (mapped_base == MAP_FAILED) {
perror("Memory mapping failed");
close(mem_fd);
return EXIT_FAILURE;
}
// 제어 레지스터 값 읽기
uint32_t ctrl_reg = read_register(mapped_base, REG_CTRL_OFFSET);
printf("Device Control Register [0x%04zX]: 0x%08X\n", REG_CTRL_OFFSET, ctrl_reg);
// 상태 레지스터 값 읽기
uint32_t stat_reg = read_register(mapped_base, REG_STATUS_OFFSET);
printf("Device Status Register [0x%04zX]: 0x%08X\n", REG_STATUS_OFFSET, stat_reg);
// 인터럽트 원인 레지스터 확인
uint32_t icr = read_register(mapped_base, REG_INTERRUPT_CAUSE);
printf("Interrupt Cause Register [0x%04zX]: 0x%08X\n", REG_INTERRUPT_CAUSE, icr);
// MAC 주소 추출
uint32_t mac_low = read_register(mapped_base, REG_MAC_ADDR_LOW);
uint32_t mac_high = read_register(mapped_base, REG_MAC_ADDR_HIGH);
printf("Current MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\n",
mac_low & 0xFF,
(mac_low >> 8) & 0xFF,
(mac_low >> 16) & 0xFF,
(mac_low >> 24) & 0xFF,
mac_high & 0xFF,
(mac_high >> 8) & 0xFF);
// 리소스 해제
munmap((void*)mapped_base, I211_REGION_SIZE);
close(mem_fd);
return EXIT_SUCCESS;
}
컴파일 및 실행 방법:
gcc -o i211_reader i211_reader.c
sudo ./i211_reader
실행 시 root 권한 또는 CAP_SYS_RAWIO 권한이 필요하며, 시스템 설정에 따라 /dev/mem 접근이 제한될 수 있으므로, 필요한 경우 커널 파라미터 조정 또는 udev 규칙 추가가 필요할 수 있다.