Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

622 lines
21 KiB
C++

/*
* Copyright (c) Advanced Micro Devices, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "include/amd_cuid.h"
#include <cstring>
#include <errno.h>
#include <fstream>
#include <getopt.h>
#include <iostream>
#include <map>
#include <string>
#include <unistd.h>
#include <vector>
/**
* @file amdcuid_tool.cc
* @brief AMD CUID command-line tool for generating and querying CUIDs
*
* This tool provides functionality to:
* - Generate CUID files from discovered hardware using amdcuid_refresh()
* - List and query device CUIDs using the public amd_cuid.h API
* - Always reads from the library's internal registry
*/
/**
* @brief Check if running with root privileges
*/
bool is_root() { return geteuid() == 0; }
void print_usage(const char *program_name) {
std::cout << "Usage: " << program_name << " [OPTIONS]\n\n";
std::cout << "AMD Component Unified Identifier (CUID) Tool\n\n";
std::cout << "Options:\n";
std::cout << " --generate-cuid Generate/refresh CUID registry "
"from discovered devices\n";
std::cout << " Uses existing key or specify "
"--generate-key/--set-key\n";
std::cout << " --generate-key Generate a new random HMAC key "
"(use with --generate-cuid)\n";
std::cout << " --set-key <key_file> Set HMAC key from file (32 "
"bytes, use with --generate-cuid)\n";
std::cout << " --notify-daemon Notify daemon to refresh device "
"registry (for udev integration)\n";
std::cout
<< " --list List all devices and their CUIDs\n";
std::cout << " --type <type> Filter by device type (gpu, "
"cpu, nic, npu, platform)\n";
std::cout
<< " Use with --list or --query-device\n";
std::cout << " --show-primary Show primary CUIDs (requires "
"root privileges)\n";
std::cout
<< " Use with --list or --query-device\n";
std::cout << " --query-device <identifier> Query specific device by device "
"path or BDF\n";
std::cout << " --version Show library version\n";
std::cout << " --help, -h Show this help message\n";
std::cout << "\nExamples:\n";
std::cout
<< " # Generate CUID registry with a new random key (requires root)\n";
std::cout << " sudo " << program_name
<< " --generate-cuid --generate-key\n\n";
std::cout << " # Generate CUID registry with existing key file\n";
std::cout << " sudo " << program_name
<< " --generate-cuid --set-key /path/to/hmac_key.bin\n\n";
std::cout << " # Generate CUID registry using previously set key\n";
std::cout << " sudo " << program_name << " --generate-cuid\n\n";
std::cout << " # Notify daemon of device changes (called by udev)\n";
std::cout << " " << program_name << " --notify-daemon\n\n";
std::cout << " # List all devices with their CUIDs\n";
std::cout << " " << program_name << " --list\n\n";
std::cout << " # List all GPUs with their CUIDs\n";
std::cout << " " << program_name << " --list --type gpu\n\n";
std::cout << " # List all devices with primary CUIDs (requires root)\n";
std::cout << " sudo " << program_name << " --list --show-primary\n\n";
std::cout << " # Query specific device by path\n";
std::cout << " " << program_name
<< " --query-device /sys/class/drm/renderD128\n\n";
std::cout << " # Query device by BDF\n";
std::cout << " " << program_name
<< " --query-device 0000:03:00.0 --type gpu\n\n";
}
const char *device_type_to_string(amdcuid_device_type_t type) {
switch (type) {
case AMDCUID_DEVICE_TYPE_PLATFORM:
return "PLATFORM";
case AMDCUID_DEVICE_TYPE_CPU:
return "CPU";
case AMDCUID_DEVICE_TYPE_GPU:
return "GPU";
case AMDCUID_DEVICE_TYPE_NIC:
return "NIC";
case AMDCUID_DEVICE_TYPE_NPU:
return "NPU";
case AMDCUID_DEVICE_TYPE_NONE:
return "NONE";
default:
return "UNKNOWN";
}
}
amdcuid_device_type_t string_to_device_type(const std::string &type_str) {
std::string upper = type_str;
for (auto &c : upper)
c = toupper(c);
if (upper == "PLATFORM")
return AMDCUID_DEVICE_TYPE_PLATFORM;
if (upper == "CPU")
return AMDCUID_DEVICE_TYPE_CPU;
if (upper == "GPU")
return AMDCUID_DEVICE_TYPE_GPU;
if (upper == "NIC")
return AMDCUID_DEVICE_TYPE_NIC;
if (upper == "NPU")
return AMDCUID_DEVICE_TYPE_NPU;
return AMDCUID_DEVICE_TYPE_NONE;
}
/**
* @brief Load HMAC key from a file
* @param key_file Path to the key file (must be 32 bytes)
* @param key Output buffer for the key (32 bytes)
* @return true on success, false on failure
*/
bool load_key_from_file(const std::string &key_file, uint8_t key[32]) {
std::ifstream file(key_file, std::ios::binary);
if (!file) {
return false;
}
file.read(reinterpret_cast<char *>(key), 32);
return file.gcount() == 32;
}
/**
* @brief Notify the daemon to refresh device registry
*
* This function is called by udev when device changes are detected.
* It triggers the daemon to re-scan devices via IPC.
*
* @return 0 on success, 1 on failure
*/
int notify_daemon() {
amdcuid_status_t status = amdcuid_refresh();
if (status != AMDCUID_STATUS_SUCCESS) {
// Silently fail for udev context - don't spam logs
// The daemon may not be running, which is acceptable
return (status == AMDCUID_STATUS_IPC_ERROR) ? 0 : 1;
}
return 0;
}
/**
* @brief Query a string property from a device handle
* @param handle The device handle
* @param query The query type
* @param result Output string
* @return Status code
*/
amdcuid_status_t query_string_property(amdcuid_id_t handle,
amdcuid_query_t query,
std::string &result) {
// First query to get required size
uint32_t length = 0;
amdcuid_status_t status =
amdcuid_query_device_property(handle, query, nullptr, &length);
if (status == AMDCUID_STATUS_INSUFFICIENT_SIZE && length > 0) {
// Allocate buffer and query again
std::vector<char> buffer(length);
status =
amdcuid_query_device_property(handle, query, buffer.data(), &length);
if (status == AMDCUID_STATUS_SUCCESS) {
// Remove null terminator if present
result = std::string(buffer.data(),
length > 0 && buffer[length - 1] == '\0' ? length - 1
: length);
}
} else if (status == AMDCUID_STATUS_SUCCESS) {
result.clear();
}
return status;
}
int generate_cuid_files(const std::string &key_file, bool generate_key) {
std::cout << "Generating/refreshing CUID registry...\n" << std::endl;
// Validate key options - cannot use both together
if (!key_file.empty() && generate_key) {
std::cerr
<< "Error: Cannot use both --generate-key and --set-key together.\n";
std::cerr << "Use --generate-key to create a new random key, or\n";
std::cerr << "Use --set-key <file> to load an existing key from a file."
<< std::endl;
return 1;
}
// Handle key setup
if (!key_file.empty()) {
// Load key from file
uint8_t key[32];
if (!load_key_from_file(key_file, key)) {
std::cerr << "Error: Failed to read HMAC key from file: " << key_file
<< std::endl;
std::cerr << "Key file must be exactly 32 bytes." << std::endl;
return 1;
}
amdcuid_status_t status = amdcuid_set_hash_key(key);
if (status != AMDCUID_STATUS_SUCCESS) {
std::cerr << "Error: Failed to set HMAC key: "
<< amdcuid_status_to_string(status) << std::endl;
return 1;
}
std::cout << "HMAC key loaded from: " << key_file << std::endl;
} else if (generate_key) {
// Generate a new random key
uint8_t key[32];
amdcuid_status_t status = amdcuid_generate_hash_key(key);
if (status != AMDCUID_STATUS_SUCCESS) {
std::cerr << "Error: Failed to generate HMAC key: "
<< amdcuid_status_to_string(status) << std::endl;
return 1;
}
status = amdcuid_set_hash_key(key);
if (status != AMDCUID_STATUS_SUCCESS) {
std::cerr << "Error: Failed to set generated HMAC key: "
<< amdcuid_status_to_string(status) << std::endl;
return 1;
}
std::cout << "Generated new HMAC key." << std::endl;
}
// else: No key option specified - attempt to use existing key (will be
// validated by amdcuid_refresh) else: No key option specified - attempt to
// use existing key (will be validated by amdcuid_refresh)
// Use amdcuid_refresh() to discover devices and generate CUID files
amdcuid_status_t status = amdcuid_refresh();
if (status != AMDCUID_STATUS_SUCCESS) {
std::cerr << "Error: Failed to refresh CUID registry: "
<< amdcuid_status_to_string(status) << std::endl;
if (status == AMDCUID_STATUS_KEY_ERROR) {
std::cerr << "No HMAC key found. Please use --generate-key or --set-key "
"<file> to create a key first."
<< std::endl;
} else if (status == AMDCUID_STATUS_PERMISSION_DENIED) {
std::cerr << "Some devices may require root privileges to discover."
<< std::endl;
}
return 1;
}
// Count discovered devices
uint32_t count = 0;
status = amdcuid_get_all_handles(nullptr, &count);
if (status == AMDCUID_STATUS_INSUFFICIENT_SIZE ||
status == AMDCUID_STATUS_SUCCESS) {
std::cout << "Discovered " << count << " device(s)" << std::endl;
}
std::cout << "\nCUID registry refreshed successfully!" << std::endl;
return 0;
}
int list_devices(bool show_primary, const std::string *filter_type) {
// Check for root privileges if requesting primary CUIDs
if (show_primary && !is_root()) {
std::cerr << "Error: Permission denied\n";
std::cerr << "Reading primary CUIDs requires root privileges. Try running "
"with sudo."
<< std::endl;
return 1;
}
// Parse filter type if provided
amdcuid_device_type_t filter_device_type = AMDCUID_DEVICE_TYPE_NONE;
if (filter_type) {
filter_device_type = string_to_device_type(*filter_type);
if (filter_device_type == AMDCUID_DEVICE_TYPE_NONE) {
std::cerr << "Error: Unknown device type '" << *filter_type << "'"
<< std::endl;
std::cerr << "Valid types: platform, cpu, gpu, nic, npu" << std::endl;
return 1;
}
}
// Get all device handles using public API
uint32_t count = 0;
amdcuid_status_t status = amdcuid_get_all_handles(nullptr, &count);
if (status != AMDCUID_STATUS_INSUFFICIENT_SIZE &&
status != AMDCUID_STATUS_SUCCESS) {
if (status == AMDCUID_STATUS_UNSUPPORTED) {
std::cout << "No devices found.\n";
std::cout << "Please run 'sudo amdcuid_tool --generate-cuid' first to "
"generate CUID registry."
<< std::endl;
return 0;
}
std::cerr << "Error: Failed to get device count: "
<< amdcuid_status_to_string(status) << std::endl;
return 1;
}
if (count == 0) {
std::cout << "No devices found." << std::endl;
return 0;
}
std::vector<amdcuid_id_t> handles(count);
status = amdcuid_get_all_handles(handles.data(), &count);
if (status != AMDCUID_STATUS_SUCCESS) {
std::cerr << "Error: Failed to get device handles: "
<< amdcuid_status_to_string(status) << std::endl;
return 1;
}
// Group handles by device type
std::map<amdcuid_device_type_t, std::vector<amdcuid_id_t>> grouped;
for (const auto &handle : handles) {
amdcuid_device_type_t device_type;
uint32_t len = sizeof(device_type);
status = amdcuid_query_device_property(handle, AMDCUID_QUERY_DEVICE_TYPE,
&device_type, &len);
if (status != AMDCUID_STATUS_SUCCESS) {
continue;
}
// Apply filter if specified
if (filter_type && device_type != filter_device_type) {
continue;
}
grouped[device_type].push_back(handle);
}
if (grouped.empty()) {
if (filter_type) {
std::cout << "No " << *filter_type << " devices found." << std::endl;
} else {
std::cout << "No devices found." << std::endl;
}
return 0;
}
// Count total entries after filtering
size_t total = 0;
for (const auto &kv : grouped) {
total += kv.second.size();
}
std::cout << "Found " << total << " device(s)";
if (filter_type) {
std::cout << " of type '" << *filter_type << "'";
}
std::cout << ":\n" << std::endl;
for (const auto &kv : grouped) {
amdcuid_device_type_t type = kv.first;
const std::vector<amdcuid_id_t> &handle_list = kv.second;
std::string type_str = device_type_to_string(type);
std::cout << "---- " << type_str << " Devices ----" << std::endl;
int device_index = 0;
for (const auto &handle : handle_list) {
if (type == AMDCUID_DEVICE_TYPE_PLATFORM) {
std::cout << type_str;
} else {
std::cout << type_str << " #" << device_index;
}
// Query primary CUID if requested and has permission
if (show_primary) {
amdcuid_id_t primary_cuid;
uint32_t len = sizeof(primary_cuid);
status = amdcuid_query_device_property(
handle, AMDCUID_QUERY_PRIMARY_CUID, &primary_cuid, &len);
if (status == AMDCUID_STATUS_SUCCESS) {
std::cout << "\n Primary CUID: "
<< amdcuid_id_to_string(primary_cuid);
}
}
// Query derived CUID (the handle itself is the derived CUID)
std::cout << "\n CUID: " << amdcuid_id_to_string(handle);
// Query device path
std::string device_path;
if (query_string_property(handle, AMDCUID_QUERY_DEVICE_PATH,
device_path) == AMDCUID_STATUS_SUCCESS) {
std::cout << "\n Device Path: " << device_path;
}
std::cout << "\n" << std::endl;
device_index++;
}
}
return 0;
}
int query_device(const std::string &identifier, bool show_primary,
const std::string *device_type_str) {
// Check for root privileges if requesting primary CUIDs
if (show_primary && !is_root()) {
std::cerr << "Error: Permission denied\n";
std::cerr << "Reading primary CUIDs requires root privileges. Try running "
"with sudo."
<< std::endl;
return 1;
}
amdcuid_id_t handle;
amdcuid_status_t status;
// Determine device type for lookup
amdcuid_device_type_t device_type = AMDCUID_DEVICE_TYPE_GPU; // Default to GPU
if (device_type_str) {
device_type = string_to_device_type(*device_type_str);
if (device_type == AMDCUID_DEVICE_TYPE_NONE) {
std::cerr << "Error: Unknown device type '" << *device_type_str << "'"
<< std::endl;
std::cerr << "Valid types: platform, cpu, gpu, nic, npu" << std::endl;
return 1;
}
} else {
// Infer device type from device path when --type is not specified
if (identifier.find("/sys/devices/system/cpu/") != std::string::npos) {
device_type = AMDCUID_DEVICE_TYPE_CPU;
} else if (identifier.find("/sys/class/net/") != std::string::npos) {
device_type = AMDCUID_DEVICE_TYPE_NIC;
} else if (identifier.find("/sys/class/drm/") != std::string::npos) {
device_type = AMDCUID_DEVICE_TYPE_GPU;
} else if (identifier.find("/sys/class/accel/") != std::string::npos) {
device_type = AMDCUID_DEVICE_TYPE_NPU;
}
}
// Check if identifier looks like a BDF (e.g., "0000:c6:00.1" or "c6:00.1").
// Must not start with '/' (which would indicate a sysfs path).
bool is_bdf = (!identifier.empty() && identifier[0] != '/' &&
identifier.find(':') != std::string::npos &&
identifier.find('.') != std::string::npos);
if (is_bdf) {
// Try as BDF
status =
amdcuid_get_handle_by_bdf(identifier.c_str(), device_type, &handle);
} else {
// Try as device path
status = amdcuid_get_handle_by_dev_path(identifier.c_str(), device_type,
&handle);
}
if (status != AMDCUID_STATUS_SUCCESS) {
std::cerr << "Error: Device not found: " << identifier << std::endl;
std::cerr << "Status: " << amdcuid_status_to_string(status) << std::endl;
return 1;
}
// Device found, display information
std::cout << "Device Found:" << std::endl;
// Query device type
amdcuid_device_type_t queried_type;
uint32_t len = sizeof(queried_type);
status = amdcuid_query_device_property(handle, AMDCUID_QUERY_DEVICE_TYPE,
&queried_type, &len);
if (status == AMDCUID_STATUS_SUCCESS) {
std::cout << " Type: " << device_type_to_string(queried_type)
<< std::endl;
}
// Query primary CUID if requested
if (show_primary) {
amdcuid_id_t primary_cuid;
len = sizeof(primary_cuid);
status = amdcuid_query_device_property(handle, AMDCUID_QUERY_PRIMARY_CUID,
&primary_cuid, &len);
if (status == AMDCUID_STATUS_SUCCESS) {
std::cout << " Primary CUID: " << amdcuid_id_to_string(primary_cuid)
<< std::endl;
} else if (status == AMDCUID_STATUS_PERMISSION_DENIED) {
std::cout << " Primary CUID: (requires root)" << std::endl;
}
}
// Display derived CUID (the handle)
std::cout << " CUID: " << amdcuid_id_to_string(handle)
<< std::endl;
// Query device path
std::string device_path;
if (query_string_property(handle, AMDCUID_QUERY_DEVICE_PATH, device_path) ==
AMDCUID_STATUS_SUCCESS) {
std::cout << " Device Path: " << device_path << std::endl;
}
return 0;
}
int main(int argc, char *argv[]) {
static struct option long_options[] = {
{"generate-cuid", no_argument, 0, 'g'},
{"generate-key", no_argument, 0, 'k'},
{"set-key", required_argument, 0, 's'},
{"notify-daemon", no_argument, 0, 'n'},
{"list", no_argument, 0, 'l'},
{"type", required_argument, 0, 't'},
{"show-primary", no_argument, 0, 'p'},
{"query-device", required_argument, 0, 'q'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}};
std::string key_file;
std::string filter_type;
std::string query_identifier;
bool do_generate = false;
bool generate_key = false;
bool do_notify = false;
bool do_list = false;
bool show_primary = false;
bool do_query = false;
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "gks:nlt:pq:vh", long_options,
&option_index)) != -1) {
switch (opt) {
case 'g':
do_generate = true;
break;
case 'k':
generate_key = true;
break;
case 's':
key_file = optarg;
break;
case 'n':
do_notify = true;
break;
case 'l':
do_list = true;
break;
case 't':
filter_type = optarg;
break;
case 'p':
show_primary = true;
break;
case 'q':
do_query = true;
query_identifier = optarg;
break;
case 'v':
std::cout << "AMD CUID Library Version: "
<< amdcuid_library_version_to_string() << std::endl;
return 0;
case 'h':
print_usage(argv[0]);
return 0;
default:
print_usage(argv[0]);
return 1;
}
}
// Validate key options usage
if ((generate_key || !key_file.empty()) && !do_generate) {
std::cerr << "Error: --generate-key and --set-key can only be used with "
"--generate-cuid"
<< std::endl;
return 1;
}
// Execute requested operation
if (do_generate) {
return generate_cuid_files(key_file, generate_key);
} else if (do_notify) {
return notify_daemon();
} else if (do_list) {
return list_devices(show_primary,
filter_type.empty() ? nullptr : &filter_type);
} else if (do_query) {
return query_device(query_identifier, show_primary,
filter_type.empty() ? nullptr : &filter_type);
} else {
print_usage(argv[0]);
return 1;
}
return 0;
}