Auxiliary Bus (보조 버스(Bus))
auxiliary_bus는 Linux 5.11에서 도입된 버스 프레임워크로, 하나의 물리 디바이스(주로 PCIe)가 제공하는 여러 기능(네트워크, RDMA, crypto, vDPA 등)을 독립적인 커널 드라이버에 분배합니다. auxiliary_device와 auxiliary_driver 자료구조, modname.function.id 명명 규칙, 2단계 등록(init+add), probe/remove 생명주기, 고유 메모리 소유권 모델, sysfs 표현, MLX5·ICE·SOF 실제 활용 사례, 대안 버스 비교, 디버깅(Debugging) 기법까지 종합적으로 다룹니다.
drivers/base/auxiliary.c, include/linux/auxiliary_bus.h — Linux 5.11 (commit 7de3697e) 이후.
관련 상위 개념은 디바이스 드라이버 문서를 참고하세요.
bus_type, device, device_driver 개념 위에 구축됩니다.
auxiliary_bus는 바로 이 "입주 관리 시스템"입니다.
핵심 요약
- auxiliary_device — 부모 디바이스가 등록하는 가상 하위 디바이스입니다. 각 기능(eth, rdma, crypto 등)을 나타냅니다.
- auxiliary_driver — 특정 기능에 바인딩되는 드라이버입니다.
id_table로 매칭되며,probe/remove콜백(Callback)을 구현합니다. - id_table —
"modname.function"형식의 문자열로 드라이버와 디바이스를 매칭합니다. - 2단계 등록 —
auxiliary_device_init()으로 초기화한 후auxiliary_device_add()로 버스에 등록합니다. 분리된 에러 경로를 제공합니다. - release 콜백 —
auxiliary_device.dev.release는 반드시 설정해야 합니다. 마지막 참조가 해제될 때 메모리를 정리하며, 설정하지 않으면 커널 경고가 발생합니다.
단계별 이해
- 개요에서 auxiliary_bus가 왜 필요한지, MFD/platform bus의 한계를 파악합니다.
- 아키텍처 개요로 전체 구조(부모→버스→드라이버)를 시각적으로 이해합니다.
- 핵심 자료구조에서 구조체(Struct) 필드를 하나씩 분석합니다.
- 디바이스 등록과 드라이버 등록(Driver Registration)으로 실제 코드 흐름을 따라갑니다.
개요 및 도입 배경
NVIDIA ConnectX, Intel ICE, SOF 오디오 등 최신 하드웨어는 하나의 PCIe 디바이스가 네트워킹, RDMA, 암호화, virtio 에뮬레이션 등 여러 독립 기능을 동시에 제공합니다. Linux 5.11 이전에는 이러한 다기능 디바이스를 분할하기 위해 platform bus나 MFD를 사용했으나, 두 접근 모두 구조적 한계가 있었습니다.
- platform bus — 디바이스 트리(DT)나 ACPI에 등록된 SoC 주변장치 전용입니다. PCIe 디바이스가 "가짜" platform_device를 만들면 NUMA 노드·IOMMU 그룹 등 부모 속성을 잃습니다.
- MFD (Multi-Function Device) — 레지스터 공간 분할에 특화되어 있으나, 기능 간 드라이버가 같은 모듈에 묶이기 쉽고, IRQ 도메인 공유 모델이 복잡합니다.
- auxiliary_bus — 부모 디바이스의
struct device를 상속하여 IOMMU·NUMA·전원 관리(Power Management)를 그대로 유지하면서, 각 기능을 독립 모듈로 분리합니다.
Kconfig 설정
# drivers/base/Kconfig
config AUXILIARY_BUS
bool
# 대부분의 드라이버가 select AUXILIARY_BUS 로 자동 활성화
# 예: drivers/net/ethernet/mellanox/mlx5/core/Kconfig
config MLX5_CORE
tristate "Mellanox 5th generation network adapters"
select AUXILIARY_BUS
/* 드라이버에서 auxiliary_bus 사용 시 필요한 헤더 */
#include <linux/auxiliary_bus.h>
아키텍처 개요
auxiliary_bus는 Linux Device Model의 bus_type으로 등록됩니다. 부모 드라이버(예: mlx5_core)가
auxiliary_device를 생성하면, auxiliary_bus 코어가 등록된 auxiliary_driver의 id_table을
확인하여 매칭되는 드라이버의 probe()를 호출합니다.
/* drivers/base/auxiliary.c — 버스 타입 정의 */
static const struct bus_type auxiliary_bus_type = {
.name = "auxiliary",
.probe = auxiliary_bus_probe,
.remove = auxiliary_bus_remove,
.match = auxiliary_match,
.uevent = auxiliary_uevent,
};
핵심 자료구조
auxiliary_bus의 핵심은 세 가지 구조체입니다: auxiliary_device(디바이스), auxiliary_driver(드라이버),
auxiliary_device_id(매칭 테이블). 이들의 관계를 이해하면 전체 프레임워크를 파악할 수 있습니다.
auxiliary_device 구조체
/* include/linux/auxiliary_bus.h */
struct auxiliary_device {
struct device dev;
const char *name; /* "function" 부분 — 예: "eth", "rdma" */
u32 id; /* 인스턴스 번호 — 예: 0, 1, 88 */
};
코드 설명
-
3행
dev— 임베디드된struct device.dev.parent에 부모 디바이스를 설정하고,dev.release에 해제 콜백을 반드시 지정해야 합니다. -
4행
name— 기능 이름 문자열. 버스에서 최종 sysfs 이름은"KBUILD_MODNAME.name.id"형식으로 구성됩니다. -
5행
id— 동일 기능의 여러 인스턴스를 구별하는 번호. MLX5의 Scalable Function은 SF 번호를 id로 사용합니다.
auxiliary_driver 구조체
struct auxiliary_driver {
int (*probe)(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id);
void (*remove)(struct auxiliary_device *auxdev);
void (*shutdown)(struct auxiliary_device *auxdev);
int (*suspend)(struct auxiliary_device *auxdev, pm_message_t state);
int (*resume)(struct auxiliary_device *auxdev);
const char *name;
struct device_driver driver;
const struct auxiliary_device_id *id_table;
};
auxiliary_device_id 구조체
struct auxiliary_device_id {
char name[32]; /* "modname.function" 형식 */
kernel_ulong_t driver_data; /* 드라이버별 사용자 데이터 */
};
디바이스 명명 규칙
auxiliary_device의 sysfs 이름은 세 부분으로 구성됩니다:
KBUILD_MODNAME . name . id
─────────────── ──── ──
부모 모듈명 기능 인스턴스
이름 번호
예: mlx5_core.eth.0
mlx5_core.rdma.0
mlx5_core.sf.88
ice.rdma.0
id 부분이 제외됩니다. 즉, id_table의 name은 "mlx5_core.eth"처럼
"modname.function"만 포함합니다. 같은 기능의 여러 인스턴스(eth.0, eth.1)는 모두 같은 드라이버에 매칭됩니다.
| 부모 모듈 | 기능 이름 | sysfs 디바이스명 | 바인딩 드라이버 |
|---|---|---|---|
| mlx5_core | eth | mlx5_core.eth.0 | mlx5e (이더넷) |
| mlx5_core | rdma | mlx5_core.rdma.0 | mlx5_ib (RDMA) |
| mlx5_core | vnet | mlx5_core.vnet.0 | mlx5_vnet (vDPA) |
| mlx5_core | sf | mlx5_core.sf.88 | mlx5_sf (Scalable Function) |
| ice | rdma | ice.rdma.0 | irdma (Intel RDMA) |
| snd_sof | dma | snd_sof.dma.0 | SOF DMA engine |
| idxd | wq | idxd.wq.0 | IDXD work queue |
디바이스 등록 과정
디바이스 등록은 2단계로 나뉩니다: auxiliary_device_init()으로 struct device를 초기화한 후,
auxiliary_device_add()로 버스에 등록합니다. 이 분리 덕분에 실패 시 정리 경로가 명확합니다.
/* 부모 드라이버에서 auxiliary_device 등록 — 전체 패턴 */
struct my_aux_container {
struct auxiliary_device auxdev;
/* 부모와 공유할 추가 데이터 */
struct my_parent_dev *parent;
void __iomem *shared_regs;
};
static void my_aux_release(struct device *dev)
{
struct auxiliary_device *auxdev = container_of(dev, struct auxiliary_device, dev);
struct my_aux_container *c = container_of(auxdev, struct my_aux_container, auxdev);
kfree(c);
}
static int my_parent_create_aux(struct my_parent_dev *pdev)
{
struct my_aux_container *c;
int ret;
c = kzalloc(sizeof(*c), GFP_KERNEL);
if (!c)
return -ENOMEM;
c->parent = pdev;
c->shared_regs = pdev->regs;
c->auxdev.name = "eth";
c->auxdev.id = 0;
c->auxdev.dev.parent = &pdev->pci_dev->dev;
c->auxdev.dev.release = my_aux_release;
ret = auxiliary_device_init(&c->auxdev);
if (ret) {
kfree(c); /* init 실패: 직접 free */
return ret;
}
ret = auxiliary_device_add(&c->auxdev);
if (ret) {
auxiliary_device_uninit(&c->auxdev); /* add 실패: put_device → release */
return ret;
}
pdev->aux_eth = &c->auxdev;
return 0;
}
코드 설명
- 2-7행 auxiliary_device를 감싸는 컨테이너(Container) 구조체. 부모와 공유할 데이터(shared_regs 등)를 같이 담습니다.
-
9-15행
release 콜백.
container_of로 컨테이너를 찾아kfree합니다. 이 콜백이 없으면 커널이 "Device has no release() function" 경고를 출력합니다. - 27-30행 기능 이름("eth"), 인스턴스(0), 부모 디바이스, release 콜백을 설정합니다.
-
32-35행
auxiliary_device_init()은device_initialize()를 호출합니다. 실패 시 refcount가 올라가지 않았으므로kfree()로 직접 해제합니다. -
37-40행
auxiliary_device_add()는device_add()를 호출합니다. 실패 시auxiliary_device_uninit()이put_device()를 호출하고, refcount가 0이 되면 release 콜백이 메모리를 해제합니다.
/* 부모 드라이버 제거 시 — 역순으로 정리 */
static void my_parent_destroy_aux(struct my_parent_dev *pdev)
{
auxiliary_device_delete(pdev->aux_eth); /* device_del(): 버스에서 제거 */
auxiliary_device_uninit(pdev->aux_eth); /* put_device(): refcount 감소 → release */
}
드라이버 등록과 매칭
auxiliary_driver는 auxiliary_driver_register()로 등록합니다. 편의 매크로(Macro)
module_auxiliary_driver()를 사용하면 모듈 init/exit를 자동 생성합니다.
/* 드라이버 등록 예시 — mlx5e 이더넷 */
static const struct auxiliary_device_id mlx5e_id_table[] = {
{ .name = "mlx5_core.eth" },
{}, /* 센티넬 (빈 항목으로 종료) */
};
MODULE_DEVICE_TABLE(auxiliary, mlx5e_id_table);
static struct auxiliary_driver mlx5e_driver = {
.name = "eth",
.probe = mlx5e_probe,
.remove = mlx5e_remove,
.id_table = mlx5e_id_table,
};
module_auxiliary_driver(mlx5e_driver);
코드 설명
-
2-5행
id_table은 NULL name 센티넬로 종료되는 배열입니다."mlx5_core.eth"라는 이름의 모든 auxiliary_device와 매칭됩니다. -
7행
MODULE_DEVICE_TABLE은 모듈 자동 로딩을 위한 alias를 생성합니다.modalias는"auxiliary:mlx5_core.eth"형식입니다. -
16행
module_auxiliary_driver()는module_init/module_exit에서auxiliary_driver_register()/_unregister()를 호출하는 코드를 자동 생성합니다.
/* drivers/base/auxiliary.c — 매칭 구현 */
static int auxiliary_match(struct device *dev, struct device_driver *drv)
{
struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
struct auxiliary_driver *auxdrv = to_auxiliary_drv(drv);
return !!auxiliary_match_id(auxdrv->id_table, auxdev->name);
}
static const struct auxiliary_device_id *auxiliary_match_id(
const struct auxiliary_device_id *id, const char *name)
{
while (id->name[0]) {
if (!strcmp(name, id->name))
return id;
id++;
}
return NULL;
}
probe/remove 콜백
매칭된 드라이버의 probe()가 호출되면, container_of 매크로로 부모가 제공한 컨테이너 구조체에 접근하여
공유 리소스(레지스터, 인터럽트)를 사용할 수 있습니다.
/* probe 콜백 구현 패턴 */
struct my_func_priv {
struct net_device *netdev;
void __iomem *regs;
};
static int my_func_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct my_aux_container *c =
container_of(auxdev, struct my_aux_container, auxdev);
struct my_func_priv *priv;
/* devm: 디바이스 해제 시 자동 free */
priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* 부모 공유 리소스 접근 */
priv->regs = c->shared_regs;
/* 기능별 초기화: 예를 들어 netdev 생성 */
priv->netdev = alloc_etherdev(0);
if (!priv->netdev)
return -ENOMEM;
dev_set_drvdata(&auxdev->dev, priv);
return register_netdev(priv->netdev);
}
static void my_func_remove(struct auxiliary_device *auxdev)
{
struct my_func_priv *priv = dev_get_drvdata(&auxdev->dev);
unregister_netdev(priv->netdev);
free_netdev(priv->netdev);
/* devm 할당은 자동 해제됨 */
}
코드 설명
-
10-11행
container_of로 auxiliary_device를 감싸는 부모 컨테이너를 역참조(Dereference)합니다. 이를 통해 부모가 제공한 공유 레지스터 등에 접근합니다. -
15행
devm_kzalloc은 디바이스 생명주기에 바인딩된 관리 메모리 할당입니다. remove 시 자동 해제되므로 별도 free가 필요 없습니다. -
27행
dev_set_drvdata로 private 데이터를 디바이스에 연결합니다. remove에서dev_get_drvdata로 꺼냅니다. - 32-37행 remove는 probe의 역순으로 정리합니다. netdev를 해제하고, devm 할당은 프레임워크가 자동 정리합니다.
/* module_auxiliary_driver 매크로 확장 */
#define module_auxiliary_driver(__auxiliary_driver) \
module_driver(__auxiliary_driver, auxiliary_driver_register, \
auxiliary_driver_unregister)
/* 수동 등록이 필요한 경우 */
static int __init my_func_init(void)
{
/* 추가 초기화 작업 후 드라이버 등록 */
return auxiliary_driver_register(&my_func_driver);
}
module_init(my_func_init);
메모리 모델과 release 콜백
auxiliary_bus에서 가장 주의할 점은 메모리 소유권입니다. auxiliary_device에 내장된
struct device는 참조 카운트(Reference Count)로 관리되므로, 메모리 해제 시점이 kfree 호출이 아닌
release 콜백에서 결정됩니다.
/* release 콜백 — 반드시 설정해야 하는 이유 */
static void my_aux_release(struct device *dev)
{
struct auxiliary_device *auxdev = container_of(dev, struct auxiliary_device, dev);
struct my_aux_container *c = container_of(auxdev, struct my_aux_container, auxdev);
/* refcount == 0 일 때만 호출됨 → 안전하게 해제 */
kfree(c);
}
/* release를 설정하지 않으면 커널이 경고를 출력합니다:
* "Device 'xxx' does not have a release() function, it is broken
* and must be fixed. See Documentation/core-api/kobject.rst."
*/
/* 잘못된 패턴 — 절대 하지 마세요! */
auxiliary_device_delete(adev);
auxiliary_device_uninit(adev);
kfree(container); /* ⚠️ release()와 이중 해제 또는 UAF! */
전원 관리 통합
auxiliary_device는 부모의 struct device를 dev.parent로 참조하므로,
PM(Power Management) 계층이 자연스럽게 상속됩니다. 부모가 suspend되면 자식 auxiliary_device도
먼저 suspend되고, resume은 역순입니다.
/* auxiliary_driver에서 전원 관리 콜백 구현 */
static int my_func_suspend(struct auxiliary_device *auxdev, pm_message_t state)
{
struct my_func_priv *priv = dev_get_drvdata(&auxdev->dev);
netif_device_detach(priv->netdev);
/* DMA 링 비우기, 인터럽트 비활성화 등 */
return 0;
}
static int my_func_resume(struct auxiliary_device *auxdev)
{
struct my_func_priv *priv = dev_get_drvdata(&auxdev->dev);
/* 하드웨어 재초기화, DMA 링 복원 */
netif_device_attach(priv->netdev);
return 0;
}
static struct auxiliary_driver my_func_driver = {
.name = "eth",
.probe = my_func_probe,
.remove = my_func_remove,
.suspend = my_func_suspend,
.resume = my_func_resume,
.id_table = my_func_id_table,
};
sysfs 표현
auxiliary_bus에 등록된 디바이스와 드라이버는 /sys/bus/auxiliary/ 아래에서 확인할 수 있습니다.
# auxiliary_bus 디바이스 목록
$ ls /sys/bus/auxiliary/devices/
mlx5_core.eth.0 mlx5_core.rdma.0 mlx5_core.vnet.0 ice.rdma.0
# 특정 디바이스 상세 정보
$ ls -la /sys/bus/auxiliary/devices/mlx5_core.eth.0/
driver -> ../../../../bus/auxiliary/drivers/mlx5_core.eth
subsystem -> ../../../../bus/auxiliary
power/
uevent
# 등록된 드라이버 목록
$ ls /sys/bus/auxiliary/drivers/
mlx5_core.eth mlx5_core.rdma mlx5_core.vnet
# MODALIAS 확인 (자동 모듈 로딩용)
$ cat /sys/bus/auxiliary/devices/mlx5_core.eth.0/uevent
MODALIAS=auxiliary:mlx5_core.eth
# 부모 디바이스 확인
$ readlink /sys/bus/auxiliary/devices/mlx5_core.eth.0/device
../../../0000:03:00.0
실제 활용: NVIDIA MLX5
MLX5(ConnectX-6/7, BlueField DPU)는 auxiliary_bus의 가장 대표적인 사용자입니다. 하나의 PCIe 디바이스에서 이더넷, RDMA, vDPA, Scalable Function, crypto 등 5가지 이상의 독립 기능을 제공합니다.
/* drivers/net/ethernet/mellanox/mlx5/core/dev.c */
/* MLX5에서 auxiliary_device 등록 */
static int mlx5_add_adev(struct mlx5_core_dev *dev, int idx)
{
struct mlx5_adev *madev;
int ret;
madev = kzalloc(sizeof(*madev), GFP_KERNEL);
if (!madev)
return -ENOMEM;
madev->mdev = dev;
madev->idx = idx;
madev->adev.name = mlx5_adev_devices[idx].suffix; /* "eth", "rdma" 등 */
madev->adev.id = 0;
madev->adev.dev.parent = dev->device;
madev->adev.dev.release = mlx5_adev_release;
ret = auxiliary_device_init(&madev->adev);
if (ret) {
kfree(madev);
return ret;
}
ret = auxiliary_device_add(&madev->adev);
if (ret) {
auxiliary_device_uninit(&madev->adev);
return ret;
}
return 0;
}
/* MLX5 이더넷 드라이버의 probe */
static int mlx5e_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev);
struct mlx5_core_dev *mdev = edev->mdev;
struct net_device *netdev;
struct mlx5e_priv *priv;
netdev = mlx5e_create_netdev(mdev, 0);
if (!netdev)
return -ENOMEM;
priv = netdev_priv(netdev);
/* mdev의 공유 리소스(FW cmd, EQ, Flow Table)를 사용하여 초기화 */
mlx5e_build_nic_params(priv, mdev);
mlx5e_build_nic_netdev(netdev);
return register_netdev(netdev);
}
/* MLX5 Scalable Function — auxiliary_device 위에 다시 mlx5_core를 초기화 */
static int mlx5_sf_dev_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
struct mlx5_sf_dev *sf_dev = container_of(adev, struct mlx5_sf_dev, adev);
struct mlx5_core_dev *mdev;
/* SF용 독립 mlx5_core_dev 생성 → 다시 eth + rdma auxiliary 등록 */
mdev = mlx5_sf_dev_to_mdev(sf_dev);
return mlx5_init_one(mdev);
}
실제 활용: Intel ICE 및 SOF
Intel ICE — RDMA 분리
Intel E800 시리즈(ICE 드라이버)는 이더넷 기능은 자체 처리하고, RDMA(iWARP/RoCEv2) 기능만
auxiliary_device로 노출하여 irdma 드라이버에 위임합니다.
/* drivers/net/ethernet/intel/ice/ice_idc.c */
static int ice_plug_aux_dev(struct ice_pf *pf)
{
struct iidc_auxiliary_dev *iadev;
iadev = kzalloc(sizeof(*iadev), GFP_KERNEL);
iadev->pf = pf;
iadev->adev.name = "rdma";
iadev->adev.id = pf->aux_idx;
iadev->adev.dev.parent = &pf->pdev->dev;
iadev->adev.dev.release = ice_adev_release;
auxiliary_device_init(&iadev->adev);
return auxiliary_device_add(&iadev->adev);
}
/* irdma 드라이버 id_table */
static const struct auxiliary_device_id irdma_auxiliary_id_table[] = {
{ .name = "ice.rdma" },
{},
};
Sound Open Firmware (SOF)
Intel SOF 오디오 드라이버는 DSP 기능을 auxiliary_device로 노출하여 DMA 엔진과 코덱 드라이버를 분리합니다. 이를 통해 같은 DSP 하드웨어에 대해 여러 오디오 파이프라인(Pipeline)을 독립적으로 관리합니다.
/* sound/soc/sof/sof-client.c — SOF 클라이언트 등록 */
static int sof_client_dev_register(struct sof_client_dev *cdev,
const char *name, u32 id)
{
cdev->auxdev.name = name; /* "dma", "ipc-flood" 등 */
cdev->auxdev.id = id;
cdev->auxdev.dev.release = sof_client_auxdev_release;
cdev->auxdev.dev.parent = sof_parent_dev;
auxiliary_device_init(&cdev->auxdev);
return auxiliary_device_add(&cdev->auxdev);
}
/* Intel IDXD도 work queue를 auxiliary_device로 노출
* → idxd.wq.0, idxd.wq.1 등으로 개별 WQ 드라이버 바인딩 */
auxiliary_bus vs 대안 비교
| 기준 | platform bus | MFD | auxiliary_bus |
|---|---|---|---|
| 도입 시기 | Linux 2.6 초기 | Linux 2.6.20 | Linux 5.11 |
| 주요 대상 | SoC 주변장치 (DT/ACPI) | 멀티펑션 IC (PMIC 등) | PCIe 다기능 디바이스 |
| 디바이스 발견 | DT/ACPI/수동 | 부모 MFD 드라이버 | 부모 PCI 드라이버 |
| IOMMU 그룹 | 별도 그룹 | 별도 그룹 (platform_device) | 부모 그룹 상속 |
| NUMA 노드 | 손실 가능 | 손실 가능 | 부모에서 상속 |
| 전원 관리 | 독립 PM 도메인 | MFD 셀 통해 간접 | 부모 device 트리 자동 연동 |
| 모듈 분리 | 가능 | 가능하나 복잡 | 설계 의도 (id_table) |
| 리소스 공유 | platform_data | mfd_cell resources | container_of (타입 안전) |
| 사용 예 | GPIO, UART, SPI (SoC) | TPS65910 PMIC, WM8994 | MLX5, ICE, SOF, IDXD |
디버깅 및 문제 해결
sysfs를 통한 탐색
# 등록된 모든 auxiliary 디바이스 확인
$ find /sys/bus/auxiliary/devices/ -maxdepth 1 -type l | sort
/sys/bus/auxiliary/devices/ice.rdma.0
/sys/bus/auxiliary/devices/mlx5_core.eth.0
/sys/bus/auxiliary/devices/mlx5_core.rdma.0
/sys/bus/auxiliary/devices/mlx5_core.vnet.0
# 디바이스-드라이버 바인딩 상태 확인
$ for d in /sys/bus/auxiliary/devices/*/driver; do
echo "$(basename $(dirname $d)) -> $(basename $(readlink $d))"
done
mlx5_core.eth.0 -> mlx5_core.eth
mlx5_core.rdma.0 -> mlx5_core.rdma
# 수동 unbind/bind (디버깅용)
$ echo "mlx5_core.eth.0" > /sys/bus/auxiliary/drivers/mlx5_core.eth/unbind
$ echo "mlx5_core.eth.0" > /sys/bus/auxiliary/drivers/mlx5_core.eth/bind
dmesg 패턴 분석
# auxiliary_bus 관련 메시지 필터링
$ dmesg | grep -i auxiliary
[ 2.345] auxiliary mlx5_core.eth.0: probe
[ 2.346] auxiliary mlx5_core.rdma.0: probe
# release 미설정 경고 확인
$ dmesg | grep "does not have a release"
[ 1.234] Device 'xxx.yyy.0' does not have a release() function
# ftrace로 probe/remove 추적
$ echo 1 > /sys/kernel/debug/tracing/events/bus/bus_probe/enable
$ echo 1 > /sys/kernel/debug/tracing/events/bus/bus_remove/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
mlx5_core-1234 [001] .... 12.345: bus_probe: ...auxiliary mlx5_core.eth.0
안티패턴과 모범 사례
안티패턴 1: release 누락 또는 직접 kfree
/* ❌ 잘못된 패턴: release 미설정 */
static int wrong_create(void)
{
struct my_container *c = kzalloc(sizeof(*c), GFP_KERNEL);
c->auxdev.name = "func";
c->auxdev.dev.parent = parent;
/* c->auxdev.dev.release = ??? ← 빠졌음! */
auxiliary_device_init(&c->auxdev);
auxiliary_device_add(&c->auxdev);
return 0;
}
/* ❌ 잘못된 패턴: uninit 후 직접 kfree */
static void wrong_destroy(struct my_container *c)
{
auxiliary_device_delete(&c->auxdev);
auxiliary_device_uninit(&c->auxdev);
kfree(c); /* ⚠️ release()와 이중 해제! */
}
/* ✅ 올바른 패턴 */
static void correct_release(struct device *dev)
{
struct auxiliary_device *adev = container_of(dev, struct auxiliary_device, dev);
struct my_container *c = container_of(adev, struct my_container, auxdev);
kfree(c);
}
static int correct_create(void)
{
struct my_container *c = kzalloc(sizeof(*c), GFP_KERNEL);
c->auxdev.dev.release = correct_release; /* ✅ release 설정 */
/* ... init + add ... */
}
static void correct_destroy(struct my_container *c)
{
auxiliary_device_delete(&c->auxdev);
auxiliary_device_uninit(&c->auxdev);
/* ✅ kfree 하지 않음 — release()가 처리 */
}
안티패턴 2: init 실패 시 uninit 호출
/* ❌ init 실패 시에는 uninit을 호출하면 안 됨 */
ret = auxiliary_device_init(&c->auxdev);
if (ret) {
auxiliary_device_uninit(&c->auxdev); /* ❌ 잘못됨 */
return ret;
}
/* ✅ init 실패: refcount가 올라가지 않았으므로 직접 kfree */
ret = auxiliary_device_init(&c->auxdev);
if (ret) {
kfree(c); /* ✅ 직접 해제 */
return ret;
}
auxiliary_device 필드 상세 분석
auxiliary_device 구조체의 각 필드는 단순해 보이지만, Linux Device Model과 밀접하게 연결되어 있습니다.
각 필드의 역할과 내부 동작을 심층적으로 분석합니다.
dev 필드 — struct device 임베딩
struct device dev는 Linux Device Model의 핵심 객체입니다. auxiliary_device가 이를 직접 임베딩(embedding)함으로써
sysfs 표현, 전원 관리, 참조 카운팅, uevent, 드라이버 바인딩 등 커널 인프라를 자동으로 활용합니다.
| dev 하위 필드 | 설정 주체 | 역할 |
|---|---|---|
dev.parent | 부모 드라이버 | 부모 디바이스 참조. NUMA 노드, IOMMU 그룹, PM 계층을 상속합니다. |
dev.release | 부모 드라이버 | refcount가 0이 될 때 호출되는 해제 콜백. 필수 설정입니다. |
dev.bus | auxiliary_bus 코어 | auxiliary_device_init()이 &auxiliary_bus_type으로 자동 설정합니다. |
dev.kobj.name | auxiliary_bus 코어 | auxiliary_device_init()이 "modname.name.id" 형식으로 설정합니다. |
dev.groups | 자식 드라이버 | probe에서 추가 sysfs 속성 그룹을 등록할 수 있습니다. |
dev.driver_data | 자식 드라이버 | dev_set_drvdata()로 드라이버 private 데이터를 연결합니다. |
dev.dma_mask | 부모에서 상속 | DMA 마스크가 부모 PCIe 디바이스에서 상속됩니다. |
/* auxiliary_device_init() 내부 — dev 필드 초기화 과정 */
int auxiliary_device_init(struct auxiliary_device *auxdev)
{
struct device *dev = &auxdev->dev;
/* bus_type 설정 → sysfs, 매칭, uevent 연동 */
dev->bus = &auxiliary_bus_type;
/* kobject 이름 생성: "KBUILD_MODNAME.name.id" */
dev_set_name(dev, "%s.%s.%d", auxdev->name ? : "unknown",
dev->parent ? dev_name(dev->parent) : "none",
auxdev->id);
/* device 초기화: refcount=1, kobject 설정 */
device_initialize(dev);
return 0;
}
name 필드 — 기능 식별자
name은 "eth", "rdma", "vnet" 같은 기능 식별 문자열입니다.
이 문자열은 최종 sysfs 이름의 중간 부분이 되고, id_table 매칭의 핵심 키입니다.
auxiliary_device_id.name은 32바이트 배열이며, 여기에 "modname." 접두사가 포함되므로
실제 기능 이름은 32 - strlen(KBUILD_MODNAME) - 1자 이내여야 합니다. 이를 초과하면 매칭이 실패합니다.
id 필드 — 인스턴스 번호
id는 동일 기능의 여러 인스턴스를 구별합니다. 이 값은 매칭에 사용되지 않습니다 — 같은 기능의
모든 인스턴스가 동일 드라이버에 바인딩됩니다. id의 실제 활용 사례:
| 드라이버 | id 활용 | 예시 |
|---|---|---|
| MLX5 SF | Scalable Function 번호 | sf.88, sf.89 (devlink SF 인덱스) |
| ICE | PF 인스턴스 인덱스 | rdma.0, rdma.1 (멀티포트 NIC) |
| IDXD | Work Queue 번호 | wq.0, wq.1, wq.2 |
| SOF | 오디오 파이프라인 인덱스 | dma.0, ipc-flood.0 |
auxiliary_driver 콜백 함수 상세
auxiliary_driver는 다섯 가지 콜백 함수를 지원합니다. 각 콜백의 호출 시점, 컨텍스트, 구현 규칙을 상세히 분석합니다.
probe() 콜백 상세
probe()는 매칭 성공 후 호출되는 초기화 콜백입니다. 반환값이 0이면 바인딩 성공, 음수이면 실패입니다.
실패 시 -EPROBE_DEFER를 반환하면 커널이 나중에 재시도합니다.
| 패턴 | 설명 | 예시 |
|---|---|---|
container_of 역참조 | 부모 컨테이너에서 공유 리소스 획득 | 공유 레지스터, FW 인터페이스 |
devm_* 관리 API | 디바이스 해제 시 자동 정리 | devm_kzalloc, devm_request_irq |
dev_set_drvdata | 드라이버 private 데이터 연결 | remove에서 dev_get_drvdata로 회수 |
-EPROBE_DEFER | 의존 리소스 미준비 시 지연(Latency) | RDMA가 이더넷 초기화 대기 |
/* probe에서 EPROBE_DEFER 사용 패턴 */
static int rdma_aux_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
struct my_aux_container *c =
container_of(adev, struct my_aux_container, auxdev);
/* 이더넷 기능이 아직 초기화되지 않았으면 지연 */
if (!c->parent->eth_ready)
return -EPROBE_DEFER;
/* RDMA 리소스 초기화 */
return init_rdma_resources(c->parent);
}
remove() 콜백 상세
remove()는 디바이스 제거 또는 드라이버 언로드 시 호출됩니다. probe()의 역순으로 리소스를 정리해야 합니다.
devm_* API로 할당한 리소스는 자동 해제되지만, 순서 의존성이 있는 정리는 명시적으로 수행해야 합니다.
/* remove 콜백 — 순서 의존적 정리 패턴 */
static void my_eth_remove(struct auxiliary_device *adev)
{
struct my_eth_priv *priv = dev_get_drvdata(&adev->dev);
/* 1. 네트워크 인터페이스 비활성화 (새 패킷 수신 차단) */
unregister_netdev(priv->netdev);
/* 2. NAPI 비활성화 */
napi_disable(&priv->napi);
/* 3. DMA 링 해제 (NAPI 비활성화 후에야 안전) */
free_dma_rings(priv);
/* 4. netdev 해제 */
free_netdev(priv->netdev);
/* devm_ 할당은 프레임워크가 자동 해제 */
}
shutdown() 콜백 상세
shutdown()은 시스템 shutdown/reboot 경로에서 호출됩니다. remove()와 달리 시간 제한이 엄격하며,
최소한의 하드웨어 정리만 수행해야 합니다. DMA 엔진을 정지하고 인터럽트를 비활성화하는 것이 핵심입니다.
/* shutdown 콜백 — 최소한의 정리 */
static void my_eth_shutdown(struct auxiliary_device *adev)
{
struct my_eth_priv *priv = dev_get_drvdata(&adev->dev);
/* DMA 엔진 정지 — 재부팅 시 stale DMA 방지 */
writel(0, priv->regs + DMA_CTRL);
/* 인터럽트 비활성화 */
disable_irq(priv->irq);
}
매칭 알고리즘 내부 동작
auxiliary_bus의 매칭은 bus_type.match 콜백인 auxiliary_match()에서 수행됩니다.
매칭 과정에서 디바이스 이름이 구성되는 방식, modalias와 자동 모듈 로딩, uevent 전달 과정을 상세히 분석합니다.
modalias 형식과 자동 모듈 로딩
auxiliary_uevent()는 디바이스가 버스에 추가될 때 MODALIAS 환경변수를 설정합니다.
이 값은 udev가 적절한 드라이버 모듈을 자동 로드하는 데 사용됩니다.
/* drivers/base/auxiliary.c — uevent 구현 */
static int auxiliary_uevent(const struct device *dev,
struct kobj_uevent_env *env)
{
const struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
const char *name = auxiliary_get_match_name(auxdev);
return add_uevent_var(env, "MODALIAS=auxiliary:%s", name);
}
/* MODULE_DEVICE_TABLE(auxiliary, ...) 가 생성하는 alias:
* alias auxiliary:mlx5_core.eth mlx5_ib
*
* /lib/modules/$(uname -r)/modules.alias 에 기록됨
*/
# modalias 및 자동 로딩 확인
$ cat /sys/bus/auxiliary/devices/mlx5_core.eth.0/modalias
auxiliary:mlx5_core.eth
# 어떤 모듈이 로드되는지 확인
$ modprobe --show-depends auxiliary:mlx5_core.eth
insmod /lib/modules/.../mlx5_core.ko
insmod /lib/modules/.../mlx5e.ko
# modules.alias 확인
$ grep "auxiliary:" /lib/modules/$(uname -r)/modules.alias
alias auxiliary:mlx5_core.eth mlx5e
alias auxiliary:mlx5_core.rdma mlx5_ib
alias auxiliary:ice.rdma irdma
sysfs 인터페이스 상세 분석
/sys/bus/auxiliary/ 아래의 디렉터리 구조는 auxiliary_bus의 전체 상태를 실시간(Real-time)으로 반영합니다.
디바이스-드라이버 관계, 부모 계층, 전원 상태, 바인딩 제어를 포함합니다.
# sysfs를 통한 상세 탐색
# 디바이스의 부모 PCIe 정보 확인
$ readlink -f /sys/bus/auxiliary/devices/mlx5_core.eth.0/device
/sys/devices/pci0000:00/0000:00:01.0/0000:03:00.0
# NUMA 노드 확인 (부모에서 상속)
$ cat /sys/bus/auxiliary/devices/mlx5_core.eth.0/numa_node
0
# Runtime PM 상태 확인
$ cat /sys/bus/auxiliary/devices/mlx5_core.eth.0/power/runtime_status
active
# 디바이스-드라이버 바인딩 매핑 전체 조회
$ for dev in /sys/bus/auxiliary/devices/*; do
name=$(basename $dev)
drv=$(readlink $dev/driver 2>/dev/null | xargs basename 2>/dev/null)
echo "$name → ${drv:-unbound}"
done
# 수동 unbind/bind 실험
$ echo "mlx5_core.eth.0" | sudo tee /sys/bus/auxiliary/drivers/mlx5_core.eth/unbind
$ echo "mlx5_core.eth.0" | sudo tee /sys/bus/auxiliary/drivers/mlx5_core.eth/bind
추가 드라이버 케이스 스터디
Intel IDXD (Data Accelerator)
Intel IDXD(Data Streaming Accelerator)는 각 Work Queue를 auxiliary_device로 노출하여
독립적인 드라이버(dmaengine, user-space via idxd-config)에 바인딩합니다. 이를 통해 하나의 DSA 디바이스에서
커널 DMA 오프로드와 사용자 공간(User Space) 직접 접근을 동시에 지원합니다.
/* drivers/dma/idxd/init.c — IDXD WQ를 auxiliary_device로 노출 */
static int idxd_wq_create_aux(struct idxd_wq *wq)
{
struct idxd_device *idxd = wq->idxd;
wq->adev.name = "wq";
wq->adev.id = wq->id;
wq->adev.dev.parent = &idxd->pdev->dev;
wq->adev.dev.release = idxd_wq_adev_release;
auxiliary_device_init(&wq->adev);
return auxiliary_device_add(&wq->adev);
}
/* WQ별 드라이버 바인딩 예시:
* idxd.wq.0 → kernel DMA engine driver
* idxd.wq.1 → user-space driver (ENQCMD via mmap)
*/
# IDXD auxiliary 디바이스 확인
$ ls /sys/bus/auxiliary/devices/ | grep idxd
idxd.wq.0
idxd.wq.1
idxd.wq.2
idxd.wq.3
# WQ별 바인딩 상태
$ cat /sys/bus/auxiliary/devices/idxd.wq.0/driver/module/name
idxd_bus
Scalable I/O Virtualization (SIOV)
SIOV(Scalable IOV)는 SR-IOV를 대체하는 차세대 가상화(Virtualization) 기술입니다. SR-IOV가 PCIe 레벨에서 VF를 생성하는 반면,
SIOV는 auxiliary_bus를 활용하여 소프트웨어 레벨에서 가상 디바이스를 생성합니다. MLX5의 Scalable Function이 대표적입니다.
| 특성 | SR-IOV (VF) | SIOV (auxiliary_bus) |
|---|---|---|
| 생성 단위 | PCIe Virtual Function | auxiliary_device (소프트웨어) |
| PCIe 자원 | VF별 config space, BAR | 부모 BAR 공유, doorbell만 분리 |
| 수량 제한 | PCIe 스펙 제한 (256 VF) | 사실상 무제한 (수천 개 가능) |
| 생성 속도 | 느림 (PCIe 열거 필요) | 빠름 (소프트웨어 등록만) |
| 핫플러그(Hotplug) | PCIe hotplug 필요 | auxiliary_device_add/delete 즉시 |
| IOMMU | VF별 독립 그룹 | 부모 그룹 내 PASID 분리 |
| 구현 예 | 일반 NIC VF | MLX5 SF, IDXD WQ |
# MLX5 Scalable Function 생성 (devlink API)
$ devlink port add pci/0000:03:00.0 flavour pcisf pfnum 0 sfnum 88
$ devlink port function set pci/0000:03:00.0/32768 hw_addr 00:11:22:33:44:55
$ devlink port function set pci/0000:03:00.0/32768 state active
# 생성된 SF의 auxiliary_device 확인
$ ls /sys/bus/auxiliary/devices/ | grep sf
mlx5_core.sf.88
# SF 내부에서 다시 auxiliary_device 생성 (재귀적 구조)
$ ls /sys/bus/auxiliary/devices/ | grep sf.88
mlx5_core.eth.1 # SF의 이더넷 기능
mlx5_core.rdma.1 # SF의 RDMA 기능
SOF (Sound Open Firmware)
Intel SOF 오디오 프레임워크는 DSP 펌웨어(Firmware)와의 통신 채널을 auxiliary_device로 분리합니다.
이를 통해 IPC(Inter-Processor Communication) 디버깅, DMA 트레이싱, 펌웨어 프로파일링(Profiling) 등
각 기능을 독립 모듈로 개발할 수 있습니다.
/* SOF 클라이언트 auxiliary_device 목록 */
static const struct sof_client_type sof_client_types[] = {
{ .name = "ipc-flood", /* IPC 스트레스 테스트 */ },
{ .name = "ipc-msg-injector", /* IPC 메시지 주입 (디버깅) */ },
{ .name = "dma-trace", /* DMA 트레이스 수집 */ },
{ .name = "probes", /* 오디오 프로브 포인트 */ },
};
/* snd_sof.ipc-flood.0, snd_sof.dma-trace.0 등으로 등록 */
핫플러그 및 동적 생성/제거
auxiliary_bus의 큰 장점은 런타임 핫플러그입니다. 부모 드라이버가 필요에 따라
auxiliary_device를 동적으로 생성하고 제거할 수 있습니다. SR-IOV의 VF 생성과 달리
PCIe 열거가 필요 없으므로 밀리초 단위로 완료됩니다.
/* 동적 생성 — 부모 드라이버의 콜백에서 호출 */
static int parent_create_sf(struct my_parent *parent, u32 sf_num)
{
struct my_sf_container *sf;
int ret;
mutex_lock(&parent->sf_lock);
sf = kzalloc(sizeof(*sf), GFP_KERNEL);
if (!sf) {
ret = -ENOMEM;
goto unlock;
}
sf->auxdev.name = "sf";
sf->auxdev.id = sf_num;
sf->auxdev.dev.parent = &parent->pci_dev->dev;
sf->auxdev.dev.release = sf_release;
ret = auxiliary_device_init(&sf->auxdev);
if (ret) {
kfree(sf);
goto unlock;
}
ret = auxiliary_device_add(&sf->auxdev);
if (ret) {
auxiliary_device_uninit(&sf->auxdev);
goto unlock;
}
list_add_tail(&sf->list, &parent->sf_list);
unlock:
mutex_unlock(&parent->sf_lock);
return ret;
}
/* 동적 제거 */
static void parent_destroy_sf(struct my_parent *parent, u32 sf_num)
{
struct my_sf_container *sf;
mutex_lock(&parent->sf_lock);
sf = find_sf_by_num(parent, sf_num);
if (sf) {
list_del(&sf->list);
auxiliary_device_delete(&sf->auxdev);
auxiliary_device_uninit(&sf->auxdev);
/* kfree는 release()에서 처리 */
}
mutex_unlock(&parent->sf_lock);
}
에러 처리 패턴 및 디버깅
auxiliary_bus 관련 에러는 등록/매칭/프로브(Probe)/제거 각 단계에서 발생할 수 있습니다. 각 에러 유형별 진단 방법과 해결 패턴을 정리합니다.
공통 에러 유형
| 에러 | 원인 | 진단 | 해결 |
|---|---|---|---|
"does not have a release()" |
dev.release 미설정 | dmesg 경고 | release 콜백 구현 및 설정 |
| probe() 실패 (-ENOMEM) | 메모리 부족 또는 리소스 할당 실패 | dmesg, /proc/meminfo | devm_ API 사용, 에러 경로 점검 |
| 드라이버 미바인딩 | id_table 불일치 또는 모듈 미로드 | sysfs, modalias | modname.function 형식 일치 확인 |
| 이중 해제 (double free) | uninit 후 직접 kfree | KASAN, slub_debug | release() 콜백에서만 해제 |
| UAF (Use-After-Free) | release 전 참조 접근 | KASAN, lockdep | get_device/put_device로 참조 관리 |
| 데드락 | probe 내 auxiliary_device_add 호출 | lockdep, hung_task | workqueue로 비동기 생성 |
ftrace를 활용한 추적
# auxiliary_bus 관련 함수 추적
$ cd /sys/kernel/debug/tracing
# auxiliary_bus 코어 함수 필터링
$ echo 'auxiliary_*' > set_ftrace_filter
$ echo function > current_tracer
$ echo 1 > tracing_on
# 모듈 로드 후 로그 확인
$ modprobe mlx5_ib
$ cat trace
mlx5_core-1234 [002] d... 45.123: auxiliary_match <-driver_match_device
mlx5_core-1234 [002] d... 45.124: auxiliary_bus_probe <-really_probe
mlx5_core-1234 [002] d... 45.125: auxiliary_match <-driver_match_device
# 특정 디바이스의 probe 시간 측정
$ echo 0 > tracing_on
$ echo 'auxiliary_bus_probe' > set_ftrace_filter
$ echo function_graph > current_tracer
$ echo 1 > tracing_on
$ echo "mlx5_core.eth.0" > /sys/bus/auxiliary/drivers/mlx5_core.eth/bind
$ cat trace
2) + 15.234 us | auxiliary_bus_probe();
devlink을 활용한 진단
# MLX5 devlink 상태 확인
$ devlink dev info pci/0000:03:00.0
pci/0000:03:00.0:
driver mlx5_core
serial_number MT2116X09299
versions:
fixed:
fw.psid MT_0000000228
running:
fw 22.36.1010
# devlink health reporter (에러 진단)
$ devlink health show pci/0000:03:00.0
pci/0000:03:00.0:
reporter fw
state healthy error 0 recover 0
reporter fw_fatal
state healthy error 0 recover 0
# devlink 이벤트 실시간 모니터링
$ devlink monitor
# SF 포트 상태 확인
$ devlink port show pci/0000:03:00.0/32768
pci/0000:03:00.0/32768: type eth netdev enp3s0f0np0sf88 flavour pcisf controller 0 pfnum 0 sfnum 88
function:
hw_addr 00:11:22:33:44:55 state active opstate attached
KASAN/KFENCE를 활용한 메모리 버그 탐지
# KASAN 활성화된 커널에서 auxiliary_bus 메모리 버그 탐지
# Kconfig:
# CONFIG_KASAN=y
# CONFIG_KASAN_GENERIC=y
# UAF 발생 시 KASAN 출력 예시
$ dmesg | grep -A 20 "BUG: KASAN"
BUG: KASAN: use-after-free in auxiliary_bus_probe+0x5c/0x120
Read of size 8 at addr ffff888123456789 by task modprobe/1234
Call Trace:
auxiliary_bus_probe+0x5c/0x120
really_probe+0x1a3/0x3e0
__driver_probe_device+0x73/0x160
Allocated by task 1230:
kzalloc+0x1a/0x30
my_parent_create_aux+0x45/0x120
Freed by task 1231:
kfree+0x4a/0x60
my_aux_release+0x30/0x40
성능 고려사항
auxiliary_bus 자체는 데이터 경로에 있지 않으므로 런타임 성능에 직접적인 영향은 없습니다. 그러나 프로브 순서, 의존성 관리, 초기화 지연이 시스템 부팅 시간과 기능 가용성에 영향을 미칩니다.
프로브 순서와 의존성
auxiliary_device는 등록 순서대로 probe됩니다. 그러나 기능 간 의존성이 있는 경우(예: RDMA가 이더넷에 의존), 프로브 순서를 제어해야 합니다.
| 전략 | 구현 | 장점 | 단점 |
|---|---|---|---|
| 순차 등록 | 부모가 의존 순서대로 add() | 단순, 예측 가능 | 부모가 의존성 관리 |
| EPROBE_DEFER | probe에서 조건 미충족 시 반환 | 커널이 재시도 관리 | 지연 발생, 재시도 오버헤드(Overhead) |
| 콜백 통지 | 부모가 기능 준비 완료 시 통지 | 명시적 동기화 | 구현 복잡도 증가 |
| 비동기 프로브 | async_schedule()로 병렬 프로브 | 부팅 시간 단축 | 레이스 컨디션 관리 필요 |
/* MLX5의 프로브 순서 제어 패턴 */
static const struct mlx5_adev_device mlx5_adev_devices[] = {
[MLX5_INTERFACE_PROTOCOL_ETH] = { .suffix = "eth", .is_supported = &mlx5_eth_supported },
[MLX5_INTERFACE_PROTOCOL_RDMA] = { .suffix = "rdma", .is_supported = &mlx5_rdma_supported },
[MLX5_INTERFACE_PROTOCOL_VNET] = { .suffix = "vnet", .is_supported = &mlx5_vnet_supported },
};
/* is_supported 콜백으로 조건부 등록 */
for (i = 0; i < ARRAY_SIZE(mlx5_adev_devices); i++) {
if (mlx5_adev_devices[i].is_supported &&
!mlx5_adev_devices[i].is_supported(dev))
continue;
mlx5_add_adev(dev, i); /* eth → rdma → vnet 순서 보장 */
}
지연 초기화 패턴
무거운 초기화(대량 DMA 버퍼(Buffer) 할당, 펌웨어 다운로드 등)를 probe 시점이 아닌 실제 사용 시점으로 지연하면 부팅 시간을 단축할 수 있습니다.
/* 지연 초기화 패턴 — 첫 open() 시에 초기화 */
static int my_eth_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
struct my_eth_priv *priv;
priv = devm_kzalloc(&adev->dev, sizeof(*priv), GFP_KERNEL);
priv->initialized = false;
/* netdev만 등록, 무거운 초기화는 하지 않음 */
priv->netdev = alloc_etherdev(0);
priv->netdev->netdev_ops = &my_netdev_ops;
dev_set_drvdata(&adev->dev, priv);
return register_netdev(priv->netdev);
}
static int my_eth_open(struct net_device *netdev)
{
struct my_eth_priv *priv = netdev_priv(netdev);
if (!priv->initialized) {
/* 첫 사용 시 무거운 초기화 수행 */
allocate_dma_rings(priv);
download_firmware(priv);
priv->initialized = true;
}
netif_start_queue(netdev);
return 0;
}
커널 버전별 변경 이력
auxiliary_bus는 Linux 5.11에서 도입된 이후 지속적으로 개선되고 있습니다. 주요 변경 사항을 버전별로 정리합니다.
| 버전 | 변경사항 | 커밋/패치(Patch) |
|---|---|---|
| 5.11 | auxiliary_bus 최초 도입. auxiliary_device, auxiliary_driver, auxiliary_device_id 구조체 정의. auxiliary_device_init/add/delete/uninit, auxiliary_driver_register/unregister API. |
commit 7de3697e9cbd4 |
| 5.12 | MLX5 Scalable Function 지원 추가. SF를 auxiliary_device로 노출하여 독립 mlx5_core 인스턴스 생성. | mlx5 SF 시리즈 |
| 5.14 | Intel ICE 드라이버가 RDMA를 auxiliary_device로 분리. irdma 드라이버 통합. | ice IDC 시리즈 |
| 5.15 | SOF 오디오 프레임워크가 클라이언트 디바이스에 auxiliary_bus 채택. | SOF client 시리즈 |
| 5.18 | IDXD Work Queue를 auxiliary_device로 리팩토링. 개별 WQ 바인딩 지원. | idxd WQ 시리즈 |
| 6.0 | bus_type 구조체를 const로 변경하여 보안 강화. auxiliary_bus_type도 const로 전환. |
bus_type const 시리즈 |
| 6.2 | devm_auxiliary_device_init() 추가 시도 — 디바이스 관리 메모리 자동화. | devm 시리즈 |
| 6.4+ | Intel MEI(Management Engine Interface)가 auxiliary_bus 채택. HDCP, PXP 등 보안 기능 분리. | mei auxiliary 시리즈 |
| 6.6+ | auxiliary_bus 사용자 확대: habanalabs AI 가속기, AMD XDNA NPU, Thunderbolt/USB4 등. | 다수 드라이버 시리즈 |
보안 고려사항
auxiliary_bus를 사용할 때 고려해야 할 보안 측면을 분석합니다. 권한 분리, IOMMU 그룹 상속의 보안 함의, 네임스페이스(Namespace) 격리(Isolation)에 대해 다룹니다.
IOMMU 그룹과 보안 경계
auxiliary_device는 부모의 IOMMU 그룹을 상속합니다. 이는 성능에는 유리하지만 보안 관점에서 주의가 필요합니다:
- 같은 IOMMU 그룹 — 모든 auxiliary_device가 동일한 DMA 주소 공간(Address Space)을 공유합니다. 하나의 기능이 손상되면 다른 기능의 DMA 버퍼에 접근 가능합니다.
- VFIO 패스스루 — auxiliary_device 하나만 게스트에 할당하더라도 같은 IOMMU 그룹의 다른 디바이스도 함께 노출될 수 있습니다.
- PASID 분리 — SIOV 환경에서는 PASID(Process Address Space ID)로 DMA 공간을 논리적으로 분리합니다.
# IOMMU 그룹 확인 — auxiliary_device와 부모가 같은 그룹인지 확인
$ readlink /sys/bus/auxiliary/devices/mlx5_core.eth.0/iommu_group
../../../../kernel/iommu_groups/15
$ readlink /sys/bus/pci/devices/0000:03:00.0/iommu_group
../../../../kernel/iommu_groups/15
# → 같은 그룹 15
# IOMMU 그룹 내 모든 디바이스 확인
$ ls /sys/kernel/iommu_groups/15/devices/
0000:03:00.0 # 부모 PCIe
mlx5_core.eth.0 # auxiliary device
mlx5_core.rdma.0 # auxiliary device
네임스페이스와 cgroup 격리
auxiliary_device의 기능(예: 네트워크 인터페이스)은 네임스페이스에 의해 격리될 수 있습니다.
그러나 /sys/bus/auxiliary/는 초기 네임스페이스에서만 완전히 보입니다.
| 격리 수준 | 지원 | 설명 |
|---|---|---|
| Network namespace | 완전 지원 | mlx5e netdev를 다른 netns로 이동 가능 |
| cgroup v2 | 부분 지원 | BPF 프로그램으로 리소스 제한 가능 |
| Device namespace | 미지원 | sysfs 디바이스는 초기 netns에서만 노출 |
| VFIO 컨테이너 | IOMMU 그룹 단위 | 개별 auxiliary_device만 분리 불가 (그룹 전체) |
sysfs bind/unbind 권한
/sys/bus/auxiliary/drivers/*/bind와 unbind 파일은 root 권한이 필요합니다.
악의적인 unbind는 서비스 중단(DoS)을 유발할 수 있으므로, 프로덕션 환경에서는 접근을 제한해야 합니다.
# bind/unbind 권한 확인
$ ls -la /sys/bus/auxiliary/drivers/mlx5_core.eth/
--w------- 1 root root 4096 ... bind
--w------- 1 root root 4096 ... unbind
# SELinux / AppArmor로 추가 제한 가능
# SELinux 예: auxiliary_bus_t 도메인 정의
버스 선택 가이드
실제 드라이버 설계에서 auxiliary_bus, platform bus, MFD 중 어떤 것을 선택할지 결정할 때 고려해야 할 구체적인 기준과 엣지 케이스를 정리합니다.
auxiliary_bus를 선택해야 하는 경우
- PCIe 디바이스의 기능 분할 — IOMMU 그룹, NUMA 노드, 전원 관리를 부모에서 상속해야 할 때
- 런타임 핫플러그 — 디바이스를 동적으로 생성/제거해야 할 때 (SF, WQ 등)
- 독립 모듈 빌드 — 각 기능을 별도 .ko로 빌드하여 선택적 로드가 필요할 때
- container_of 리소스 공유 — 타입 안전한 구조체 공유가 필요할 때
- DT/ACPI 없음 — 하드웨어가 소프트웨어적으로 기능을 발견할 때
auxiliary_bus를 피해야 하는 경우
- DT/ACPI 기반 SoC 주변장치 — platform bus가 적합합니다
- 레지스터 공간 셀 분할 — MFD의 resource 모델이 적합합니다
- 단일 기능 디바이스 — 분할이 불필요하면 PCI 드라이버 단일 구현이 간결합니다
- 데이터 경로 분리 — auxiliary_bus는 제어 경로 분리용이며, 고속 데이터 경로에는 직접 함수 호출이 적합합니다
하이브리드 접근
실제 대형 드라이버는 여러 버스 메커니즘을 혼합합니다. MLX5가 대표적입니다:
| 메커니즘 | MLX5에서의 용도 |
|---|---|
| PCI 서브시스템 | PCIe 디바이스 열거, BAR 매핑(Mapping), MSI-X 벡터 |
| auxiliary_bus | eth/rdma/vnet/sf/crypto 기능 분할 |
| devlink | SF 생성/삭제, eSwitch 모드, health 리포터 |
| netlink | 네트워크 설정 (ip link, tc, ethtool) |
| VFIO/mdev | 게스트 VM에 VF/SF 패스스루 |
개발 체크리스트
auxiliary_bus를 사용하는 드라이버를 개발할 때 확인해야 할 항목을 부모 드라이버와 자식 드라이버로 나누어 정리합니다.
부모 드라이버 체크리스트
| 항목 | 확인 사항 | 실패 시 증상 |
|---|---|---|
dev.release 설정 | container_of + kfree 패턴 | 커널 경고, 메모리 누수 |
dev.parent 설정 | 부모 struct device 포인터 | NUMA/IOMMU 상속 실패 |
| 2단계 등록 | init → add 순서 | 에러 경로 불일치 |
| init 실패: kfree | 직접 kfree (uninit 호출 금지) | 이중 해제 |
| add 실패: uninit | auxiliary_device_uninit 호출 | refcount 누수 |
| 제거 순서 | delete → uninit (kfree 금지) | UAF, 이중 해제 |
| 동시성 보호 | SF 리스트 등 자체 mutex | 레이스 컨디션 |
| name 길이 | 32 - strlen(modname) - 1 이내 | 매칭 실패 |
자식 드라이버 체크리스트
| 항목 | 확인 사항 | 실패 시 증상 |
|---|---|---|
id_table | "modname.function" 형식 + 센티넬 | 드라이버 미바인딩 |
MODULE_DEVICE_TABLE | auxiliary 타입으로 선언 | 자동 모듈 로딩 실패 |
| probe: devm_ API | 가능한 한 devm_ 사용 | remove에서 누수 |
| probe: 에러 경로 | 할당 역순 해제 | 리소스 누수 |
| remove: 역순 정리 | probe의 역순으로 해제 | 의존성 버그 |
| shutdown: 최소 정리 | DMA 정지, IRQ 비활성화만 | 재부팅 지연/실패 |
| PM 콜백 | suspend/resume 구현 (선택) | sleep/resume 실패 |
| Kconfig | select AUXILIARY_BUS | 빌드 실패 |
참고자료
커널 공식 문서
- Auxiliary Bus — Auxiliary Bus 공식 문서
- Bus Types — 리눅스 버스 타입 문서
- Driver Model Overview — 드라이버 모델 개요
커널 소스 코드
drivers/base/auxiliary.c— auxiliary bus 코어 구현include/linux/auxiliary_bus.h— auxiliary_device, auxiliary_driver 정의drivers/net/ethernet/intel/ice/ice_main.c— Intel ICE 드라이버 (auxiliary bus 사용 예시)drivers/net/ethernet/mellanox/mlx5/core/main.c— Mellanox mlx5 (auxiliary bus 사용 예시)drivers/vdpa/mlx5/net/mlx5_vnet.c— mlx5 vDPA (auxiliary device 예시)drivers/infiniband/hw/mlx5/main.c— mlx5 IB (auxiliary driver 예시)
외부 자료
- The auxiliary bus — LWN, auxiliary bus 소개 기사
- Auxiliary devices and drivers — LWN, auxiliary 디바이스와 드라이버 해설
관련 문서
- 디바이스 드라이버 — Linux Device Model, struct device, device_driver 기초
- 디바이스 드라이버 — Linux Device Model, bus_type, struct device 기초
- PCI / PCIe — PCIe 디바이스 열거, BAR, MSI-X, SR-IOV
- SmartNIC / DPU — MLX5 기반 DPU에서의 auxiliary_bus 활용
- InfiniBand / RDMA — mlx5_ib 드라이버가 auxiliary_bus로 분리되는 사례
- VFIO & mdev — Mediated Device: 또 다른 가상 디바이스 분할 프레임워크