| // SPDX-License-Identifier: GPL-2.0-only |
| |
| #define pr_fmt(fmt) "papr-vpd: " fmt |
| |
| #include <linux/anon_inodes.h> |
| #include <linux/build_bug.h> |
| #include <linux/file.h> |
| #include <linux/fs.h> |
| #include <linux/init.h> |
| #include <linux/lockdep.h> |
| #include <linux/kernel.h> |
| #include <linux/miscdevice.h> |
| #include <linux/signal.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/string_helpers.h> |
| #include <linux/uaccess.h> |
| #include <asm/machdep.h> |
| #include <asm/papr-vpd.h> |
| #include <asm/rtas-work-area.h> |
| #include <asm/rtas.h> |
| #include <uapi/asm/papr-vpd.h> |
| |
| /* |
| * Function-specific return values for ibm,get-vpd, derived from PAPR+ |
| * v2.13 7.3.20 "ibm,get-vpd RTAS Call". |
| */ |
| #define RTAS_IBM_GET_VPD_COMPLETE 0 /* All VPD has been retrieved. */ |
| #define RTAS_IBM_GET_VPD_MORE_DATA 1 /* More VPD is available. */ |
| #define RTAS_IBM_GET_VPD_START_OVER -4 /* VPD changed, restart call sequence. */ |
| |
| /** |
| * struct rtas_ibm_get_vpd_params - Parameters (in and out) for ibm,get-vpd. |
| * @loc_code: In: Caller-provided location code buffer. Must be RTAS-addressable. |
| * @work_area: In: Caller-provided work area buffer for results. |
| * @sequence: In: Sequence number. Out: Next sequence number. |
| * @written: Out: Bytes written by ibm,get-vpd to @work_area. |
| * @status: Out: RTAS call status. |
| */ |
| struct rtas_ibm_get_vpd_params { |
| const struct papr_location_code *loc_code; |
| struct rtas_work_area *work_area; |
| u32 sequence; |
| u32 written; |
| s32 status; |
| }; |
| |
| /** |
| * rtas_ibm_get_vpd() - Call ibm,get-vpd to fill a work area buffer. |
| * @params: See &struct rtas_ibm_get_vpd_params. |
| * |
| * Calls ibm,get-vpd until it errors or successfully deposits data |
| * into the supplied work area. Handles RTAS retry statuses. Maps RTAS |
| * error statuses to reasonable errno values. |
| * |
| * The caller is expected to invoke rtas_ibm_get_vpd() multiple times |
| * to retrieve all the VPD for the provided location code. Only one |
| * sequence should be in progress at any time; starting a new sequence |
| * will disrupt any sequence already in progress. Serialization of VPD |
| * retrieval sequences is the responsibility of the caller. |
| * |
| * The caller should inspect @params.status to determine whether more |
| * calls are needed to complete the sequence. |
| * |
| * Context: May sleep. |
| * Return: -ve on error, 0 otherwise. |
| */ |
| static int rtas_ibm_get_vpd(struct rtas_ibm_get_vpd_params *params) |
| { |
| const struct papr_location_code *loc_code = params->loc_code; |
| struct rtas_work_area *work_area = params->work_area; |
| u32 rets[2]; |
| s32 fwrc; |
| int ret; |
| |
| lockdep_assert_held(&rtas_ibm_get_vpd_lock); |
| |
| do { |
| fwrc = rtas_call(rtas_function_token(RTAS_FN_IBM_GET_VPD), 4, 3, |
| rets, |
| __pa(loc_code), |
| rtas_work_area_phys(work_area), |
| rtas_work_area_size(work_area), |
| params->sequence); |
| } while (rtas_busy_delay(fwrc)); |
| |
| switch (fwrc) { |
| case RTAS_HARDWARE_ERROR: |
| ret = -EIO; |
| break; |
| case RTAS_INVALID_PARAMETER: |
| ret = -EINVAL; |
| break; |
| case RTAS_IBM_GET_VPD_START_OVER: |
| ret = -EAGAIN; |
| break; |
| case RTAS_IBM_GET_VPD_MORE_DATA: |
| params->sequence = rets[0]; |
| fallthrough; |
| case RTAS_IBM_GET_VPD_COMPLETE: |
| params->written = rets[1]; |
| /* |
| * Kernel or firmware bug, do not continue. |
| */ |
| if (WARN(params->written > rtas_work_area_size(work_area), |
| "possible write beyond end of work area")) |
| ret = -EFAULT; |
| else |
| ret = 0; |
| break; |
| default: |
| ret = -EIO; |
| pr_err_ratelimited("unexpected ibm,get-vpd status %d\n", fwrc); |
| break; |
| } |
| |
| params->status = fwrc; |
| return ret; |
| } |
| |
| /* |
| * Internal VPD "blob" APIs for accumulating ibm,get-vpd results into |
| * an immutable buffer to be attached to a file descriptor. |
| */ |
| struct vpd_blob { |
| const char *data; |
| size_t len; |
| }; |
| |
| static bool vpd_blob_has_data(const struct vpd_blob *blob) |
| { |
| return blob->data && blob->len; |
| } |
| |
| static void vpd_blob_free(const struct vpd_blob *blob) |
| { |
| if (blob) { |
| kvfree(blob->data); |
| kfree(blob); |
| } |
| } |
| |
| /** |
| * vpd_blob_extend() - Append data to a &struct vpd_blob. |
| * @blob: The blob to extend. |
| * @data: The new data to append to @blob. |
| * @len: The length of @data. |
| * |
| * Context: May sleep. |
| * Return: -ENOMEM on allocation failure, 0 otherwise. |
| */ |
| static int vpd_blob_extend(struct vpd_blob *blob, const char *data, size_t len) |
| { |
| const size_t new_len = blob->len + len; |
| const size_t old_len = blob->len; |
| const char *old_ptr = blob->data; |
| char *new_ptr; |
| |
| new_ptr = old_ptr ? |
| kvrealloc(old_ptr, old_len, new_len, GFP_KERNEL_ACCOUNT) : |
| kvmalloc(len, GFP_KERNEL_ACCOUNT); |
| |
| if (!new_ptr) |
| return -ENOMEM; |
| |
| memcpy(&new_ptr[old_len], data, len); |
| blob->data = new_ptr; |
| blob->len = new_len; |
| return 0; |
| } |
| |
| /** |
| * vpd_blob_generate() - Construct a new &struct vpd_blob. |
| * @generator: Function that supplies the blob data. |
| * @arg: Context pointer supplied by caller, passed to @generator. |
| * |
| * The @generator callback is invoked until it returns NULL. @arg is |
| * passed to @generator in its first argument on each call. When |
| * @generator returns data, it should store the data length in its |
| * second argument. |
| * |
| * Context: May sleep. |
| * Return: A completely populated &struct vpd_blob, or NULL on error. |
| */ |
| static const struct vpd_blob * |
| vpd_blob_generate(const char * (*generator)(void *, size_t *), void *arg) |
| { |
| struct vpd_blob *blob; |
| const char *buf; |
| size_t len; |
| int err = 0; |
| |
| blob = kzalloc(sizeof(*blob), GFP_KERNEL_ACCOUNT); |
| if (!blob) |
| return NULL; |
| |
| while (err == 0 && (buf = generator(arg, &len))) |
| err = vpd_blob_extend(blob, buf, len); |
| |
| if (err != 0 || !vpd_blob_has_data(blob)) |
| goto free_blob; |
| |
| return blob; |
| free_blob: |
| vpd_blob_free(blob); |
| return NULL; |
| } |
| |
| /* |
| * Internal VPD sequence APIs. A VPD sequence is a series of calls to |
| * ibm,get-vpd for a given location code. The sequence ends when an |
| * error is encountered or all VPD for the location code has been |
| * returned. |
| */ |
| |
| /** |
| * struct vpd_sequence - State for managing a VPD sequence. |
| * @error: Shall be zero as long as the sequence has not encountered an error, |
| * -ve errno otherwise. Use vpd_sequence_set_err() to update this. |
| * @params: Parameter block to pass to rtas_ibm_get_vpd(). |
| */ |
| struct vpd_sequence { |
| int error; |
| struct rtas_ibm_get_vpd_params params; |
| }; |
| |
| /** |
| * vpd_sequence_begin() - Begin a VPD retrieval sequence. |
| * @seq: Uninitialized sequence state. |
| * @loc_code: Location code that defines the scope of the VPD to return. |
| * |
| * Initializes @seq with the resources necessary to carry out a VPD |
| * sequence. Callers must pass @seq to vpd_sequence_end() regardless |
| * of whether the sequence succeeds. |
| * |
| * Context: May sleep. |
| */ |
| static void vpd_sequence_begin(struct vpd_sequence *seq, |
| const struct papr_location_code *loc_code) |
| { |
| /* |
| * Use a static data structure for the location code passed to |
| * RTAS to ensure it's in the RMA and avoid a separate work |
| * area allocation. Guarded by the function lock. |
| */ |
| static struct papr_location_code static_loc_code; |
| |
| /* |
| * We could allocate the work area before acquiring the |
| * function lock, but that would allow concurrent requests to |
| * exhaust the limited work area pool for no benefit. So |
| * allocate the work area under the lock. |
| */ |
| mutex_lock(&rtas_ibm_get_vpd_lock); |
| static_loc_code = *loc_code; |
| *seq = (struct vpd_sequence) { |
| .params = { |
| .work_area = rtas_work_area_alloc(SZ_4K), |
| .loc_code = &static_loc_code, |
| .sequence = 1, |
| }, |
| }; |
| } |
| |
| /** |
| * vpd_sequence_end() - Finalize a VPD retrieval sequence. |
| * @seq: Sequence state. |
| * |
| * Releases resources obtained by vpd_sequence_begin(). |
| */ |
| static void vpd_sequence_end(struct vpd_sequence *seq) |
| { |
| rtas_work_area_free(seq->params.work_area); |
| mutex_unlock(&rtas_ibm_get_vpd_lock); |
| } |
| |
| /** |
| * vpd_sequence_should_stop() - Determine whether a VPD retrieval sequence |
| * should continue. |
| * @seq: VPD sequence state. |
| * |
| * Examines the sequence error state and outputs of the last call to |
| * ibm,get-vpd to determine whether the sequence in progress should |
| * continue or stop. |
| * |
| * Return: True if the sequence has encountered an error or if all VPD for |
| * this sequence has been retrieved. False otherwise. |
| */ |
| static bool vpd_sequence_should_stop(const struct vpd_sequence *seq) |
| { |
| bool done; |
| |
| if (seq->error) |
| return true; |
| |
| switch (seq->params.status) { |
| case 0: |
| if (seq->params.written == 0) |
| done = false; /* Initial state. */ |
| else |
| done = true; /* All data consumed. */ |
| break; |
| case 1: |
| done = false; /* More data available. */ |
| break; |
| default: |
| done = true; /* Error encountered. */ |
| break; |
| } |
| |
| return done; |
| } |
| |
| static int vpd_sequence_set_err(struct vpd_sequence *seq, int err) |
| { |
| /* Preserve the first error recorded. */ |
| if (seq->error == 0) |
| seq->error = err; |
| |
| return seq->error; |
| } |
| |
| /* |
| * Generator function to be passed to vpd_blob_generate(). |
| */ |
| static const char *vpd_sequence_fill_work_area(void *arg, size_t *len) |
| { |
| struct vpd_sequence *seq = arg; |
| struct rtas_ibm_get_vpd_params *p = &seq->params; |
| |
| if (vpd_sequence_should_stop(seq)) |
| return NULL; |
| if (vpd_sequence_set_err(seq, rtas_ibm_get_vpd(p))) |
| return NULL; |
| *len = p->written; |
| return rtas_work_area_raw_buf(p->work_area); |
| } |
| |
| /* |
| * Higher-level VPD retrieval code below. These functions use the |
| * vpd_blob_* and vpd_sequence_* APIs defined above to create fd-based |
| * VPD handles for consumption by user space. |
| */ |
| |
| /** |
| * papr_vpd_run_sequence() - Run a single VPD retrieval sequence. |
| * @loc_code: Location code that defines the scope of VPD to return. |
| * |
| * Context: May sleep. Holds a mutex and an RTAS work area for its |
| * duration. Typically performs multiple sleepable slab |
| * allocations. |
| * |
| * Return: A populated &struct vpd_blob on success. Encoded error |
| * pointer otherwise. |
| */ |
| static const struct vpd_blob *papr_vpd_run_sequence(const struct papr_location_code *loc_code) |
| { |
| const struct vpd_blob *blob; |
| struct vpd_sequence seq; |
| |
| vpd_sequence_begin(&seq, loc_code); |
| blob = vpd_blob_generate(vpd_sequence_fill_work_area, &seq); |
| if (!blob) |
| vpd_sequence_set_err(&seq, -ENOMEM); |
| vpd_sequence_end(&seq); |
| |
| if (seq.error) { |
| vpd_blob_free(blob); |
| return ERR_PTR(seq.error); |
| } |
| |
| return blob; |
| } |
| |
| /** |
| * papr_vpd_retrieve() - Return the VPD for a location code. |
| * @loc_code: Location code that defines the scope of VPD to return. |
| * |
| * Run VPD sequences against @loc_code until a blob is successfully |
| * instantiated, or a hard error is encountered, or a fatal signal is |
| * pending. |
| * |
| * Context: May sleep. |
| * Return: A fully populated VPD blob when successful. Encoded error |
| * pointer otherwise. |
| */ |
| static const struct vpd_blob *papr_vpd_retrieve(const struct papr_location_code *loc_code) |
| { |
| const struct vpd_blob *blob; |
| |
| /* |
| * EAGAIN means the sequence errored with a -4 (VPD changed) |
| * status from ibm,get-vpd, and we should attempt a new |
| * sequence. PAPR+ v2.13 R1–7.3.20–5 indicates that this |
| * should be a transient condition, not something that happens |
| * continuously. But we'll stop trying on a fatal signal. |
| */ |
| do { |
| blob = papr_vpd_run_sequence(loc_code); |
| if (!IS_ERR(blob)) /* Success. */ |
| break; |
| if (PTR_ERR(blob) != -EAGAIN) /* Hard error. */ |
| break; |
| pr_info_ratelimited("VPD changed during retrieval, retrying\n"); |
| cond_resched(); |
| } while (!fatal_signal_pending(current)); |
| |
| return blob; |
| } |
| |
| static ssize_t papr_vpd_handle_read(struct file *file, char __user *buf, size_t size, loff_t *off) |
| { |
| const struct vpd_blob *blob = file->private_data; |
| |
| /* bug: we should not instantiate a handle without any data attached. */ |
| if (!vpd_blob_has_data(blob)) { |
| pr_err_once("handle without data\n"); |
| return -EIO; |
| } |
| |
| return simple_read_from_buffer(buf, size, off, blob->data, blob->len); |
| } |
| |
| static int papr_vpd_handle_release(struct inode *inode, struct file *file) |
| { |
| const struct vpd_blob *blob = file->private_data; |
| |
| vpd_blob_free(blob); |
| |
| return 0; |
| } |
| |
| static loff_t papr_vpd_handle_seek(struct file *file, loff_t off, int whence) |
| { |
| const struct vpd_blob *blob = file->private_data; |
| |
| return fixed_size_llseek(file, off, whence, blob->len); |
| } |
| |
| |
| static const struct file_operations papr_vpd_handle_ops = { |
| .read = papr_vpd_handle_read, |
| .llseek = papr_vpd_handle_seek, |
| .release = papr_vpd_handle_release, |
| }; |
| |
| /** |
| * papr_vpd_create_handle() - Create a fd-based handle for reading VPD. |
| * @ulc: Location code in user memory; defines the scope of the VPD to |
| * retrieve. |
| * |
| * Handler for PAPR_VPD_IOC_CREATE_HANDLE ioctl command. Validates |
| * @ulc and instantiates an immutable VPD "blob" for it. The blob is |
| * attached to a file descriptor for reading by user space. The memory |
| * backing the blob is freed when the file is released. |
| * |
| * The entire requested VPD is retrieved by this call and all |
| * necessary RTAS interactions are performed before returning the fd |
| * to user space. This keeps the read handler simple and ensures that |
| * the kernel can prevent interleaving of ibm,get-vpd call sequences. |
| * |
| * Return: The installed fd number if successful, -ve errno otherwise. |
| */ |
| static long papr_vpd_create_handle(struct papr_location_code __user *ulc) |
| { |
| struct papr_location_code klc; |
| const struct vpd_blob *blob; |
| struct file *file; |
| long err; |
| int fd; |
| |
| if (copy_from_user(&klc, ulc, sizeof(klc))) |
| return -EFAULT; |
| |
| if (!string_is_terminated(klc.str, ARRAY_SIZE(klc.str))) |
| return -EINVAL; |
| |
| blob = papr_vpd_retrieve(&klc); |
| if (IS_ERR(blob)) |
| return PTR_ERR(blob); |
| |
| fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC); |
| if (fd < 0) { |
| err = fd; |
| goto free_blob; |
| } |
| |
| file = anon_inode_getfile("[papr-vpd]", &papr_vpd_handle_ops, |
| (void *)blob, O_RDONLY); |
| if (IS_ERR(file)) { |
| err = PTR_ERR(file); |
| goto put_fd; |
| } |
| |
| file->f_mode |= FMODE_LSEEK | FMODE_PREAD; |
| fd_install(fd, file); |
| return fd; |
| put_fd: |
| put_unused_fd(fd); |
| free_blob: |
| vpd_blob_free(blob); |
| return err; |
| } |
| |
| /* |
| * Top-level ioctl handler for /dev/papr-vpd. |
| */ |
| static long papr_vpd_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) |
| { |
| void __user *argp = (__force void __user *)arg; |
| long ret; |
| |
| switch (ioctl) { |
| case PAPR_VPD_IOC_CREATE_HANDLE: |
| ret = papr_vpd_create_handle(argp); |
| break; |
| default: |
| ret = -ENOIOCTLCMD; |
| break; |
| } |
| return ret; |
| } |
| |
| static const struct file_operations papr_vpd_ops = { |
| .unlocked_ioctl = papr_vpd_dev_ioctl, |
| }; |
| |
| static struct miscdevice papr_vpd_dev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "papr-vpd", |
| .fops = &papr_vpd_ops, |
| }; |
| |
| static __init int papr_vpd_init(void) |
| { |
| if (!rtas_function_implemented(RTAS_FN_IBM_GET_VPD)) |
| return -ENODEV; |
| |
| return misc_register(&papr_vpd_dev); |
| } |
| machine_device_initcall(pseries, papr_vpd_init); |