mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
5.2 KiB
252 lines
5.2 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
// Copyright (c) 2018-2021 Intel Corporation |
|
|
|
#include <linux/bitfield.h> |
|
#include <linux/peci.h> |
|
#include <linux/peci-cpu.h> |
|
#include <linux/slab.h> |
|
|
|
#include "internal.h" |
|
|
|
/* |
|
* PECI device can be removed using sysfs, but the removal can also happen as |
|
* a result of controller being removed. |
|
* Mutex is used to protect PECI device from being double-deleted. |
|
*/ |
|
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) |
|
{ |
|
/* |
|
* PECI Ping is a command encoded by tx_len = 0, rx_len = 0. |
|
* We expect correct Write FCS if the device at the target address |
|
* is able to respond. |
|
*/ |
|
struct peci_request req = { 0 }; |
|
int ret; |
|
|
|
mutex_lock(&controller->bus_lock); |
|
ret = controller->ops->xfer(controller, addr, &req); |
|
mutex_unlock(&controller->bus_lock); |
|
|
|
return ret; |
|
} |
|
|
|
static bool peci_addr_valid(u8 addr) |
|
{ |
|
return addr >= PECI_BASE_ADDR && addr < PECI_BASE_ADDR + PECI_DEVICE_NUM_MAX; |
|
} |
|
|
|
static int peci_dev_exists(struct device *dev, void *data) |
|
{ |
|
struct peci_device *device = to_peci_device(dev); |
|
u8 *addr = data; |
|
|
|
if (device->addr == *addr) |
|
return -EBUSY; |
|
|
|
return 0; |
|
} |
|
|
|
int peci_device_create(struct peci_controller *controller, u8 addr) |
|
{ |
|
struct peci_device *device; |
|
int ret; |
|
|
|
if (!peci_addr_valid(addr)) |
|
return -EINVAL; |
|
|
|
/* Check if we have already detected this device before. */ |
|
ret = device_for_each_child(&controller->dev, &addr, peci_dev_exists); |
|
if (ret) |
|
return 0; |
|
|
|
ret = peci_detect(controller, addr); |
|
if (ret) { |
|
/* |
|
* Device not present or host state doesn't allow successful |
|
* detection at this time. |
|
*/ |
|
if (ret == -EIO || ret == -ETIMEDOUT) |
|
return 0; |
|
|
|
return ret; |
|
} |
|
|
|
device = kzalloc(sizeof(*device), GFP_KERNEL); |
|
if (!device) |
|
return -ENOMEM; |
|
|
|
device_initialize(&device->dev); |
|
|
|
device->addr = addr; |
|
device->dev.parent = &controller->dev; |
|
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; |
|
|
|
ret = device_add(&device->dev); |
|
if (ret) |
|
goto err_put; |
|
|
|
return 0; |
|
|
|
err_put: |
|
put_device(&device->dev); |
|
|
|
return ret; |
|
} |
|
|
|
void peci_device_destroy(struct peci_device *device) |
|
{ |
|
mutex_lock(&peci_device_del_lock); |
|
if (!device->deleted) { |
|
device_unregister(&device->dev); |
|
device->deleted = true; |
|
} |
|
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); |
|
|
|
kfree(device); |
|
} |
|
|
|
struct device_type peci_device_type = { |
|
.groups = peci_device_groups, |
|
.release = peci_device_release, |
|
};
|
|
|