| // SPDX-License-Identifier: GPL-2.0 |
| // Copyright (c) 2018 Intel Corporation |
| |
| #include <linux/bitfield.h> |
| #include <linux/crc8.h> |
| #include <linux/delay.h> |
| #include <linux/fs.h> |
| #include <linux/module.h> |
| #include <linux/of_device.h> |
| #include <linux/peci.h> |
| #include <linux/pm_domain.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| |
| /* Mask for getting minor revision number from DIB */ |
| #define REVISION_NUM_MASK GENMASK(15, 8) |
| |
| /* CRC8 table for Assure Write Frame Check */ |
| #define PECI_CRC8_POLYNOMIAL 0x07 |
| DECLARE_CRC8_TABLE(peci_crc8_table); |
| |
| static struct device_type peci_adapter_type; |
| static struct device_type peci_client_type; |
| |
| /* Max number of peci cdev */ |
| #define PECI_CDEV_MAX 16 |
| |
| static dev_t peci_devt; |
| static bool is_registered; |
| |
| static DEFINE_MUTEX(core_lock); |
| static DEFINE_IDR(peci_adapter_idr); |
| |
| static struct peci_adapter *peci_get_adapter(int nr) |
| { |
| struct peci_adapter *adapter; |
| |
| mutex_lock(&core_lock); |
| adapter = idr_find(&peci_adapter_idr, nr); |
| if (!adapter) |
| goto out_unlock; |
| |
| if (try_module_get(adapter->owner)) |
| get_device(&adapter->dev); |
| else |
| adapter = NULL; |
| |
| out_unlock: |
| mutex_unlock(&core_lock); |
| return adapter; |
| } |
| |
| static void peci_put_adapter(struct peci_adapter *adapter) |
| { |
| if (!adapter) |
| return; |
| |
| put_device(&adapter->dev); |
| module_put(adapter->owner); |
| } |
| |
| static ssize_t name_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| return sprintf(buf, "%s\n", dev->type == &peci_client_type ? |
| to_peci_client(dev)->name : to_peci_adapter(dev)->name); |
| } |
| static DEVICE_ATTR_RO(name); |
| |
| static void peci_client_dev_release(struct device *dev) |
| { |
| struct peci_client *client = to_peci_client(dev); |
| |
| dev_dbg(dev, "%s: %s\n", __func__, client->name); |
| peci_put_adapter(client->adapter); |
| kfree(client); |
| } |
| |
| static struct attribute *peci_device_attrs[] = { |
| &dev_attr_name.attr, |
| NULL |
| }; |
| ATTRIBUTE_GROUPS(peci_device); |
| |
| static struct device_type peci_client_type = { |
| .groups = peci_device_groups, |
| .release = peci_client_dev_release, |
| }; |
| |
| /** |
| * peci_verify_client - return parameter as peci_client, or NULL |
| * @dev: device, probably from some driver model iterator |
| * |
| * Return: pointer to peci_client on success, else NULL. |
| */ |
| struct peci_client *peci_verify_client(struct device *dev) |
| { |
| return (dev->type == &peci_client_type) |
| ? to_peci_client(dev) |
| : NULL; |
| } |
| EXPORT_SYMBOL_GPL(peci_verify_client); |
| |
| static u8 peci_aw_fcs(u8 *data, int len) |
| { |
| return crc8(peci_crc8_table, data, (size_t)len, 0); |
| } |
| |
| static int __peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg, |
| bool do_retry, bool has_aw_fcs) |
| { |
| ktime_t start, end; |
| s64 elapsed_ms; |
| int rc = 0; |
| |
| /** |
| * For some commands, the PECI originator may need to retry a command if |
| * the processor PECI client responds with a 0x8x completion code. In |
| * each instance, the processor PECI client may have started the |
| * operation but not completed it yet. When the 'retry' bit is set, the |
| * PECI client will ignore a new request if it exactly matches a |
| * previous valid request. |
| */ |
| |
| if (do_retry) |
| start = ktime_get(); |
| |
| do { |
| rc = adapter->xfer(adapter, msg); |
| |
| if (!do_retry || rc) |
| break; |
| |
| if (msg->rx_buf[0] == DEV_PECI_CC_SUCCESS) |
| break; |
| |
| /* Retry is needed when completion code is 0x8x */ |
| if ((msg->rx_buf[0] & DEV_PECI_CC_RETRY_CHECK_MASK) != |
| DEV_PECI_CC_NEED_RETRY) { |
| rc = -EIO; |
| break; |
| } |
| |
| /* Set the retry bit to indicate a retry attempt */ |
| msg->tx_buf[1] |= DEV_PECI_RETRY_BIT; |
| |
| /* Recalculate the AW FCS if it has one */ |
| if (has_aw_fcs) |
| msg->tx_buf[msg->tx_len - 1] = 0x80 ^ |
| peci_aw_fcs((u8 *)msg, |
| 2 + msg->tx_len); |
| |
| /** |
| * Retry for at least 250ms before returning an error. |
| * Retry interval guideline: |
| * No minimum < Retry Interval < No maximum |
| * (recommend 10ms) |
| */ |
| end = ktime_get(); |
| elapsed_ms = ktime_to_ms(ktime_sub(end, start)); |
| if (elapsed_ms >= DEV_PECI_RETRY_TIME_MS) { |
| dev_dbg(&adapter->dev, "Timeout retrying xfer!\n"); |
| rc = -ETIMEDOUT; |
| break; |
| } |
| |
| usleep_range((DEV_PECI_RETRY_INTERVAL_USEC >> 2) + 1, |
| DEV_PECI_RETRY_INTERVAL_USEC); |
| } while (true); |
| |
| if (rc) |
| dev_dbg(&adapter->dev, "xfer error, rc: %d\n", rc); |
| |
| return rc; |
| } |
| |
| static int peci_xfer(struct peci_adapter *adapter, struct peci_xfer_msg *msg) |
| { |
| return __peci_xfer(adapter, msg, false, false); |
| } |
| |
| static int peci_xfer_with_retries(struct peci_adapter *adapter, |
| struct peci_xfer_msg *msg, |
| bool has_aw_fcs) |
| { |
| return __peci_xfer(adapter, msg, true, has_aw_fcs); |
| } |
| |
| static int peci_scan_cmd_mask(struct peci_adapter *adapter) |
| { |
| struct peci_xfer_msg msg; |
| u8 revision; |
| int rc = 0; |
| u64 dib; |
| |
| /* Update command mask just once */ |
| if (adapter->cmd_mask & BIT(PECI_CMD_XFER)) |
| return 0; |
| |
| msg.addr = PECI_BASE_ADDR; |
| msg.tx_len = GET_DIB_WR_LEN; |
| msg.rx_len = GET_DIB_RD_LEN; |
| msg.tx_buf[0] = GET_DIB_PECI_CMD; |
| |
| rc = peci_xfer(adapter, &msg); |
| if (rc) |
| return rc; |
| |
| dib = le64_to_cpup((__le64 *)msg.rx_buf); |
| |
| /* Check special case for Get DIB command */ |
| if (dib == 0) { |
| dev_dbg(&adapter->dev, "DIB read as 0\n"); |
| return -EIO; |
| } |
| |
| /** |
| * Setting up the supporting commands based on minor revision number. |
| * See PECI Spec Table 3-1. |
| */ |
| revision = FIELD_GET(REVISION_NUM_MASK, dib); |
| if (revision >= 0x36) /* Rev. 3.6 */ |
| adapter->cmd_mask |= BIT(PECI_CMD_WR_IA_MSR); |
| if (revision >= 0x35) /* Rev. 3.5 */ |
| adapter->cmd_mask |= BIT(PECI_CMD_WR_PCI_CFG); |
| if (revision >= 0x34) /* Rev. 3.4 */ |
| adapter->cmd_mask |= BIT(PECI_CMD_RD_PCI_CFG); |
| if (revision >= 0x33) { /* Rev. 3.3 */ |
| adapter->cmd_mask |= BIT(PECI_CMD_RD_PCI_CFG_LOCAL); |
| adapter->cmd_mask |= BIT(PECI_CMD_WR_PCI_CFG_LOCAL); |
| } |
| if (revision >= 0x32) /* Rev. 3.2 */ |
| adapter->cmd_mask |= BIT(PECI_CMD_RD_IA_MSR); |
| if (revision >= 0x31) { /* Rev. 3.1 */ |
| adapter->cmd_mask |= BIT(PECI_CMD_RD_PKG_CFG); |
| adapter->cmd_mask |= BIT(PECI_CMD_WR_PKG_CFG); |
| } |
| |
| adapter->cmd_mask |= BIT(PECI_CMD_XFER); |
| adapter->cmd_mask |= BIT(PECI_CMD_GET_TEMP); |
| adapter->cmd_mask |= BIT(PECI_CMD_GET_DIB); |
| adapter->cmd_mask |= BIT(PECI_CMD_PING); |
| |
| return rc; |
| } |
| |
| static int peci_cmd_support(struct peci_adapter *adapter, enum peci_cmd cmd) |
| { |
| if (!(adapter->cmd_mask & BIT(PECI_CMD_PING)) && |
| peci_scan_cmd_mask(adapter) < 0) { |
| dev_dbg(&adapter->dev, "Failed to scan command mask\n"); |
| return -EIO; |
| } |
| |
| if (!(adapter->cmd_mask & BIT(cmd))) { |
| dev_dbg(&adapter->dev, "Command %d is not supported\n", cmd); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int peci_ioctl_xfer(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_xfer_msg *msg = vmsg; |
| |
| return peci_xfer(adapter, msg); |
| } |
| |
| static int peci_ioctl_ping(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_ping_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| |
| msg.addr = umsg->addr; |
| msg.tx_len = 0; |
| msg.rx_len = 0; |
| |
| return peci_xfer(adapter, &msg); |
| } |
| |
| static int peci_ioctl_get_dib(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_get_dib_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| int rc; |
| |
| msg.addr = umsg->addr; |
| msg.tx_len = GET_DIB_WR_LEN; |
| msg.rx_len = GET_DIB_RD_LEN; |
| msg.tx_buf[0] = GET_DIB_PECI_CMD; |
| |
| rc = peci_xfer(adapter, &msg); |
| if (rc) |
| return rc; |
| |
| umsg->dib = le64_to_cpup((__le64 *)msg.rx_buf); |
| |
| return 0; |
| } |
| |
| static int peci_ioctl_get_temp(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_get_temp_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| int rc; |
| |
| msg.addr = umsg->addr; |
| msg.tx_len = GET_TEMP_WR_LEN; |
| msg.rx_len = GET_TEMP_RD_LEN; |
| msg.tx_buf[0] = GET_TEMP_PECI_CMD; |
| |
| rc = peci_xfer(adapter, &msg); |
| if (rc) |
| return rc; |
| |
| umsg->temp_raw = le16_to_cpup((__le16 *)msg.rx_buf); |
| |
| return 0; |
| } |
| |
| static int peci_ioctl_rd_pkg_cfg(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_rd_pkg_cfg_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| int rc = 0; |
| |
| /* Per the PECI spec, the read length must be a byte, word, or dword */ |
| if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 4) { |
| dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", |
| umsg->rx_len); |
| return -EINVAL; |
| } |
| |
| msg.addr = umsg->addr; |
| msg.tx_len = RDPKGCFG_WRITE_LEN; |
| /* read lengths of 1 and 2 result in an error, so only use 4 for now */ |
| msg.rx_len = RDPKGCFG_READ_LEN_BASE + umsg->rx_len; |
| msg.tx_buf[0] = RDPKGCFG_PECI_CMD; |
| msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ |
| /* Host ID is 0 for PECI 3.0 */ |
| msg.tx_buf[2] = umsg->index; /* RdPkgConfig index */ |
| msg.tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ |
| msg.tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ |
| |
| rc = peci_xfer_with_retries(adapter, &msg, false); |
| if (!rc) |
| memcpy(umsg->pkg_config, &msg.rx_buf[1], umsg->rx_len); |
| |
| return rc; |
| } |
| |
| static int peci_ioctl_wr_pkg_cfg(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_wr_pkg_cfg_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| int rc = 0, i; |
| |
| /* Per the PECI spec, the write length must be a dword */ |
| if (umsg->tx_len != 4) { |
| dev_dbg(&adapter->dev, "Invalid write length, tx_len: %d\n", |
| umsg->tx_len); |
| return -EINVAL; |
| } |
| |
| msg.addr = umsg->addr; |
| msg.tx_len = WRPKGCFG_WRITE_LEN_BASE + umsg->tx_len; |
| /* read lengths of 1 and 2 result in an error, so only use 4 for now */ |
| msg.rx_len = WRPKGCFG_READ_LEN; |
| msg.tx_buf[0] = WRPKGCFG_PECI_CMD; |
| msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ |
| /* Host ID is 0 for PECI 3.0 */ |
| msg.tx_buf[2] = umsg->index; /* RdPkgConfig index */ |
| msg.tx_buf[3] = (u8)umsg->param; /* LSB - Config parameter */ |
| msg.tx_buf[4] = (u8)(umsg->param >> 8); /* MSB - Config parameter */ |
| for (i = 0; i < umsg->tx_len; i++) |
| msg.tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); |
| |
| /* Add an Assure Write Frame Check Sequence byte */ |
| msg.tx_buf[5 + i] = 0x80 ^ |
| peci_aw_fcs((u8 *)&msg, 8 + umsg->tx_len); |
| |
| rc = peci_xfer_with_retries(adapter, &msg, true); |
| |
| return rc; |
| } |
| |
| static int peci_ioctl_rd_ia_msr(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_rd_ia_msr_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| int rc = 0; |
| |
| msg.addr = umsg->addr; |
| msg.tx_len = RDIAMSR_WRITE_LEN; |
| msg.rx_len = RDIAMSR_READ_LEN; |
| msg.tx_buf[0] = RDIAMSR_PECI_CMD; |
| msg.tx_buf[1] = 0; |
| msg.tx_buf[2] = umsg->thread_id; |
| msg.tx_buf[3] = (u8)umsg->address; |
| msg.tx_buf[4] = (u8)(umsg->address >> 8); |
| |
| rc = peci_xfer_with_retries(adapter, &msg, false); |
| if (!rc) |
| memcpy(&umsg->value, &msg.rx_buf[1], sizeof(uint64_t)); |
| |
| return rc; |
| } |
| |
| static int peci_ioctl_rd_pci_cfg(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_rd_pci_cfg_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| u32 address; |
| int rc = 0; |
| |
| address = umsg->reg; /* [11:0] - Register */ |
| address |= (u32)umsg->function << 12; /* [14:12] - Function */ |
| address |= (u32)umsg->device << 15; /* [19:15] - Device */ |
| address |= (u32)umsg->bus << 20; /* [27:20] - Bus */ |
| /* [31:28] - Reserved */ |
| msg.addr = umsg->addr; |
| msg.tx_len = RDPCICFG_WRITE_LEN; |
| msg.rx_len = RDPCICFG_READ_LEN; |
| msg.tx_buf[0] = RDPCICFG_PECI_CMD; |
| msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ |
| /* Host ID is 0 for PECI 3.0 */ |
| msg.tx_buf[2] = (u8)address; /* LSB - PCI Config Address */ |
| msg.tx_buf[3] = (u8)(address >> 8); /* PCI Config Address */ |
| msg.tx_buf[4] = (u8)(address >> 16); /* PCI Config Address */ |
| msg.tx_buf[5] = (u8)(address >> 24); /* MSB - PCI Config Address */ |
| |
| rc = peci_xfer_with_retries(adapter, &msg, false); |
| if (!rc) |
| memcpy(umsg->pci_config, &msg.rx_buf[1], 4); |
| |
| return rc; |
| } |
| |
| static int peci_ioctl_rd_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_rd_pci_cfg_local_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| u32 address; |
| int rc = 0; |
| |
| /* Per the PECI spec, the read length must be a byte, word, or dword */ |
| if (umsg->rx_len != 1 && umsg->rx_len != 2 && umsg->rx_len != 4) { |
| dev_dbg(&adapter->dev, "Invalid read length, rx_len: %d\n", |
| umsg->rx_len); |
| return -EINVAL; |
| } |
| |
| address = umsg->reg; /* [11:0] - Register */ |
| address |= (u32)umsg->function << 12; /* [14:12] - Function */ |
| address |= (u32)umsg->device << 15; /* [19:15] - Device */ |
| address |= (u32)umsg->bus << 20; /* [23:20] - Bus */ |
| |
| msg.addr = umsg->addr; |
| msg.tx_len = RDPCICFGLOCAL_WRITE_LEN; |
| msg.rx_len = RDPCICFGLOCAL_READ_LEN_BASE + umsg->rx_len; |
| msg.tx_buf[0] = RDPCICFGLOCAL_PECI_CMD; |
| msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ |
| /* Host ID is 0 for PECI 3.0 */ |
| msg.tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ |
| msg.tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ |
| msg.tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ |
| |
| rc = peci_xfer_with_retries(adapter, &msg, false); |
| if (!rc) |
| memcpy(umsg->pci_config, &msg.rx_buf[1], umsg->rx_len); |
| |
| return rc; |
| } |
| |
| static int peci_ioctl_wr_pci_cfg_local(struct peci_adapter *adapter, void *vmsg) |
| { |
| struct peci_wr_pci_cfg_local_msg *umsg = vmsg; |
| struct peci_xfer_msg msg; |
| int rc = 0, i; |
| u32 address; |
| |
| /* Per the PECI spec, the write length must be a byte, word, or dword */ |
| if (umsg->tx_len != 1 && umsg->tx_len != 2 && umsg->tx_len != 4) { |
| dev_dbg(&adapter->dev, "Invalid write length, tx_len: %d\n", |
| umsg->tx_len); |
| return -EINVAL; |
| } |
| |
| address = umsg->reg; /* [11:0] - Register */ |
| address |= (u32)umsg->function << 12; /* [14:12] - Function */ |
| address |= (u32)umsg->device << 15; /* [19:15] - Device */ |
| address |= (u32)umsg->bus << 20; /* [23:20] - Bus */ |
| |
| msg.addr = umsg->addr; |
| msg.tx_len = WRPCICFGLOCAL_WRITE_LEN_BASE + umsg->tx_len; |
| msg.rx_len = WRPCICFGLOCAL_READ_LEN; |
| msg.tx_buf[0] = WRPCICFGLOCAL_PECI_CMD; |
| msg.tx_buf[1] = 0; /* request byte for Host ID | Retry bit */ |
| /* Host ID is 0 for PECI 3.0 */ |
| msg.tx_buf[2] = (u8)address; /* LSB - PCI Configuration Address */ |
| msg.tx_buf[3] = (u8)(address >> 8); /* PCI Configuration Address */ |
| msg.tx_buf[4] = (u8)(address >> 16); /* PCI Configuration Address */ |
| for (i = 0; i < umsg->tx_len; i++) |
| msg.tx_buf[5 + i] = (u8)(umsg->value >> (i << 3)); |
| |
| /* Add an Assure Write Frame Check Sequence byte */ |
| msg.tx_buf[5 + i] = 0x80 ^ |
| peci_aw_fcs((u8 *)&msg, 8 + umsg->tx_len); |
| |
| rc = peci_xfer_with_retries(adapter, &msg, true); |
| |
| return rc; |
| } |
| |
| typedef int (*peci_ioctl_fn_type)(struct peci_adapter *, void *); |
| |
| static const peci_ioctl_fn_type peci_ioctl_fn[PECI_CMD_MAX] = { |
| peci_ioctl_xfer, |
| peci_ioctl_ping, |
| peci_ioctl_get_dib, |
| peci_ioctl_get_temp, |
| peci_ioctl_rd_pkg_cfg, |
| peci_ioctl_wr_pkg_cfg, |
| peci_ioctl_rd_ia_msr, |
| NULL, /* Reserved */ |
| peci_ioctl_rd_pci_cfg, |
| NULL, /* Reserved */ |
| peci_ioctl_rd_pci_cfg_local, |
| peci_ioctl_wr_pci_cfg_local, |
| }; |
| |
| /** |
| * peci_command - transfer function of a PECI command |
| * @adapter: pointer to peci_adapter |
| * @vmsg: pointer to PECI messages |
| * Context: can sleep |
| * |
| * This performs a transfer of a PECI command using PECI messages parameter |
| * which has various formats on each command. |
| * |
| * Return: zero on success, else a negative error code. |
| */ |
| int peci_command(struct peci_adapter *adapter, enum peci_cmd cmd, void *vmsg) |
| { |
| int rc = 0; |
| |
| if (cmd >= PECI_CMD_MAX || cmd < PECI_CMD_XFER) |
| return -EINVAL; |
| |
| dev_dbg(&adapter->dev, "%s, cmd=0x%02x\n", __func__, cmd); |
| |
| if (!peci_ioctl_fn[cmd]) |
| return -EINVAL; |
| |
| rt_mutex_lock(&adapter->bus_lock); |
| |
| rc = peci_cmd_support(adapter, cmd); |
| if (!rc) |
| rc = peci_ioctl_fn[cmd](adapter, vmsg); |
| |
| rt_mutex_unlock(&adapter->bus_lock); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(peci_command); |
| |
| static long peci_ioctl(struct file *file, unsigned int iocmd, unsigned long arg) |
| { |
| struct peci_adapter *adapter = file->private_data; |
| void __user *argp = (void __user *)arg; |
| unsigned int msg_len; |
| enum peci_cmd cmd; |
| int rc = 0; |
| u8 *msg; |
| |
| if (!capable(CAP_SYS_ADMIN)) |
| return -EPERM; |
| |
| dev_dbg(&adapter->dev, "ioctl, cmd=0x%x, arg=0x%lx\n", iocmd, arg); |
| |
| switch (iocmd) { |
| case PECI_IOC_XFER: |
| case PECI_IOC_PING: |
| case PECI_IOC_GET_DIB: |
| case PECI_IOC_GET_TEMP: |
| case PECI_IOC_RD_PKG_CFG: |
| case PECI_IOC_WR_PKG_CFG: |
| case PECI_IOC_RD_IA_MSR: |
| case PECI_IOC_RD_PCI_CFG: |
| case PECI_IOC_RD_PCI_CFG_LOCAL: |
| case PECI_IOC_WR_PCI_CFG_LOCAL: |
| cmd = _IOC_NR(iocmd); |
| msg_len = _IOC_SIZE(iocmd); |
| break; |
| |
| default: |
| dev_dbg(&adapter->dev, "Invalid ioctl cmd : 0x%x\n", iocmd); |
| return -ENOTTY; |
| } |
| |
| if (!access_ok(argp, msg_len)) |
| return -EFAULT; |
| |
| msg = memdup_user(argp, msg_len); |
| if (IS_ERR(msg)) |
| return PTR_ERR(msg); |
| |
| rc = peci_command(adapter, cmd, msg); |
| |
| if (!rc && copy_to_user(argp, msg, msg_len)) |
| rc = -EFAULT; |
| |
| kfree(msg); |
| return (long)rc; |
| } |
| |
| static int peci_open(struct inode *inode, struct file *file) |
| { |
| unsigned int minor = iminor(inode); |
| struct peci_adapter *adapter; |
| |
| adapter = peci_get_adapter(minor); |
| if (!adapter) |
| return -ENODEV; |
| |
| file->private_data = adapter; |
| |
| return 0; |
| } |
| |
| static int peci_release(struct inode *inode, struct file *file) |
| { |
| struct peci_adapter *adapter = file->private_data; |
| |
| peci_put_adapter(adapter); |
| file->private_data = NULL; |
| |
| return 0; |
| } |
| |
| static const struct file_operations peci_fops = { |
| .owner = THIS_MODULE, |
| .unlocked_ioctl = peci_ioctl, |
| .open = peci_open, |
| .release = peci_release, |
| }; |
| |
| static int peci_detect(struct peci_adapter *adapter, u8 addr) |
| { |
| struct peci_ping_msg msg; |
| |
| msg.addr = addr; |
| |
| return peci_command(adapter, PECI_CMD_PING, &msg); |
| } |
| |
| static const struct of_device_id * |
| peci_of_match_device(const struct of_device_id *matches, |
| struct peci_client *client) |
| { |
| #if IS_ENABLED(CONFIG_OF) |
| if (!(client && matches)) |
| return NULL; |
| |
| return of_match_device(matches, &client->dev); |
| #else |
| return NULL; |
| #endif |
| } |
| |
| static const struct peci_device_id * |
| peci_match_id(const struct peci_device_id *id, struct peci_client *client) |
| { |
| if (!(id && client)) |
| return NULL; |
| |
| while (id->name[0]) { |
| if (!strncmp(client->name, id->name, PECI_NAME_SIZE)) |
| return id; |
| id++; |
| } |
| |
| return NULL; |
| } |
| |
| static int peci_device_match(struct device *dev, struct device_driver *drv) |
| { |
| struct peci_client *client = peci_verify_client(dev); |
| struct peci_driver *driver; |
| |
| /* Attempt an OF style match */ |
| if (peci_of_match_device(drv->of_match_table, client)) |
| return 1; |
| |
| driver = to_peci_driver(drv); |
| |
| /* Finally an ID match */ |
| if (peci_match_id(driver->id_table, client)) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int peci_device_probe(struct device *dev) |
| { |
| struct peci_client *client = peci_verify_client(dev); |
| struct peci_driver *driver; |
| int status = -EINVAL; |
| |
| if (!client) |
| return 0; |
| |
| driver = to_peci_driver(dev->driver); |
| |
| if (!driver->id_table && |
| !peci_of_match_device(dev->driver->of_match_table, client)) |
| return -ENODEV; |
| |
| dev_dbg(dev, "%s: name:%s\n", __func__, client->name); |
| |
| status = dev_pm_domain_attach(&client->dev, true); |
| if (status == -EPROBE_DEFER) |
| return status; |
| |
| if (driver->probe) |
| status = driver->probe(client); |
| else |
| status = -EINVAL; |
| |
| if (status) |
| goto err_detach_pm_domain; |
| |
| return 0; |
| |
| err_detach_pm_domain: |
| dev_pm_domain_detach(&client->dev, true); |
| return status; |
| } |
| |
| static int peci_device_remove(struct device *dev) |
| { |
| struct peci_client *client = peci_verify_client(dev); |
| struct peci_driver *driver; |
| int status = 0; |
| |
| if (!client || !dev->driver) |
| return 0; |
| |
| driver = to_peci_driver(dev->driver); |
| if (driver->remove) { |
| dev_dbg(dev, "%s: name:%s\n", __func__, client->name); |
| status = driver->remove(client); |
| } |
| |
| dev_pm_domain_detach(&client->dev, true); |
| |
| return status; |
| } |
| |
| static void peci_device_shutdown(struct device *dev) |
| { |
| struct peci_client *client = peci_verify_client(dev); |
| struct peci_driver *driver; |
| |
| if (!client || !dev->driver) |
| return; |
| |
| dev_dbg(dev, "%s: name:%s\n", __func__, client->name); |
| |
| driver = to_peci_driver(dev->driver); |
| if (driver->shutdown) |
| driver->shutdown(client); |
| } |
| |
| static struct bus_type peci_bus_type = { |
| .name = "peci", |
| .match = peci_device_match, |
| .probe = peci_device_probe, |
| .remove = peci_device_remove, |
| .shutdown = peci_device_shutdown, |
| }; |
| |
| static int peci_check_addr_validity(u8 addr) |
| { |
| if (addr < PECI_BASE_ADDR && addr > PECI_BASE_ADDR + PECI_OFFSET_MAX) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int peci_check_client_busy(struct device *dev, void *client_new_p) |
| { |
| struct peci_client *client = peci_verify_client(dev); |
| struct peci_client *client_new = client_new_p; |
| |
| if (client && client->addr == client_new->addr) |
| return -EBUSY; |
| |
| return 0; |
| } |
| |
| /** |
| * peci_get_cpu_id - read CPU ID from the Package Configuration Space of CPU |
| * @adapter: pointer to peci_adapter |
| * @addr: address of the PECI client CPU |
| * @cpu_id: where the CPU ID will be stored |
| * Context: can sleep |
| * |
| * Return: zero on success, else a negative error code. |
| */ |
| int peci_get_cpu_id(struct peci_adapter *adapter, u8 addr, u32 *cpu_id) |
| { |
| struct peci_rd_pkg_cfg_msg msg; |
| int rc; |
| |
| msg.addr = addr; |
| msg.index = MBX_INDEX_CPU_ID; |
| msg.param = PKG_ID_CPU_ID; |
| msg.rx_len = 4; |
| |
| rc = peci_command(adapter, PECI_CMD_RD_PKG_CFG, &msg); |
| if (!rc) |
| *cpu_id = le32_to_cpup((__le32 *)msg.pkg_config); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(peci_get_cpu_id); |
| |
| static struct peci_client *peci_new_device(struct peci_adapter *adapter, |
| struct peci_board_info const *info) |
| { |
| struct peci_client *client; |
| int rc; |
| |
| /* Increase reference count for the adapter assigned */ |
| if (!peci_get_adapter(adapter->nr)) |
| return NULL; |
| |
| client = kzalloc(sizeof(*client), GFP_KERNEL); |
| if (!client) |
| goto err_put_adapter; |
| |
| client->adapter = adapter; |
| client->addr = info->addr; |
| strlcpy(client->name, info->type, sizeof(client->name)); |
| |
| rc = peci_check_addr_validity(client->addr); |
| if (rc) { |
| dev_err(&adapter->dev, "Invalid PECI CPU address 0x%02hx\n", |
| client->addr); |
| goto err_free_client_silent; |
| } |
| |
| /* Check online status of client */ |
| rc = peci_detect(adapter, client->addr); |
| if (rc) |
| goto err_free_client; |
| |
| rc = device_for_each_child(&adapter->dev, client, |
| peci_check_client_busy); |
| if (rc) |
| goto err_free_client; |
| |
| client->dev.parent = &client->adapter->dev; |
| client->dev.bus = &peci_bus_type; |
| client->dev.type = &peci_client_type; |
| client->dev.of_node = info->of_node; |
| dev_set_name(&client->dev, "%d-%02x", adapter->nr, client->addr); |
| |
| rc = device_register(&client->dev); |
| if (rc) |
| goto err_free_client; |
| |
| dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n", |
| client->name, dev_name(&client->dev)); |
| |
| return client; |
| |
| err_free_client: |
| dev_err(&adapter->dev, |
| "Failed to register peci client %s at 0x%02x (%d)\n", |
| client->name, client->addr, rc); |
| err_free_client_silent: |
| kfree(client); |
| err_put_adapter: |
| peci_put_adapter(adapter); |
| return NULL; |
| } |
| |
| static void peci_unregister_device(struct peci_client *client) |
| { |
| if (!client) |
| return; |
| |
| if (client->dev.of_node) |
| of_node_clear_flag(client->dev.of_node, OF_POPULATED); |
| |
| device_unregister(&client->dev); |
| } |
| |
| static int peci_unregister_client(struct device *dev, void *dummy) |
| { |
| struct peci_client *client = peci_verify_client(dev); |
| |
| peci_unregister_device(client); |
| |
| return 0; |
| } |
| |
| static void peci_adapter_dev_release(struct device *dev) |
| { |
| struct peci_adapter *adapter = to_peci_adapter(dev); |
| |
| dev_dbg(dev, "%s: %s\n", __func__, adapter->name); |
| mutex_destroy(&adapter->userspace_clients_lock); |
| rt_mutex_destroy(&adapter->bus_lock); |
| kfree(adapter); |
| } |
| |
| static ssize_t peci_sysfs_new_device(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct peci_adapter *adapter = to_peci_adapter(dev); |
| struct peci_board_info info = {}; |
| struct peci_client *client; |
| char *blank, end; |
| int rc; |
| |
| /* Parse device type */ |
| blank = strchr(buf, ' '); |
| if (!blank) { |
| dev_err(dev, "%s: Missing parameters\n", "new_device"); |
| return -EINVAL; |
| } |
| if (blank - buf > PECI_NAME_SIZE - 1) { |
| dev_err(dev, "%s: Invalid device type\n", "new_device"); |
| return -EINVAL; |
| } |
| memcpy(info.type, buf, blank - buf); |
| |
| /* Parse remaining parameters, reject extra parameters */ |
| rc = sscanf(++blank, "%hi%c", &info.addr, &end); |
| if (rc < 1) { |
| dev_err(dev, "%s: Can't parse client address\n", "new_device"); |
| return -EINVAL; |
| } |
| if (rc > 1 && end != '\n') { |
| dev_err(dev, "%s: Extra parameters\n", "new_device"); |
| return -EINVAL; |
| } |
| |
| client = peci_new_device(adapter, &info); |
| if (!client) |
| return -EINVAL; |
| |
| /* Keep track of the added device */ |
| mutex_lock(&adapter->userspace_clients_lock); |
| list_add_tail(&client->detected, &adapter->userspace_clients); |
| mutex_unlock(&adapter->userspace_clients_lock); |
| dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device", |
| info.type, info.addr); |
| |
| return count; |
| } |
| static DEVICE_ATTR(new_device, 0200, NULL, peci_sysfs_new_device); |
| |
| static ssize_t peci_sysfs_delete_device(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct peci_adapter *adapter = to_peci_adapter(dev); |
| struct peci_client *client, *next; |
| struct peci_board_info info = {}; |
| struct peci_driver *driver; |
| char *blank, end; |
| int rc; |
| |
| /* Parse device type */ |
| blank = strchr(buf, ' '); |
| if (!blank) { |
| dev_err(dev, "%s: Missing parameters\n", "delete_device"); |
| return -EINVAL; |
| } |
| if (blank - buf > PECI_NAME_SIZE - 1) { |
| dev_err(dev, "%s: Invalid device type\n", "delete_device"); |
| return -EINVAL; |
| } |
| memcpy(info.type, buf, blank - buf); |
| |
| /* Parse remaining parameters, reject extra parameters */ |
| rc = sscanf(++blank, "%hi%c", &info.addr, &end); |
| if (rc < 1) { |
| dev_err(dev, "%s: Can't parse client address\n", |
| "delete_device"); |
| return -EINVAL; |
| } |
| if (rc > 1 && end != '\n') { |
| dev_err(dev, "%s: Extra parameters\n", "delete_device"); |
| return -EINVAL; |
| } |
| |
| /* Make sure the device was added through sysfs */ |
| rc = -ENOENT; |
| mutex_lock(&adapter->userspace_clients_lock); |
| list_for_each_entry_safe(client, next, &adapter->userspace_clients, |
| detected) { |
| driver = to_peci_driver(client->dev.driver); |
| |
| if (client->addr == info.addr && |
| !strncmp(client->name, info.type, PECI_NAME_SIZE)) { |
| dev_info(dev, "%s: Deleting device %s at 0x%02hx\n", |
| "delete_device", client->name, client->addr); |
| list_del(&client->detected); |
| peci_unregister_device(client); |
| rc = count; |
| break; |
| } |
| } |
| mutex_unlock(&adapter->userspace_clients_lock); |
| |
| if (rc < 0) |
| dev_err(dev, "%s: Can't find device in list\n", |
| "delete_device"); |
| |
| return rc; |
| } |
| static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL, |
| peci_sysfs_delete_device); |
| |
| static struct attribute *peci_adapter_attrs[] = { |
| &dev_attr_name.attr, |
| &dev_attr_new_device.attr, |
| &dev_attr_delete_device.attr, |
| NULL |
| }; |
| ATTRIBUTE_GROUPS(peci_adapter); |
| |
| static struct device_type peci_adapter_type = { |
| .groups = peci_adapter_groups, |
| .release = peci_adapter_dev_release, |
| }; |
| |
| /** |
| * peci_verify_adapter - return parameter as peci_adapter, or NULL |
| * @dev: device, probably from some driver model iterator |
| * |
| * Return: pointer to peci_adapter on success, else NULL. |
| */ |
| struct peci_adapter *peci_verify_adapter(struct device *dev) |
| { |
| return (dev->type == &peci_adapter_type) |
| ? to_peci_adapter(dev) |
| : NULL; |
| } |
| EXPORT_SYMBOL_GPL(peci_verify_adapter); |
| |
| #if IS_ENABLED(CONFIG_OF) |
| static struct peci_client *peci_of_register_device(struct peci_adapter *adapter, |
| struct device_node *node) |
| { |
| struct peci_board_info info = {}; |
| struct peci_client *result; |
| const __be32 *addr_be; |
| int len; |
| |
| dev_dbg(&adapter->dev, "register %pOF\n", node); |
| |
| if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) { |
| dev_err(&adapter->dev, "modalias failure on %pOF\n", node); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| addr_be = of_get_property(node, "reg", &len); |
| if (!addr_be || len < sizeof(*addr_be)) { |
| dev_err(&adapter->dev, "invalid reg on %pOF\n", node); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| info.addr = be32_to_cpup(addr_be); |
| info.of_node = of_node_get(node); |
| |
| result = peci_new_device(adapter, &info); |
| if (!result) |
| result = ERR_PTR(-EINVAL); |
| |
| of_node_put(node); |
| return result; |
| } |
| |
| static void peci_of_register_devices(struct peci_adapter *adapter) |
| { |
| struct device_node *bus, *node; |
| struct peci_client *client; |
| |
| /* Only register child devices if the adapter has a node pointer set */ |
| if (!adapter->dev.of_node) |
| return; |
| |
| bus = of_get_child_by_name(adapter->dev.of_node, "peci-bus"); |
| if (!bus) |
| bus = of_node_get(adapter->dev.of_node); |
| |
| for_each_available_child_of_node(bus, node) { |
| if (of_node_test_and_set_flag(node, OF_POPULATED)) |
| continue; |
| |
| client = peci_of_register_device(adapter, node); |
| if (IS_ERR(client)) { |
| dev_warn(&adapter->dev, |
| "Failed to create PECI device for %pOF\n", |
| node); |
| of_node_clear_flag(node, OF_POPULATED); |
| } |
| } |
| |
| of_node_put(bus); |
| } |
| #else |
| static void peci_of_register_devices(struct peci_adapter *adapter) { } |
| #endif /* CONFIG_OF */ |
| |
| #if IS_ENABLED(CONFIG_OF_DYNAMIC) |
| static int peci_of_match_node(struct device *dev, void *data) |
| { |
| return dev->of_node == data; |
| } |
| |
| /* must call put_device() when done with returned peci_client device */ |
| static struct peci_client *peci_of_find_device(struct device_node *node) |
| { |
| struct peci_client *client; |
| struct device *dev; |
| |
| dev = bus_find_device(&peci_bus_type, NULL, node, peci_of_match_node); |
| if (!dev) |
| return NULL; |
| |
| client = peci_verify_client(dev); |
| if (!client) |
| put_device(dev); |
| |
| return client; |
| } |
| |
| /* must call put_device() when done with returned peci_adapter device */ |
| static struct peci_adapter *peci_of_find_adapter(struct device_node *node) |
| { |
| struct peci_adapter *adapter; |
| struct device *dev; |
| |
| dev = bus_find_device(&peci_bus_type, NULL, node, peci_of_match_node); |
| if (!dev) |
| return NULL; |
| |
| adapter = peci_verify_adapter(dev); |
| if (!adapter) |
| put_device(dev); |
| |
| return adapter; |
| } |
| |
| static int peci_of_notify(struct notifier_block *nb, |
| unsigned long action, |
| void *arg) |
| { |
| struct of_reconfig_data *rd = arg; |
| struct peci_adapter *adapter; |
| struct peci_client *client; |
| |
| switch (of_reconfig_get_state_change(action, rd)) { |
| case OF_RECONFIG_CHANGE_ADD: |
| adapter = peci_of_find_adapter(rd->dn->parent); |
| if (!adapter) |
| return NOTIFY_OK; /* not for us */ |
| |
| if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) { |
| put_device(&adapter->dev); |
| return NOTIFY_OK; |
| } |
| |
| client = peci_of_register_device(adapter, rd->dn); |
| put_device(&adapter->dev); |
| |
| if (IS_ERR(client)) { |
| dev_err(&adapter->dev, |
| "failed to create client for '%pOF'\n", rd->dn); |
| of_node_clear_flag(rd->dn, OF_POPULATED); |
| return notifier_from_errno(PTR_ERR(client)); |
| } |
| break; |
| case OF_RECONFIG_CHANGE_REMOVE: |
| /* already depopulated? */ |
| if (!of_node_check_flag(rd->dn, OF_POPULATED)) |
| return NOTIFY_OK; |
| |
| /* find our device by node */ |
| client = peci_of_find_device(rd->dn); |
| if (!client) |
| return NOTIFY_OK; /* no? not meant for us */ |
| |
| /* unregister takes one ref away */ |
| peci_unregister_device(client); |
| |
| /* and put the reference of the find */ |
| put_device(&client->dev); |
| break; |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block peci_of_notifier = { |
| .notifier_call = peci_of_notify, |
| }; |
| #else |
| extern struct notifier_block peci_of_notifier; |
| #endif /* CONFIG_OF_DYNAMIC */ |
| |
| /** |
| * peci_alloc_adapter - allocate a PECI adapter |
| * @dev: the adapter, possibly using the platform_bus |
| * @size: how much zeroed driver-private data to allocate; the pointer to this |
| * memory is in the driver_data field of the returned device, |
| * accessible with peci_get_adapdata(). |
| * Context: can sleep |
| * |
| * This call is used only by PECI adapter drivers, which are the only ones |
| * directly touching chip registers. It's how they allocate a peci_adapter |
| * structure, prior to calling peci_add_adapter(). |
| * |
| * This must be called from context that can sleep. |
| * |
| * The caller is responsible for initializing the adapter's methods before |
| * calling peci_add_adapter(); and (after errors while adding the device) |
| * calling put_device() to prevent a memory leak. |
| * |
| * Return: the peci_adapter structure on success, else NULL. |
| */ |
| struct peci_adapter *peci_alloc_adapter(struct device *dev, unsigned int size) |
| { |
| struct peci_adapter *adapter; |
| |
| if (!dev) |
| return NULL; |
| |
| adapter = kzalloc(size + sizeof(*adapter), GFP_KERNEL); |
| if (!adapter) |
| return NULL; |
| |
| device_initialize(&adapter->dev); |
| adapter->dev.parent = dev; |
| adapter->dev.bus = &peci_bus_type; |
| adapter->dev.type = &peci_adapter_type; |
| peci_set_adapdata(adapter, &adapter[1]); |
| |
| return adapter; |
| } |
| EXPORT_SYMBOL_GPL(peci_alloc_adapter); |
| |
| static int peci_register_adapter(struct peci_adapter *adapter) |
| { |
| int rc = -EINVAL; |
| |
| /* Can't register until after driver model init */ |
| if (WARN_ON(!is_registered)) |
| goto err_free_idr; |
| |
| if (WARN(!adapter->name[0], "peci adapter has no name")) |
| goto err_free_idr; |
| |
| if (WARN(!adapter->xfer, "peci adapter has no xfer function\n")) |
| goto err_free_idr; |
| |
| rt_mutex_init(&adapter->bus_lock); |
| mutex_init(&adapter->userspace_clients_lock); |
| INIT_LIST_HEAD(&adapter->userspace_clients); |
| |
| dev_set_name(&adapter->dev, "peci-%d", adapter->nr); |
| |
| /* cdev */ |
| cdev_init(&adapter->cdev, &peci_fops); |
| adapter->cdev.owner = THIS_MODULE; |
| adapter->dev.devt = MKDEV(MAJOR(peci_devt), adapter->nr); |
| rc = cdev_add(&adapter->cdev, adapter->dev.devt, 1); |
| if (rc) { |
| pr_err("adapter '%s': can't add cdev (%d)\n", |
| adapter->name, rc); |
| goto err_free_idr; |
| } |
| rc = device_add(&adapter->dev); |
| if (rc) { |
| pr_err("adapter '%s': can't add device (%d)\n", |
| adapter->name, rc); |
| goto err_del_cdev; |
| } |
| |
| dev_dbg(&adapter->dev, "adapter [%s] registered\n", adapter->name); |
| |
| pm_runtime_no_callbacks(&adapter->dev); |
| pm_suspend_ignore_children(&adapter->dev, true); |
| pm_runtime_enable(&adapter->dev); |
| |
| /* create pre-declared device nodes */ |
| peci_of_register_devices(adapter); |
| |
| return 0; |
| |
| err_del_cdev: |
| cdev_del(&adapter->cdev); |
| err_free_idr: |
| mutex_lock(&core_lock); |
| idr_remove(&peci_adapter_idr, adapter->nr); |
| mutex_unlock(&core_lock); |
| return rc; |
| } |
| |
| static int peci_add_numbered_adapter(struct peci_adapter *adapter) |
| { |
| int id; |
| |
| mutex_lock(&core_lock); |
| id = idr_alloc(&peci_adapter_idr, adapter, |
| adapter->nr, adapter->nr + 1, GFP_KERNEL); |
| mutex_unlock(&core_lock); |
| if (WARN(id < 0, "couldn't get idr")) |
| return id == -ENOSPC ? -EBUSY : id; |
| |
| return peci_register_adapter(adapter); |
| } |
| |
| /** |
| * peci_add_adapter - add a PECI adapter |
| * @adapter: initialized adapter, originally from peci_alloc_adapter() |
| * Context: can sleep |
| * |
| * PECI adapters connect to their drivers using some non-PECI bus, |
| * such as the platform bus. The final stage of probe() in that code |
| * includes calling peci_add_adapter() to hook up to this PECI bus glue. |
| * |
| * This must be called from context that can sleep. |
| * |
| * It returns zero on success, else a negative error code (dropping the |
| * adapter's refcount). After a successful return, the caller is responsible |
| * for calling peci_del_adapter(). |
| * |
| * Return: zero on success, else a negative error code. |
| */ |
| int peci_add_adapter(struct peci_adapter *adapter) |
| { |
| struct device *dev = &adapter->dev; |
| int id; |
| |
| if (dev->of_node) { |
| id = of_alias_get_id(dev->of_node, "peci"); |
| if (id >= 0) { |
| adapter->nr = id; |
| return peci_add_numbered_adapter(adapter); |
| } |
| } |
| |
| mutex_lock(&core_lock); |
| id = idr_alloc(&peci_adapter_idr, adapter, 0, 0, GFP_KERNEL); |
| mutex_unlock(&core_lock); |
| if (WARN(id < 0, "couldn't get idr")) |
| return id; |
| |
| adapter->nr = id; |
| |
| return peci_register_adapter(adapter); |
| } |
| EXPORT_SYMBOL_GPL(peci_add_adapter); |
| |
| /** |
| * peci_del_adapter - delete a PECI adapter |
| * @adapter: the adpater being deleted |
| * Context: can sleep |
| * |
| * This call is used only by PECI adpater drivers, which are the only ones |
| * directly touching chip registers. |
| * |
| * This must be called from context that can sleep. |
| * |
| * Note that this function also drops a reference to the adapter. |
| */ |
| void peci_del_adapter(struct peci_adapter *adapter) |
| { |
| struct peci_client *client, *next; |
| struct peci_adapter *found; |
| int nr; |
| |
| /* First make sure that this adapter was ever added */ |
| mutex_lock(&core_lock); |
| found = idr_find(&peci_adapter_idr, adapter->nr); |
| mutex_unlock(&core_lock); |
| |
| if (found != adapter) |
| return; |
| |
| /* Remove devices instantiated from sysfs */ |
| mutex_lock(&adapter->userspace_clients_lock); |
| list_for_each_entry_safe(client, next, &adapter->userspace_clients, |
| detected) { |
| dev_dbg(&adapter->dev, "Removing %s at 0x%x\n", client->name, |
| client->addr); |
| list_del(&client->detected); |
| peci_unregister_device(client); |
| } |
| mutex_unlock(&adapter->userspace_clients_lock); |
| |
| /** |
| * Detach any active clients. This can't fail, thus we do not |
| * check the returned value. |
| */ |
| device_for_each_child(&adapter->dev, NULL, peci_unregister_client); |
| |
| /* device name is gone after device_unregister */ |
| dev_dbg(&adapter->dev, "adapter [%s] unregistered\n", adapter->name); |
| |
| /* free cdev */ |
| cdev_del(&adapter->cdev); |
| |
| pm_runtime_disable(&adapter->dev); |
| |
| nr = adapter->nr; |
| |
| device_unregister(&adapter->dev); |
| |
| /* free bus id */ |
| mutex_lock(&core_lock); |
| idr_remove(&peci_adapter_idr, nr); |
| mutex_unlock(&core_lock); |
| } |
| EXPORT_SYMBOL_GPL(peci_del_adapter); |
| |
| /** |
| * peci_register_driver - register a PECI driver |
| * @owner: owner module of the driver being registered |
| * @driver: the driver being registered |
| * Context: can sleep |
| * |
| * Return: zero on success, else a negative error code. |
| */ |
| int peci_register_driver(struct module *owner, struct peci_driver *driver) |
| { |
| int rc; |
| |
| /* Can't register until after driver model init */ |
| if (WARN_ON(!is_registered)) |
| return -EAGAIN; |
| |
| /* add the driver to the list of peci drivers in the driver core */ |
| driver->driver.owner = owner; |
| driver->driver.bus = &peci_bus_type; |
| |
| /** |
| * When registration returns, the driver core |
| * will have called probe() for all matching-but-unbound devices. |
| */ |
| rc = driver_register(&driver->driver); |
| if (rc) |
| return rc; |
| |
| pr_debug("driver [%s] registered\n", driver->driver.name); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(peci_register_driver); |
| |
| /** |
| * peci_del_driver - unregister a PECI driver |
| * @driver: the driver being unregistered |
| * Context: can sleep |
| */ |
| void peci_del_driver(struct peci_driver *driver) |
| { |
| driver_unregister(&driver->driver); |
| pr_debug("driver [%s] unregistered\n", driver->driver.name); |
| } |
| EXPORT_SYMBOL_GPL(peci_del_driver); |
| |
| static int __init peci_init(void) |
| { |
| int ret; |
| |
| ret = bus_register(&peci_bus_type); |
| if (ret < 0) { |
| pr_err("peci: Failed to register PECI bus type!\n"); |
| return ret; |
| } |
| |
| ret = alloc_chrdev_region(&peci_devt, 0, PECI_CDEV_MAX, "peci"); |
| if (ret < 0) { |
| pr_err("peci: Failed to allocate chr dev region!\n"); |
| bus_unregister(&peci_bus_type); |
| return ret; |
| } |
| |
| crc8_populate_msb(peci_crc8_table, PECI_CRC8_POLYNOMIAL); |
| |
| if (IS_ENABLED(CONFIG_OF_DYNAMIC)) |
| WARN_ON(of_reconfig_notifier_register(&peci_of_notifier)); |
| |
| is_registered = true; |
| |
| return 0; |
| } |
| |
| static void __exit peci_exit(void) |
| { |
| if (IS_ENABLED(CONFIG_OF_DYNAMIC)) |
| WARN_ON(of_reconfig_notifier_unregister(&peci_of_notifier)); |
| |
| unregister_chrdev_region(peci_devt, PECI_CDEV_MAX); |
| bus_unregister(&peci_bus_type); |
| } |
| |
| postcore_initcall(peci_init); |
| module_exit(peci_exit); |
| |
| MODULE_AUTHOR("Jason M Biils <jason.m.bills@linux.intel.com>"); |
| MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); |
| MODULE_DESCRIPTION("PECI bus core module"); |
| MODULE_LICENSE("GPL v2"); |