diff options
Diffstat (limited to 'drivers/platform/x86/amd/hsmp')
| -rw-r--r-- | drivers/platform/x86/amd/hsmp/acpi.c | 220 | ||||
| -rw-r--r-- | drivers/platform/x86/amd/hsmp/hsmp.c | 328 | ||||
| -rw-r--r-- | drivers/platform/x86/amd/hsmp/hsmp.h | 19 | ||||
| -rw-r--r-- | drivers/platform/x86/amd/hsmp/hwmon.c | 3 | ||||
| -rw-r--r-- | drivers/platform/x86/amd/hsmp/plat.c | 38 |
5 files changed, 549 insertions, 59 deletions
diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 97ed71593bdf..ddd7a04ee753 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -15,12 +15,18 @@ #include <linux/array_size.h> #include <linux/bits.h> #include <linux/bitfield.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/dev_printk.h> #include <linux/ioport.h> +#include <linux/kref.h> #include <linux/kstrtox.h> +#include <linux/lockdep.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/rwsem.h> +#include <linux/slab.h> +#include <linux/string.h> #include <linux/sysfs.h> #include <linux/topology.h> #include <linux/uuid.h> @@ -38,6 +44,17 @@ static struct hsmp_plat_device *hsmp_pdev; +/* + * Tracks the ACPI socket platform devices that share the socket array and the + * /dev/hsmp misc device. The first probe initializes it, each further probe + * takes a reference and every remove (or probe failure) drops one; the last + * put frees the shared state via hsmp_acpi_sock_release(). All get/put run + * under hsmp_sock_rwsem held for write, so the counting is already serialized + * and the atomic in kref is not strictly needed; kref is used for the clearer + * get/put interface and its release callback. + */ +static struct kref hsmp_acpi_sock_kref; + struct hsmp_sys_attr { struct device_attribute dattr; u32 msg_id; @@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) * bytes to integer. */ uid = acpi_device_uid(ACPI_COMPANION(dev)); + if (!uid || strlen(uid) < 3) + return -EINVAL; return kstrtou16(uid + 2, 10, sock_ind); } @@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) return AE_OK; } -static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) +static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *guid, *mailbox_package; @@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) int ret = 0; int j; - status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, + status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, &buf, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", acpi_format_exception(status)); return -ENODEV; } @@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) guid = &dsd->package.elements[0]; mailbox_package = &dsd->package.elements[1]; if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { - dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); + dev_err(dev, "Invalid hsmp _DSD table data\n"); ret = -EINVAL; goto free_buf; } @@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) union acpi_object *msgobj, *msgstr, *msgint; msgobj = &mailbox_package->package.elements[j]; - msgstr = &msgobj->package.elements[0]; - msgint = &msgobj->package.elements[1]; /* package should have 1 string and 1 integer object */ if (msgobj->type != ACPI_TYPE_PACKAGE || - msgstr->type != ACPI_TYPE_STRING || + msgobj->package.count < 2) { + ret = -EINVAL; + goto free_buf; + } + + msgstr = &msgobj->package.elements[0]; + msgint = &msgobj->package.elements[1]; + + if (msgstr->type != ACPI_TYPE_STRING || msgint->type != ACPI_TYPE_INTEGER) { ret = -EINVAL; goto free_buf; @@ -183,14 +208,14 @@ free_buf: return ret; } -static int hsmp_read_acpi_crs(struct hsmp_socket *sock) +static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) { acpi_status status; - status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, + status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, hsmp_resource, sock); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", acpi_format_exception(status)); return -EINVAL; } @@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) return -EINVAL; /* The mapped region should be un-cached */ - sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, + sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, sock->mbinfo.size); if (!sock->virt_base_addr) { - dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); + dev_err(dev, "Failed to ioremap MP1 base address\n"); return -ENOMEM; } @@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) int ret; sock->sock_ind = sock_ind; - sock->dev = dev; sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; sema_init(&sock->hsmp_sem, 1); @@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) dev_set_drvdata(dev, sock); /* Read MP1 base address from CRS method */ - ret = hsmp_read_acpi_crs(sock); + ret = hsmp_read_acpi_crs(dev, sock); if (ret) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(sock); + ret = hsmp_read_acpi_dsd(dev, sock); + if (ret) + return ret; + + /* + * Publish sock->dev last. hsmp_send_message() uses it (via + * smp_load_acquire()) as the readiness gate for the lock-free data + * plane, so it must become visible only after virt_base_addr, the + * mailbox offsets and the semaphore are fully initialized. On a + * multi-socket system socket 0 exposes /dev/hsmp before later sockets + * finish probing, so without this an ioctl aimed at a socket still in + * bring-up could pass the gate and dereference a NULL virt_base_addr. + */ + smp_store_release(&sock->dev, dev); + + return 0; } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, @@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj struct device *dev = container_of(kobj, struct device, kobj); struct hsmp_socket *sock = dev_get_drvdata(dev); + /* + * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE. + * It can therefore only carry the protocol version 6 metric table + * (struct hsmp_metric_table). The larger tables defined from protocol + * version 7 onwards do not fit; userspace on those systems must read + * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp. + * Surface the unsupported case here as -EOPNOTSUPP rather than + * silently truncating the snapshot. + */ + if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6) + return -EOPNOTSUPP; + return hsmp_metric_tbl_read(sock, buf, count); } static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, const struct bin_attribute *battr, int id) { - if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) + /* + * Keep metrics_bin visible on protocol version 7 and later as well, + * so that userspace which expects the file to exist gets a clear + * -EOPNOTSUPP from the read handler instead of -ENOENT, and is + * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path. + */ + if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) return battr->attr.mode; return 0; @@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att return len; } +/* + * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox + * handshake and register its sysfs/hwmon interfaces. + * + * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the + * per-socket bring-up cannot race a concurrent probe or remove. + */ static int init_acpi(struct device *dev) { u16 sock_ind; int ret; + lockdep_assert_held_write(&hsmp_sock_rwsem); + ret = hsmp_get_uid(dev, &sock_ind); if (ret) return ret; @@ -491,7 +557,7 @@ static int init_acpi(struct device *dev) return ret; } - if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { + if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { ret = hsmp_get_tbl_dram_base(sock_ind); if (ret) dev_info(dev, "Failed to init metric table\n"); @@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = { }; MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids); +/* + * kref release: tear down the shared ACPI socket state once the last socket + * drops its reference. Deregister /dev/hsmp if it was registered, unmap any + * metric-table DRAM, destroy the per-socket mutexes and free the socket array. + * + * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove + * and probe-failure paths both drop their reference under that lock. The write + * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox + * and freeing the array cannot race the data plane. + */ +static void hsmp_acpi_sock_release(struct kref *kref) +{ + lockdep_assert_held_write(&hsmp_sock_rwsem); + + if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) + hsmp_misc_deregister(); + hsmp_unmap_metric_tbls(hsmp_pdev); + hsmp_destroy_metric_read_locks(hsmp_pdev); + kfree(hsmp_pdev->sock); + hsmp_pdev->sock = NULL; + hsmp_pdev->num_sockets = 0; + hsmp_pdev->proto_ver = 0; +} + +/** + * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe. + * @dev: ACPI companion device whose probe failed. + * + * This device already took a reference on entry to hsmp_acpi_probe(), so clear + * its sock->dev and drop that reference; the shared state is released if it was + * the last one. + * + * Clearing sock->dev matters on multi-socket systems: when a non-first socket + * fails, the array stays alive (owned by an already-probed socket) and + * remove() is never called for this device, yet devres unmaps its mailbox once + * probe() returns. Without clearing dev, a later message to this index would + * pass every gate in hsmp_send_message() and reach the unmapped mailbox. + * + * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata. + * + * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write. + */ +static void hsmp_acpi_probe_failure_cleanup(struct device *dev) +{ + struct hsmp_socket *sock = dev_get_drvdata(dev); + + lockdep_assert_held_write(&hsmp_sock_rwsem); + + if (sock) + sock->dev = NULL; + + kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); +} + static int hsmp_acpi_probe(struct platform_device *pdev) { int ret; @@ -584,34 +704,60 @@ static int hsmp_acpi_probe(struct platform_device *pdev) if (!hsmp_pdev) return -ENOMEM; - if (!hsmp_pdev->is_probed) { + /* + * Multiple ACPI socket devices probe in parallel, but the one-time + * socket-array allocation and /dev/hsmp registration below must run + * exactly once. Hold the socket rwsem for write across the whole + * bring-up so it cannot race a concurrent probe or remove, and so the + * probe-failure teardown drains the data plane. + */ + guard(rwsem_write)(&hsmp_sock_rwsem); + + if (!hsmp_pdev->sock) { hsmp_pdev->num_sockets = topology_max_packages(); if (!hsmp_pdev->num_sockets) { dev_err(&pdev->dev, "No CPU sockets detected\n"); return -ENODEV; } - hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, - sizeof(*hsmp_pdev->sock), - GFP_KERNEL); + hsmp_pdev->sock = kzalloc_objs(*hsmp_pdev->sock, + hsmp_pdev->num_sockets); if (!hsmp_pdev->sock) return -ENOMEM; + + hsmp_init_metric_read_locks(hsmp_pdev); + kref_init(&hsmp_acpi_sock_kref); + } else { + kref_get(&hsmp_acpi_sock_kref); } + /* + * This socket now holds a reference (kref_init on the first socket, + * kref_get afterwards). Every failure path below drops it via + * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to + * hsmp_acpi_remove(). + */ ret = init_acpi(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); + hsmp_acpi_probe_failure_cleanup(&pdev->dev); return ret; } - if (!hsmp_pdev->is_probed) { - ret = hsmp_misc_register(&pdev->dev); + if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) { + /* + * Register /dev/hsmp unparented. It is a singleton shared by all + * ACPI sockets and outlives all but the last of them, so + * parenting it to this socket's device would leave a dangling + * parent once that socket is unbound. + */ + ret = hsmp_misc_register(NULL); if (ret) { dev_err(&pdev->dev, "Failed to register misc device\n"); + hsmp_acpi_probe_failure_cleanup(&pdev->dev); return ret; } - hsmp_pdev->is_probed = true; - dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); + dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n"); } return 0; @@ -619,14 +765,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev) static void hsmp_acpi_remove(struct platform_device *pdev) { + struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev); + /* - * We register only one misc_device even on multi-socket system. - * So, deregister should happen only once. + * Serialize the kref_put() and any release it triggers against a + * concurrent probe, and drain the data plane for the whole + * teardown: this covers the per-socket unbind, whose mailbox devres + * unmaps once we return, and the last unbind that frees the socket + * array in hsmp_acpi_sock_release(). */ - if (hsmp_pdev->is_probed) { - hsmp_misc_deregister(); - hsmp_pdev->is_probed = false; - } + guard(rwsem_write)(&hsmp_sock_rwsem); + + /* + * Clear this socket's dev so hsmp_send_message() rejects it before + * devres unmaps the mailbox. On a non-final unbind the socket array + * stays alive, so without this a later message to this index would + * reach an unmapped iomem region. + */ + sock->dev = NULL; + + kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); } static struct platform_driver amd_hsmp_driver = { diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 631ffc0978d1..5e123a4ecea9 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -10,10 +10,17 @@ #include <asm/amd/hsmp.h> #include <linux/acpi.h> +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/nospec.h> +#include <linux/rwsem.h> #include <linux/semaphore.h> +#include <linux/slab.h> #include <linux/sysfs.h> +#include <linux/uaccess.h> #include "hsmp.h" @@ -41,6 +48,17 @@ static struct hsmp_plat_device hsmp_pdev; /* + * Gates the AMD HSMP data plane against socket bring-up and teardown. + * + * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads + * run concurrently. Probe and remove take it for write: probe brings sockets + * up (running the mailbox handshake via hsmp_send_message_locked()) and remove + * tears them down, both excluding and draining the data plane. + */ +DECLARE_RWSEM(hsmp_sock_rwsem); +EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); + +/* * Send a message to the HSMP port via PCI-e config space registers * or by writing to MMIO space. * @@ -182,28 +200,33 @@ static int validate_message(struct hsmp_message *msg) return -EINVAL; /* - * Some older HSMP SET messages are updated to add GET in the same message. - * In these messages, GET returns the current value and SET also returns - * the successfully set value. To support this GET and SET in same message - * while maintaining backward compatibility for the HSMP users, - * hsmp_msg_desc_table[] indicates only maximum allowed response_sz. + * As the HSMP protocol evolves, newer platforms may define more + * response arguments for existing messages. Use an upper-bound + * check so that older userspace callers requesting fewer response + * words than what the current hsmp_msg_desc_table[] defines are + * still accepted, while rejecting requests that exceed the + * hardware capability. */ - if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) { - if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) - return -EINVAL; - } else { - /* only HSMP_SET or HSMP_GET messages go through this strict check */ - if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) - return -EINVAL; - } + if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) + return -EINVAL; + return 0; } -int hsmp_send_message(struct hsmp_message *msg) +/* + * Core message send. The caller must hold hsmp_sock_rwsem: the data plane + * takes it for read so many messages run concurrently, while the probe-time + * senders run under the write lock taken by probe. Holding it here serializes + * every message against socket teardown, which also holds it for write. + */ +static int hsmp_send_message_locked(struct hsmp_message *msg) { struct hsmp_socket *sock; + unsigned int sock_ind; int ret; + lockdep_assert_held(&hsmp_sock_rwsem); + if (!msg) return -EINVAL; ret = validate_message(msg); @@ -212,7 +235,29 @@ int hsmp_send_message(struct hsmp_message *msg) if (!hsmp_pdev.sock || msg->sock_ind >= hsmp_pdev.num_sockets) return -ENODEV; - sock = &hsmp_pdev.sock[msg->sock_ind]; + + /* + * Sanitize sock_ind after the bounds check. A mispredicted branch can + * still let the CPU speculatively use msg->sock_ind as an index into + * hsmp_pdev.sock[] (Spectre v1, CVE-2017-5753), including for callers + * other than hsmp_ioctl_msg() that pass a user-derived socket index. + */ + sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + + /* + * A slot exists for every possible socket, but it is only usable once + * that socket has actually been probed. Reject messages aimed at a + * socket that was never brought up or is still in bring-up, so we never + * operate on a zero-initialized semaphore or an unmapped mailbox. A + * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and + * the semaphore are visible. + * + * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev) + * in hsmp_parse_acpi_table(). + */ + if (!smp_load_acquire(&sock->dev)) + return -ENODEV; ret = down_interruptible(&sock->hsmp_sem); if (ret < 0) @@ -224,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg) return ret; } + +int hsmp_send_message(struct hsmp_message *msg) +{ + /* + * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue + * messages from here. Take hsmp_sock_rwsem for read so messages run + * concurrently with each other but are drained and kept out while + * probe/remove hold it for write to tear a socket down. + */ + guard(rwsem_read)(&hsmp_sock_rwsem); + + return hsmp_send_message_locked(msg); +} EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args) @@ -264,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value) msg.args[0] = value; msg.sock_ind = sock_ind; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; @@ -292,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg) return false; } -long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) { int __user *arguser = (int __user *)arg; struct hsmp_message msg = { 0 }; @@ -308,6 +366,19 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX) return -ENOMSG; + /* + * Sanitize the user-controlled msg_id against speculative + * execution. The bounds check above retires the out-of-range + * case with -ENOMSG, but a mispredicted branch can still let the + * CPU speculatively use msg_id as an index into + * hsmp_msg_desc_table[] (here and in validate_message() / + * is_get_msg() called downstream via hsmp_send_message()), and + * pull arbitrary kernel memory into the cache (Spectre v1, + * CVE-2017-5753). Clamp once into msg.msg_id so every downstream + * dereference sees the sanitized value. + */ + msg.msg_id = array_index_nospec(msg.msg_id, HSMP_MSG_ID_MAX); + switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) { case FMODE_WRITE: /* @@ -348,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) return 0; } -ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) +static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, + size_t size); + +/* + * Fetch the firmware metric (telemetry) table for the requested socket and + * copy it to the userspace buffer described by the request. + * + * The metric table size is variable across HSMP protocol versions and on + * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer + * size, which may be anything up to the size firmware reported for this + * socket's table. + */ +static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg) +{ + void *kbuf __free(kvfree) = NULL; + void __user *arguser = (void __user *)arg; + struct hsmp_telemetry_data req; + struct hsmp_socket *sock; + void __user *user_buf; + size_t tbl_size; + unsigned int sock_ind; + int ret; + + /* Telemetry data is read-only; require read access on the fd. */ + if (!(fp->f_mode & FMODE_READ)) + return -EPERM; + + if (copy_from_user(&req, arguser, sizeof(req))) + return -EFAULT; + + /* + * Reserved fields must be zero so future kernels can safely + * repurpose them without breaking already-deployed userspace. + */ + if (req.reserved) + return -EINVAL; + + user_buf = u64_to_user_ptr(req.buf); + + /* + * /dev/hsmp is a singleton character device that outlives an individual + * socket unbind, so an ioctl on an already-open fd can run concurrently + * with socket teardown. Hold hsmp_sock_rwsem for read across the socket + * lookup, the checks on its metric-table state and the read itself: + * probe and remove take the same lock for write, so they cannot free the + * socket array, unmap the table or destroy the per-socket mutex while + * this runs. + * + * The lock is dropped before the copy_to_user() below. Faulting in the + * destination can block indefinitely on a userfaultfd-backed buffer, + * which would leave a socket unbind waiting for the write lock. + */ + scoped_guard(rwsem_read, &hsmp_sock_rwsem) { + if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets) + return -ENODEV; + + /* + * Sanitize the user-controlled socket index against speculative + * execution. The bounds check above retires the out-of-range + * case with -ENODEV, but a mispredicted branch can still let the + * CPU speculatively use sock_ind as an index into + * hsmp_pdev.sock[] and pull arbitrary kernel memory into the + * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns + * the bounds check into a data-flow clamp so the speculative + * load is in-range too. + */ + sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + if (!sock->metric_tbl_addr) + return -ENODEV; + + tbl_size = sock->metric_tbl_size; + if (!tbl_size) + return -ENODEV; + + /* + * A request shorter than the firmware table is served with the + * leading @size bytes of the snapshot, so userspace built + * against an older table layout keeps working on firmware that + * grew the table. Asking for more than firmware provides is + * rejected rather than short-written, so a caller can never + * mistake a partial copy for a full one. + */ + if (!req.size || req.size > tbl_size) + return -EINVAL; + + /* + * The bounce buffer is overwritten in full by memcpy_fromio() + * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid + * the zeroing cost of kvzalloc() on the ~13 KB allocation done + * on every ioctl call. + */ + kbuf = kvmalloc(tbl_size, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size); + } + + if (ret < 0) + return ret; + + if (copy_to_user(user_buf, kbuf, req.size)) + return -EFAULT; + + return 0; +} + +long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case HSMP_IOCTL_CMD: + return hsmp_ioctl_msg(fp, arg); + case HSMP_IOCTL_GET_TELEMETRY_DATA: + return hsmp_ioctl_get_telemetry(fp, arg); + default: + return -ENOTTY; + } +} + +/* + * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping + * and its metric_read_lock alive: probe and remove take the same lock for + * write while they bring sockets up and tear them down. + */ +static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, + size_t size) { struct hsmp_message msg = { 0 }; int ret; + lockdep_assert_held(&hsmp_sock_rwsem); + if (!sock || !buf) return -EINVAL; @@ -361,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) return -ENOMEM; } - /* Do not support lseek(), also don't allow more than the size of metric table */ - if (size != sizeof(struct hsmp_metric_table)) { + if (size != sock->metric_tbl_size) { dev_err(sock->dev, "Wrong buffer size\n"); return -EINVAL; } @@ -370,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) msg.msg_id = HSMP_GET_METRIC_TABLE; msg.sock_ind = sock->sock_ind; - ret = hsmp_send_message(&msg); + /* + * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared + * metric DRAM region, which is then copied out below. Hold the + * per-socket lock across the fill-and-copy so concurrent readers of the + * same socket cannot return a torn snapshot. + */ + guard(mutex)(&sock->metric_read_lock); + + ret = hsmp_send_message_locked(&msg); if (ret) return ret; memcpy_fromio(buf, sock->metric_tbl_addr, size); return size; } + +ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) +{ + guard(rwsem_read)(&hsmp_sock_rwsem); + + return hsmp_metric_tbl_read_locked(sock, buf, size); +} EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); +void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) +{ + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) + mutex_init(&pdev->sock[i].metric_read_lock); +} +EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP"); + +void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev) +{ + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) + mutex_destroy(&pdev->sock[i].metric_read_lock); +} +EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP"); + +void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) +{ + struct hsmp_socket *sock; + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) { + sock = &pdev->sock[i]; + if (sock->metric_tbl_addr) { + iounmap(sock->metric_tbl_addr); + sock->metric_tbl_addr = NULL; + } + sock->metric_tbl_size = 0; + } +} +EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); + int hsmp_get_tbl_dram_base(u16 sock_ind) { struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; struct hsmp_message msg = { 0 }; phys_addr_t dram_addr; + size_t tbl_size; int ret; msg.sock_ind = sock_ind; msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; @@ -403,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) dev_err(sock->dev, "Invalid DRAM address for metric table\n"); return -ENOMEM; } - sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, - sizeof(struct hsmp_metric_table)); + /* + * The ACPI socket array is shared across sockets and outlives a + * per-socket unbind, so metric_tbl_addr may hold a mapping from an + * earlier bind of this socket. Unmap it before remapping so an + * unbind/rebind cycle does not leak a metric-table mapping. This runs + * during probe before the metric sysfs attribute is exposed, so no + * reader can be using it. + */ + if (sock->metric_tbl_addr) { + iounmap(sock->metric_tbl_addr); + sock->metric_tbl_addr = NULL; + } + sock->metric_tbl_size = 0; + + /* SMU returns table size from Family 1Ah Model 50h and forward */ + if (msg.args[2]) + tbl_size = msg.args[2]; + else + tbl_size = sizeof(struct hsmp_metric_table); + + sock->metric_tbl_addr = ioremap(dram_addr, tbl_size); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); return -ENOMEM; } + sock->metric_tbl_size = tbl_size; + return 0; } EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP"); @@ -422,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind) msg.sock_ind = sock_ind; msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (!ret) hsmp_pdev.proto_ver = msg.args[0]; @@ -441,6 +710,14 @@ int hsmp_misc_register(struct device *dev) hsmp_pdev.mdev.name = HSMP_CDEV_NAME; hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR; hsmp_pdev.mdev.fops = &hsmp_fops; + /* + * The caller chooses the parent. The platform driver has a single + * device whose lifetime matches /dev/hsmp and parents it there. The + * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by + * per-socket devices that can be unbound individually and out of order, + * so parenting it to one would leave it attached to an already-removed + * device. + */ hsmp_pdev.mdev.parent = dev; hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; hsmp_pdev.mdev.mode = 0644; @@ -452,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP"); void hsmp_misc_deregister(void) { misc_deregister(&hsmp_pdev.mdev); + hsmp_pdev.mdev.this_device = NULL; } EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP"); diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 0509a442eaae..8dbff16a87b1 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -15,9 +15,12 @@ #include <linux/hwmon.h> #include <linux/kconfig.h> #include <linux/miscdevice.h> +#include <linux/mutex.h> #include <linux/pci.h> +#include <linux/rwsem.h> #include <linux/semaphore.h> #include <linux/sysfs.h> +#include <linux/types.h> #define HSMP_METRICS_TABLE_NAME "metrics_bin" @@ -27,7 +30,7 @@ #define HSMP_DEVNODE_NAME "hsmp" #define ACPI_HSMP_DEVICE_HID "AMDI0097" -#define DRIVER_VERSION "2.5" +#define DRIVER_VERSION "2.6" struct hsmp_mbaddr_info { u32 base_addr; @@ -41,8 +44,12 @@ struct hsmp_socket { struct bin_attribute hsmp_attr; struct hsmp_mbaddr_info mbinfo; void __iomem *metric_tbl_addr; + /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */ + size_t metric_tbl_size; void __iomem *virt_base_addr; struct semaphore hsmp_sem; + /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ + struct mutex metric_read_lock; char name[HSMP_ATTR_GRP_NAME_SIZE]; struct device *dev; u16 sock_ind; @@ -54,7 +61,6 @@ struct hsmp_plat_device { struct hsmp_socket *sock; u32 proto_ver; u16 num_sockets; - bool is_probed; }; int hsmp_cache_proto_ver(u16 sock_ind); @@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); void hsmp_misc_deregister(void); int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); +void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); +void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev); +void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev); ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); #if IS_ENABLED(CONFIG_HWMON) @@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } #endif int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); + +/* + * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and + * remove take it for write to bring sockets up and tear them down. + */ +extern struct rw_semaphore hsmp_sock_rwsem; #endif /* HSMP_H */ diff --git a/drivers/platform/x86/amd/hsmp/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c index 0cc9a742497f..c8314eee06f4 100644 --- a/drivers/platform/x86/amd/hsmp/hwmon.c +++ b/drivers/platform/x86/amd/hsmp/hwmon.c @@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type, if (attr != hwmon_power_cap) return -EOPNOTSUPP; + if (val < 0) + return -EINVAL; + msg.num_args = 1; msg.args[0] = val / MICROWATT_PER_MILLIWATT; msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT; diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index e07f68575055..e9b2b809c0f5 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -13,12 +13,14 @@ #include <linux/acpi.h> #include <linux/build_bug.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/dev_printk.h> #include <linux/kconfig.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/platform_device.h> +#include <linux/rwsem.h> #include <linux/sysfs.h> #include <asm/amd/node.h> @@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev) return 0; } +/* + * The socket array is devm-managed and freed by the driver core, but the + * metric-table DRAM regions are mapped with plain ioremap() during probe and + * the per-socket mutexes need an explicit mutex_destroy(), neither of which + * devres covers. + * + * Take the data-plane rwsem for write to drain any in-flight + * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop + * the global socket pointer, all before devres frees the array. Registered as + * a devres action so it runs on both remove and probe failure. + */ +static void hsmp_pltdrv_release(void *data) +{ + guard(rwsem_write)(&hsmp_sock_rwsem); + hsmp_unmap_metric_tbls(hsmp_pdev); + hsmp_destroy_metric_read_locks(hsmp_pdev); + hsmp_pdev->sock = NULL; +} + static int hsmp_pltdrv_probe(struct platform_device *pdev) { int ret; @@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) if (!hsmp_pdev->sock) return -ENOMEM; - ret = init_platform_device(&pdev->dev); + hsmp_init_metric_read_locks(hsmp_pdev); + + ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); + if (ret) + return ret; + + /* + * init_platform_device() runs the mailbox handshake via the probe-only + * senders, which issue messages through hsmp_send_message_locked() and + * so require hsmp_sock_rwsem held. Hold it for write, matching probe's + * role as a socket bring-up path. The lock is not held across + * devm_add_action_or_reset() above so the release action, which also + * takes it for write, does not deadlock if that registration fails. + */ + scoped_guard(rwsem_write, &hsmp_sock_rwsem) + ret = init_platform_device(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); return ret; |
