Skip to content
Permalink
Browse files
peci: Add support for PECI device drivers
Add support for PECI device drivers, which unlike PECI controller
drivers are actually able to provide functionalities to userspace.

Also, extend peci_request API to allow querying more details about PECI
device (e.g. model/family), that's going to be used to find a compatible
peci_driver.

Signed-off-by: Iwona Winiarska <iwona.winiarska@intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
  • Loading branch information
iklimasz authored and intel-lab-lkp committed Jan 12, 2022
1 parent af9ffb2 commit 0c9888d465568adc8526df1407c9a75be5ce6cd4
Show file tree
Hide file tree
Showing 5 changed files with 481 additions and 0 deletions.
@@ -160,8 +160,52 @@ struct peci_controller *devm_peci_controller_add(struct device *dev,
}
EXPORT_SYMBOL_NS_GPL(devm_peci_controller_add, PECI);

static const struct peci_device_id *
peci_bus_match_device_id(const struct peci_device_id *id, struct peci_device *device)
{
while (id->family != 0) {
if (id->family == device->info.family &&
id->model == device->info.model)
return id;
id++;
}

return NULL;
}

static int peci_bus_device_match(struct device *dev, struct device_driver *drv)
{
struct peci_device *device = to_peci_device(dev);
struct peci_driver *peci_drv = to_peci_driver(drv);

if (dev->type != &peci_device_type)
return 0;

return !!peci_bus_match_device_id(peci_drv->id_table, device);
}

static int peci_bus_device_probe(struct device *dev)
{
struct peci_device *device = to_peci_device(dev);
struct peci_driver *driver = to_peci_driver(dev->driver);

return driver->probe(device, peci_bus_match_device_id(driver->id_table, device));
}

static void peci_bus_device_remove(struct device *dev)
{
struct peci_device *device = to_peci_device(dev);
struct peci_driver *driver = to_peci_driver(dev->driver);

if (driver->remove)
driver->remove(device);
}

struct bus_type peci_bus_type = {
.name = "peci",
.match = peci_bus_device_match,
.probe = peci_bus_device_probe,
.remove = peci_bus_device_remove,
.bus_groups = peci_bus_groups,
};

@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2018-2021 Intel Corporation

#include <linux/bitfield.h>
#include <linux/peci.h>
#include <linux/slab.h>

@@ -13,6 +14,104 @@
*/
static DEFINE_MUTEX(peci_device_del_lock);

#define REVISION_NUM_MASK GENMASK(15, 8)
static int peci_get_revision(struct peci_device *device, u8 *revision)
{
struct peci_request *req;
u64 dib;

req = peci_xfer_get_dib(device);
if (IS_ERR(req))
return PTR_ERR(req);

/*
* PECI device may be in a state where it is unable to return a proper
* DIB, in which case it returns 0 as DIB value.
* Let's treat this as an error to avoid carrying on with the detection
* using invalid revision.
*/
dib = peci_request_dib_read(req);
if (dib == 0) {
peci_request_free(req);
return -EIO;
}

*revision = FIELD_GET(REVISION_NUM_MASK, dib);

peci_request_free(req);

return 0;
}

static int peci_get_cpu_id(struct peci_device *device, u32 *cpu_id)
{
struct peci_request *req;
int ret;

req = peci_xfer_pkg_cfg_readl(device, PECI_PCS_PKG_ID, PECI_PKG_ID_CPU_ID);
if (IS_ERR(req))
return PTR_ERR(req);

ret = peci_request_status(req);
if (ret)
goto out_req_free;

*cpu_id = peci_request_data_readl(req);
out_req_free:
peci_request_free(req);

return ret;
}

static unsigned int peci_x86_cpu_family(unsigned int sig)
{
unsigned int x86;

x86 = (sig >> 8) & 0xf;

if (x86 == 0xf)
x86 += (sig >> 20) & 0xff;

return x86;
}

static unsigned int peci_x86_cpu_model(unsigned int sig)
{
unsigned int fam, model;

fam = peci_x86_cpu_family(sig);

model = (sig >> 4) & 0xf;

if (fam >= 0x6)
model += ((sig >> 16) & 0xf) << 4;

return model;
}

static int peci_device_info_init(struct peci_device *device)
{
u8 revision;
u32 cpu_id;
int ret;

ret = peci_get_cpu_id(device, &cpu_id);
if (ret)
return ret;

device->info.family = peci_x86_cpu_family(cpu_id);
device->info.model = peci_x86_cpu_model(cpu_id);

ret = peci_get_revision(device, &revision);
if (ret)
return ret;
device->info.peci_revision = revision;

device->info.socket_id = device->addr - PECI_BASE_ADDR;

return 0;
}

static int peci_detect(struct peci_controller *controller, u8 addr)
{
/*
@@ -82,6 +181,10 @@ int peci_device_create(struct peci_controller *controller, u8 addr)
device->dev.bus = &peci_bus_type;
device->dev.type = &peci_device_type;

ret = peci_device_info_init(device);
if (ret)
goto err_put;

ret = dev_set_name(&device->dev, "%d-%02x", controller->id, device->addr);
if (ret)
goto err_put;
@@ -108,6 +211,33 @@ void peci_device_destroy(struct peci_device *device)
mutex_unlock(&peci_device_del_lock);
}

int __peci_driver_register(struct peci_driver *driver, struct module *owner,
const char *mod_name)
{
driver->driver.bus = &peci_bus_type;
driver->driver.owner = owner;
driver->driver.mod_name = mod_name;

if (!driver->probe) {
pr_err("peci: trying to register driver without probe callback\n");
return -EINVAL;
}

if (!driver->id_table) {
pr_err("peci: trying to register driver without device id table\n");
return -EINVAL;
}

return driver_register(&driver->driver);
}
EXPORT_SYMBOL_NS_GPL(__peci_driver_register, PECI);

void peci_driver_unregister(struct peci_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL_NS_GPL(peci_driver_unregister, PECI);

static void peci_device_release(struct device *dev)
{
struct peci_device *device = to_peci_device(dev);
@@ -19,6 +19,35 @@ struct peci_request;
struct peci_request *peci_request_alloc(struct peci_device *device, u8 tx_len, u8 rx_len);
void peci_request_free(struct peci_request *req);

int peci_request_status(struct peci_request *req);

u64 peci_request_dib_read(struct peci_request *req);

u8 peci_request_data_readb(struct peci_request *req);
u16 peci_request_data_readw(struct peci_request *req);
u32 peci_request_data_readl(struct peci_request *req);
u64 peci_request_data_readq(struct peci_request *req);

struct peci_request *peci_xfer_get_dib(struct peci_device *device);
struct peci_request *peci_xfer_get_temp(struct peci_device *device);

struct peci_request *peci_xfer_pkg_cfg_readb(struct peci_device *device, u8 index, u16 param);
struct peci_request *peci_xfer_pkg_cfg_readw(struct peci_device *device, u8 index, u16 param);
struct peci_request *peci_xfer_pkg_cfg_readl(struct peci_device *device, u8 index, u16 param);
struct peci_request *peci_xfer_pkg_cfg_readq(struct peci_device *device, u8 index, u16 param);

/**
* struct peci_device_id - PECI device data to match
* @data: pointer to driver private data specific to device
* @family: device family
* @model: device model
*/
struct peci_device_id {
const void *data;
u16 family;
u8 model;
};

extern struct device_type peci_device_type;
extern const struct attribute_group *peci_device_groups[];

@@ -28,6 +57,51 @@ void peci_device_destroy(struct peci_device *device);
extern struct bus_type peci_bus_type;
extern const struct attribute_group *peci_bus_groups[];

/**
* struct peci_driver - PECI driver
* @driver: inherit device driver
* @probe: probe callback
* @remove: remove callback
* @id_table: PECI device match table to decide which device to bind
*/
struct peci_driver {
struct device_driver driver;
int (*probe)(struct peci_device *device, const struct peci_device_id *id);
void (*remove)(struct peci_device *device);
const struct peci_device_id *id_table;
};

static inline struct peci_driver *to_peci_driver(struct device_driver *d)
{
return container_of(d, struct peci_driver, driver);
}

int __peci_driver_register(struct peci_driver *driver, struct module *owner,
const char *mod_name);
/**
* peci_driver_register() - register PECI driver
* @driver: the driver to be registered
*
* PECI drivers that don't need to do anything special in module init should
* use the convenience "module_peci_driver" macro instead
*
* Return: zero on success, else a negative error code.
*/
#define peci_driver_register(driver) \
__peci_driver_register(driver, THIS_MODULE, KBUILD_MODNAME)
void peci_driver_unregister(struct peci_driver *driver);

/**
* module_peci_driver() - helper macro for registering a modular PECI driver
* @__peci_driver: peci_driver struct
*
* Helper macro for PECI drivers which do not do anything special in module
* init/exit. This eliminates a lot of boilerplate. Each module may only
* use this macro once, and calling it replaces module_init() and module_exit()
*/
#define module_peci_driver(__peci_driver) \
module_driver(__peci_driver, peci_driver_register, peci_driver_unregister)

extern struct device_type peci_controller_type;

int peci_controller_scan_devices(struct peci_controller *controller);

0 comments on commit 0c9888d

Please sign in to comment.