| // SPDX-License-Identifier: GPL-2.0-only |
| |
| #define pr_fmt(fmt) "papr-common: " fmt |
| |
| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/signal.h> |
| #include <linux/slab.h> |
| #include <linux/file.h> |
| #include <linux/fs.h> |
| #include <linux/anon_inodes.h> |
| #include <linux/sched/signal.h> |
| #include "papr-rtas-common.h" |
| |
| /* |
| * Sequence based RTAS HCALL has to issue multiple times to retrieve |
| * complete data from the hypervisor. For some of these RTAS calls, |
| * the OS should not interleave calls with different input until the |
| * sequence is completed. So data is collected for these calls during |
| * ioctl handle and export to user space with read() handle. |
| * This file provides common functions needed for such sequence based |
| * RTAS calls Ex: ibm,get-vpd and ibm,get-indices. |
| */ |
| |
| bool papr_rtas_blob_has_data(const struct papr_rtas_blob *blob) |
| { |
| return blob->data && blob->len; |
| } |
| |
| void papr_rtas_blob_free(const struct papr_rtas_blob *blob) |
| { |
| if (blob) { |
| kvfree(blob->data); |
| kfree(blob); |
| } |
| } |
| |
| /** |
| * papr_rtas_blob_extend() - Append data to a &struct papr_rtas_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 papr_rtas_blob_extend(struct papr_rtas_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 = kvrealloc(old_ptr, new_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; |
| } |
| |
| /** |
| * papr_rtas_blob_generate() - Construct a new &struct papr_rtas_blob. |
| * @seq: work function of the caller that is called to obtain |
| * data with the caller RTAS call. |
| * |
| * The @work callback is invoked until it returns NULL. @seq is |
| * passed to @work in its first argument on each call. When |
| * @work returns data, it should store the data length in its |
| * second argument. |
| * |
| * Context: May sleep. |
| * Return: A completely populated &struct papr_rtas_blob, or NULL on error. |
| */ |
| static const struct papr_rtas_blob * |
| papr_rtas_blob_generate(struct papr_rtas_sequence *seq) |
| { |
| struct papr_rtas_blob *blob; |
| const char *buf; |
| size_t len; |
| int err = 0; |
| |
| blob = kzalloc_obj(*blob, GFP_KERNEL_ACCOUNT); |
| if (!blob) |
| return NULL; |
| |
| if (!seq->work) |
| return ERR_PTR(-EINVAL); |
| |
| |
| while (err == 0 && (buf = seq->work(seq, &len))) |
| err = papr_rtas_blob_extend(blob, buf, len); |
| |
| if (err != 0 || !papr_rtas_blob_has_data(blob)) |
| goto free_blob; |
| |
| return blob; |
| free_blob: |
| papr_rtas_blob_free(blob); |
| return NULL; |
| } |
| |
| int papr_rtas_sequence_set_err(struct papr_rtas_sequence *seq, int err) |
| { |
| /* Preserve the first error recorded. */ |
| if (seq->error == 0) |
| seq->error = err; |
| |
| return seq->error; |
| } |
| |
| /* |
| * Higher-level retrieval code below. These functions use the |
| * papr_rtas_blob_* and sequence_* APIs defined above to create fd-based |
| * handles for consumption by user space. |
| */ |
| |
| /** |
| * papr_rtas_run_sequence() - Run a single retrieval sequence. |
| * @seq: Functions of the caller to complete the sequence |
| * |
| * Context: May sleep. Holds a mutex and an RTAS work area for its |
| * duration. Typically performs multiple sleepable slab |
| * allocations. |
| * |
| * Return: A populated &struct papr_rtas_blob on success. Encoded error |
| * pointer otherwise. |
| */ |
| static const struct papr_rtas_blob *papr_rtas_run_sequence(struct papr_rtas_sequence *seq) |
| { |
| const struct papr_rtas_blob *blob; |
| |
| if (seq->begin) |
| seq->begin(seq); |
| |
| blob = papr_rtas_blob_generate(seq); |
| if (!blob) |
| papr_rtas_sequence_set_err(seq, -ENOMEM); |
| |
| if (seq->end) |
| seq->end(seq); |
| |
| |
| if (seq->error) { |
| papr_rtas_blob_free(blob); |
| return ERR_PTR(seq->error); |
| } |
| |
| return blob; |
| } |
| |
| /** |
| * papr_rtas_retrieve() - Return the data blob that is exposed to |
| * user space. |
| * @seq: RTAS call specific functions to be invoked until the |
| * sequence is completed. |
| * |
| * Run sequences against @param 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 data blob when successful. Encoded error |
| * pointer otherwise. |
| */ |
| const struct papr_rtas_blob *papr_rtas_retrieve(struct papr_rtas_sequence *seq) |
| { |
| const struct papr_rtas_blob *blob; |
| |
| /* |
| * EAGAIN means the sequence returns error with a -4 (data |
| * changed and need to start the sequence) status from RTAS calls |
| * and we should attempt a new sequence. PAPR+ (v2.13 R1–7.3.20–5 |
| * - ibm,get-vpd, R1–7.3.17–6 - ibm,get-indices) 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_rtas_run_sequence(seq); |
| if (!IS_ERR(blob)) /* Success. */ |
| break; |
| if (PTR_ERR(blob) != -EAGAIN) /* Hard error. */ |
| break; |
| cond_resched(); |
| } while (!fatal_signal_pending(current)); |
| |
| return blob; |
| } |
| |
| /** |
| * papr_rtas_setup_file_interface - Complete the sequence and obtain |
| * the data and export to user space with fd-based handles. Then the |
| * user spave gets the data with read() handle. |
| * @seq: RTAS call specific functions to get the data. |
| * @fops: RTAS call specific file operations such as read(). |
| * @name: RTAS call specific char device node. |
| * |
| * Return: FD handle for consumption by user space |
| */ |
| long papr_rtas_setup_file_interface(struct papr_rtas_sequence *seq, |
| const struct file_operations *fops, |
| char *name) |
| { |
| const struct papr_rtas_blob *blob; |
| int fd; |
| |
| blob = papr_rtas_retrieve(seq); |
| if (IS_ERR(blob)) |
| return PTR_ERR(blob); |
| |
| fd = FD_ADD(O_RDONLY | O_CLOEXEC, |
| anon_inode_getfile_fmode(name, fops, (void *)blob, O_RDONLY, |
| FMODE_LSEEK | FMODE_PREAD)); |
| if (fd < 0) |
| papr_rtas_blob_free(blob); |
| return fd; |
| } |
| |
| /* |
| * papr_rtas_sequence_should_stop() - Determine whether RTAS retrieval |
| * sequence should continue. |
| * |
| * Examines the sequence error state and outputs of the last call to |
| * the specific RTAS to determine whether the sequence in progress |
| * should continue or stop. |
| * |
| * Return: True if the sequence has encountered an error or if all data |
| * for this sequence has been retrieved. False otherwise. |
| */ |
| bool papr_rtas_sequence_should_stop(const struct papr_rtas_sequence *seq, |
| s32 status, bool init_state) |
| { |
| bool done; |
| |
| if (seq->error) |
| return true; |
| |
| switch (status) { |
| case RTAS_SEQ_COMPLETE: |
| if (init_state) |
| done = false; /* Initial state. */ |
| else |
| done = true; /* All data consumed. */ |
| break; |
| case RTAS_SEQ_MORE_DATA: |
| done = false; /* More data available. */ |
| break; |
| default: |
| done = true; /* Error encountered. */ |
| break; |
| } |
| |
| return done; |
| } |
| |
| /* |
| * User space read to retrieve data for the corresponding RTAS call. |
| * papr_rtas_blob is filled with the data using the corresponding RTAS |
| * call sequence API. |
| */ |
| ssize_t papr_rtas_common_handle_read(struct file *file, |
| char __user *buf, size_t size, loff_t *off) |
| { |
| const struct papr_rtas_blob *blob = file->private_data; |
| |
| /* We should not instantiate a handle without any data attached. */ |
| if (!papr_rtas_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); |
| } |
| |
| int papr_rtas_common_handle_release(struct inode *inode, |
| struct file *file) |
| { |
| const struct papr_rtas_blob *blob = file->private_data; |
| |
| papr_rtas_blob_free(blob); |
| |
| return 0; |
| } |
| |
| loff_t papr_rtas_common_handle_seek(struct file *file, loff_t off, |
| int whence) |
| { |
| const struct papr_rtas_blob *blob = file->private_data; |
| |
| return fixed_size_llseek(file, off, whence, blob->len); |
| } |