root/drivers/peci/device.c
// 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.x86_vfm = IFM(peci_x86_cpu_family(cpu_id), 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_obj(*device);
        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);
}

const struct device_type peci_device_type = {
        .groups         = peci_device_groups,
        .release        = peci_device_release,
};