AMD SMI NIC integration guide#
Purpose: This is a contribution guide for adding NIC device support to
the AMD SMI bare-metal (BM) library. It is not an end-user reference for the
amd-smi CLI or Python interface; it is intended for developers extending AMD
SMI with new NIC backends (AMD or third-party). This guide uses the in-tree
Pensando (ionic) plugin throughout as the worked example of the
vendor-integration pattern.
This document describes the AMD SMI public APIs, data structures, interfaces, and conventions for integrating Network Interface Card (NIC) device support into the AMD SMI framework.
Scope: This guide covers the bare-metal (BM) AMD SMI library only.
The Host AMD SMI library is a separate codebase with its own rules,
conventions, build system, and contribution process; the only components it
shares with the BM library are the public AMD SMI header (amdsmi.h, common
API subset) and the NIC library sources. Anything outside those two shared
components is BM-specific. For the host library, see:
User guide: AMD SMI for virtualization (Instinct docs)
AMD SMI provides a unified management interface for AMD accelerators (GPUs, CPUs), and is extended to support NIC devices via its public C API. This document covers:
NIC Information APIs (ASIC, bus, NUMA, ports, driver, firmware, RDMA, statistics, vendor statistics)
Vendor plugin integration pattern (with Pensando as reference)
Architecture#
Device model#
AMD SMI uses a hierarchical device model:
System
└── Socket(s)
└── Processor(s)
├── GPU (AMDSMI_PROCESSOR_TYPE_AMD_GPU)
├── CPU (AMDSMI_PROCESSOR_TYPE_AMD_CPU / _AMD_CPU_CORE)
└── NIC (AMDSMI_PROCESSOR_TYPE_AMD_NIC)
AMD SMI surfaces every discovered NIC, AMD or third-party, through the single
AMDSMI_PROCESSOR_TYPE_AMD_NIC processor type. The processor type does not tell
vendors apart; amdsmi_get_nic_type() and the vendor name from
amdsmi_get_nic_asic_info() do, and discovery is done by the NIC library’s plugin layer (see
Vendor plugin architecture).
Each NIC is represented as a processor handle (amdsmi_processor_handle).
You obtain handles from the standard AMD SMI discovery APIs, then pass them to
device-specific query functions.
A socket owns the processors it holds, NICs included. amdsmi_shut_down() frees
them, so an init, shutdown, init cycle (including a NIC-only one) starts from an
empty device list.
flowchart TD
System[System] --> Socket0[Socket 0]
System --> SocketN[Socket N ...]
Socket0 --> GPU[AMD GPU<br/>AMDSMI_PROCESSOR_TYPE_AMD_GPU]
Socket0 --> CPU[AMD CPU / CPU Core<br/>AMDSMI_PROCESSOR_TYPE_AMD_CPU]
Socket0 --> NIC[NIC<br/>AMDSMI_PROCESSOR_TYPE_AMD_NIC]
Processor types#
The amdsmi_processor_type_t enum defines all device types detectable by AMD SMI.
The value relevant to NIC devices is:
typedef enum {
AMDSMI_PROCESSOR_TYPE_UNKNOWN = 0, // Unknown processor type
AMDSMI_PROCESSOR_TYPE_AMD_GPU, // AMD GPU
AMDSMI_PROCESSOR_TYPE_AMD_CPU, // AMD CPU
AMDSMI_PROCESSOR_TYPE_NON_AMD_GPU, // Non-AMD GPU
AMDSMI_PROCESSOR_TYPE_NON_AMD_CPU, // Non-AMD CPU
AMDSMI_PROCESSOR_TYPE_AMD_CPU_CORE, // AMD CPU Core
AMDSMI_PROCESSOR_TYPE_AMD_APU, // AMD APU (GPU + CPU on a single die)
AMDSMI_PROCESSOR_TYPE_AMD_NIC // NIC (AMD or third-party)
} amdsmi_processor_type_t;
All NIC backends report their devices under AMDSMI_PROCESSOR_TYPE_AMD_NIC. A
new vendor does not add a processor-type value; it registers a discovery
plugin instead (see Vendor plugin architecture).
Initialization flags#
To discover NIC devices, pass the AMDSMI_INIT_AMD_NICS flag during initialization:
#define AMDSMI_INIT_AMD_NICS (1 << 4) // Initialize NIC discovery
Note
You can also discover NIC devices by passing AMDSMI_INIT_ALL_PROCESSORS, or by
OR-ing AMDSMI_INIT_AMD_NICS with other initialization flags (for example,
AMDSMI_INIT_AMD_NICS | AMDSMI_INIT_AMD_GPUS). All registered vendor plugins
run during discovery regardless of vendor (see
Vendor plugin architecture).
Vendor plugin architecture#
NIC support lives in a self-contained static library, amdsminic, under
src/nic/ai-nic/amdsmi_unified/. That library exposes a vendor-neutral C
interface (smi_nic_interface.h) to the AMD SMI dispatch layer and, internally,
drives discovery through a set of vendor plugins. A plugin is a C++ class
that implements the SmiNicSubsystem interface and knows how to recognize and
enumerate one vendor’s hardware.
Key design points:
One registration point.
make_default_vendor_plugins()(insrc/vendors/vendor_registry.{h,cpp}) returns one plugin instance per supported vendor, in this order: Pensando (AINIC), UALoE endpoint, Broadcom. Adding a partner is a newsrc/vendors/<name>/subdirectory plus onepush_backline in this function; nothing else in the core discovery path changes.Plugins implement
SmiNicSubsystem. The base interface (inc/smi_nic_subsystem.h) is:class SmiNicSubsystem { public: virtual void discover(const std::string& pci_path, const std::string& net_path, std::shared_ptr<amd::smi::nic::transport::NicTransport> transport) = 0; virtual NicVendor vendor() const = 0; // descriptive only virtual bool driver_loaded(const std::string& bdf, DriverType driver_type) const = 0; virtual auto driver_loaded(DriverType driver_type) const -> bool = 0; virtual const std::vector<std::unique_ptr<SmiNic>>& get_nics() const = 0; protected: std::pair<uint16_t, uint16_t> read_pci_ids(const std::string& sysfs_bus_path) const; bool resolve_bdf(const std::string& symlink, std::string& bdf) const; bool is_driver_bound_to_bdf(const std::string& driver_dir, const std::string& bdf, bool is_canonical_match) const; };
The protected helpers
read_pci_ids(),resolve_bdf()andis_driver_bound_to_bdf()are shared by every plugin so vendors don’t reimplement sysfs PCI parsing or driver-binding checks.DriverTypenames a role (Mainfor the netdev/PCI driver,Rdmafor the RDMA auxiliary driver), and each plugin maps it to its own driver path.vendor()is descriptive only: two plugins can report the same vendor (UALoE and Pensando are bothAMD), so it never routes a query.Discovery is id-match based.
SmiNicSystem::discover_nics()(src/smi_nic_system.cpp) walks/sys/bus/pci/devicesand/sys/class/netand calls each registered plugin’sdiscover(). A plugin scans the PCI path, usesread_pci_ids()to match its own PCI ids, builds aSmiNicfor each match, then associates network ports with it (a UALoE endpoint has none). The system aggregates every plugin’sget_nics()into one BDF-sorted list.Ports reuse the shared ethtool transport.
SmiNicSystemcreates one transport withcreate_transport(NicBackend_t::Auto)(inc/smi_nic_transport.h) and passes it to every plugin and port.Autotries the netlink backend first (kernel 5.6+ with libnl-3) and falls back to the ioctl backend for pause and link settings; driver info, ethtool statistics and the permanent address always use ioctl, and FEC statistics exist only on the netlink backend. A plugin that builds onSmiNic/SmiNicPortinherits all of these and does not need to create a transport of its own. Active FEC and driver info are read by the interface layer with a direct ethtool ioctl, not through the transport.Public surface stays unified. Plugins are internal to
amdsminic. They do not add symbols toamdsmi.h; all access is through the standardamdsmi_get_nic_*APIs, which the dispatch layer forwards into the library’ssmi_nic_*C interface.Driver statistics are read with a consistency check.
ethtool -Stakes three ioctl requests (count, names, values), and a driver’s count can change between them.read_driver_statistics()(inc/smi_nic_driver_stats.h) allocates each buffer with 64 entries of headroom, passes the expected count as the input length, and compares the counts the kernel returns. A mismatch restarts the sequence, up to 3 attempts, and then fails withEAGAIN. Kernels that include commit7b07be1ff1cb(December 2025) answer 0 when the input count no longer matches; older kernels write their current count whatever size was allocated, so the headroom narrows that window but cannot close it. The three requests are injectable, which is howtests/amd_smi_test/unit/nic/nic_driver_stats_test.ccmodels both kinds of kernel.Identity VPD is read once per NIC.
SmiNicreads the NIC’s VPD, and its first port’s for the fields the NIC’s own image lacks, on the first call toproduct_name(),part_number(),serial_number()oris_vpd_readable(). The four accessors share that read, andadd_nic_port()drops it because a port’s VPD fills in missing fields. The cache has no lock of its own: callers hold the context mutex, assmi_get_nic_asic_info()does.
The reference plugin is Pensando/ionic
(src/vendors/pensando/pensando_subsystem.{h,cpp}):
class SmiNicSubsystemPensando : public SmiNicSubsystem {
// ...
private:
static constexpr uint16_t VENDOR_ID = 0x1dd8;
static constexpr std::array<uint16_t, 3> BRIDGE_DEVICE_IDS = {0x0008, 0x1008, 0x1478};
static constexpr uint16_t PORT_ID = 0x1002; // downstream port function
static constexpr uint16_t MID_BRIDGE_ID = 0x1001; // internal bridge, never a NIC
static constexpr uint16_t MGMT_ID = 0x100c; // pds_core management function
};
Its discover() matches VENDOR_ID and one of BRIDGE_DEVICE_IDS for the bridge
(the bridge reports a different id depending on the card), then discover_ports()
walks /sys/class/net, resolves each interface’s PCI BDF, matches PORT_ID,
confirms the port is downstream of the bridge, and collects InfiniBand/RDMA info
and statistics on each accepted port. discover_mgmt_function() finds the
MGMT_ID function under the same bridge. A 1dd8 PCI bridge that is neither
listed nor MID_BRIDGE_ID is logged at debug level and not added.
The other two plugins follow the same interface: UALoE
(src/vendors/amd/ualoe_subsystem.{h,cpp}) matches PCI ids 1022:1747, has no
ports and no netdev, and reports FWCTL when an ifoe.cmd.* entry exists;
Broadcom (src/vendors/broadcom/broadcom_subsystem.{h,cpp}) matches netdevs
whose PCI function is bound to bnxt_en or bng_en.
flowchart LR
App[User Application / CLI / Python] --> AmdSmi[libamd_smi.so<br/>Public API: amdsmi.h]
AmdSmi --> Dispatch[Dispatch layer<br/>src/amd_smi/amd_smi.cc]
Dispatch --> NicLib[amdsminic static lib<br/>smi_nic_interface.h]
NicLib --> System[SmiNicSystem<br/>discover_nics]
System --> Registry[make_default_vendor_plugins]
Registry --> Pensando[Pensando plugin<br/>src/vendors/pensando/]
Registry --> UALoE[UALoE plugin<br/>src/vendors/amd/]
Registry --> Broadcom[Broadcom plugin<br/>src/vendors/broadcom/]
Registry --> VendorX[Other vendor plugin<br/>src/vendors/<name>/]
Pensando --> Transport[Shared ethtool transport<br/>ioctl / netlink]
Broadcom --> Transport
VendorX --> Transport
Transport --> Sysfs[(Linux sysfs / netdev /<br/>infiniband / ethtool)]
Public API conventions#
Naming#
All public AMD SMI functions follow this pattern:
amdsmi_get_<device_type>_<data_category>(processor_handle, output_struct*)
Examples:
amdsmi_get_nic_asic_info()amdsmi_get_nic_bus_info()amdsmi_get_nic_port_info()amdsmi_get_nic_telemetry()amdsmi_get_nic_hwmon_sensors()amdsmi_get_nic_rdma_port_statistics()
Parameter conventions#
Parameter |
Convention |
|---|---|
|
Always the first parameter. Obtained from discovery APIs. |
Output structs |
Pointer to caller-allocated struct. Must not be |
Two-call pattern |
For variable-length data: first call with |
Return values#
All APIs return amdsmi_status_t:
Status |
Meaning |
|---|---|
|
Operation completed successfully |
|
Invalid argument (for example, |
|
Feature not supported on this device |
|
Failed to read sysfs file |
|
Insufficient permissions |
|
Library not initialized |
|
Processor, port or counter not found |
|
The caller’s buffer is too small |
|
No data was found for the given input |
|
The device driver is not loaded |
|
Device mutex could not be acquired |
Data structures#
Size constants#
#define AMDSMI_MAX_STRING_LENGTH 256 // Max string buffer length
#define AMDSMI_MAX_NIC_PORTS 32 // Max NIC ports
#define AMDSMI_MAX_NIC_RDMA_DEV 32 // Max RDMA devices
#define AMDSMI_MAX_NIC_FW 16 // Max firmware entries
NIC ASIC information#
typedef struct {
uint16_t vendor_id;
uint16_t subvendor_id;
uint16_t device_id;
uint16_t subsystem_id;
uint8_t revision;
char permanent_address[AMDSMI_MAX_STRING_LENGTH];
char product_name[AMDSMI_MAX_STRING_LENGTH];
char part_number[AMDSMI_MAX_STRING_LENGTH];
char serial_number[AMDSMI_MAX_STRING_LENGTH];
char vendor_name[AMDSMI_MAX_STRING_LENGTH];
} amdsmi_nic_asic_info_t;
Sysfs sources:
vendor_id:/sys/bus/pci/devices/<BDF>/vendordevice_id:/sys/bus/pci/devices/<BDF>/devicesubvendor_id:/sys/bus/pci/devices/<BDF>/subsystem_vendorsubsystem_id:/sys/bus/pci/devices/<BDF>/subsystem_devicerevision:/sys/bus/pci/devices/<BDF>/revisionproduct_name,part_number,serial_number: VPD data, parsed from/sys/bus/pci/devices/<BDF>/vpdwhen present. When the device exposes no sysfs VPD entry,product_nameandpart_numberare reported as unavailable. When VPD is readable but carries no serial number,serial_numberfalls back to the devlink board serial (DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER) and is reported as unavailable only when that is also absent. The devlinkDEVLINK_ATTR_INFO_SERIAL_NUMBERattribute is deliberately not used; it is a MAC-derived EUI, not the physical board serial.
The NIC type and the capability bitmask are not fields of this struct, which keeps its previous size. Read them with amdsmi_get_nic_type() and amdsmi_get_nic_capability(). Both come from the discovered device, not sysfs. The capability is a bitmask of amdsmi_nic_capability_bits_t: AMDSMI_NIC_CAP_FWCTL (firmware-control management function, e.g. Pensando/POLLARA) and AMDSMI_NIC_CAP_NETDEV (host network port(s) present).
NIC type#
typedef enum {
AMDSMI_NIC_TYPE_UNKNOWN = 0, // the type could not be determined
AMDSMI_NIC_TYPE_AINIC = 1, // AMD Pensando AINIC
AMDSMI_NIC_TYPE_UALOE = 2, // UALoE accelerator-fabric endpoint
AMDSMI_NIC_TYPE_OTHER = 3, // any other vendor
} amdsmi_nic_type_t;
MODE is not a reliable way to tell the kinds apart: a Pensando card whose ports were not found is fwctl-only too. Use the type. For AMDSMI_NIC_TYPE_OTHER the vendor name identifies the device.
NIC bus information#
typedef struct {
amdsmi_bdf_t bdf;
uint8_t max_pcie_width;
uint32_t max_pcie_speed; // in GT/s
char pcie_interface_version[AMDSMI_MAX_STRING_LENGTH];
char slot_type[AMDSMI_MAX_STRING_LENGTH];
} amdsmi_nic_bus_info_t;
Sysfs sources:
bdf: Parsed from PCI enumerationmax_pcie_width:/sys/bus/pci/devices/<BDF>/max_link_widthmax_pcie_speed:/sys/bus/pci/devices/<BDF>/max_link_speed
BDF (Bus-Device-Function)#
amdsmi_bdf_t is a union that provides three ways to access the BDF:
typedef union {
struct bdf_ {
uint64_t function_number : 3;
uint64_t device_number : 5;
uint64_t bus_number : 8;
uint64_t domain_number : 48;
} bdf;
struct { // anonymous access (same layout)
uint64_t function_number : 3;
uint64_t device_number : 5;
uint64_t bus_number : 8;
uint64_t domain_number : 48;
};
uint64_t as_uint; // raw 64-bit value
} amdsmi_bdf_t;
Fields can be accessed directly (for example, bdf.function_number) or via the
named struct (for example, bdf.bdf.function_number). The as_uint member
provides the packed 64-bit representation.
NIC NUMA information#
typedef struct {
uint8_t node;
char affinity[AMDSMI_MAX_STRING_LENGTH];
} amdsmi_nic_numa_info_t;
Sysfs sources:
node:/sys/bus/pci/devices/<BDF>/numa_nodeaffinity:/sys/devices/system/node/node<N>/cpulist(where<N>is the value read fromnuma_node)
NIC port information#
typedef struct {
amdsmi_bdf_t bdf;
uint32_t port_num;
char type[AMDSMI_MAX_STRING_LENGTH];
char flavour[AMDSMI_MAX_STRING_LENGTH];
char netdev[AMDSMI_MAX_STRING_LENGTH];
uint8_t ifindex; // 0 when unavailable or above 255; see amdsmi_get_nic_port_ifindex()
char mac_address[AMDSMI_MAX_STRING_LENGTH];
uint8_t carrier;
uint16_t mtu;
char link_state[AMDSMI_MAX_STRING_LENGTH];
uint32_t link_speed;
uint32_t active_fec; // Active FEC modes bitmask
char autoneg[AMDSMI_MAX_STRING_LENGTH];
char pause_autoneg[AMDSMI_MAX_STRING_LENGTH];
char pause_rx[AMDSMI_MAX_STRING_LENGTH];
char pause_tx[AMDSMI_MAX_STRING_LENGTH];
} amdsmi_nic_port_t;
typedef struct {
uint32_t num_ports;
amdsmi_nic_port_t ports[AMDSMI_MAX_NIC_PORTS];
} amdsmi_nic_port_info_t;
The port fields sourced from ethtool (active_fec, the pause_* fields, and
link_speed) are populated through the shared transport layer described in
Vendor plugin architecture.
Active FEC bitmask values (example mapping from ethtool_fecparam):
Note
The values below are a representative example. The exact bit values and the set
of supported modes may differ between Linux kernel and ethtool versions. Refer
to the ethtool_fecparam definitions in the kernel UAPI headers
(<linux/ethtool.h>) of the target system rather than treating the table below
as a fixed contract.
Value |
Mode |
|---|---|
0x01 |
|
0x02 |
|
0x04 |
|
0x08 |
|
0x10 |
|
0x20 |
|
NIC driver information#
typedef struct {
char name[AMDSMI_MAX_STRING_LENGTH];
char version[AMDSMI_MAX_STRING_LENGTH];
} amdsmi_nic_driver_info_t;
NIC firmware information#
typedef struct {
char name[AMDSMI_MAX_STRING_LENGTH];
char version[AMDSMI_MAX_STRING_LENGTH];
} amdsmi_nic_fw_entry_t;
typedef struct {
uint32_t num_fw;
amdsmi_nic_fw_entry_t fw[AMDSMI_MAX_NIC_FW]; // AMDSMI_MAX_NIC_FW is 16
} amdsmi_nic_fw_info_t;
The public entry keeps the layout of earlier releases, so it carries a name and a version and
nothing else, and a NIC reports at most 16 entries. The NIC library’s own entry
(smi_nic_fw_version_t) also holds the kind of version (fixed, running or stored) and allows 64.
amdsmi_get_nic_fw_info() gives the first version of a name the bare name, and a later version of
the same name its kind in parentheses, for example fw.version (stored). amdsmi_unified has a
wider entry (type plus fw, 64 entries). The public header can follow it when the library
version allows an ABI break.
NIC telemetry#
Live health and port-split state, which come over devlink (libnl3). Temperatures
are not part of telemetry: read them from amdsmi_get_nic_hwmon_sensors(). A
metric the NIC does not expose is reported through a width-max reserved value in
its sub-struct (UINT32_MAX or UINT8_MAX), not a failed call.
typedef enum {
AMDSMI_NIC_HEALTH_UNKNOWN = 0, // reserved: reporter present, state indeterminate
AMDSMI_NIC_HEALTH_HEALTHY = 1,
AMDSMI_NIC_HEALTH_WARNING = 2, // reserved: no producing path yet
AMDSMI_NIC_HEALTH_ERROR = 3,
AMDSMI_NIC_HEALTH_UNSUPPORTED = 4, // no health reporter exposed
} amdsmi_nic_health_state_t;
typedef struct {
uint8_t state; // an amdsmi_nic_health_state_t value
uint32_t error_count; // UINT32_MAX when unavailable; real counts saturate to UINT32_MAX-1
char reporter[64]; // devlink reporter name; "" if none
uint64_t reserved[4];
} amdsmi_nic_health_t;
typedef struct {
uint8_t splittable;
uint8_t split_count;
uint64_t reserved[1];
} amdsmi_nic_port_split_t;
typedef struct {
amdsmi_nic_health_t health;
amdsmi_nic_port_split_t port_split;
uint64_t reserved[4];
} amdsmi_nic_telemetry_t;
NIC RDMA device information#
typedef struct {
char netdev[AMDSMI_MAX_STRING_LENGTH];
char state[AMDSMI_MAX_STRING_LENGTH];
uint8_t rdma_port;
uint16_t max_mtu;
uint16_t active_mtu;
} amdsmi_nic_rdma_port_info_t;
typedef struct {
char rdma_dev[AMDSMI_MAX_STRING_LENGTH];
char node_guid[AMDSMI_MAX_STRING_LENGTH];
char node_type[AMDSMI_MAX_STRING_LENGTH];
char sys_image_guid[AMDSMI_MAX_STRING_LENGTH];
char fw_ver[AMDSMI_MAX_STRING_LENGTH];
uint8_t num_rdma_ports;
amdsmi_nic_rdma_port_info_t rdma_port_info[AMDSMI_MAX_NIC_PORTS];
} amdsmi_nic_rdma_dev_info_t;
typedef struct {
uint8_t num_rdma_dev;
amdsmi_nic_rdma_dev_info_t rdma_dev_info[AMDSMI_MAX_NIC_RDMA_DEV];
} amdsmi_nic_rdma_devices_info_t;
RDMA information is optional. A NIC whose RDMA driver is unavailable (for
example, ionic_rdma blacklisted) is still enumerated, and
amdsmi_get_nic_rdma_dev_info() returns AMDSMI_STATUS_SUCCESS with
num_rdma_dev set to 0. Check num_rdma_dev rather than the return value to
tell whether any RDMA device was reported.
max_mtu and active_mtu come from libibverbs, with the sysfs max_mtu and
active_mtu files as the fallback; they read N/A when neither answers
(some drivers, such as bng_re, expose no MTU files). The query opens the RDMA
device in a short-lived child process, because opening it loads the vendor’s
user-space provider, which can crash or hang. The child is stopped after two
seconds and handles fatal signals itself, so a failing provider costs the MTU and
nothing else.
NIC statistics#
typedef struct {
char name[AMDSMI_MAX_STRING_LENGTH];
uint64_t value;
} amdsmi_nic_stat_t;
Used by amdsmi_get_nic_rdma_port_statistics(),
amdsmi_get_nic_port_statistics(), and amdsmi_get_nic_vendor_statistics().
Public API reference#
The canonical reference for every AMD SMI public API is the header
projects/amdsmi/include/amd_smi/amdsmi.h
and the rendered Sphinx documentation at the
AMD SMI documentation.
For an end-to-end usage example, see
amd_smi_nic.cc. The summaries below provide a
NIC-focused subset of that reference; use the header and the in-tree example as
the source of truth.
Note
The signatures listed in this section reflect the bare-metal (BM) AMD SMI
library. The host AMD SMI library is a separate codebase with its own user
guide; see
amd/MxGPU-Virtualization (smi-lib)
and the
AMD SMI for virtualization user guide.
Initialization and shutdown#
// Initialize AMD SMI with NIC support
amdsmi_status_t amdsmi_init(uint64_t init_flags);
// Use: amdsmi_init(AMDSMI_INIT_AMD_NICS);
// Shutdown AMD SMI
amdsmi_status_t amdsmi_shut_down(void);
Device discovery#
// Get socket handles
amdsmi_status_t amdsmi_get_socket_handles(uint32_t *socket_count,
amdsmi_socket_handle *socket_handles);
// Get processor handles filtered by type
amdsmi_status_t amdsmi_get_processor_handles_by_type(
amdsmi_socket_handle socket_handle,
amdsmi_processor_type_t processor_type,
amdsmi_processor_handle *processor_handles,
uint32_t *processor_count);
NIC handles are requested with processor_type = AMDSMI_PROCESSOR_TYPE_AMD_NIC.
Two-call pattern for discovery:
Call with
processor_handles = NULLto getprocessor_count.Allocate an array of
processor_counthandles.Call again with the allocated array.
NIC information APIs#
The following APIs are implemented and available in the current AMD SMI release:
// NIC ASIC Information
amdsmi_status_t amdsmi_get_nic_asic_info(
amdsmi_processor_handle processor_handle,
amdsmi_nic_asic_info_t *info);
// NIC Type
amdsmi_status_t amdsmi_get_nic_type(
amdsmi_processor_handle processor_handle,
amdsmi_nic_type_t *type);
// NIC Capability (bitmask of amdsmi_nic_capability_bits_t)
amdsmi_status_t amdsmi_get_nic_capability(
amdsmi_processor_handle processor_handle,
uint32_t *capability);
// Full interface index of a NIC port (amdsmi_nic_port_t::ifindex holds one byte)
amdsmi_status_t amdsmi_get_nic_port_ifindex(
amdsmi_processor_handle processor_handle,
uint32_t port_index,
uint32_t *ifindex);
// NIC Bus Information
amdsmi_status_t amdsmi_get_nic_bus_info(
amdsmi_processor_handle processor_handle,
amdsmi_nic_bus_info_t *info);
// NIC NUMA Information
amdsmi_status_t amdsmi_get_nic_numa_info(
amdsmi_processor_handle processor_handle,
amdsmi_nic_numa_info_t *info);
// NIC Driver Information
amdsmi_status_t amdsmi_get_nic_driver_info(
amdsmi_processor_handle processor_handle,
amdsmi_nic_driver_info_t *info);
// NIC Firmware Information (firmware versions read over devlink/libnl3)
amdsmi_status_t amdsmi_get_nic_fw_info(
amdsmi_processor_handle processor_handle,
amdsmi_nic_fw_info_t *info);
// NIC Telemetry (health, port-split)
amdsmi_status_t amdsmi_get_nic_telemetry(
amdsmi_processor_handle processor_handle,
amdsmi_nic_telemetry_t *info);
// NIC hwmon sensors (temperature, voltage, power and their thresholds)
amdsmi_status_t amdsmi_get_nic_hwmon_sensors(
amdsmi_processor_handle processor_handle,
uint32_t *num_sensors,
amdsmi_nic_sensor_t *sensors);
// NIC Port Information
amdsmi_status_t amdsmi_get_nic_port_info(
amdsmi_processor_handle processor_handle,
amdsmi_nic_port_info_t *info);
// NIC RDMA Device Information
amdsmi_status_t amdsmi_get_nic_rdma_dev_info(
amdsmi_processor_handle processor_handle,
amdsmi_nic_rdma_devices_info_t *info);
NIC link type#
amdsmi_status_t amdsmi_topo_get_link_type(
amdsmi_processor_handle processor_handle_src,
amdsmi_processor_handle processor_handle_dst,
uint64_t *hops,
amdsmi_link_type_t *type);
amdsmi_topo_get_link_type() accepts a NIC handle for either argument (NIC to GPU
or NIC to NIC). The link type is computed from sysfs and needs no loaded driver:
Type |
Meaning |
|---|---|
|
Both devices sit behind the same PCIe switch |
|
Same NUMA node, different PCIe switch (two functions of one card read |
|
Different NUMA nodes |
|
A BDF is invalid or a NUMA node cannot be read |
hops may be NULL. For NIC pairs it is not meaningful and reads UINT64_MAX.
A GPU compute partition reports its partition number as the PCI function, so on a
platform where that function is also a real device (a UALoE endpoint), the two
share one BDF string; the result is still correct because both are on the same
NUMA node.
NIC RDMA port statistics#
This API uses a two-call pattern:
amdsmi_status_t amdsmi_get_nic_rdma_port_statistics(
amdsmi_processor_handle processor_handle,
uint32_t rdma_port_index,
uint32_t *num_stats,
amdsmi_nic_stat_t *stats);
Usage:
Call with
stats = NULLto getnum_stats(count of available statistics).Allocate an array of
num_statselements.Call again with the allocated array. If there are more statistics than the array holds, the first ones are filled,
num_statsis set to the count needed, and the call returnsAMDSMI_STATUS_INSUFFICIENT_SIZE.
The statistics are the driver’s RDMA hardware counters, the same set as
rdma statistic show link DEV/PORT, and are read on every call.
rdma_port_index counts RDMA ports from 0 across the NIC’s RDMA devices, in the
order of amdsmi_nic_rdma_devices_info_t (each device’s ports in turn). On an
AI-NIC with one RDMA device and one port, the only valid index is 0. An index past
the last RDMA port, or a NIC without an RDMA device, returns
AMDSMI_STATUS_NOT_SUPPORTED.
Not supported: rdma statistic show mr, rdma statistic mode supported, and
setting optional counters with rdma statistic set link DEV/PORT optional-counters.
The Python binding is amdsmi_get_nic_rdma_port_statistics(processor_handle, rdma_port_index). It returns a dictionary of counter name to value.
NIC port statistics and vendor statistics#
Both APIs use a two-call pattern and return per-port counters. Port
statistics are the standard driver counters for a NIC port; vendor statistics
are the driver-defined counters exposed via ethtool -S, in two tiers. The default call
returns the default tier; amdsmi_get_nic_vendor_statistics_with_scope() takes the tier
as a parameter.
amdsmi_status_t amdsmi_get_nic_port_statistics(
amdsmi_processor_handle processor_handle,
uint32_t port_index,
uint32_t *num_stats,
amdsmi_nic_stat_t *stats);
amdsmi_status_t amdsmi_get_nic_vendor_statistics(
amdsmi_processor_handle processor_handle,
uint32_t port_index,
uint32_t *num_stats,
amdsmi_nic_stat_t *stats);
// Same as amdsmi_get_nic_vendor_statistics(), with the tier given in the call:
// AMDSMI_NIC_STAT_SCOPE_DEFAULT, or AMDSMI_NIC_STAT_SCOPE_EXTENDED (a superset).
// Any other value returns AMDSMI_STATUS_INVAL.
amdsmi_status_t amdsmi_get_nic_vendor_statistics_with_scope(
amdsmi_processor_handle processor_handle,
uint32_t port_index,
uint32_t *num_stats,
amdsmi_nic_stat_t *stats,
amdsmi_nic_stat_scope_t scope);
Usage:
Call with
stats = NULLto getnum_stats(count of available statistics for the port).Allocate an array of
num_statselements.Call again with the allocated array. If there are more statistics than the array holds, the first ones are filled,
num_statsis set to the count needed, and the call returnsAMDSMI_STATUS_INSUFFICIENT_SIZE.
Vendor plugin implementation#
This section is for contributors working on the NIC library itself. Public
consumers should use the amdsmi_* API described above; they never see the
internal C interface or the C++ plugin classes discussed here.
Internal C interface (smi_nic_interface.h)#
The amdsminic static library exposes a vendor-neutral C interface,
include/amd_smi/impl/nic/amdsmi_unified/interface/smi_nic_interface.h, that the
AMD SMI dispatch layer (src/amd_smi/amd_smi.cc) calls. It is context-based:
smi_nic_status_t smi_nic_create_context(smi_nic_ctx_t* ctx);
smi_nic_status_t smi_nic_destroy_context(smi_nic_ctx_t ctx);
smi_nic_status_t smi_discover_nics(smi_nic_ctx_t ctx, smi_nic_discovery_t* discovery);
smi_nic_status_t smi_get_nic_asic_info(smi_nic_ctx_t ctx, uint64_t device,
smi_nic_asic_info_t* info);
smi_nic_status_t smi_get_nic_bus_info(smi_nic_ctx_t ctx, uint64_t device,
smi_nic_bus_info_t* info);
// ...and the remaining smi_get_nic_* queries.
This header is shared with amdsmi_unified: it has the same functions, types and
macros, and the same struct layout, as the copy there, and it is plain C. What only
the bare-metal library has (the NIC kind and capability, the port interface index,
telemetry, hwmon sensors and the scoped vendor statistics) is declared in
include/amd_smi/impl/nic/amd_smi_nic_interface.h, which includes the shared header.
Change the shared header only together with amdsmi_unified.
smi_nic_status_t and the smi_nic_* structures are internal to the NIC
library. The dispatch layer converts them to amdsmi_status_t and the
amdsmi_nic_* structures at the public boundary; none of the smi_nic_* types
appear in amdsmi.h.
C++ plugin layer#
Behind the C interface, SmiNicSystem (src/smi_nic_system.cpp) drives
discovery through the vendor plugins:
Its constructor calls
make_default_vendor_plugins()and registers each returnedSmiNicSubsystem.discover_nics()iterates the registered plugins, callingdiscover("/sys/bus/pci/devices", "/sys/class/net", transport)on each and collecting theirget_nics()into one BDF-sorted list.driver_loaded()finds the plugin that owns the NIC (by object identity over each plugin’sget_nics(), not byvendor()) and asks it. The BDF passed in is a port BDF.
The Pensando plugin (src/vendors/pensando/) is the reference implementation.
It matches its PCI IDs during discover(), walks /sys/class/net in
discover_ports() to associate ports with the device, and implements
driver_loaded() for the ionic and ionic_rdma drivers.
Device discovery details (Pensando reference)#
Step |
Mechanism |
|---|---|
Bridge match |
Scan |
Port match |
Walk |
Downstream check |
Confirm the resolved path of the port function contains |
Management function |
Find |
Driver presence |
|
The vendor’s kernel driver must be loaded for its ports to appear under
/sys/class/net; without it, no ports are discovered for that device.
AMD SMI CLI commands for NIC#
The amd-smi CLI enumerates every NIC the library discovers through the same
public discovery path used above. There is no per-vendor CLI code. NIC-capable
subcommands accept a device selector:
-N, --nic <ID | BDF | UUID> ...
A bare --nic with no value selects every NIC.
Run the NIC commands as root. PCI VPD (/sys/bus/pci/devices/<BDF>/vpd) is readable by root only, so without it
product_name, part_number and serial_number read N/A.
The Broadcom-specific options of earlier builds are gone: --brcm_nic, --brcm_switch,
--switch, topology --nic_topo and topology --nic_switch. Broadcom NICs are listed and
queried through --nic like every other NIC.
amd-smi list includes discovered NICs alongside GPUs, and amd-smi static
reports NIC identity details (BDF, product name, part/serial number, vendor)
when a NIC is selected with -N/--nic. amd-smi metric --nic reports NIC
the temperatures (from the hwmon sensors), health and port-split, and amd-smi firmware --nic reports
NIC firmware versions. The selector works only when you initialize NIC discovery
(see Initialization flags).
amd-smi list --nic and amd-smi metric --nic print a TYPE line for each NIC: AINIC,
UALoE, or the vendor name for any other NIC (N/A if the type is unknown). In metric --nic
it is the first field of each block, so the all-N/A block of a UALoE endpoint, which has no
telemetry source, is not mistaken for a failure.
Per-port NIC statistics with metric --nic --port#
The metric --nic command supports per-port statistics collection via the
--port flag:
amd-smi metric --nic <ID | BDF | UUID> --port [PORT_NUM] [--extended] [--json]
Flags:
--port(bare, no argument): Report statistics for all ports on the NIC.--port PORT_NUM: Report statistics for a single port (by numeric index).--extended: Include extended (vendor-specific) counters. Requires--portto be specified. Applies to text and JSON output.--json: Output as JSON. Uses the same scope as text:DEFAULTunless--extendedis given.
Scopes:
DEFAULT: The vendor-tier subset ofVENDOR_STATISTICS(e.g.frames_rx_okfor ionic,rx_pfc_framesfor bnxt).EXTENDED: The catalogued vendor-specific counters (viaethtool -Splus, for ionic, netlink FEC stats). A superset ofDEFAULT. A counter the driver reports but the catalogue does not list is not shown, in either scope. Requires--extended, in text and JSON.
STATISTICS (the standard driver counters, e.g. RX_PACKETS, TX_PACKETS,
RX_BYTES) is a separate, untiered block; it isn’t affected by scope.
ionic FEC counters. EXTENDED adds up to 19 FEC names for an ionic port, all read over
ethtool netlink (ethtool -I --show-fec), never through ethtool -S:
corrected_blocks, uncorrectable_blocks, corrected_bits and the 16-bin symbol-error
histogram fec_symbols_err_0 to fec_symbols_err_15. A port shows only the names its kernel
and driver report. A kernel without FEC statistics (for example 6.8 with driver 25.12) reports
none, and the histogram needs a kernel that carries it (upstream since 6.18). Each histogram bin
is named after its symbol-error count; a bin spanning several counts is skipped.
Output structure:
PORTS:
PORT_<N>:
NETDEV: <interface name>
VENDOR_STATISTICS:
<COUNTER_NAME>: <value> # DEFAULT scope, or EXTENDED with --extended
...
STATISTICS:
<COUNTER_NAME>: <value>
...
VENDOR_STATISTICS is always present; only its scope (DEFAULT or
EXTENDED) varies with --extended.
Examples:
# All ports on NIC 0, DEFAULT scope
sudo amd-smi metric --nic 0 --port
# Port 0 on NIC 0, EXTENDED scope (text)
sudo amd-smi metric --nic 0 --port 0 --extended
# All ports on NIC 0, EXTENDED scope (JSON)
sudo amd-smi metric --nic 0 --port --extended --json
RDMA port statistics with metric --nic --rdma#
The metric --nic command reports RDMA hardware counters per RDMA port with the
--rdma flag:
amd-smi metric --nic <ID | BDF | UUID> --rdma [RDMA_PORT_NUM] [--json]
Flags:
--rdma(bare, no argument): Report counters for every RDMA port on the NIC.--rdma RDMA_PORT_NUM: Report one RDMA port. The number counts the NIC’s RDMA ports from 0, across its RDMA devices. It is not a netdev port index (--port) and not a BDF. To find the valid numbers, runamd-smi static --nic <ID>, which lists each RDMA device and its ports. It numbers ports per device (each device restarts atPORT_0), so on a NIC with several RDMA devices or ports the number to pass is the position across all of them, not thestaticlabel. On an AI-NIC with one device and one port, the only valid number is 0.--rdmadoes not need--port, and the two can be combined.--extendedstill requires--port: RDMA counters have no default and extended sets.
Output structure:
RDMA_DEVICES:
RDMA_DEVICE_<N>:
RDMA_DEV: <RDMA device name, for example ionic_0>
PORTS:
PORT_<M>:
STATISTICS:
<COUNTER_NAME>: <value>
...
RDMA_DEVICE_<N> and PORT_<M> are the labels static --nic prints.
A NIC without RDMA ports prints RDMA_DEVICES: No RDMA ports found for this NIC in text
and {} in JSON. That is the case for a UALoE endpoint, which has no RDMA device.
Examples:
# Every RDMA port on NIC 0
sudo amd-smi metric --nic 0 --rdma
# The first RDMA port on a NIC selected by BDF, as JSON
sudo amd-smi metric --nic 0001:01:00.0 --rdma 0 --json
RDMA ports are reported for AI-NIC (ionic) and Broadcom (bnxt_re, bng_re) NICs.
UALoE endpoints have none. rdma statistic show mr, rdma statistic mode supported, and
setting optional counters are not supported.
Hardware-monitor sensors with metric --nic --sensors#
The metric --nic command reports the NIC’s hwmon sensors (temperature, voltage, and power,
with their thresholds) with the --sensors flag:
amd-smi metric --nic <ID | BDF | UUID> --sensors [--json]
Flags:
--sensors: Add aSENSORSblock to the NIC output. Without it the output is unchanged and no sensor is read.--json: Output as JSON. Each reading is{"value": <number>, "unit": "<C|V|W>"}.
Output structure:
SENSORS:
SENSOR_<N>:
LABEL: <driver label, or N/A>
CATEGORY: <TEMPERATURE | POWER | VOLTAGE>
INSTANCE: <hwmon instance number>
CHANNEL: <channel number within the category>
VALUE: <reading>
<THRESHOLD>: <reading> # MIN, MAX, LCRIT, CRIT, EMERGENCY, SHUTDOWN, CAP, when the driver exposes them
<ALARM_*>: <0 or 1> # ALARM_MIN, ALARM_MAX, ALARM_LCRIT, ALARM_CRIT, ALARM_EMERGENCY, ALARM_SHUTDOWN, ALARM_CAP
Each SENSOR_<N> block is one sensor, numbered from 0 in the order the library returns them
(by hwmon instance, then category in the order TEMPERATURE, POWER, VOLTAGE, then
channel number). LABEL is the driver label, or N/A when the driver
gives none. Text output shows units as C, V, and W. CSV flattens the blocks into
keys such as SENSOR_0_LABEL and SENSOR_0_VALUE, with plain numbers for the readings.
A NIC without hwmon sensors prints SENSORS: No sensors found for this NIC in text and
{} in JSON.
Example (labels and readings as sensors reports them on an ionic NIC; the instance and
channel numbers are illustrative):
SENSORS:
SENSOR_0:
LABEL: board temperature
CATEGORY: TEMPERATURE
INSTANCE: 3
CHANNEL: 1
VALUE: 37.0 °C
CRIT: 80.0 °C
SENSOR_1:
LABEL: die temperature
CATEGORY: TEMPERATURE
INSTANCE: 3
CHANNEL: 2
VALUE: 53.0 °C
CRIT: 105.0 °C
SENSOR_2:
LABEL: total power
CATEGORY: POWER
INSTANCE: 3
CHANNEL: 1
VALUE: 35.34 W
MAX: 40.0 W
SENSOR_3:
LABEL: core power
CATEGORY: POWER
INSTANCE: 3
CHANNEL: 2
VALUE: 10.0 W
SENSOR_4:
LABEL: input voltage
CATEGORY: VOLTAGE
INSTANCE: 3
CHANNEL: 1
VALUE: 12.03 V
SENSOR_5:
LABEL: core voltage
CATEGORY: VOLTAGE
INSTANCE: 3
CHANNEL: 2
VALUE: 0.748 V
# Sensors of NIC 0
sudo amd-smi metric --nic 0 --sensors
# The same, as JSON
sudo amd-smi metric --nic 0 --sensors --json
Sysfs data source reference#
Base paths#
Type |
Base Path |
|---|---|
PCI device |
|
Network interface |
|
InfiniBand / RDMA |
|
PCI power runtime |
|
NIC sysfs mapping#
Data Category |
Sysfs Files |
|---|---|
PCI Device Info |
|
PCIe Link |
|
DMA |
|
Interrupt |
|
AER Errors |
|
SR-IOV |
|
ARI |
|
PCI Power State |
|
Power Runtime |
|
NUMA |
|
CPU Affinity |
|
Network port |
|
RDMA |
|
VPD Data |
|
Board serial (fallback) |
devlink |
Code organization#
Scope: The directory layout below describes the bare-metal (BM) AMD SMI
build under ROCm, which is the focus of this contribution guide. The Host AMD
SMI library is a separate codebase with its own rules, conventions, and
build system (see
amd/MxGPU-Virtualization (smi-lib)
and the
AMD SMI for virtualization user guide).
The only components shared between the two are:
the public header
include/amd_smi/amdsmi.h(the common API subset), andthe NIC library sources under
src/nic/(and its interface headers underinclude/amd_smi/impl/nic/).
When modifying the NIC library or the public header, keep in mind that those changes are consumed by both the BM build and the host build and must remain compatible across them. Anything outside the two shared components above is BM-specific.
The AMD SMI NIC implementation (BM build) follows this directory structure:
projects/amdsmi/
├── include/amd_smi/
│ ├── amdsmi.h # Public API header (structs + function declarations)
│ └── impl/
│ ├── amd_smi_common.h # Internal common definitions
│ ├── amd_smi_utils.h # Utility functions (sysfs readers, helpers)
│ ├── amd_smi_rdma_port.h # was_rdma_port_resolved(): flat RDMA index to (port, IB device, RDMA port)
│ ├── amd_smi_nic_stats.h # copy_nic_stats() and was_nic_stat_scope_resolved(): the statistics getters' array and scope rules
│ └── nic/
│ ├── amd_smi_ainic_device.h # AMDSmiAINICDevice and the AINICInfo snapshot
│ ├── amd_smi_nic_interface.h # C shim calls only the bare-metal library has (kind, capability, telemetry, sensors, scoped statistics)
│ └── amdsmi_unified/interface/
│ └── smi_nic_interface.h # Internal C interface consumed by the dispatch layer
├── src/
│ ├── amd_smi/
│ │ ├── amd_smi.cc # Public API implementation (dispatch layer)
│ │ ├── amd_smi_system.cc # System-level init, device discovery
│ │ └── amd_smi_utils.cc # Sysfs reading utilities
│ └── nic/ai-nic/amdsmi_unified/ # amdsminic static library
│ ├── CMakeLists.txt # Explicit CPP_SRCS list; add new sources here (no globbing)
│ ├── inc/ # Internal headers
│ │ ├── smi_nic.h # SmiNic / SmiNicPort / SmiInfiniBand
│ │ ├── smi_nic_subsystem.h # SmiNicSubsystem base interface + shared helpers
│ │ ├── smi_nic_system.h # SmiNicSystem
│ │ ├── smi_nic_transport.h # NicTransport + create_transport() factory
│ │ ├── smi_nic_stats.h # StatTier_t and StatTable_t (per-vendor counter tables)
│ │ ├── smi_nic_telemetry.h # NicTelemetry: health and port split (devlink)
│ │ ├── smi_nic_hwmon.h # hwmon sensor scan: scan_sensors()
│ │ ├── smi_nic_log.h # Debug-log sink used by the library (RSMI_LOGGING)
│ │ ├── smi_pci_topology.h # PCI link type: same switch, same NUMA node or different NUMA nodes
│ │ ├── smi_ibverbs.h # libibverbs MTU query, run in an isolated child process
│ │ ├── smi_nic_vpd.h # PCI VPD (Vital Product Data) TLV parser interface
│ │ ├── smi_ethtool_ioctl.h # ethtool ioctl helpers (ioctl transport)
│ │ ├── smi_nic_driver_stats.h # read_driver_statistics(): the ethtool -S read, with injectable requests (not installed)
│ │ ├── smi_devlink_netlink.h # devlink-over-netlink helpers
│ │ ├── netlink_wrapper.h # NLSocket / NLMessage (libnl-3 RAII wrappers)
│ │ ├── netlink_generic.h # GenericNetlinkClient (generic netlink)
│ │ ├── netlink_ethtool.h # EthtoolNetlinkClient (ethtool over netlink)
│ │ └── smi_sysfs.h # Sysfs readers
│ └── src/
│ ├── smi_nic.cpp # SmiNic / SmiNicPort (owns the Auto transport)
│ ├── smi_nic_system.cpp # SmiNicSystem::discover_nics()
│ ├── smi_nic_subsystem.cpp # read_pci_ids() / resolve_bdf() / is_driver_bound_to_bdf() helpers
│ ├── smi_nic_interface.cpp # C interface implementation
│ ├── smi_nic_transport_ioctl.cpp # ioctl transport backend + create_transport() factory
│ ├── smi_nic_transport_netlink.cpp # netlink transport backend and AutoBackend
│ ├── smi_nic_transport_log.cpp # debug-log decorator applied to every transport backend
│ ├── smi_nic_telemetry.cpp # NicTelemetry implementation
│ ├── smi_nic_hwmon.cpp # hwmon scan: per-attribute records, label-based ASIC and BOARD lookup
│ ├── smi_nic_log.cpp # debug-log sink and value masking
│ ├── smi_pci_topology.cpp # sysfs PCI topology and link type
│ ├── smi_ibverbs.cpp # dlopen of libibverbs, MTU query and the child-process isolation
│ ├── smi_ethtool_ioctl.cpp # ethtool ioctl backend
│ ├── smi_nic_vpd.cpp # PCI VPD TLV parser (product/part/serial)
│ ├── smi_devlink_netlink.cpp # devlink-over-netlink backend
│ ├── netlink_wrapper.cpp # libnl-3 socket/message wrappers
│ ├── netlink_generic.cpp # generic netlink (genl) client
│ ├── netlink_ethtool.cpp # ethtool-over-netlink client
│ ├── smi_sysfs.cpp
│ └── vendors/
│ ├── vendor_registry.{h,cpp} # make_default_vendor_plugins()
│ ├── pensando/
│ │ ├── pensando_subsystem.{h,cpp} # Reference vendor plugin (registered)
│ │ └── pensando_stats.{h,cpp} # kPensandoStatTable: counter names by tier
│ ├── amd/
│ │ └── ualoe_subsystem.{h,cpp} # UALoE endpoint plugin (registered)
│ └── broadcom/
│ ├── broadcom_subsystem.{h,cpp} # Broadcom plugin (compiled and registered)
│ └── broadcom_stats.{h,cpp} # kBroadcomStatTable
├── py-interface/
│ ├── amdsmi_interface.py # Python wrapper (high-level, public AMD SMI API only)
│ └── amdsmi_wrapper.py # Python ctypes bindings to amdsmi.h (auto-generated)
├── amdsmi_cli/
│ ├── amdsmi_commands.py # Unified CLI command implementations
│ ├── amdsmi_parser.py # CLI argument parsing (--nic, --port, --extended, --rdma)
│ └── subcommands/ # list_devices.py, metric.py, static.py, firmware.py, version.py serve --nic
└── example/
└── amd_smi_nic.cc # NIC C++ example (public AMD SMI API)
Key integration points#
Public header (
include/amd_smi/amdsmi.h): All public NIC structs and API declarations. New NIC functionality is exposed here through unifiedamdsmi_get_nic_*APIs. Vendor plugins do not add symbols here.Dispatch layer (
src/amd_smi/amd_smi.cc): Routes publicamdsmi_get_nic_*calls into theamdsminiclibrary’ssmi_nic_*C interface and converts the library’s status codes and structures to theiramdsmi_*equivalents at the boundary.NIC library C interface (
include/amd_smi/impl/nic/amdsmi_unified/interface/smi_nic_interface.h): The vendor-neutral, context-based C API that the dispatch layer consumes.Discovery and plugins (
src/nic/ai-nic/amdsmi_unified/):SmiNicSystemdrives discovery viamake_default_vendor_plugins(); each plugin implementsSmiNicSubsystem. This is where a new vendor is added.Shared transport (
inc/smi_nic_transport.h,src/smi_nic_transport_{ioctl,netlink}.cpp): The ethtool transport used bySmiNicPort.create_transport()selects the backend (NicBackend_t::{Auto, Ioctl, Netlink}) and wraps it in the debug-log decorator (src/smi_nic_transport_log.cpp).Python interface (
py-interface/amdsmi_interface.py,py-interface/amdsmi_wrapper.py): Bindings to the publicamdsmi.honly. A new backend is reachable from Python as soon as it is reachable from C; no per-vendor Python module is needed.CLI (
amdsmi_cli/amdsmi_commands.py): Enumerates every NIC through the public discovery API; no per-vendor delegation class.Example (
example/amd_smi_nic.cc): Uses the public AMD SMI API and exercises every NIC backend.
Tests#
Suite |
Path |
Run with |
Covers |
|---|---|---|---|
C++ unit ( |
|
|
transport parsing, NIC type layout, RDMA status and port resolver, statistics copy, socket ownership, driver-statistics retry, device text |
Standalone C++ suites |
|
|
telemetry, discovery, statistics, log, VPD and the check suite; no hardware |
Python unit |
|
|
CLI and interface behavior against stubs |
Hardware |
|
|
need a NIC |
Tools that need a live interface (test_nic_transport_suite,
benchmark_nic_transport, probe_devlink_*) are run by hand.
Example: Querying NIC information (C)#
A complete, build-tested C/C++ example for querying NIC ASIC, bus, NUMA,
port, and RDMA statistics is maintained in the repository at
projects/amdsmi/example/amd_smi_nic.cc. It is
compiled as part of the BUILD_EXAMPLES=ON CMake target.
The abbreviated snippet below illustrates the canonical init → discover → query → shutdown flow. See that file for the full implementation, including error handling and all supported queries.
#include <amd_smi/amdsmi.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
int main() {
amdsmi_status_t status;
// Initialize with NIC support (use AMDSMI_INIT_ALL_PROCESSORS to also
// include GPUs and CPUs in the same process).
status = amdsmi_init(AMDSMI_INIT_AMD_NICS);
if (status != AMDSMI_STATUS_SUCCESS) {
fprintf(stderr, "Failed to initialize AMD SMI: %d\n", status);
return 1;
}
// Discover sockets
uint32_t socket_count = 0;
status = amdsmi_get_socket_handles(&socket_count, NULL);
amdsmi_socket_handle *sockets = malloc(socket_count * sizeof(amdsmi_socket_handle));
status = amdsmi_get_socket_handles(&socket_count, sockets);
// Discover NIC processors
for (uint32_t s = 0; s < socket_count; s++) {
uint32_t nic_count = 0;
status = amdsmi_get_processor_handles_by_type(
sockets[s], AMDSMI_PROCESSOR_TYPE_AMD_NIC, NULL, &nic_count);
amdsmi_processor_handle *nic_handles = malloc(nic_count * sizeof(amdsmi_processor_handle));
status = amdsmi_get_processor_handles_by_type(
sockets[s], AMDSMI_PROCESSOR_TYPE_AMD_NIC, nic_handles, &nic_count);
for (uint32_t n = 0; n < nic_count; n++) {
// Query ASIC info
amdsmi_nic_asic_info_t asic = {0};
status = amdsmi_get_nic_asic_info(nic_handles[n], &asic);
if (status == AMDSMI_STATUS_SUCCESS) {
printf("NIC %u:\n", n);
printf(" Vendor ID: 0x%04x\n", asic.vendor_id);
printf(" Device ID: 0x%04x\n", asic.device_id);
printf(" Product: %s\n", asic.product_name);
printf(" Part Number: %s\n", asic.part_number);
printf(" Serial: %s\n", asic.serial_number);
uint32_t capability = 0;
if (amdsmi_get_nic_capability(nic_handles[n], &capability) == AMDSMI_STATUS_SUCCESS) {
printf(" Capability: 0x%08x\n", capability);
}
}
// Query Bus info
amdsmi_nic_bus_info_t bus = {0};
status = amdsmi_get_nic_bus_info(nic_handles[n], &bus);
if (status == AMDSMI_STATUS_SUCCESS) {
printf(" BDF: %04lx:%02x:%02x.%lx\n",
(unsigned long)bus.bdf.domain_number,
(unsigned)bus.bdf.bus_number,
(unsigned)bus.bdf.device_number,
(unsigned long)bus.bdf.function_number);
printf(" Max PCIe Width: %u\n", bus.max_pcie_width);
printf(" Max PCIe Speed: %u GT/s\n", bus.max_pcie_speed);
}
// Query NUMA info
amdsmi_nic_numa_info_t numa = {0};
status = amdsmi_get_nic_numa_info(nic_handles[n], &numa);
if (status == AMDSMI_STATUS_SUCCESS) {
printf(" NUMA Node: %u\n", numa.node);
printf(" CPU Affinity: %s\n", numa.affinity);
}
// Query Port info
amdsmi_nic_port_info_t ports = {0};
status = amdsmi_get_nic_port_info(nic_handles[n], &ports);
if (status == AMDSMI_STATUS_SUCCESS) {
for (uint32_t p = 0; p < ports.num_ports; p++) {
printf(" Port %u:\n", p);
printf(" Netdev: %s\n", ports.ports[p].netdev);
printf(" MAC: %s\n", ports.ports[p].mac_address);
printf(" Link State: %s\n", ports.ports[p].link_state);
printf(" Link Speed: %u Mb/s\n", ports.ports[p].link_speed);
printf(" MTU: %u\n", ports.ports[p].mtu);
}
}
// Query RDMA port statistics (two-call pattern)
uint32_t num_stats = 0;
status = amdsmi_get_nic_rdma_port_statistics(
nic_handles[n], 0, &num_stats, NULL);
if (status == AMDSMI_STATUS_SUCCESS && num_stats > 0) {
amdsmi_nic_stat_t *stats = malloc(num_stats * sizeof(amdsmi_nic_stat_t));
status = amdsmi_get_nic_rdma_port_statistics(
nic_handles[n], 0, &num_stats, stats);
if (status == AMDSMI_STATUS_SUCCESS) {
printf(" RDMA Port 0 Statistics (%u):\n", num_stats);
for (uint32_t i = 0; i < num_stats; i++) {
printf(" %s = %" PRIu64 "\n", stats[i].name, stats[i].value);
}
}
free(stats);
}
}
free(nic_handles);
}
free(sockets);
amdsmi_shut_down();
return 0;
}
Example: Querying NIC information (Python)#
import amdsmi
# The NIC getters are not re-exported by the amdsmi package; call them from amdsmi_interface.
from amdsmi import amdsmi_interface
# Initialize with NIC support
amdsmi.amdsmi_init(amdsmi.AmdSmiInitFlags.INIT_AMD_NICS)
try:
# Discover sockets
sockets = amdsmi.amdsmi_get_socket_handles()
for socket in sockets:
# Discover NIC processors
nic_handles = amdsmi.amdsmi_get_processor_handles_by_type(
socket, amdsmi.AmdSmiProcessorType.AMD_AINIC
)
for idx, nic in enumerate(nic_handles["processor_handles"]):
# Summary info: BDF, product name, part/serial number, vendor
info = amdsmi_interface.amdsmi_get_ainic_info(nic)
print(f"NIC {idx}:")
print(f" BDF: {info['bdf']}")
print(f" Product Name: {info['Product Name']}")
print(f" Part Number: {info['Part Number']}")
print(f" Serial Number: {info['Serial Number']}")
print(f" Vendor Name: {info['Vendor Name']}")
# Detailed info: pass detail=True for the full nested dict (ASIC IDs, etc.)
detail = amdsmi_interface.amdsmi_get_ainic_info(nic, detail=True)
asic = detail["ASIC"]
print(f" Vendor ID: {asic['VENDOR_ID']}")
print(f" Device ID: {asic['DEVICE_ID']}")
print(f" Revision: {asic['REVISION']}")
print(f" Capability: {asic['CAPABILITY']}") # e.g. ['FWCTL', 'NETDEV']
# Firmware versions (name -> version).
fw = amdsmi_interface.amdsmi_get_nic_fw_info(nic)
for name, version in fw.items():
print(f" FW {name}: {version}")
finally:
amdsmi.amdsmi_shut_down()
Integration guidelines for external contributors#
Adding a new NIC vendor#
Adding support for a new NIC vendor means adding a discovery plugin to the
amdsminic library. The public API surface does not change: your devices are
reported under AMDSMI_PROCESSOR_TYPE_AMD_NIC and queried through the existing
amdsmi_get_nic_* APIs.
Create the plugin sources under
src/nic/ai-nic/amdsmi_unified/src/vendors/<name>/:Add
<name>_subsystem.{h,cpp}with a class that derives fromSmiNicSubsystem(inc/smi_nic_subsystem.h) and implementsdiscover(),vendor(),driver_loaded(), andget_nics().Match your hardware by PCI vendor/device ID in
discover()using the inheritedread_pci_ids()helper; useresolve_bdf()to map a network interface back to its PCI BDF. Followvendors/pensando/as the model.Build your NIC and port objects on
SmiNic/SmiNicPortso you inherit the shared ethtool transport (FEC, pause, link settings, driver info, statistics) and sysfs readers. The defaultcreate_transport(Auto)backend already used bySmiNicPortneeds no per-vendor setup.
Register the plugin in
src/nic/ai-nic/amdsmi_unified/src/vendors/vendor_registry.cpp:#include "<name>/<name>_subsystem.h".Add one
plugins.push_back(std::make_unique<SmiNicSubsystem<Name>>());line insidemake_default_vendor_plugins().
Add the source(s) to CMake and re-run.
CMakeLists.txtenumerates sources explicitly (no globbing): append your.cppfiles toCPP_SRCS, or toCPP_SRCS_NETLINKif they depend on libnl-3. Then re-runcmake(do not rely on an incrementalmake).CLI and Python: No changes needed. The unified CLI and the Python bindings reach your devices through the public discovery API as soon as the plugin is registered.
Tests: Add functional tests under
tests/.
Data source strategy#
The following rules govern where a backend should read its data from. They are the single most important guideline for keeping the public API surface generic across vendors.
sysfs first, for everything common. Read all metrics, identifiers, link state, AER counters, SR-IOV state, power-runtime state, and any other attribute exposed by the standard Linux PCI, netdev, or RDMA subsystems from sysfs:
/sys/bus/pci/devices/<BDF>/.../sys/class/net/<iface>/.../sys/class/infiniband/<dev>/ports/<N>/...
Backends must use sysfs (via the shared
smi_sysfsreaders) as the source for any field that sysfs exposes.ethtool via the shared transport. For FEC, pause, link settings, driver info, and driver statistics, use the transport layer (
NicTransport, obtained fromcreate_transport()) rather than shelling out toethtool. TheAutobackend uses netlink where available and falls back to ioctl.VPD via sysfs
vpd. Parse product name, part number, and serial number from/sys/bus/pci/devices/<BDF>/vpdwhen the binary VPD blob is exposed. When a device does not expose the sysfs VPD entry, product name and part number are reported as unavailable. Backends must not shell out to external tools for VPD. For the serial number only, when VPD is readable but carries none, fall back to the devlink board serial (DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER) before giving up; this is a distinct physical source, not synthesized data (rule 5), so the fallback is compliant. Do not useDEVLINK_ATTR_INFO_SERIAL_NUMBER; it is a MAC-derived EUI.Vendor RPC, only for vendor-specific data. A backend may use a vendor ioctl, netlink, or library call only when the needed data is not available via sysfs or the shared transport. Keep such code inside the plugin’s
vendors/<name>/files and don’t leak vendor types into the public header or thesmi_nic_interface.hC interface.No synthesized data. If a source doesn’t exist for a given device, return the documented “unsupported” reserved value (see Unsupported or unavailable fields) or omit the entry from the variable-length list; never substitute zero.
Key principles#
Use standard AMD SMI naming conventions:
amdsmi_get_nic_*().All output structs are caller-allocated: You allocate the struct and pass a pointer.
Two-call pattern for variable-length data: First call with
NULLto get size, second call to fill.sysfs and the shared transport are the primary data sources; use vendor RPC only for vendor-specific data (see Data source strategy).
Generic over vendor-specific: Prefer reusable APIs (for example,
amdsmi_get_nic_asic_info()) over vendor-locked APIs. Vendor-specific naming must not appear in the public API.Error handling: Always validate pointers, check init state, and return appropriate status codes.
No vendor-specific types in public headers: Don’t expose internal vendor-specific structures through
amdsmi.h.
Appendix: Environment and build#
Repositories and public documentation#
This contribution guide targets the bare-metal (BM) AMD SMI library.
The Host AMD SMI library is a separate codebase with its own repository,
user guide, and contribution process; the only components it shares with the
BM library are the common subset of the public amdsmi.h header and the NIC
library sources.
Surface |
Source location |
Public docs |
|---|---|---|
Bare-metal (BM) under ROCm |
||
Host AMD SMI (virtualization host) |
External contributions described in this guide target the bare-metal AMD SMI library; see CONTRIBUTING.md for guidelines. The host AMD SMI library has its own contribution process, documented in its repository and user guide linked above.
Supported platforms#
Bare-metal (BM) under ROCm: Linux bare-metal with ROCm installed; primary deployment surface and the focus of this guide.
Host AMD SMI (virtualization host): Linux virtualization host (KVM/QEMU) managing AMD devices passed through to guests; maintained in its own repository (linked above).
NIC APIs documented in this guide are part of the shared public header. Those tagged
@platform{gpu_bm_linux} only, such as amdsmi_get_nic_type() and
amdsmi_get_nic_telemetry(), are exposed by the bare-metal library alone. A NIC backend requires its vendor kernel driver to
be loaded on the platform performing the queries.
Build system#
The BM AMD SMI library uses CMake (≥ 3.20) with a C++17-compatible compiler.
The NIC library (amdsminic) is built as part of the main library. The netlink
transport backend links libnl-3, which the top-level build requires (a bundled
copy or pkg-config); the ioctl backend stays the runtime fallback.
CMake also probes the Linux UAPI headers. Without linux/ethtool_netlink.h and
ETHTOOL_A_FEC_STAT_CORR_BITS (kernel 5.13 headers or newer), the build uses a stub
ethtool client: the transport falls back to ioctl and FEC statistics are
unavailable. Without the DEVLINK_ATTR_PORT_SPLITTABLE or
DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER devlink attribute, the port split
state reads N/A and the board serial is empty. A build host with kernel 5.4
headers, such as Ubuntu 20.04, therefore builds with this reduced data.
The host AMD SMI library has its own build system, documented in its repository
linked above.
CMake Option |
Default |
Description (BM build) |
|---|---|---|
|
|
Build test suite |
|
|
Build example programs |
|
|
Build ESMI Library |
Dependencies#
The following runtime dependencies are relevant to the BM AMD SMI NIC library:
Linux kernel sysfs (PCIe device info, netdev, InfiniBand/RDMA, VPD)
libnl-3 (required by the top-level build; the netlink ethtool transport backend uses it, and FEC statistics need kernel UAPI headers 5.13 or newer at build time)
libibverbs (optional; the build needs
infiniband/verbs.hand the library is loaded at run time aslibibverbs.so.1; without either, the RDMA MTU falls back to sysfs or readsN/A)The relevant NIC vendor kernel driver (required for that vendor’s device discovery)
Revision history#
Version |
Date |
Description |
|---|---|---|
2.1 |
October 23, 2026 |
Aligned the NIC API with |
2.0 |
July 16, 2026 |
Replaced the retired vendor-SMI-module example with the in-tree |
1.0 |
April 26, 2026 |
Initial public release. Covered AMD SMI bare-metal (BM) NIC public APIs, data structures, conventions, sysfs data sources, code organization, CLI commands, and external contribution guidelines for adding new NIC vendors. |