mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-30 21:52:21 +00:00

The RTAS call can be normal where retrieves the data form the hypervisor once or sequence based RTAS call which has to issue multiple times until the complete data is obtained. For some of these sequence RTAS calls, the OS should not interleave calls with different input until the sequence is completed. The data is collected for each call and copy to the buffer for the entire sequence during ioctl() handle and then expose this buffer to the user space with read() handle. One such sequence RTAS call is ibm,get-vpd and its support is already included in the current code. To add the similar support for other sequence based calls, move the common functions in to separate file and update papr_rtas_sequence struct with the following callbacks so that RTAS call specific code will be defined and executed to complete the sequence. struct papr_rtas_sequence { int error; void params; void (*begin) (struct papr_rtas_sequence *); void (*end) (struct papr_rtas_sequence *); const char * (*work) (struct papr_rtas_sequence *, size_t *); }; params: Input parameters used to pass for RTAS call. Begin: RTAS call specific function to initialize data including work area allocation. End: RTAS call specific function to free up resources (free work area) after the sequence is completed. Work: The actual RTAS call specific function which collects the data from the hypervisor. Signed-off-by: Haren Myneni <haren@linux.ibm.com> Tested-by: Sathvika Vasireddy <sv@linux.ibm.com> Signed-off-by: Madhavan Srinivasan <maddy@linux.ibm.com> Link: https://patch.msgid.link/20250416225743.596462-2-haren@linux.ibm.com
312 lines
7.8 KiB
C
312 lines
7.8 KiB
C
// 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(sizeof(*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;
|
||
struct file *file;
|
||
long ret;
|
||
int fd;
|
||
|
||
blob = papr_rtas_retrieve(seq);
|
||
if (IS_ERR(blob))
|
||
return PTR_ERR(blob);
|
||
|
||
fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
|
||
if (fd < 0) {
|
||
ret = fd;
|
||
goto free_blob;
|
||
}
|
||
|
||
file = anon_inode_getfile_fmode(name, fops, (void *)blob,
|
||
O_RDONLY, FMODE_LSEEK | FMODE_PREAD);
|
||
if (IS_ERR(file)) {
|
||
ret = PTR_ERR(file);
|
||
goto put_fd;
|
||
}
|
||
|
||
fd_install(fd, file);
|
||
return fd;
|
||
|
||
put_fd:
|
||
put_unused_fd(fd);
|
||
free_blob:
|
||
papr_rtas_blob_free(blob);
|
||
return ret;
|
||
}
|
||
|
||
/*
|
||
* 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);
|
||
}
|