acrn-kernel/drivers/gpu/drm/i915/i915_sriov_sysfs.c

618 lines
14 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include "i915_drv.h"
#include "i915_sriov_sysfs.h"
#include "i915_sriov_sysfs_types.h"
#include "i915_sysfs.h"
/*
* /sys/class/drm/card*
* └── iov/
* ├── ...
* ├── pf/
* │   └── ...
* ├── vf1/
* │   └── ...
*/
#define SRIOV_PRELIMINARY "prelim_"
#define SRIOV_KOBJ_HOME_NAME SRIOV_PRELIMINARY "iov"
#define SRIOV_EXT_KOBJ_PF_NAME "pf"
#define SRIOV_EXT_KOBJ_VFn_NAME "vf%u"
#define SRIOV_DEVICE_LINK_NAME "device"
struct drm_i915_private *sriov_kobj_to_i915(struct i915_sriov_kobj *kobj)
{
struct device *kdev = kobj_to_dev(kobj->base.parent);
struct drm_i915_private *i915 = kdev_minor_to_i915(kdev);
return i915;
}
struct drm_i915_private *sriov_ext_kobj_to_i915(struct i915_sriov_ext_kobj *kobj)
{
return sriov_kobj_to_i915(to_sriov_kobj(kobj->base.parent));
}
static inline bool sriov_ext_kobj_is_pf(struct i915_sriov_ext_kobj *kobj)
{
return !kobj->id;
}
/* core SR-IOV attributes */
static ssize_t mode_sriov_attr_show(struct drm_i915_private *i915, char *buf)
{
return sysfs_emit(buf, "%s\n", i915_iov_mode_to_string(IOV_MODE(i915)));
}
I915_SRIOV_ATTR_RO(mode);
static struct attribute *sriov_attrs[] = {
&mode_sriov_attr.attr,
NULL
};
static const struct attribute_group sriov_attr_group = {
.attrs = sriov_attrs,
};
static const struct attribute_group *default_sriov_attr_groups[] = {
&sriov_attr_group,
NULL
};
/* extended (PF and VFs) SR-IOV attributes */
static ssize_t auto_provisioning_sriov_ext_attr_show(struct drm_i915_private *i915,
unsigned int id, char *buf)
{
int value = i915_sriov_pf_is_auto_provisioning_enabled(i915);
return sysfs_emit(buf, "%d\n", value);
}
static ssize_t auto_provisioning_sriov_ext_attr_store(struct drm_i915_private *i915,
unsigned int id,
const char *buf, size_t count)
{
bool value;
int err;
err = kstrtobool(buf, &value);
if (err)
return err;
err = i915_sriov_pf_set_auto_provisioning(i915, value);
return err ?: count;
}
I915_SRIOV_EXT_ATTR(auto_provisioning);
static ssize_t id_sriov_ext_attr_show(struct drm_i915_private *i915,
unsigned int id, char *buf)
{
return sysfs_emit(buf, "%u\n", id);
}
#define CONTROL_STOP "stop"
#define CONTROL_PAUSE "pause"
#define CONTROL_RESUME "resume"
#define CONTROL_CLEAR "clear"
static ssize_t control_sriov_ext_attr_store(struct drm_i915_private *i915,
unsigned int id,
const char *buf, size_t count)
{
int err = -EPERM;
if (sysfs_streq(buf, CONTROL_STOP)) {
err = i915_sriov_pf_stop_vf(i915, id);
} else if (sysfs_streq(buf, CONTROL_PAUSE)) {
err = i915_sriov_pf_pause_vf(i915, id);
} else if (sysfs_streq(buf, CONTROL_RESUME)) {
err = i915_sriov_pf_resume_vf(i915, id);
} else if (sysfs_streq(buf, CONTROL_CLEAR)) {
err = i915_sriov_pf_clear_vf(i915, id);
} else {
err = -EINVAL;
}
return err ?: count;
}
I915_SRIOV_EXT_ATTR_RO(id);
I915_SRIOV_EXT_ATTR_WO(control);
static struct attribute *sriov_ext_attrs[] = {
NULL
};
static const struct attribute_group sriov_ext_attr_group = {
.attrs = sriov_ext_attrs,
};
static struct attribute *pf_ext_attrs[] = {
&auto_provisioning_sriov_ext_attr.attr,
NULL
};
static umode_t pf_ext_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int index)
{
struct i915_sriov_ext_kobj *sriov_kobj = to_sriov_ext_kobj(kobj);
if (!sriov_ext_kobj_is_pf(sriov_kobj))
return 0;
return attr->mode;
}
static const struct attribute_group pf_ext_attr_group = {
.attrs = pf_ext_attrs,
.is_visible = pf_ext_attr_is_visible,
};
static struct attribute *vf_ext_attrs[] = {
&id_sriov_ext_attr.attr,
&control_sriov_ext_attr.attr,
NULL
};
static umode_t vf_ext_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int index)
{
struct i915_sriov_ext_kobj *sriov_kobj = to_sriov_ext_kobj(kobj);
if (sriov_ext_kobj_is_pf(sriov_kobj))
return 0;
return attr->mode;
}
static const struct attribute_group vf_ext_attr_group = {
.attrs = vf_ext_attrs,
.is_visible = vf_ext_attr_is_visible,
};
static const struct attribute_group *default_sriov_ext_attr_groups[] = {
&sriov_ext_attr_group,
&pf_ext_attr_group,
&vf_ext_attr_group,
NULL,
};
/* no user serviceable parts below */
static ssize_t sriov_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct drm_i915_private *i915 = sriov_kobj_to_i915(to_sriov_kobj(kobj));
struct i915_sriov_attr *sriov_attr = to_sriov_attr(attr);
return sriov_attr->show ? sriov_attr->show(i915, buf) : -EIO;
}
static ssize_t sriov_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct drm_i915_private *i915 = sriov_kobj_to_i915(to_sriov_kobj(kobj));
struct i915_sriov_attr *sriov_attr = to_sriov_attr(attr);
return sriov_attr->store ? sriov_attr->store(i915, buf, count) : -EIO;
}
static const struct sysfs_ops sriov_sysfs_ops = {
.show = sriov_attr_show,
.store = sriov_attr_store,
};
static void sriov_kobj_release(struct kobject *kobj)
{
struct i915_sriov_kobj *sriov_kobj = to_sriov_kobj(kobj);
kfree(sriov_kobj);
}
static struct kobj_type sriov_ktype = {
.release = sriov_kobj_release,
.sysfs_ops = &sriov_sysfs_ops,
.default_groups = default_sriov_attr_groups,
};
static ssize_t sriov_ext_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct i915_sriov_ext_kobj *sriov_kobj = to_sriov_ext_kobj(kobj);
struct i915_sriov_ext_attr *sriov_attr = to_sriov_ext_attr(attr);
struct drm_i915_private *i915 = sriov_ext_kobj_to_i915(sriov_kobj);
unsigned int id = sriov_kobj->id;
return sriov_attr->show ? sriov_attr->show(i915, id, buf) : -EIO;
}
static ssize_t sriov_ext_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct i915_sriov_ext_kobj *sriov_kobj = to_sriov_ext_kobj(kobj);
struct i915_sriov_ext_attr *sriov_attr = to_sriov_ext_attr(attr);
struct drm_i915_private *i915 = sriov_ext_kobj_to_i915(sriov_kobj);
unsigned int id = sriov_kobj->id;
return sriov_attr->store ? sriov_attr->store(i915, id, buf, count) : -EIO;
}
static const struct sysfs_ops sriov_ext_sysfs_ops = {
.show = sriov_ext_attr_show,
.store = sriov_ext_attr_store,
};
static void sriov_ext_kobj_release(struct kobject *kobj)
{
struct i915_sriov_ext_kobj *sriov_kobj = to_sriov_ext_kobj(kobj);
kfree(sriov_kobj);
}
static struct kobj_type sriov_ext_ktype = {
.release = sriov_ext_kobj_release,
.sysfs_ops = &sriov_ext_sysfs_ops,
.default_groups = default_sriov_ext_attr_groups,
};
static unsigned int pf_nodes_count(struct drm_i915_private *i915)
{
/* 1 x PF + n x VFs */
return 1 + i915_sriov_pf_get_totalvfs(i915);
}
static int pf_setup_failed(struct drm_i915_private *i915, int err, const char *what)
{
i915_probe_error(i915, "Failed to setup SR-IOV sysfs %s (%pe)\n",
what, ERR_PTR(err));
return err;
}
static int pf_setup_home(struct drm_i915_private *i915)
{
struct device *kdev = i915->drm.primary->kdev;
struct i915_sriov_pf *pf = &i915->sriov.pf;
struct i915_sriov_kobj *home = pf->sysfs.home;
int err;
GEM_BUG_ON(!IS_SRIOV_PF(i915));
GEM_BUG_ON(home);
err = i915_inject_probe_error(i915, -ENOMEM);
if (unlikely(err))
goto failed;
home = kzalloc(sizeof(*home), GFP_KERNEL);
if (unlikely(!home)) {
err = -ENOMEM;
goto failed;
}
err = kobject_init_and_add(&home->base, &sriov_ktype, &kdev->kobj, SRIOV_KOBJ_HOME_NAME);
if (unlikely(err)) {
goto failed_init;
}
GEM_BUG_ON(pf->sysfs.home);
pf->sysfs.home = home;
return 0;
failed_init:
kobject_put(&home->base);
failed:
return pf_setup_failed(i915, err, "home");
}
static void pf_teardown_home(struct drm_i915_private *i915)
{
struct i915_sriov_pf *pf = &i915->sriov.pf;
struct i915_sriov_kobj *home = fetch_and_zero(&pf->sysfs.home);
if (home)
kobject_put(&home->base);
}
static int pf_setup_tree(struct drm_i915_private *i915)
{
struct i915_sriov_pf *pf = &i915->sriov.pf;
struct i915_sriov_kobj *home = pf->sysfs.home;
struct i915_sriov_ext_kobj **kobjs;
struct i915_sriov_ext_kobj *kobj;
unsigned int count = pf_nodes_count(i915);
unsigned int n;
int err;
err = i915_inject_probe_error(i915, -ENOMEM);
if (unlikely(err))
goto failed;
kobjs = kcalloc(count, sizeof(*kobjs), GFP_KERNEL);
if (unlikely(!kobjs)) {
err = -ENOMEM;
goto failed;
}
for (n = 0; n < count; n++) {
kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
if (!kobj) {
err = -ENOMEM;
goto failed_kobj_n;
}
kobj->id = n;
if (n) {
err = kobject_init_and_add(&kobj->base, &sriov_ext_ktype,
&home->base, SRIOV_EXT_KOBJ_VFn_NAME, n);
} else {
err = kobject_init_and_add(&kobj->base, &sriov_ext_ktype,
&home->base, SRIOV_EXT_KOBJ_PF_NAME);
}
if (unlikely(err))
goto failed_kobj_n;
err = i915_inject_probe_error(i915, -EEXIST);
if (unlikely(err))
goto failed_kobj_n;
kobjs[n] = kobj;
}
GEM_BUG_ON(pf->sysfs.kobjs);
pf->sysfs.kobjs = kobjs;
return 0;
failed_kobj_n:
if (kobj)
kobject_put(&kobj->base);
while (n--)
kobject_put(&kobjs[n]->base);
failed:
return pf_setup_failed(i915, err, "tree");
}
static void pf_teardown_tree(struct drm_i915_private *i915)
{
struct i915_sriov_pf *pf = &i915->sriov.pf;
struct i915_sriov_ext_kobj **kobjs = fetch_and_zero(&pf->sysfs.kobjs);
unsigned int count = pf_nodes_count(i915);
unsigned int n;
if (!kobjs)
return;
for (n = 0; n < count; n++)
kobject_put(&kobjs[n]->base);
kfree(kobjs);
}
static int pf_setup_device_link(struct drm_i915_private *i915)
{
struct i915_sriov_pf *pf = &i915->sriov.pf;
struct i915_sriov_ext_kobj **kobjs = pf->sysfs.kobjs;
int err;
err = i915_inject_probe_error(i915, -EEXIST);
if (unlikely(err))
goto failed;
err = sysfs_create_link(&kobjs[0]->base, &i915->drm.dev->kobj, SRIOV_DEVICE_LINK_NAME);
if (unlikely(err))
goto failed;
return 0;
failed:
return pf_setup_failed(i915, err, "link");
}
static void pf_teardown_device_link(struct drm_i915_private *i915)
{
struct i915_sriov_pf *pf = &i915->sriov.pf;
struct i915_sriov_ext_kobj **kobjs = pf->sysfs.kobjs;
sysfs_remove_link(&kobjs[0]->base, SRIOV_DEVICE_LINK_NAME);
}
static void pf_welcome(struct drm_i915_private *i915)
{
#if IS_ENABLED(CONFIG_DRM_I915_DEBUG)
struct i915_sriov_pf *pf = &i915->sriov.pf;
const char *path = kobject_get_path(&pf->sysfs.home->base, GFP_KERNEL);
drm_dbg(&i915->drm, "SR-IOV sysfs available at /sys%s\n", path);
kfree(path);
#endif
GEM_BUG_ON(!i915->sriov.pf.sysfs.kobjs);
}
static void pf_goodbye(struct drm_i915_private *i915)
{
GEM_WARN_ON(i915->sriov.pf.sysfs.kobjs);
GEM_WARN_ON(i915->sriov.pf.sysfs.home);
}
static bool pf_initialized(struct drm_i915_private *i915)
{
GEM_WARN_ON(i915->sriov.pf.sysfs.home && !i915->sriov.pf.sysfs.kobjs);
GEM_WARN_ON(!i915->sriov.pf.sysfs.home && i915->sriov.pf.sysfs.kobjs);
return i915->sriov.pf.sysfs.home;
}
/**
* i915_sriov_sysfs_setup - Setup SR-IOV sysfs tree.
* @i915: the i915 struct
*
* On SR-IOV PF this function will setup dedicated sysfs tree
* with PF and VFs attributes.
*
* Return: 0 on success or a negative error code on failure.
*/
int i915_sriov_sysfs_setup(struct drm_i915_private *i915)
{
int err;
if (!IS_SRIOV_PF(i915))
return 0;
if (i915_sriov_pf_aborted(i915))
return 0;
err = pf_setup_home(i915);
if (unlikely(err))
goto failed;
err = pf_setup_tree(i915);
if (unlikely(err))
goto failed_tree;
err = pf_setup_device_link(i915);
if (unlikely(err))
goto failed_link;
pf_welcome(i915);
return 0;
failed_link:
pf_teardown_tree(i915);
failed_tree:
pf_teardown_home(i915);
failed:
return pf_setup_failed(i915, err, "");
}
/**
* i915_sriov_sysfs_teardown - Cleanup SR-IOV sysfs tree.
* @i915: the i915 struct
*
* Cleanup data initialized by @i915_sriov_sysfs_setup.
*/
void i915_sriov_sysfs_teardown(struct drm_i915_private *i915)
{
if (!IS_SRIOV_PF(i915))
return;
if (!pf_initialized(i915))
return;
pf_teardown_device_link(i915);
pf_teardown_tree(i915);
pf_teardown_home(i915);
pf_goodbye(i915);
}
/* our Gen12 SR-IOV platforms are simple */
#define GEN12_VF_OFFSET 1
#define GEN12_VF_STRIDE 1
#define GEN12_VF_ROUTING_OFFSET(id) (GEN12_VF_OFFSET + ((id) - 1) * GEN12_VF_STRIDE)
static struct pci_dev *pf_get_vf_pci_dev(struct drm_i915_private *i915, unsigned int id)
{
struct pci_dev *pdev = to_pci_dev(i915->drm.dev);
u16 vf_devid = pci_dev_id(pdev) + GEN12_VF_ROUTING_OFFSET(id);
GEM_BUG_ON(!dev_is_pf(&pdev->dev));
GEM_BUG_ON(!id);
/* caller must use pci_dev_put() */
return pci_get_domain_bus_and_slot(pci_domain_nr(pdev->bus),
PCI_BUS_NUM(vf_devid),
PCI_DEVFN(PCI_SLOT(vf_devid),
PCI_FUNC(vf_devid)));
}
static int pf_add_vfs_device_links(struct drm_i915_private *i915)
{
struct i915_sriov_pf *pf = &i915->sriov.pf;
struct i915_sriov_ext_kobj **kobjs = pf->sysfs.kobjs;
struct pci_dev *pf_pdev = to_pci_dev(i915->drm.dev);
struct pci_dev *vf_pdev = NULL;
unsigned int numvfs = pci_num_vf(pf_pdev);
unsigned int n;
int err;
if (!kobjs)
return 0;
GEM_BUG_ON(numvfs > pf_nodes_count(i915));
for (n = 1; n <= numvfs; n++) {
err = i915_inject_probe_error(i915, -ENODEV);
if (unlikely(err)) {
vf_pdev = NULL;
goto failed_n;
}
vf_pdev = pf_get_vf_pci_dev(i915, n);
if (unlikely(!vf_pdev)) {
err = -ENODEV;
goto failed_n;
}
err = i915_inject_probe_error(i915, -EEXIST);
if (unlikely(err))
goto failed_n;
err = sysfs_create_link(&kobjs[n]->base, &vf_pdev->dev.kobj,
SRIOV_DEVICE_LINK_NAME);
if (unlikely(err))
goto failed_n;
/* balance pf_get_vf_pci_dev() */
pci_dev_put(vf_pdev);
}
return 0;
failed_n:
if (vf_pdev)
pci_dev_put(vf_pdev);
while (n-- > 1)
sysfs_remove_link(&kobjs[n]->base, SRIOV_DEVICE_LINK_NAME);
return pf_setup_failed(i915, err, "links");
}
static void pf_remove_vfs_device_links(struct drm_i915_private *i915)
{
struct i915_sriov_pf *pf = &i915->sriov.pf;
struct i915_sriov_ext_kobj **kobjs = pf->sysfs.kobjs;
struct pci_dev *pf_pdev = to_pci_dev(i915->drm.dev);
unsigned int numvfs = pci_num_vf(pf_pdev);
unsigned int n;
if (!kobjs)
return;
GEM_BUG_ON(numvfs > pf_nodes_count(i915));
for (n = 1; n <= numvfs; n++)
sysfs_remove_link(&kobjs[n]->base, SRIOV_DEVICE_LINK_NAME);
}
/**
* i915_sriov_sysfs_update_links - Update links in SR-IOV sysfs tree.
* @i915: the i915 struct
* @add: FIXME missing doc
*
* On PF this function will add or remove PCI device links from VFs.
*/
void i915_sriov_sysfs_update_links(struct drm_i915_private *i915, bool add)
{
if (!IS_SRIOV_PF(i915))
return;
if (add)
pf_add_vfs_device_links(i915);
else
pf_remove_vfs_device_links(i915);
}