Auxiliary 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 실제 활용 사례, 대안 버스 비교, 디버깅 기법까지 종합적으로 다룹니다.
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콜백을 구현합니다. - id_table —
"modname.function"형식의 문자열로 드라이버와 디바이스를 매칭합니다. - 2단계 등록 —
auxiliary_device_init()으로 초기화한 후auxiliary_device_add()로 버스에 등록합니다. 분리된 에러 경로를 제공합니다. - release 콜백 —
auxiliary_device.dev.release는 반드시 설정해야 합니다. 마지막 참조가 해제될 때 메모리를 정리하며, 설정하지 않으면 커널 경고가 발생합니다.
개요 및 도입 배경
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·전원 관리를 그대로 유지하면서, 각 기능을 독립 모듈로 분리합니다.
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를 감싸는 컨테이너 구조체. 부모와 공유할 데이터(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()로 등록합니다. 편의 매크로
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를 감싸는 부모 컨테이너를 역참조합니다. 이를 통해 부모가 제공한 공유 레지스터 등에 접근합니다. -
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는 참조 카운트로 관리되므로, 메모리 해제 시점이 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 하드웨어에 대해 여러 오디오 파이프라인을 독립적으로 관리합니다.
/* 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;
}
관련 문서
- 디바이스 드라이버 — Linux Device Model, struct device, device_driver 기초
- 커널 버스 서브시스템 — bus_type, PCI/USB/SPI/I2C/MFD 전체 개요
- PCI / PCIe — PCIe 디바이스 열거, BAR, MSI-X, SR-IOV
- SmartNIC / DPU — MLX5 기반 DPU에서의 auxiliary_bus 활용
- InfiniBand / RDMA — mlx5_ib 드라이버가 auxiliary_bus로 분리되는 사례
- VFIO & mdev — Mediated Device: 또 다른 가상 디바이스 분할 프레임워크