#define dev_fmt(fmt) "PCI/TSM: " fmt
#include <linux/bitfield.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
#include <linux/pci-tsm.h>
#include <linux/sysfs.h>
#include <linux/tsm.h>
#include <linux/xarray.h>
#include "pci.h"
static DECLARE_RWSEM(pci_tsm_rwsem);
static int pci_tsm_link_count;
static int pci_tsm_devsec_count;
static const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm)
{
return tsm->tsm_dev->pci_ops;
}
static inline bool is_dsm(struct pci_dev *pdev)
{
return pdev->tsm && pdev->tsm->dsm_dev == pdev;
}
static inline bool has_tee(struct pci_dev *pdev)
{
return pdev->devcap & PCI_EXP_DEVCAP_TEE;
}
static struct pci_tsm_pf0 *to_pci_tsm_pf0(struct pci_tsm *tsm)
{
struct pci_dev *pf0 = tsm->dsm_dev;
if (!is_pci_tsm_pf0(pf0) || !is_dsm(pf0)) {
pci_WARN_ONCE(tsm->pdev, 1, "invalid context object\n");
return NULL;
}
return container_of(pf0->tsm, struct pci_tsm_pf0, base_tsm);
}
static void tsm_remove(struct pci_tsm *tsm)
{
struct pci_dev *pdev;
if (!tsm)
return;
pdev = tsm->pdev;
to_pci_tsm_ops(tsm)->remove(tsm);
pdev->tsm = NULL;
}
DEFINE_FREE(tsm_remove, struct pci_tsm *, if (_T) tsm_remove(_T))
static void pci_tsm_walk_fns(struct pci_dev *pdev,
int (*cb)(struct pci_dev *pdev, void *data),
void *data)
{
for (int i = 0; i < 8; i++) {
struct pci_dev *pf __free(pci_dev_put) = pci_get_slot(
pdev->bus, PCI_DEVFN(PCI_SLOT(pdev->devfn), i));
if (!pf)
continue;
if (i > 0)
cb(pf, data);
for (int j = 0; j < pci_num_vf(pf); j++) {
struct pci_dev *vf __free(pci_dev_put) =
pci_get_domain_bus_and_slot(
pci_domain_nr(pf->bus),
pci_iov_virtfn_bus(pf, j),
pci_iov_virtfn_devfn(pf, j));
if (!vf)
continue;
cb(vf, data);
}
}
if (pci_pcie_type(pdev) == PCI_EXP_TYPE_UPSTREAM && is_dsm(pdev))
pci_walk_bus(pdev->subordinate, cb, data);
}
static void pci_tsm_walk_fns_reverse(struct pci_dev *pdev,
int (*cb)(struct pci_dev *pdev,
void *data),
void *data)
{
if (pci_pcie_type(pdev) == PCI_EXP_TYPE_UPSTREAM && is_dsm(pdev))
pci_walk_bus_reverse(pdev->subordinate, cb, data);
for (int i = 7; i >= 0; i--) {
struct pci_dev *pf __free(pci_dev_put) = pci_get_slot(
pdev->bus, PCI_DEVFN(PCI_SLOT(pdev->devfn), i));
if (!pf)
continue;
for (int j = pci_num_vf(pf) - 1; j >= 0; j--) {
struct pci_dev *vf __free(pci_dev_put) =
pci_get_domain_bus_and_slot(
pci_domain_nr(pf->bus),
pci_iov_virtfn_bus(pf, j),
pci_iov_virtfn_devfn(pf, j));
if (!vf)
continue;
cb(vf, data);
}
if (i > 0)
cb(pf, data);
}
}
static void link_sysfs_disable(struct pci_dev *pdev)
{
sysfs_update_group(&pdev->dev.kobj, &pci_tsm_auth_attr_group);
sysfs_update_group(&pdev->dev.kobj, &pci_tsm_attr_group);
}
static void link_sysfs_enable(struct pci_dev *pdev)
{
bool tee = has_tee(pdev);
pci_dbg(pdev, "%s Security Manager detected (%s%s%s)\n",
pdev->tsm ? "Device" : "Platform TEE",
pdev->ide_cap ? "IDE" : "", pdev->ide_cap && tee ? " " : "",
tee ? "TEE" : "");
sysfs_update_group(&pdev->dev.kobj, &pci_tsm_auth_attr_group);
sysfs_update_group(&pdev->dev.kobj, &pci_tsm_attr_group);
}
static int probe_fn(struct pci_dev *pdev, void *dsm)
{
struct pci_dev *dsm_dev = dsm;
const struct pci_tsm_ops *ops = to_pci_tsm_ops(dsm_dev->tsm);
pdev->tsm = ops->probe(dsm_dev->tsm->tsm_dev, pdev);
pci_dbg(pdev, "setup TSM context: DSM: %s status: %s\n",
pci_name(dsm_dev), pdev->tsm ? "success" : "failed");
if (pdev->tsm)
link_sysfs_enable(pdev);
return 0;
}
static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
{
int rc;
struct pci_tsm_pf0 *tsm_pf0;
const struct pci_tsm_ops *ops = tsm_dev->pci_ops;
struct pci_tsm *pci_tsm __free(tsm_remove) = ops->probe(tsm_dev, pdev);
lockdep_assert_held_write(&pci_tsm_rwsem);
if (!pci_tsm)
return -ENXIO;
pdev->tsm = pci_tsm;
tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
ACQUIRE(mutex_intr, lock)(&tsm_pf0->lock);
if ((rc = ACQUIRE_ERR(mutex_intr, &lock)))
return rc;
rc = ops->connect(pdev);
if (rc)
return rc;
pdev->tsm = no_free_ptr(pci_tsm);
if (has_tee(pdev))
pci_tsm_walk_fns(pdev, probe_fn, pdev);
return 0;
}
static ssize_t connect_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct tsm_dev *tsm_dev;
int rc;
ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
return rc;
if (!pdev->tsm)
return sysfs_emit(buf, "\n");
tsm_dev = pdev->tsm->tsm_dev;
return sysfs_emit(buf, "%s\n", dev_name(&tsm_dev->dev));
}
static bool is_link_tsm(struct tsm_dev *tsm_dev)
{
return tsm_dev && tsm_dev->pci_ops && tsm_dev->pci_ops->link_ops.probe;
}
static bool is_devsec_tsm(struct tsm_dev *tsm_dev)
{
return tsm_dev && tsm_dev->pci_ops && tsm_dev->pci_ops->devsec_ops.lock;
}
static ssize_t connect_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t len)
{
struct pci_dev *pdev = to_pci_dev(dev);
int rc, id;
rc = sscanf(buf, "tsm%d\n", &id);
if (rc != 1)
return -EINVAL;
ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem);
if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock)))
return rc;
if (pdev->tsm)
return -EBUSY;
struct tsm_dev *tsm_dev __free(put_tsm_dev) = find_tsm_dev(id);
if (!is_link_tsm(tsm_dev))
return -ENXIO;
rc = pci_tsm_connect(pdev, tsm_dev);
if (rc)
return rc;
return len;
}
static DEVICE_ATTR_RW(connect);
static int remove_fn(struct pci_dev *pdev, void *data)
{
tsm_remove(pdev->tsm);
link_sysfs_disable(pdev);
return 0;
}
static int __pci_tsm_unbind(struct pci_dev *pdev, void *data)
{
struct pci_tdi *tdi;
struct pci_tsm_pf0 *tsm_pf0;
lockdep_assert_held(&pci_tsm_rwsem);
if (!pdev->tsm)
return 0;
tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
guard(mutex)(&tsm_pf0->lock);
tdi = pdev->tsm->tdi;
if (!tdi)
return 0;
to_pci_tsm_ops(pdev->tsm)->unbind(tdi);
pdev->tsm->tdi = NULL;
return 0;
}
void pci_tsm_unbind(struct pci_dev *pdev)
{
guard(rwsem_read)(&pci_tsm_rwsem);
__pci_tsm_unbind(pdev, NULL);
}
EXPORT_SYMBOL_GPL(pci_tsm_unbind);
int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id)
{
struct pci_tsm_pf0 *tsm_pf0;
struct pci_tdi *tdi;
if (!kvm)
return -EINVAL;
guard(rwsem_read)(&pci_tsm_rwsem);
if (!pdev->tsm)
return -EINVAL;
if (!is_link_tsm(pdev->tsm->tsm_dev))
return -ENXIO;
tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
guard(mutex)(&tsm_pf0->lock);
if (pdev->tsm->tdi) {
if (pdev->tsm->tdi->kvm != kvm)
return -EBUSY;
return 0;
}
tdi = to_pci_tsm_ops(pdev->tsm)->bind(pdev, kvm, tdi_id);
if (IS_ERR(tdi))
return PTR_ERR(tdi);
pdev->tsm->tdi = tdi;
return 0;
}
EXPORT_SYMBOL_GPL(pci_tsm_bind);
ssize_t pci_tsm_guest_req(struct pci_dev *pdev, enum pci_tsm_req_scope scope,
sockptr_t req_in, size_t in_len, sockptr_t req_out,
size_t out_len, u64 *tsm_code)
{
struct pci_tsm_pf0 *tsm_pf0;
struct pci_tdi *tdi;
int rc;
if (scope > PCI_TSM_REQ_STATE_CHANGE)
return -EINVAL;
ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
return rc;
if (!pdev->tsm)
return -ENXIO;
if (!is_link_tsm(pdev->tsm->tsm_dev))
return -ENXIO;
tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
ACQUIRE(mutex_intr, ops_lock)(&tsm_pf0->lock);
if ((rc = ACQUIRE_ERR(mutex_intr, &ops_lock)))
return rc;
tdi = pdev->tsm->tdi;
if (!tdi)
return -ENXIO;
return to_pci_tsm_ops(pdev->tsm)->guest_req(tdi, scope, req_in, in_len,
req_out, out_len, tsm_code);
}
EXPORT_SYMBOL_GPL(pci_tsm_guest_req);
static void pci_tsm_unbind_all(struct pci_dev *pdev)
{
pci_tsm_walk_fns_reverse(pdev, __pci_tsm_unbind, NULL);
__pci_tsm_unbind(pdev, NULL);
}
static void __pci_tsm_disconnect(struct pci_dev *pdev)
{
struct pci_tsm_pf0 *tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
const struct pci_tsm_ops *ops = to_pci_tsm_ops(pdev->tsm);
lockdep_assert_held_write(&pci_tsm_rwsem);
pci_tsm_unbind_all(pdev);
guard(mutex)(&tsm_pf0->lock);
pci_tsm_walk_fns_reverse(pdev, remove_fn, NULL);
ops->disconnect(pdev);
}
static void pci_tsm_disconnect(struct pci_dev *pdev)
{
__pci_tsm_disconnect(pdev);
tsm_remove(pdev->tsm);
}
static ssize_t disconnect_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t len)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct tsm_dev *tsm_dev;
int rc;
ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem);
if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock)))
return rc;
if (!pdev->tsm)
return -ENXIO;
tsm_dev = pdev->tsm->tsm_dev;
if (!sysfs_streq(buf, dev_name(&tsm_dev->dev)))
return -EINVAL;
pci_tsm_disconnect(pdev);
return len;
}
static DEVICE_ATTR_WO(disconnect);
static ssize_t bound_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pci_tsm_pf0 *tsm_pf0;
struct pci_tsm *tsm;
int rc;
ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
return rc;
tsm = pdev->tsm;
if (!tsm)
return sysfs_emit(buf, "\n");
tsm_pf0 = to_pci_tsm_pf0(tsm);
ACQUIRE(mutex_intr, ops_lock)(&tsm_pf0->lock);
if ((rc = ACQUIRE_ERR(mutex_intr, &ops_lock)))
return rc;
if (!tsm->tdi)
return sysfs_emit(buf, "\n");
return sysfs_emit(buf, "%s\n", dev_name(&tsm->tsm_dev->dev));
}
static DEVICE_ATTR_RO(bound);
static ssize_t dsm_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct pci_tsm *tsm;
int rc;
ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
return rc;
tsm = pdev->tsm;
if (!tsm)
return sysfs_emit(buf, "\n");
return sysfs_emit(buf, "%s\n", pci_name(tsm->dsm_dev));
}
static DEVICE_ATTR_RO(dsm);
static bool pci_tsm_link_group_visible(struct kobject *kobj)
{
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
if (!pci_tsm_link_count)
return false;
if (!pci_is_pcie(pdev))
return false;
if (is_pci_tsm_pf0(pdev))
return true;
if (pdev->tsm)
return true;
return false;
}
DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(pci_tsm_link);
static umode_t pci_tsm_attr_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
if (pci_tsm_link_group_visible(kobj)) {
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
if (attr == &dev_attr_bound.attr) {
if (is_pci_tsm_pf0(pdev) && has_tee(pdev))
return attr->mode;
if (pdev->tsm && has_tee(pdev->tsm->dsm_dev))
return attr->mode;
}
if (attr == &dev_attr_dsm.attr) {
if (is_pci_tsm_pf0(pdev))
return attr->mode;
if (pdev->tsm && has_tee(pdev->tsm->dsm_dev))
return attr->mode;
}
if (attr == &dev_attr_connect.attr ||
attr == &dev_attr_disconnect.attr) {
if (is_pci_tsm_pf0(pdev))
return attr->mode;
}
}
return 0;
}
static bool pci_tsm_group_visible(struct kobject *kobj)
{
return pci_tsm_link_group_visible(kobj);
}
DEFINE_SYSFS_GROUP_VISIBLE(pci_tsm);
static struct attribute *pci_tsm_attrs[] = {
&dev_attr_connect.attr,
&dev_attr_disconnect.attr,
&dev_attr_bound.attr,
&dev_attr_dsm.attr,
NULL
};
const struct attribute_group pci_tsm_attr_group = {
.name = "tsm",
.attrs = pci_tsm_attrs,
.is_visible = SYSFS_GROUP_VISIBLE(pci_tsm),
};
static ssize_t authenticated_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return connect_show(dev, attr, buf);
}
static DEVICE_ATTR_RO(authenticated);
static struct attribute *pci_tsm_auth_attrs[] = {
&dev_attr_authenticated.attr,
NULL
};
const struct attribute_group pci_tsm_auth_attr_group = {
.attrs = pci_tsm_auth_attrs,
.is_visible = SYSFS_GROUP_VISIBLE(pci_tsm_link),
};
static struct pci_dev *pf0_dev_get(struct pci_dev *pdev)
{
struct pci_dev *pf_dev = pci_physfn(pdev);
if (PCI_FUNC(pf_dev->devfn) == 0)
return pci_dev_get(pf_dev);
return pci_get_slot(pf_dev->bus,
pf_dev->devfn - PCI_FUNC(pf_dev->devfn));
}
static struct pci_dev *find_dsm_dev(struct pci_dev *pdev)
{
struct device *grandparent;
struct pci_dev *uport;
if (is_pci_tsm_pf0(pdev))
return pdev;
struct pci_dev *pf0 __free(pci_dev_put) = pf0_dev_get(pdev);
if (!pf0)
return NULL;
if (is_dsm(pf0))
return pf0;
if (!pdev->dev.parent)
return NULL;
grandparent = pdev->dev.parent->parent;
if (!grandparent)
return NULL;
if (!dev_is_pci(grandparent))
return NULL;
uport = to_pci_dev(grandparent);
if (!pci_is_pcie(uport) ||
pci_pcie_type(uport) != PCI_EXP_TYPE_UPSTREAM)
return NULL;
if (is_dsm(uport))
return uport;
return NULL;
}
void pci_tsm_tdi_constructor(struct pci_dev *pdev, struct pci_tdi *tdi,
struct kvm *kvm, u32 tdi_id)
{
tdi->pdev = pdev;
tdi->kvm = kvm;
tdi->tdi_id = tdi_id;
}
EXPORT_SYMBOL_GPL(pci_tsm_tdi_constructor);
int pci_tsm_link_constructor(struct pci_dev *pdev, struct pci_tsm *tsm,
struct tsm_dev *tsm_dev)
{
if (!is_link_tsm(tsm_dev))
return -EINVAL;
tsm->dsm_dev = find_dsm_dev(pdev);
if (!tsm->dsm_dev) {
pci_warn(pdev, "failed to find Device Security Manager\n");
return -ENXIO;
}
tsm->pdev = pdev;
tsm->tsm_dev = tsm_dev;
return 0;
}
EXPORT_SYMBOL_GPL(pci_tsm_link_constructor);
int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm,
struct tsm_dev *tsm_dev)
{
mutex_init(&tsm->lock);
tsm->doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
PCI_DOE_FEATURE_CMA);
if (!tsm->doe_mb) {
pci_warn(pdev, "TSM init failure, no CMA mailbox\n");
return -ENODEV;
}
return pci_tsm_link_constructor(pdev, &tsm->base_tsm, tsm_dev);
}
EXPORT_SYMBOL_GPL(pci_tsm_pf0_constructor);
void pci_tsm_pf0_destructor(struct pci_tsm_pf0 *pf0_tsm)
{
mutex_destroy(&pf0_tsm->lock);
}
EXPORT_SYMBOL_GPL(pci_tsm_pf0_destructor);
int pci_tsm_register(struct tsm_dev *tsm_dev)
{
struct pci_dev *pdev = NULL;
if (!tsm_dev)
return -EINVAL;
if (!is_link_tsm(tsm_dev) && !is_devsec_tsm(tsm_dev))
return -EINVAL;
if (is_link_tsm(tsm_dev) && is_devsec_tsm(tsm_dev))
return -EINVAL;
guard(rwsem_write)(&pci_tsm_rwsem);
if (is_link_tsm(tsm_dev) && pci_tsm_link_count++ == 0) {
for_each_pci_dev(pdev)
if (is_pci_tsm_pf0(pdev))
link_sysfs_enable(pdev);
} else if (is_devsec_tsm(tsm_dev)) {
pci_tsm_devsec_count++;
}
return 0;
}
static void pci_tsm_fn_exit(struct pci_dev *pdev)
{
__pci_tsm_unbind(pdev, NULL);
tsm_remove(pdev->tsm);
}
static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
{
struct pci_tsm *tsm = pdev->tsm;
lockdep_assert_held_write(&pci_tsm_rwsem);
if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev) && !pci_tsm_link_count)
link_sysfs_disable(pdev);
if (!tsm)
return;
if (!tsm_dev)
tsm_dev = tsm->tsm_dev;
else if (tsm_dev != tsm->tsm_dev)
return;
if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev))
pci_tsm_disconnect(pdev);
else
pci_tsm_fn_exit(pdev);
}
void pci_tsm_destroy(struct pci_dev *pdev)
{
guard(rwsem_write)(&pci_tsm_rwsem);
__pci_tsm_destroy(pdev, NULL);
}
void pci_tsm_init(struct pci_dev *pdev)
{
guard(rwsem_read)(&pci_tsm_rwsem);
if (pdev->tsm)
return;
if (pci_tsm_link_count) {
struct pci_dev *dsm = find_dsm_dev(pdev);
if (!dsm)
return;
if (!dsm->tsm)
return;
probe_fn(pdev, dsm);
}
}
void pci_tsm_unregister(struct tsm_dev *tsm_dev)
{
struct pci_dev *pdev = NULL;
guard(rwsem_write)(&pci_tsm_rwsem);
if (is_link_tsm(tsm_dev))
pci_tsm_link_count--;
if (is_devsec_tsm(tsm_dev))
pci_tsm_devsec_count--;
for_each_pci_dev_reverse(pdev)
__pci_tsm_destroy(pdev, tsm_dev);
}
int pci_tsm_doe_transfer(struct pci_dev *pdev, u8 type, const void *req,
size_t req_sz, void *resp, size_t resp_sz)
{
struct pci_tsm_pf0 *tsm;
if (!pdev->tsm || !is_pci_tsm_pf0(pdev))
return -ENXIO;
tsm = to_pci_tsm_pf0(pdev->tsm);
if (!tsm->doe_mb)
return -ENXIO;
return pci_doe(tsm->doe_mb, PCI_VENDOR_ID_PCI_SIG, type, req, req_sz,
resp, resp_sz);
}
EXPORT_SYMBOL_GPL(pci_tsm_doe_transfer);