From dd49583224cbbf4580f08bdb63f563d457d8db08 Mon Sep 17 00:00:00 2001 From: GH <781213930@qq.com> Date: Fri, 24 Jul 2026 16:11:30 +0800 Subject: [PATCH] feat(lg200): add DRM device discovery --- platforms/lg200/CMakeLists.txt | 23 ++ platforms/lg200/README.md | 32 +++ .../lg200/hal/include/xsched/lg200/hal.h | 43 +++ .../hal/include/xsched/lg200/hal/resource.h | 12 + platforms/lg200/hal/src/resource.cpp | 264 ++++++++++++++++++ platforms/lg200/test/CMakeLists.txt | 12 + platforms/lg200/test/lg200_resource_test.cpp | 111 ++++++++ platforms/lg200/tools/lg200_info.cpp | 96 +++++++ 8 files changed, 593 insertions(+) create mode 100644 platforms/lg200/CMakeLists.txt create mode 100644 platforms/lg200/README.md create mode 100644 platforms/lg200/hal/include/xsched/lg200/hal.h create mode 100644 platforms/lg200/hal/include/xsched/lg200/hal/resource.h create mode 100644 platforms/lg200/hal/src/resource.cpp create mode 100644 platforms/lg200/test/CMakeLists.txt create mode 100644 platforms/lg200/test/lg200_resource_test.cpp create mode 100644 platforms/lg200/tools/lg200_info.cpp diff --git a/platforms/lg200/CMakeLists.txt b/platforms/lg200/CMakeLists.txt new file mode 100644 index 00000000..00400b8a --- /dev/null +++ b/platforms/lg200/CMakeLists.txt @@ -0,0 +1,23 @@ +add_hal_lib(lg200) +set_target_properties(hallg200 PROPERTIES BUILD_WITH_INSTALL_RPATH FALSE) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND + CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9) + target_link_libraries(hallg200 PRIVATE stdc++fs) +endif() + +add_executable(lg200_info tools/lg200_info.cpp) +target_include_directories(lg200_info PRIVATE + ${XSCHED_INCLUDE_DIR} + ${CMAKE_CURRENT_LIST_DIR}/hal/include +) +target_compile_options(lg200_info PRIVATE -Wall -Wextra -Werror) +target_link_libraries(lg200_info PRIVATE hallg200) +install(TARGETS lg200_info RUNTIME DESTINATION bin/lg200) +set_target_properties(lg200_info PROPERTIES + INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib + BUILD_WITH_INSTALL_RPATH TRUE + INSTALL_RPATH_USE_LINK_PATH TRUE +) + +add_platform_test() diff --git a/platforms/lg200/README.md b/platforms/lg200/README.md new file mode 100644 index 00000000..02e432c2 --- /dev/null +++ b/platforms/lg200/README.md @@ -0,0 +1,32 @@ +# Loongson LG200 device discovery + +This module discovers LG200 DRM devices owned by the `loonggpu` kernel driver +and reads optional utilization and local-memory attributes exported through +sysfs. + +This change intentionally does **not** add an LG200 `HwQueue` or `HwCommand`. +Generic application callbacks can already use XSched's +`HwCommandCreateCallback` interface. Hardware scheduling and queue preemption +belong in a separate adapter backed by LoongGPU driver capabilities. + +## Build + +```shell +cmake -S . -B build-lg200 \ + -DPLATFORM_LG200=ON \ + -DBUILD_TEST=ON +cmake --build build-lg200 --target lg200_info lg200_resource_test +``` + +Run `lg200_info --require-device` on a Loongson system to require at least one +matching DRM device. + +## API + +- `Lg200DeviceEnumerate()` enumerates `cardN` nodes whose driver name starts + with `loonggpu` or whose product identity contains `LG200`. +- `Lg200DeviceReadStats()` reads `gpu_busy_percent`, + `mem_info_vram_used`, and `mem_info_vram_total` when the driver exports them. + +Statistics are optional. An unavailable utilization value is returned as +`-1.0`; unavailable memory counters are returned as zero. diff --git a/platforms/lg200/hal/include/xsched/lg200/hal.h b/platforms/lg200/hal/include/xsched/lg200/hal.h new file mode 100644 index 00000000..df78825c --- /dev/null +++ b/platforms/lg200/hal/include/xsched/lg200/hal.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "xsched/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define XSCHED_LG200_DEVICE_NAME_MAX 64 + +typedef struct +{ + XDeviceId device_id; + uint32_t drm_card; + char name[XSCHED_LG200_DEVICE_NAME_MAX]; + char vendor[XSCHED_LG200_DEVICE_NAME_MAX]; + char driver[XSCHED_LG200_DEVICE_NAME_MAX]; + uint64_t local_memory_bytes; +} Lg200DeviceInfo; + +typedef struct +{ + uint32_t drm_card; + double gpu_utilization; + uint64_t local_memory_used_bytes; + uint64_t local_memory_total_bytes; +} Lg200DeviceStats; + +// Enumerate DRM cards owned by the loonggpu kernel driver. Passing devices as +// NULL queries the required count. If the supplied array is too small, count +// is updated to the required size and kXSchedErrorInvalidValue is returned. +XResult Lg200DeviceEnumerate(Lg200DeviceInfo *devices, uint32_t *count); + +// Read optional statistics exported by the loonggpu DRM sysfs node. Missing +// utilization or memory attributes are reported as unknown values, while a +// non-LG200 card returns kXSchedErrorNotFound. +XResult Lg200DeviceReadStats(uint32_t drm_card, Lg200DeviceStats *stats); + +#ifdef __cplusplus +} +#endif diff --git a/platforms/lg200/hal/include/xsched/lg200/hal/resource.h b/platforms/lg200/hal/include/xsched/lg200/hal/resource.h new file mode 100644 index 00000000..7121f6c9 --- /dev/null +++ b/platforms/lg200/hal/include/xsched/lg200/hal/resource.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include "xsched/lg200/hal.h" + +namespace xsched::lg200 +{ + +std::vector DiscoverDevices(); + +} // namespace xsched::lg200 diff --git a/platforms/lg200/hal/src/resource.cpp b/platforms/lg200/hal/src/resource.cpp new file mode 100644 index 00000000..1e7ba80e --- /dev/null +++ b/platforms/lg200/hal/src/resource.cpp @@ -0,0 +1,264 @@ +#include "xsched/lg200/hal/resource.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xsched/utils/common.h" +#include "xsched/utils/log.h" +#include "xsched/utils/pci.h" + +namespace fs = std::filesystem; + +namespace xsched::lg200 +{ + +#if defined(__linux__) + +static fs::path DrmRoot() +{ + const char *override_path = std::getenv("XSCHED_LG200_DRM_ROOT"); + return override_path == nullptr || override_path[0] == '\0' + ? fs::path("/sys/class/drm") + : fs::path(override_path); +} + +static std::string ReadTextFile(const fs::path &path) +{ + std::ifstream in(path); + if (!in) return ""; + + std::ostringstream ss; + ss << in.rdbuf(); + auto text = ss.str(); + while (!text.empty() && std::isspace(static_cast(text.back()))) { + text.pop_back(); + } + return text; +} + +static std::string Lower(std::string value) +{ + std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + return value; +} + +static void CopyField(char *dst, size_t dst_size, const std::string &src) +{ + if (dst_size == 0) return; + std::strncpy(dst, src.c_str(), dst_size - 1); + dst[dst_size - 1] = '\0'; +} + +static uint32_t ParseCardIndex(const std::string &name) +{ + if (name.rfind("card", 0) != 0) return UINT32_MAX; + const std::string number = name.substr(4); + if (number.empty()) return UINT32_MAX; + + uint32_t value = 0; + for (unsigned char character : number) { + if (character < static_cast('0') || character > static_cast('9')) { + return UINT32_MAX; + } + const uint32_t digit = character - static_cast('0'); + if (value > (UINT32_MAX - digit) / 10U) return UINT32_MAX; + value = value * 10U + digit; + } + return value; +} + +static bool ParsePciId(const std::string &pci_name, XDeviceId *device_id) +{ + unsigned int domain = 0; + unsigned int bus = 0; + unsigned int device = 0; + unsigned int function = 0; + + if (std::sscanf(pci_name.c_str(), "%x:%x:%x.%x", + &domain, &bus, &device, &function) + != 4) { + return false; + } + + *device_id = MakePciId(domain, bus, device, function); + return true; +} + +static std::string ReadUeventValue(const fs::path &device_path, const std::string &key) +{ + const std::string content = ReadTextFile(device_path / "uevent"); + std::istringstream lines(content); + std::string line; + const std::string prefix = key + "="; + + while (std::getline(lines, line)) { + if (line.rfind(prefix, 0) == 0) return line.substr(prefix.size()); + } + + return ""; +} + +static uint64_t ReadMemoryBytes(const fs::path &device_path) +{ + const std::string value = ReadTextFile(device_path / "mem_info_vram_total"); + if (value.empty()) return 0; + try { + return std::stoull(value); + } catch (...) { + return 0; + } +} + +static uint64_t ReadUint64File(const fs::path &path) +{ + const std::string value = ReadTextFile(path); + if (value.empty()) return 0; + try { + return std::stoull(value); + } catch (...) { + return 0; + } +} + +static double ReadDoubleFile(const fs::path &path) +{ + const std::string value = ReadTextFile(path); + if (value.empty()) return -1.0; + try { + return std::stod(value); + } catch (...) { + return -1.0; + } +} + +static bool IsLg200Candidate(const std::string &vendor, + const std::string &driver, + const std::string &name) +{ + const std::string text = Lower(vendor + " " + driver + " " + name); + const std::string normalized_driver = Lower(driver); + // The Loongson PCI vendor id alone is not a GPU identity: other display + // or bridge devices can use the same vendor. Require either the LG200 + // product name or the vendor kernel driver's explicit loonggpu identity. + return text.find("lg200") != std::string::npos || normalized_driver.rfind("loonggpu", 0) == 0; +} + +#endif + +std::vector DiscoverDevices() +{ + std::vector devices; + +#if defined(__linux__) + const fs::path drm_root = DrmRoot(); + if (!fs::exists(drm_root)) return devices; + + for (const auto &entry : fs::directory_iterator(drm_root)) { + const std::string card_name = entry.path().filename().string(); + const uint32_t card_index = ParseCardIndex(card_name); + if (card_index == UINT32_MAX) continue; + + const fs::path device_path = entry.path() / "device"; + if (!fs::exists(device_path)) continue; + + const std::string vendor = ReadTextFile(device_path / "vendor"); + const std::string driver = ReadUeventValue(device_path, "DRIVER"); + const std::string pci_id = ReadUeventValue(device_path, "PCI_ID"); + const std::string model = pci_id.empty() ? card_name : pci_id; + if (!IsLg200Candidate(vendor, driver, model)) continue; + + Lg200DeviceInfo info { }; + info.drm_card = card_index; + info.local_memory_bytes = ReadMemoryBytes(device_path); + CopyField(info.name, sizeof(info.name), model); + CopyField(info.vendor, sizeof(info.vendor), vendor); + CopyField(info.driver, sizeof(info.driver), driver); + + std::error_code ec; + const fs::path real_device = fs::canonical(device_path, ec); + if (!ec) ParsePciId(real_device.filename().string(), &info.device_id); + + devices.push_back(info); + } + std::sort(devices.begin(), devices.end(), [](const auto &left, const auto &right) { + return left.drm_card < right.drm_card; + }); +#endif + + return devices; +} + +} // namespace xsched::lg200 + +EXPORT_C_FUNC XResult Lg200DeviceEnumerate(Lg200DeviceInfo *devices, uint32_t *count) +{ + if (count == nullptr) return kXSchedErrorInvalidValue; + + std::vector discovered; + try { + discovered = xsched::lg200::DiscoverDevices(); + } catch (const std::exception &error) { + XWARN("LG200 device discovery failed: %s", error.what()); + return kXSchedErrorUnknown; + } + + const uint32_t required = static_cast(discovered.size()); + if (devices == nullptr) { + *count = required; + return kXSchedSuccess; + } + + const uint32_t capacity = *count; + const uint32_t copied = std::min(capacity, required); + for (uint32_t i = 0; i < copied; ++i) { + devices[i] = discovered[i]; + } + + *count = required; + return capacity < required ? kXSchedErrorInvalidValue : kXSchedSuccess; +} + +EXPORT_C_FUNC XResult Lg200DeviceReadStats(uint32_t drm_card, Lg200DeviceStats *stats) +{ + if (stats == nullptr) return kXSchedErrorInvalidValue; + + *stats = { }; + stats->drm_card = drm_card; + stats->gpu_utilization = -1.0; + +#if defined(__linux__) + try { + const auto devices = xsched::lg200::DiscoverDevices(); + const bool discovered = std::any_of( + devices.begin(), devices.end(), [drm_card](const Lg200DeviceInfo &device) { + return device.drm_card == drm_card; + }); + if (!discovered) return kXSchedErrorNotFound; + } catch (const std::exception &error) { + XWARN("LG200 device validation failed: %s", error.what()); + return kXSchedErrorUnknown; + } + + const fs::path device_path = xsched::lg200::DrmRoot() / ("card" + std::to_string(drm_card)) / "device"; + std::error_code ec; + if (!fs::exists(device_path, ec) || ec) return kXSchedErrorNotFound; + + stats->gpu_utilization = xsched::lg200::ReadDoubleFile(device_path / "gpu_busy_percent"); + stats->local_memory_used_bytes = xsched::lg200::ReadUint64File(device_path / "mem_info_vram_used"); + stats->local_memory_total_bytes = xsched::lg200::ReadUint64File(device_path / "mem_info_vram_total"); + return kXSchedSuccess; +#else + return kXSchedErrorNotSupported; +#endif +} diff --git a/platforms/lg200/test/CMakeLists.txt b/platforms/lg200/test/CMakeLists.txt new file mode 100644 index 00000000..9b865020 --- /dev/null +++ b/platforms/lg200/test/CMakeLists.txt @@ -0,0 +1,12 @@ +if(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") + return() +endif() + +add_executable(lg200_resource_test lg200_resource_test.cpp) +target_compile_options(lg200_resource_test PRIVATE -Wall -Wextra -Werror) +target_link_libraries(lg200_resource_test PRIVATE hallg200) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND + CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9) + target_link_libraries(lg200_resource_test PRIVATE stdc++fs) +endif() diff --git a/platforms/lg200/test/lg200_resource_test.cpp b/platforms/lg200/test/lg200_resource_test.cpp new file mode 100644 index 00000000..a1b8fe47 --- /dev/null +++ b/platforms/lg200/test/lg200_resource_test.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "xsched/lg200/hal.h" + +namespace fs = std::filesystem; + +namespace +{ + +void WriteFile(const fs::path &path, const std::string &contents) +{ + std::ofstream output(path); + output << contents; + if (!output) throw std::runtime_error("failed to write " + path.string()); +} + +bool Check(bool condition, const char *message) +{ + if (condition) return true; + std::cerr << "FAIL: " << message << '\n'; + return false; +} + +} // namespace + +int main() +{ + const fs::path root = fs::temp_directory_path() / ("xsched-lg200-resource-" + std::to_string(getpid())); + std::error_code ec; + fs::remove_all(root, ec); + + try { + const fs::path device = root / "devices" / "0000:01:02.0"; + fs::create_directories(device); + fs::create_directories(root / "card3"); + fs::create_directory_symlink(device, root / "card3" / "device"); + + const fs::path non_gpu = root / "devices" / "0000:02:00.0"; + fs::create_directories(non_gpu); + fs::create_directories(root / "card1"); + fs::create_directory_symlink(non_gpu, root / "card1" / "device"); + + WriteFile(device / "vendor", "0x0014\n"); + WriteFile(device / "uevent", "DRIVER=loonggpu\nPCI_ID=0014:7a25\n"); + WriteFile(device / "gpu_busy_percent", "73.5\n"); + WriteFile(device / "mem_info_vram_used", "1048576\n"); + WriteFile(device / "mem_info_vram_total", "8388608\n"); + WriteFile(non_gpu / "vendor", "0x0014\n"); + WriteFile(non_gpu / "uevent", "DRIVER=othergpu\nPCI_ID=0014:1234\n"); + + if (setenv("XSCHED_LG200_DRM_ROOT", root.string().c_str(), 1) != 0) { + throw std::runtime_error("setenv failed"); + } + + bool ok = true; + std::uint32_t count = 0; + ok &= Check(Lg200DeviceEnumerate(nullptr, &count) == kXSchedSuccess, + "count query failed"); + ok &= Check(count == 1, "vendor-only non-LG200 device must be rejected"); + + std::vector devices(count); + std::uint32_t capacity = count; + ok &= Check(Lg200DeviceEnumerate(devices.data(), &capacity) == kXSchedSuccess, + "device enumeration failed"); + ok &= Check(capacity == 1, "enumeration returned wrong count"); + ok &= Check(devices[0].drm_card == 3, "wrong DRM card index"); + ok &= Check(std::string(devices[0].driver) == "loonggpu", "wrong driver name"); + ok &= Check(std::string(devices[0].vendor) == "0x0014", "wrong vendor id"); + ok &= Check(devices[0].local_memory_bytes == 8388608, "wrong total memory"); + + Lg200DeviceInfo dummy { }; + capacity = 0; + ok &= Check(Lg200DeviceEnumerate(&dummy, &capacity) == kXSchedErrorInvalidValue, + "short buffer must fail"); + ok &= Check(capacity == 1, "short buffer must report required capacity"); + ok &= Check(Lg200DeviceEnumerate(nullptr, nullptr) == kXSchedErrorInvalidValue, + "null count must fail"); + + Lg200DeviceStats stats { }; + ok &= Check(Lg200DeviceReadStats(3, &stats) == kXSchedSuccess, + "stat query failed"); + ok &= Check(stats.gpu_utilization == 73.5, "wrong GPU utilization"); + ok &= Check(stats.local_memory_used_bytes == 1048576, "wrong used memory"); + ok &= Check(stats.local_memory_total_bytes == 8388608, "wrong stat total memory"); + ok &= Check(Lg200DeviceReadStats(1, &stats) == kXSchedErrorNotFound, + "non-LG200 card stats must return not found"); + ok &= Check(Lg200DeviceReadStats(99, &stats) == kXSchedErrorNotFound, + "missing card must return not found"); + + unsetenv("XSCHED_LG200_DRM_ROOT"); + fs::remove_all(root, ec); + if (!ok) return 1; + std::cout << "LG200 resource test passed.\n"; + return 0; + } catch (const std::exception &error) { + unsetenv("XSCHED_LG200_DRM_ROOT"); + fs::remove_all(root, ec); + std::cerr << "FAIL: " << error.what() << '\n'; + return 1; + } +} diff --git a/platforms/lg200/tools/lg200_info.cpp b/platforms/lg200/tools/lg200_info.cpp new file mode 100644 index 00000000..caef0cd7 --- /dev/null +++ b/platforms/lg200/tools/lg200_info.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "xsched/lg200/hal.h" + +static const char *OrUnknown(const char *value) +{ + return value[0] == '\0' ? "unknown" : value; +} + +static std::string FormatBytes(uint64_t bytes) +{ + if (bytes == 0) return "unknown"; + + const char *units[] = { "B", "KiB", "MiB", "GiB" }; + double value = static_cast(bytes); + size_t unit = 0; + while (value >= 1024.0 && unit + 1 < std::size(units)) { + value /= 1024.0; + ++unit; + } + + std::ostringstream out; + out << std::fixed << std::setprecision(unit == 0 ? 0 : 2) << value << ' ' << units[unit]; + return out.str(); +} + +static std::string FormatUtilization(double value) +{ + if (value < 0.0) return "unknown"; + + std::ostringstream out; + out << std::fixed << std::setprecision(1) << value << '%'; + return out.str(); +} + +int main(int argc, char **argv) +{ + bool require_device = false; + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + if (arg == "--require-device") { + require_device = true; + } else if (arg == "-h" || arg == "--help") { + std::cout << "Usage: lg200_info [--require-device]\n"; + return 0; + } else { + std::cerr << "unknown option: " << arg << '\n'; + return 2; + } + } + + uint32_t count = 0; + XResult res = Lg200DeviceEnumerate(nullptr, &count); + if (res != kXSchedSuccess) { + std::cerr << "failed to enumerate LG200 devices: " << res << '\n'; + return 1; + } + + if (count == 0) { + std::cout << "No LG200 DRM devices found.\n"; + return require_device ? 3 : 0; + } + + std::vector devices(count); + res = Lg200DeviceEnumerate(devices.data(), &count); + if (res != kXSchedSuccess) { + std::cerr << "failed to read LG200 device info: " << res << '\n'; + return 1; + } + + std::cout << "LG200 devices: " << count << '\n'; + for (uint32_t i = 0; i < count; ++i) { + const auto &dev = devices[i]; + Lg200DeviceStats stats { }; + const bool has_stats = Lg200DeviceReadStats(dev.drm_card, &stats) == kXSchedSuccess; + std::cout << " [" << i << "] drm=card" << dev.drm_card + << " device_id=0x" << std::hex << dev.device_id << std::dec + << " name=" << OrUnknown(dev.name) + << " vendor=" << OrUnknown(dev.vendor) + << " driver=" << OrUnknown(dev.driver) + << " local_memory=" << FormatBytes(dev.local_memory_bytes) + << " gpu_utilization=" + << (has_stats ? FormatUtilization(stats.gpu_utilization) : "unknown") + << " memory_used=" + << (has_stats ? FormatBytes(stats.local_memory_used_bytes) : "unknown") + << '\n'; + } + + return 0; +}