--- /dev/null
+// 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);
+}
 
 
 #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 <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. */
+#include "papr-rtas-common.h"
 
 /**
  * struct rtas_ibm_get_vpd_params - Parameters (in and out) for ibm,get-vpd.
        case RTAS_INVALID_PARAMETER:
                ret = -EINVAL;
                break;
-       case RTAS_IBM_GET_VPD_START_OVER:
+       case RTAS_SEQ_START_OVER:
                ret = -EAGAIN;
+               pr_info_ratelimited("VPD changed during retrieval, retrying\n");
                break;
-       case RTAS_IBM_GET_VPD_MORE_DATA:
+       case RTAS_SEQ_MORE_DATA:
                params->sequence = rets[0];
                fallthrough;
-       case RTAS_IBM_GET_VPD_COMPLETE:
+       case RTAS_SEQ_COMPLETE:
                params->written = rets[1];
                /*
                 * Kernel or firmware bug, do not continue.
        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 = 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;
-}
-
-/**
- * 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
  * 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.
+ * @seq: vpd call parameters from sequence struct
  *
  * Context: May sleep.
  */
-static void vpd_sequence_begin(struct vpd_sequence *seq,
-                              const struct papr_location_code *loc_code)
+static void vpd_sequence_begin(struct papr_rtas_sequence *seq)
 {
+       struct rtas_ibm_get_vpd_params *vpd_params;
        /*
         * Use a static data structure for the location code passed to
         * RTAS to ensure it's in the RMA and avoid a separate work
         */
        static struct papr_location_code static_loc_code;
 
+       vpd_params =  (struct rtas_ibm_get_vpd_params *)seq->params;
        /*
         * We could allocate the work area before acquiring the
         * function lock, but that would allow concurrent requests to
         * 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,
-               },
-       };
+       static_loc_code = *(struct papr_location_code *)vpd_params->loc_code;
+       vpd_params =  (struct rtas_ibm_get_vpd_params *)seq->params;
+       vpd_params->work_area = rtas_work_area_alloc(SZ_4K);
+       vpd_params->loc_code = &static_loc_code;
+       vpd_params->sequence = 1;
+       vpd_params->status = 0;
 }
 
 /**
  *
  * Releases resources obtained by vpd_sequence_begin().
  */
-static void vpd_sequence_end(struct vpd_sequence *seq)
+static void vpd_sequence_end(struct papr_rtas_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;
+       struct rtas_ibm_get_vpd_params *vpd_params;
 
-       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;
+       vpd_params =  (struct rtas_ibm_get_vpd_params *)seq->params;
+       rtas_work_area_free(vpd_params->work_area);
+       mutex_unlock(&rtas_ibm_get_vpd_lock);
 }
 
 /*
- * Generator function to be passed to vpd_blob_generate().
+ * Generator function to be passed to papr_rtas_blob_generate().
  */
-static const char *vpd_sequence_fill_work_area(void *arg, size_t *len)
+static const char *vpd_sequence_fill_work_area(struct papr_rtas_sequence *seq,
+                                               size_t *len)
 {
-       struct vpd_sequence *seq = arg;
-       struct rtas_ibm_get_vpd_params *p = &seq->params;
+       struct rtas_ibm_get_vpd_params *p;
+       bool init_state;
 
-       if (vpd_sequence_should_stop(seq))
+       p = (struct rtas_ibm_get_vpd_params *)seq->params;
+       init_state = (p->written == 0) ? true : false;
+
+       if (papr_rtas_sequence_should_stop(seq, p->status, init_state))
                return NULL;
-       if (vpd_sequence_set_err(seq, rtas_ibm_get_vpd(p)))
+       if (papr_rtas_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,
+       .read = papr_rtas_common_handle_read,
+       .llseek = papr_rtas_common_handle_seek,
+       .release = papr_rtas_common_handle_release,
 };
 
 /**
  */
 static long papr_vpd_create_handle(struct papr_location_code __user *ulc)
 {
+       struct rtas_ibm_get_vpd_params vpd_params = {};
+       struct papr_rtas_sequence seq = {};
        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)))
        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);
+       seq = (struct papr_rtas_sequence) {
+               .begin = vpd_sequence_begin,
+               .end = vpd_sequence_end,
+               .work = vpd_sequence_fill_work_area,
+       };
 
-       fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
-       if (fd < 0) {
-               err = fd;
-               goto free_blob;
-       }
+       vpd_params.loc_code = &klc;
+       seq.params = (void *)&vpd_params;
+
+       fd = papr_rtas_setup_file_interface(&seq, &papr_vpd_handle_ops,
+                       "[papr-vpd]");
 
-       file = anon_inode_getfile_fmode("[papr-vpd]", &papr_vpd_handle_ops,
-                                 (void *)blob, O_RDONLY,
-                                 FMODE_LSEEK | FMODE_PREAD);
-       if (IS_ERR(file)) {
-               err = PTR_ERR(file);
-               goto put_fd;
-       }
-       fd_install(fd, file);
        return fd;
-put_fd:
-       put_unused_fd(fd);
-free_blob:
-       vpd_blob_free(blob);
-       return err;
 }
 
 /*