From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 17A83E9E2E2 for ; Wed, 11 Feb 2026 11:21:13 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id B8C4A10E3AF; Wed, 11 Feb 2026 11:21:12 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.b="CX7byyk4"; dkim-atps=neutral Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.13]) by gabe.freedesktop.org (Postfix) with ESMTPS id AABD410E3AD for ; Wed, 11 Feb 2026 11:21:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1770808872; x=1802344872; h=message-id:date:mime-version:subject:to:cc:references: from:in-reply-to:content-transfer-encoding; bh=mGfK6aYqOIxH9BX/k9OIX6PQzxtBO5dWQjHow7oT5Yw=; b=CX7byyk4tpMLDRo5Pa/FmZll66KxyN32dK62IJQjRa98uUWHjSyd02Yi /v15qLQQNxhqUzjoWWP3FejZkTxu0UYPN2SrkuXf5fHrPpERPJmt3enk+ op84mrVPSFPAo+RAM29d8iHDgjWgShOwXYge3iQtdXSCpQJ54XtP58jUt Djl0H6akepBCHgt0aZHDtRhAKWBpxSS+4Dwzb6yeHjcpYbGUowxtbBIig BBDCv1rOx1vCQE5JF3K105dwb2YZuBsEoxzXEgAGHCiG09Ujdast1W6NI fSVDgndvNiXMnJLpQYGMV8N6BcDCKoDJ9SX8StX54tkWKSCZBWwnnXWO8 g==; X-CSE-ConnectionGUID: Jlcv88SxRnqyOlEi5/VdIg== X-CSE-MsgGUID: 154XfT39TWKqaMjRVV1lBA== X-IronPort-AV: E=McAfee;i="6800,10657,11697"; a="74552116" X-IronPort-AV: E=Sophos;i="6.21,283,1763452800"; d="scan'208";a="74552116" Received: from orviesa006.jf.intel.com ([10.64.159.146]) by fmvoesa107.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 11 Feb 2026 03:21:11 -0800 X-CSE-ConnectionGUID: teFvhs37TYmJTeVxV2X5sw== X-CSE-MsgGUID: hPJWkTJSQhetCUpVwzqNiQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.21,283,1763452800"; d="scan'208";a="211327999" Received: from soc-5cg43972f8.clients.intel.com (HELO [172.28.182.85]) ([172.28.182.85]) by orviesa006-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 11 Feb 2026 03:21:10 -0800 Message-ID: Date: Wed, 11 Feb 2026 12:21:07 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v3 i-g-t 2/7] lib/igt_pci: Add generic PCI driver override and bind/unbind helpers To: "Laguna, Lukasz" , igt-dev@lists.freedesktop.org Cc: Adam Miszczak , Jakub Kolakowski , Kamil Konieczny References: <20260204163217.121305-1-marcin.bernatowicz@linux.intel.com> <20260204163217.121305-3-marcin.bernatowicz@linux.intel.com> <2ebfcc0d-49ca-4fde-a48f-27a014b5f729@intel.com> <1c068311-2769-4f90-9c7c-9733a45d9029@linux.intel.com> <3f0acb60-218d-4935-ba2f-0314ba481cf3@intel.com> Content-Language: en-US From: "Bernatowicz, Marcin" In-Reply-To: <3f0acb60-218d-4935-ba2f-0314ba481cf3@intel.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-BeenThere: igt-dev@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development mailing list for IGT GPU Tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: igt-dev-bounces@lists.freedesktop.org Sender: "igt-dev" On 2/11/2026 7:39 AM, Laguna, Lukasz wrote: > > On 2/10/2026 11:19, Bernatowicz, Marcin wrote: >> >> On 2/9/2026 11:16 AM, Laguna, Lukasz wrote: >>> >>> On 2/4/2026 17:32, Marcin Bernatowicz wrote: >>>> Add generic helpers for controlling PCI driver binding via sysfs. >>>> >>>> The new APIs provide driver- and device-centric primitives for: >>>>    - setting and clearing driver_override >>>>    - triggering PCI driver reprobe >>>>    - binding and unbinding devices to a specific PCI driver >>>>    - query the currently bound PCI driver >>>> >>>> Signed-off-by: Marcin Bernatowicz >>>> Cc: Adam Miszczak >>>> Cc: Jakub Kolakowski >>>> Cc: Kamil Konieczny >>>> Cc: Lukasz Laguna >>>> >>>> --- >>>> v2: >>>> - Add igt_pci_get_bound_driver_name() to query the currently bound PCI >>>>    driver via the /sys/bus/pci/devices//driver symlink. >>>> - Extend igt_pci_bind_driver_override() and >>>> igt_pci_unbind_driver_override() >>>>    with a timeout_ms parameter so callers can wait for bind/unbind to >>>>    actually complete, instead of relying on drivers_probe write >>>> success. >>>> >>>> --- >>>>   lib/igt_pci.c | 351 >>>> ++++++++++++++++++++++++++++++++++++++++++++++++++ >>>>   lib/igt_pci.h |  13 +- >>>>   2 files changed, 363 insertions(+), 1 deletion(-) >>>> >>>> diff --git a/lib/igt_pci.c b/lib/igt_pci.c >>>> index 61aaf939d..80aaf07c5 100644 >>>> --- a/lib/igt_pci.c >>>> +++ b/lib/igt_pci.c >>>> @@ -3,9 +3,18 @@ >>>>    * Copyright © 2022 Intel Corporation >>>>    */ >>>>   +#include >>>> +#include >>>> +#include >>>> +#include >>>> +#include >>>> +#include >>>> +#include >>>>   #include >>>> +#include "igt_aux.h" >>>>   #include "igt_core.h" >>>>   #include "igt_pci.h" >>>> +#include "igt_sysfs.h" >>>>     static int find_pci_cap_offset_at(struct pci_device *dev, enum >>>> pci_cap_id cap_id, >>>>                     int start_offset) >>>> @@ -51,3 +60,345 @@ int find_pci_cap_offset(struct pci_device *dev, >>>> enum pci_cap_id cap_id) >>>>   { >>>>       return find_pci_cap_offset_at(dev, cap_id, PCI_CAPS_START); >>>>   } >>>> + >>>> +static int open_pci_driver_dir(const char *driver) >>>> +{ >>>> +    char path[PATH_MAX]; >>>> + >>>> +    snprintf(path, sizeof(path), "/sys/bus/pci/drivers/%s", driver); >>>> +    return open(path, O_RDONLY | O_CLOEXEC); >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_device_unbind: >>>> + * @pci_slot: BDF like "0000:01:00.0" >>>> + * >>>> + * Unbind @pci_slot from its currently bound driver, if any. >>>> + * Returns 0 on success, or a negative errno-like value. >>>> + */ >>>> +int igt_pci_device_unbind(const char *pci_slot) >>>> +{ >>>> +    char path[PATH_MAX]; >>>> +    int dirfd; >>>> +    int ret; >>>> + >>>> +    snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/driver", >>>> pci_slot); >>>> +    dirfd = open(path, O_RDONLY | O_CLOEXEC); >>>> +    if (dirfd < 0) >>>> +        return 0; /* already unbound */ >>>> + >>>> +    ret = igt_sysfs_set(dirfd, "unbind", pci_slot) ? 0 : -errno; >>>> +    close(dirfd); >>>> + >>>> +    return ret; >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_driver_bind: >>>> + * @driver: PCI driver name under /sys/bus/pci/drivers/ >>>> + * @pci_slot: device to bind >>>> + * >>>> + * Bind @pci_slot to @driver. Driver must be present/loaded. >>>> + * Returns 0 on success, or a negative errno-like value. >>>> + */ >>>> +int igt_pci_driver_bind(const char *driver, const char *pci_slot) >>>> +{ >>>> +    int dirfd, ret; >>>> + >>>> +    dirfd = open_pci_driver_dir(driver); >>>> +    if (dirfd < 0) >>>> +        return -errno; >>>> + >>>> +    ret = igt_sysfs_set(dirfd, "bind", pci_slot) ? 0 : -errno; >>>> +    close(dirfd); >>>> + >>>> +    return ret; >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_driver_unbind: >>>> + * @driver: PCI driver name >>>> + * @pci_slot: device to unbind >>>> + * >>>> + * Unbind @pci_slot from @driver. >>>> + * Returns 0 on success, or a negative errno-like value. >>>> + */ >>>> +int igt_pci_driver_unbind(const char *driver, const char *pci_slot) >>>> +{ >>>> +    int dirfd, ret; >>>> + >>>> +    dirfd = open_pci_driver_dir(driver); >>>> +    if (dirfd < 0) >>>> +        return -errno; >>>> + >>>> +    ret = igt_sysfs_set(dirfd, "unbind", pci_slot) ? 0 : -errno; >>>> +    close(dirfd); >>>> + >>>> +    return ret; >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_driver_unbind_all: >>>> + * @driver: PCI driver name >>>> + * >>>> + * Unbind all devices currently bound to @driver. >>>> + * Returns 0 on success, or a negative errno-like value. >>>> + */ >>>> +int igt_pci_driver_unbind_all(const char *driver) >>>> +{ >>>> +    char path[PATH_MAX]; >>>> +    DIR *dir; >>>> +    struct dirent *de; >>>> +    int driver_fd; >>>> + >>>> +    snprintf(path, sizeof(path), "/sys/bus/pci/drivers/%s", driver); >>>> +    dir = opendir(path); >>>> +    if (!dir) >>>> +        return -errno; >>>> + >>>> +    driver_fd = dirfd(dir); >>>> + >>>> +    while ((de = readdir(dir))) { >>>> +        bool ok; >>>> + >>>> +        /* BDF symlinks are like "0000:01:00.0" and start with >>>> digit */ >>>> +        if (de->d_type != DT_LNK || !isdigit(de->d_name[0])) >>>> +            continue; >>>> + >>>> +        ok = igt_sysfs_set(driver_fd, "unbind", de->d_name); >>>> +        if (!ok) { >>>> +            int err = -errno; >>>> + >>>> +            closedir(dir); >>>> +            return err; >>>> +        } >>>> +    } >>>> + >>>> +    closedir(dir); >>>> +    return 0; >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_set_driver_override: >>>> + * @pci_slot: PCI device BDF (e.g. "0000:01:00.0") >>>> + * @driver: PCI driver name to force-bind (e.g. "xe-vfio-pci"), or >>>> + *          NULL / empty string to clear an existing override >>>> + * >>>> + * Set or clear the PCI driver_override for @pci_slot via sysfs. >>>> + * >>>> + * This does not trigger driver reprobe by itself. Call >>>> + * igt_pci_probe_drivers() afterwards to apply the override. >>>> + * >>>> + * Returns: 0 on success, negative errno on failure. >>>> + */ >>>> +int igt_pci_set_driver_override(const char *pci_slot, const char >>>> *driver) >>>> +{ >>>> +    char devpath[PATH_MAX]; >>>> +    int dev; >>>> +    bool ok; >>>> + >>>> +    snprintf(devpath, sizeof(devpath), "/sys/bus/pci/devices/%s", >>>> pci_slot); >>>> +    dev = open(devpath, O_DIRECTORY | O_RDONLY); >>>> +    if (dev < 0) >>>> +        return -errno; >>>> + >>>> +    ok = igt_sysfs_set(dev, "driver_override", driver ? driver : ""); >>>> +    close(dev); >>>> + >>>> +    return ok ? 0 : -errno; >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_probe_drivers: >>>> + * @pci_slot: PCI device BDF (e.g. "0000:01:00.0") >>>> + * >>>> + * Trigger PCI driver reprobe for @pci_slot by writing to >>>> + * /sys/bus/pci/drivers_probe. >>>> + * >>>> + * This causes the kernel to attempt binding the device, honoring any >>>> + * driver_override previously set. >>>> + * >>>> + * Note: a successful write only means the reprobe request was >>>> accepted. >>>> + * It does not guarantee that a driver actually bound to the device. >>>> + * >>>> + * Returns: 0 on success, negative errno on failure. >>>> + */ >>>> +int igt_pci_probe_drivers(const char *pci_slot) >>>> +{ >>>> +    int pci; >>>> +    bool ok; >>>> + >>>> +    pci = open("/sys/bus/pci", O_DIRECTORY | O_RDONLY); >>>> +    if (pci < 0) >>>> +        return -errno; >>>> + >>>> +    ok = igt_sysfs_set(pci, "drivers_probe", pci_slot); >>>> +    close(pci); >>>> + >>>> +    return ok ? 0 : -errno; >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_get_bound_driver_name: >>>> + * @pci_slot: PCI device BDF (e.g. "0000:01:00.0") >>>> + * @driver: destination buffer for the bound driver name >>>> + * @driver_len: size of @driver in bytes >>>> + * >>>> + * Read the currently bound PCI driver name for @pci_slot by >>>> inspecting the >>>> + * /sys/bus/pci/devices//driver symlink. >>>> + * >>>> + * Return values: >>>> + *  1: device is bound and @driver contains the driver name >>>> + *  0: device is unbound (no driver symlink) >>>> + * <0: negative errno-like value on error >>>> + */ >>>> +int igt_pci_get_bound_driver_name(const char *pci_slot, char >>>> *driver, size_t driver_len) >>>> +{ >>>> +    char path[PATH_MAX]; >>>> +    char link[PATH_MAX]; >>>> +    const char *base; >>>> +    ssize_t len; >>>> + >>>> +    if (driver && driver_len) >>>> +        driver[0] = '\0'; >>>> + >>>> +    snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/driver", >>>> pci_slot); >>>> +    len = readlink(path, link, sizeof(link) - 1); >>>> +    if (len < 0) { >>>> +        if (errno == ENOENT) >>>> +            return 0; /* unbound */ >>>> + >>>> +        return -errno; >>>> +    } >>>> + >>>> +    link[len] = '\0'; >>>> +    base = strrchr(link, '/'); >>>> +    base = base ? base + 1 : link; >>>> + >>>> +    if (driver && driver_len) >>> >>> You can check the input params at the beginning of the function and >>> return error if they are invalid. >> >> driver/driver_len are optional, callers may pass NULL and/or 0 when >> they only need the >> >> return value (bound/unbound) and don’t care about the driver name... >> > > Right... now I see it, but it wasn't clear at first glance. Maybe > function doc should be updated? > Or probably it would be even better to introduce separate helper > igt_pci_is_driver_bound()? It would call > igt_pci_get_bound_driver_name() without passing buffer. > Send v4 with updated doc. >>> >>>> +        snprintf(driver, driver_len, "%s", base); >>>> + >>>> +    return 1; >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_bind_driver_override: >>>> + * @pci_slot: PCI device BDF (e.g. "0000:01:00.0") >>>> + * @driver: PCI driver name to bind (must not be NULL or empty) >>>> + * @timeout_ms: how long to wait for the device to become bound. >>>> + *              If 0, don't wait (best-effort immediate check only). >>>> + * >>>> + * Bind @pci_slot to @driver using the driver_override mechanism. >>>> + * >>>> + * This helper sets driver_override and immediately triggers driver >>>> + * reprobe so that the device is bound to the requested driver. >>>> + * >>>> + * Returns: 0 on success, negative errno-like value on failure. >>>> + * A reprobe request can be accepted by sysfs while the driver probe >>>> + * fails later; this helper verifies the device ended up bound. >>>> + * >>>> + * On bind failure, returns a negative error and the failure >>>> reason may >>>> + * also be logged to dmesg by the kernel driver. >>>> + */ >>>> +int igt_pci_bind_driver_override(const char *pci_slot, const char >>>> *driver, >>>> +                 unsigned int timeout_ms) >>>> +{ >>>> +    int ret; >>>> +    char bound[64]; >>>> +    int bound_ret; >>>> +    bool bound_ok; >>>> + >>>> +    if (!driver || !driver[0]) >>>> +        return -EINVAL; >>>> + >>>> +    ret = igt_pci_set_driver_override(pci_slot, driver); >>>> +    if (ret) >>>> +        return ret; >>>> + >>>> +    ret = igt_pci_probe_drivers(pci_slot); >>>> +    if (ret) >>>> +        return ret; >>>> + >>>> +    /* >>>> +     * Writing to drivers_probe only tells us the kernel accepted >>>> the request. >>>> +     * The actual driver probe may still fail (and only be >>>> reported via dmesg). >>>> +     * Verify that the device ended up bound to the requested driver. >>>> +     */ >>>> +    bound_ret = igt_pci_get_bound_driver_name(pci_slot, bound, >>>> sizeof(bound)); >>>> +    if (bound_ret < 0) >>>> +        return bound_ret; >>>> + >>>> +    if (timeout_ms == 0) { >>>> +        /* >>>> +         * No waiting requested. If the device is already bound, >>>> validate >>>> +         * it is bound to the expected driver; otherwise treat as >>>> +         * best-effort request-only success. >>>> +         */ >>>> +        if (bound_ret > 0 && strcmp(bound, driver)) >>>> +            return -EBUSY; >>>> + >>>> +        return 0; >>>> +    } >>>> + >>>> +    bound_ok = igt_wait((bound_ret = >>>> +                 igt_pci_get_bound_driver_name(pci_slot, bound, >>>> sizeof(bound))) != 0, >>>> +                timeout_ms, 1); >>>> +    if (!bound_ok) >>>> +        return -EIO; >>>> + >>>> +    if (bound_ret < 0) >>>> +        return bound_ret; >>>> + >>>> +    if (strcmp(bound, driver)) >>>> +        return -EBUSY; >>>> + >>>> +    return 0; >>>> +} >>>> + >>>> +/** >>>> + * igt_pci_unbind_driver_override: >>>> + * @pci_slot: PCI device BDF (e.g. "0000:01:00.0") >>>> + * @timeout_ms: how long to wait for the device to become unbound. >>>> + *              If 0, don't wait (best-effort immediate check only). >>>> + * >>>> + * Unbind @pci_slot from its currently bound driver (if any) and >>>> clear >>>> + * any driver_override setting. >>>> + * >>>> + * This is the inverse operation of igt_pci_bind_driver_override(). >>>> + * >>>> + * Returns: 0 on success, negative errno on failure. >>>> + */ >>>> +int igt_pci_unbind_driver_override(const char *pci_slot, unsigned >>>> int timeout_ms) >>>> +{ >>>> +    int ret; >>>> +    int bound_ret; >>>> +    char bound[64]; >>>> +    bool unbound_ok; >>>> + >>>> +    ret = igt_pci_device_unbind(pci_slot); >>>> +    if (ret) >>>> +        return ret; >>>> + >>>> +    ret = igt_pci_set_driver_override(pci_slot, ""); >>>> +    if (ret) >>>> +        return ret; >>>> + >>>> +    bound_ret = igt_pci_get_bound_driver_name(pci_slot, bound, >>>> sizeof(bound)); >>>> +    if (bound_ret < 0) >>>> +        return bound_ret; >>>> + >>>> +    if (timeout_ms == 0) >>>> +        return 0; >>>> + >>>> +    /* Verify the device actually ends up unbound (driver symlink >>>> removed). */ >>>> +    unbound_ok = igt_wait((bound_ret = >>>> +                   igt_pci_get_bound_driver_name(pci_slot, bound, >>>> sizeof(bound))) == 0, >>>> +                  timeout_ms, 1); >>>> +    if (!unbound_ok) >>>> +        return -EBUSY; >>>> + >>>> +    if (bound_ret < 0) >>>> +        return bound_ret; >>>> + >>>> +    return 0; >>>> +} >>>> diff --git a/lib/igt_pci.h b/lib/igt_pci.h >>>> index 92b9cc392..a66eeebf2 100644 >>>> --- a/lib/igt_pci.h >>>> +++ b/lib/igt_pci.h >>>> @@ -6,8 +6,9 @@ >>>>   #ifndef __IGT_PCI_H__ >>>>   #define __IGT_PCI_H__ >>>>   -#include >>>>   #include >>>> +#include >>>> +#include >>>>     /* forward declaration */ >>>>   struct pci_device; >>>> @@ -24,5 +25,15 @@ enum pci_cap_id { >>>>   #define  PCI_SLOT_PWR_CTRL_PRESENT (1 << 1) >>>>     int find_pci_cap_offset(struct pci_device *dev, enum pci_cap_id >>>> cap_id); >>>> +int igt_pci_device_unbind(const char *pci_slot); >>>> +int igt_pci_driver_bind(const char *driver, const char *pci_slot); >>>> +int igt_pci_driver_unbind(const char *driver, const char *pci_slot); >>>> +int igt_pci_driver_unbind_all(const char *driver); >>>> +int igt_pci_set_driver_override(const char *pci_slot, const char >>>> *driver); >>>> +int igt_pci_probe_drivers(const char *pci_slot); >>>> +int igt_pci_get_bound_driver_name(const char *pci_slot, char >>>> *driver, size_t driver_len); >>>> +int igt_pci_bind_driver_override(const char *pci_slot, const char >>>> *driver, >>>> +                 unsigned int timeout_ms); >>>> +int igt_pci_unbind_driver_override(const char *pci_slot, unsigned >>>> int timeout_ms); >>>>     #endif