// SPDX-License-Identifier: GPL-2.0-only #define pr_fmt(fmt) "papr-indices: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "papr-rtas-common.h" /* * Function-specific return values for ibm,set-dynamic-indicator and * ibm,get-dynamic-sensor-state RTAS calls. * PAPR+ v2.13 7.3.18 and 7.3.19. */ #define RTAS_IBM_DYNAMIC_INDICE_NO_INDICATOR -3 /** * struct rtas_get_indices_params - Parameters (in and out) for * ibm,get-indices. * @is_sensor: In: Caller-provided whether sensor or indicator. * @indice_type:In: Caller-provided indice (sensor or indicator) token * @work_area: In: Caller-provided work area buffer for results. * @next: In: Sequence number. Out: Next sequence number. * @status: Out: RTAS call status. */ struct rtas_get_indices_params { u8 is_sensor; u32 indice_type; struct rtas_work_area *work_area; u32 next; s32 status; }; /* * rtas_ibm_get_indices() - Call ibm,get-indices to fill a work area buffer. * @params: See &struct rtas_ibm_get_indices_params. * * Calls ibm,get-indices 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_indices() multiple times * to retrieve all indices data for the provided indice type. Only one * sequence should be in progress at any time; starting a new sequence * will disrupt any sequence already in progress. Serialization of * indices 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_indices(struct rtas_get_indices_params *params) { struct rtas_work_area *work_area = params->work_area; const s32 token = rtas_function_token(RTAS_FN_IBM_GET_INDICES); u32 rets; s32 fwrc; int ret; if (token == RTAS_UNKNOWN_SERVICE) return -ENOENT; lockdep_assert_held(&rtas_ibm_get_indices_lock); do { fwrc = rtas_call(token, 5, 2, &rets, params->is_sensor, params->indice_type, rtas_work_area_phys(work_area), rtas_work_area_size(work_area), params->next); } while (rtas_busy_delay(fwrc)); switch (fwrc) { case RTAS_HARDWARE_ERROR: ret = -EIO; break; case RTAS_INVALID_PARAMETER: /* Indicator type is not supported */ ret = -EINVAL; break; case RTAS_SEQ_START_OVER: ret = -EAGAIN; pr_info_ratelimited("Indices changed during retrieval, retrying\n"); params->next = 1; break; case RTAS_SEQ_MORE_DATA: params->next = rets; ret = 0; break; case RTAS_SEQ_COMPLETE: params->next = 0; ret = 0; break; default: ret = -EIO; pr_err_ratelimited("unexpected ibm,get-indices status %d\n", fwrc); break; } params->status = fwrc; return ret; } /* * Internal indices sequence APIs. A sequence is a series of calls to * ibm,get-indices for a given location code. The sequence ends when * an error is encountered or all indices for the input has been * returned. */ /* * indices_sequence_begin() - Begin a indices retrieval sequence. * * Context: May sleep. */ static void indices_sequence_begin(struct papr_rtas_sequence *seq) { struct rtas_get_indices_params *param; param = (struct rtas_get_indices_params *)seq->params; /* * 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_indices_lock); param->work_area = rtas_work_area_alloc(RTAS_GET_INDICES_BUF_SIZE); param->next = 1; param->status = 0; } /* * indices_sequence_end() - Finalize a indices retrieval sequence. * * Releases resources obtained by indices_sequence_begin(). */ static void indices_sequence_end(struct papr_rtas_sequence *seq) { struct rtas_get_indices_params *param; param = (struct rtas_get_indices_params *)seq->params; rtas_work_area_free(param->work_area); mutex_unlock(&rtas_ibm_get_indices_lock); } /* * Work function to be passed to papr_rtas_blob_generate(). * * ibm,get-indices RTAS call fills the work area with the certain * format but does not return the bytes written in the buffer. So * instead of kernel parsing this work area to determine the buffer * length, copy the complete work area (RTAS_GET_INDICES_BUF_SIZE) * to the blob and let the user space to obtain the data. * Means RTAS_GET_INDICES_BUF_SIZE data will be returned for each * read(). */ static const char *indices_sequence_fill_work_area(struct papr_rtas_sequence *seq, size_t *len) { struct rtas_get_indices_params *p; bool init_state; p = (struct rtas_get_indices_params *)seq->params; init_state = (p->next == 1) ? true : false; if (papr_rtas_sequence_should_stop(seq, p->status, init_state)) return NULL; if (papr_rtas_sequence_set_err(seq, rtas_ibm_get_indices(p))) return NULL; *len = RTAS_GET_INDICES_BUF_SIZE; return rtas_work_area_raw_buf(p->work_area); } /* * papr_indices_handle_read - returns indices blob data to the user space * * ibm,get-indices RTAS call fills the work area with the certian * format but does not return the bytes written in the buffer and * copied RTAS_GET_INDICES_BUF_SIZE data to the blob for each RTAS * call. So send RTAS_GET_INDICES_BUF_SIZE buffer to the user space * for each read(). */ static ssize_t papr_indices_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; } if (size < RTAS_GET_INDICES_BUF_SIZE) { pr_err_once("Invalid buffer length %ld, expect %d\n", size, RTAS_GET_INDICES_BUF_SIZE); return -EINVAL; } else if (size > RTAS_GET_INDICES_BUF_SIZE) size = RTAS_GET_INDICES_BUF_SIZE; return simple_read_from_buffer(buf, size, off, blob->data, blob->len); } static const struct file_operations papr_indices_handle_ops = { .read = papr_indices_handle_read, .llseek = papr_rtas_common_handle_seek, .release = papr_rtas_common_handle_release, }; /* * papr_indices_create_handle() - Create a fd-based handle for reading * indices data * @ubuf: Input parameters to RTAS call such as whether sensor or indicator * and indice type in user memory * * Handler for PAPR_INDICES_IOC_GET ioctl command. Validates @ubuf * and instantiates an immutable indices "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 indices 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-indices call sequences. * * Return: The installed fd number if successful, -ve errno otherwise. */ static long papr_indices_create_handle(struct papr_indices_io_block __user *ubuf) { struct papr_rtas_sequence seq = {}; struct rtas_get_indices_params params = {}; int fd; if (get_user(params.is_sensor, &ubuf->indices.is_sensor)) return -EFAULT; if (get_user(params.indice_type, &ubuf->indices.indice_type)) return -EFAULT; seq = (struct papr_rtas_sequence) { .begin = indices_sequence_begin, .end = indices_sequence_end, .work = indices_sequence_fill_work_area, }; seq.params = ¶ms; fd = papr_rtas_setup_file_interface(&seq, &papr_indices_handle_ops, "[papr-indices]"); return fd; } /* * Create work area with the input parameters. This function is used * for both ibm,set-dynamic-indicator and ibm,get-dynamic-sensor-state * RTAS Calls. */ static struct rtas_work_area * papr_dynamic_indice_buf_from_user(struct papr_indices_io_block __user *ubuf, struct papr_indices_io_block *kbuf) { struct rtas_work_area *work_area; u32 length; __be32 len_be; if (copy_from_user(kbuf, ubuf, sizeof(*kbuf))) return ERR_PTR(-EFAULT); if (!string_is_terminated(kbuf->dynamic_param.location_code_str, ARRAY_SIZE(kbuf->dynamic_param.location_code_str))) return ERR_PTR(-EINVAL); /* * The input data in the work area should be as follows: * - 32-bit integer length of the location code string, * including NULL. * - Location code string, NULL terminated, identifying the * token (sensor or indicator). * PAPR 2.13 - R1–7.3.18–5 ibm,set-dynamic-indicator * - R1–7.3.19–5 ibm,get-dynamic-sensor-state */ /* * Length that user space passed should also include NULL * terminator. */ length = strlen(kbuf->dynamic_param.location_code_str) + 1; if (length > LOC_CODE_SIZE) return ERR_PTR(-EINVAL); len_be = cpu_to_be32(length); work_area = rtas_work_area_alloc(LOC_CODE_SIZE + sizeof(u32)); memcpy(rtas_work_area_raw_buf(work_area), &len_be, sizeof(u32)); memcpy((rtas_work_area_raw_buf(work_area) + sizeof(u32)), &kbuf->dynamic_param.location_code_str, length); return work_area; } /** * papr_dynamic_indicator_ioc_set - ibm,set-dynamic-indicator RTAS Call * PAPR 2.13 7.3.18 * * @ubuf: Input parameters to RTAS call such as indicator token and * new state. * * Returns success or -errno. */ static long papr_dynamic_indicator_ioc_set(struct papr_indices_io_block __user *ubuf) { struct papr_indices_io_block kbuf; struct rtas_work_area *work_area; s32 fwrc, token, ret; token = rtas_function_token(RTAS_FN_IBM_SET_DYNAMIC_INDICATOR); if (token == RTAS_UNKNOWN_SERVICE) return -ENOENT; mutex_lock(&rtas_ibm_set_dynamic_indicator_lock); work_area = papr_dynamic_indice_buf_from_user(ubuf, &kbuf); if (IS_ERR(work_area)) { ret = PTR_ERR(work_area); goto out; } do { fwrc = rtas_call(token, 3, 1, NULL, kbuf.dynamic_param.token, kbuf.dynamic_param.state, rtas_work_area_phys(work_area)); } while (rtas_busy_delay(fwrc)); rtas_work_area_free(work_area); switch (fwrc) { case RTAS_SUCCESS: ret = 0; break; case RTAS_IBM_DYNAMIC_INDICE_NO_INDICATOR: /* No such indicator */ ret = -EOPNOTSUPP; break; default: pr_err("unexpected ibm,set-dynamic-indicator result %d\n", fwrc); fallthrough; case RTAS_HARDWARE_ERROR: /* Hardware/platform error */ ret = -EIO; break; } out: mutex_unlock(&rtas_ibm_set_dynamic_indicator_lock); return ret; } /** * papr_dynamic_sensor_ioc_get - ibm,get-dynamic-sensor-state RTAS Call * PAPR 2.13 7.3.19 * * @ubuf: Input parameters to RTAS call such as sensor token * Copies the state in user space buffer. * * * Returns success or -errno. */ static long papr_dynamic_sensor_ioc_get(struct papr_indices_io_block __user *ubuf) { struct papr_indices_io_block kbuf; struct rtas_work_area *work_area; s32 fwrc, token, ret; u32 rets; token = rtas_function_token(RTAS_FN_IBM_GET_DYNAMIC_SENSOR_STATE); if (token == RTAS_UNKNOWN_SERVICE) return -ENOENT; mutex_lock(&rtas_ibm_get_dynamic_sensor_state_lock); work_area = papr_dynamic_indice_buf_from_user(ubuf, &kbuf); if (IS_ERR(work_area)) { ret = PTR_ERR(work_area); goto out; } do { fwrc = rtas_call(token, 2, 2, &rets, kbuf.dynamic_param.token, rtas_work_area_phys(work_area)); } while (rtas_busy_delay(fwrc)); rtas_work_area_free(work_area); switch (fwrc) { case RTAS_SUCCESS: if (put_user(rets, &ubuf->dynamic_param.state)) ret = -EFAULT; else ret = 0; break; case RTAS_IBM_DYNAMIC_INDICE_NO_INDICATOR: /* No such indicator */ ret = -EOPNOTSUPP; break; default: pr_err("unexpected ibm,get-dynamic-sensor result %d\n", fwrc); fallthrough; case RTAS_HARDWARE_ERROR: /* Hardware/platform error */ ret = -EIO; break; } out: mutex_unlock(&rtas_ibm_get_dynamic_sensor_state_lock); return ret; } /* * Top-level ioctl handler for /dev/papr-indices. */ static long papr_indices_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) { void __user *argp = (__force void __user *)arg; long ret; switch (ioctl) { case PAPR_INDICES_IOC_GET: ret = papr_indices_create_handle(argp); break; case PAPR_DYNAMIC_SENSOR_IOC_GET: ret = papr_dynamic_sensor_ioc_get(argp); break; case PAPR_DYNAMIC_INDICATOR_IOC_SET: if (filp->f_mode & FMODE_WRITE) ret = papr_dynamic_indicator_ioc_set(argp); else ret = -EBADF; break; default: ret = -ENOIOCTLCMD; break; } return ret; } static const struct file_operations papr_indices_ops = { .unlocked_ioctl = papr_indices_dev_ioctl, }; static struct miscdevice papr_indices_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "papr-indices", .fops = &papr_indices_ops, }; static __init int papr_indices_init(void) { if (!rtas_function_implemented(RTAS_FN_IBM_GET_INDICES)) return -ENODEV; if (!rtas_function_implemented(RTAS_FN_IBM_SET_DYNAMIC_INDICATOR)) return -ENODEV; if (!rtas_function_implemented(RTAS_FN_IBM_GET_DYNAMIC_SENSOR_STATE)) return -ENODEV; return misc_register(&papr_indices_dev); } machine_device_initcall(pseries, papr_indices_init);