--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "papr-platform-dump: " fmt
+
+#include <linux/anon_inodes.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <asm/machdep.h>
+#include <asm/rtas-work-area.h>
+#include <asm/rtas.h>
+#include <uapi/asm/papr-platform-dump.h>
+
+/*
+ * Function-specific return values for ibm,platform-dump, derived from
+ * PAPR+ v2.13 7.3.3.4.1 "ibm,platform-dump RTAS Call".
+ */
+#define        RTAS_IBM_PLATFORM_DUMP_COMPLETE 0       /* Complete dump retrieved. */
+#define        RTAS_IBM_PLATFORM_DUMP_CONTINUE 1       /* Continue dump */
+#define        RTAS_NOT_AUTHORIZED             -9002   /* Not Authorized */
+
+#define        RTAS_IBM_PLATFORM_DUMP_START    2 /* Linux status to start dump */
+
+/**
+ * struct ibm_platform_dump_params - Parameters (in and out) for
+ *                                              ibm,platform-dump
+ * @work_area:         In: work area buffer for results.
+ * @buf_length:                In: work area buffer length in bytes
+ * @dump_tag_hi:       In: Most-significant 32 bits of a Dump_Tag representing
+ *                      an id of the dump being processed.
+ * @dump_tag_lo:       In: Least-significant 32 bits of a Dump_Tag representing
+ *                      an id of the dump being processed.
+ * @sequence_hi:       In: Sequence number in most-significant 32 bits.
+ *                      Out: Next sequence number in most-significant 32 bits.
+ * @sequence_lo:       In: Sequence number in Least-significant 32 bits
+ *                      Out: Next sequence number in Least-significant 32 bits.
+ * @bytes_ret_hi:      Out: Bytes written in most-significant 32 bits.
+ * @bytes_ret_lo:      Out: Bytes written in Least-significant 32 bits.
+ * @status:            Out: RTAS call status.
+ * @list:              Maintain the list of dumps are in progress. Can
+ *                      retrieve multiple dumps with different dump IDs at
+ *                      the same time but not with the same dump ID. This list
+ *                      is used to determine whether the dump for the same ID
+ *                      is in progress.
+ */
+struct ibm_platform_dump_params {
+       struct rtas_work_area   *work_area;
+       u32                     buf_length;
+       u32                     dump_tag_hi;
+       u32                     dump_tag_lo;
+       u32                     sequence_hi;
+       u32                     sequence_lo;
+       u32                     bytes_ret_hi;
+       u32                     bytes_ret_lo;
+       s32                     status;
+       struct list_head        list;
+};
+
+/*
+ * Multiple dumps with different dump IDs can be retrieved at the same
+ * time, but not with dame dump ID. platform_dump_list_mutex and
+ * platform_dump_list are used to prevent this behavior.
+ */
+static DEFINE_MUTEX(platform_dump_list_mutex);
+static LIST_HEAD(platform_dump_list);
+
+/**
+ * rtas_ibm_platform_dump() - Call ibm,platform-dump to fill a work area
+ * buffer.
+ * @params: See &struct ibm_platform_dump_params.
+ * @buf_addr: Address of dump buffer (work_area)
+ * @buf_length: Length of the buffer in bytes (min. 1024)
+ *
+ * Calls ibm,platform-dump until it errors or successfully deposits data
+ * into the supplied work area. Handles RTAS retry statuses. Maps RTAS
+ * error statuses to reasonable errno values.
+ *
+ * Can request multiple dumps with different dump IDs at the same time,
+ * but not with the same dump ID which is prevented with the check in
+ * the ioctl code (papr_platform_dump_create_handle()).
+ *
+ * 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 for dump complete and 1 for continue dump
+ */
+static int rtas_ibm_platform_dump(struct ibm_platform_dump_params *params,
+                               phys_addr_t buf_addr, u32 buf_length)
+{
+       u32 rets[4];
+       s32 fwrc;
+       int ret = 0;
+
+       do {
+               fwrc = rtas_call(rtas_function_token(RTAS_FN_IBM_PLATFORM_DUMP),
+                               6, 5,
+                               rets,
+                               params->dump_tag_hi,
+                               params->dump_tag_lo,
+                               params->sequence_hi,
+                               params->sequence_lo,
+                               buf_addr,
+                               buf_length);
+       } while (rtas_busy_delay(fwrc));
+
+       switch (fwrc) {
+       case RTAS_HARDWARE_ERROR:
+               ret = -EIO;
+               break;
+       case RTAS_NOT_AUTHORIZED:
+               ret = -EPERM;
+               break;
+       case RTAS_IBM_PLATFORM_DUMP_CONTINUE:
+       case RTAS_IBM_PLATFORM_DUMP_COMPLETE:
+               params->sequence_hi = rets[0];
+               params->sequence_lo = rets[1];
+               params->bytes_ret_hi = rets[2];
+               params->bytes_ret_lo = rets[3];
+               break;
+       default:
+               ret = -EIO;
+               pr_err_ratelimited("unexpected ibm,platform-dump status %d\n",
+                               fwrc);
+               break;
+       }
+
+       params->status = fwrc;
+       return ret;
+}
+
+/*
+ * Platform dump is used with multiple RTAS calls to retrieve the
+ * complete dump for the provided dump ID. Once the complete dump is
+ * retrieved, the hypervisor returns dump complete status (0) for the
+ * last RTAS call and expects the caller issues one more call with
+ * NULL buffer to invalidate the dump so that the hypervisor can remove
+ * the dump.
+ *
+ * After the specific dump is invalidated in the hypervisor, expect the
+ * dump complete status for the new sequence - the user space initiates
+ * new request for the same dump ID.
+ */
+static ssize_t papr_platform_dump_handle_read(struct file *file,
+               char __user *buf, size_t size, loff_t *off)
+{
+       struct ibm_platform_dump_params *params = file->private_data;
+       u64 total_bytes;
+       s32 fwrc;
+
+       /*
+        * Dump already completed with the previous read calls.
+        * In case if the user space issues further reads, returns
+        * -EINVAL.
+        */
+       if (!params->buf_length) {
+               pr_warn_once("Platform dump completed for dump ID %llu\n",
+                       (u64) (((u64)params->dump_tag_hi << 32) |
+                               params->dump_tag_lo));
+               return -EINVAL;
+       }
+
+       /*
+        * The hypervisor returns status 0 if no more data available to
+        * download. The dump will be invalidated with ioctl (see below).
+        */
+       if (params->status == RTAS_IBM_PLATFORM_DUMP_COMPLETE) {
+               params->buf_length = 0;
+               /*
+                * Returns 0 to the user space so that user
+                * space read stops.
+                */
+               return 0;
+       }
+
+       if (size < SZ_1K) {
+               pr_err_once("Buffer length should be minimum 1024 bytes\n");
+               return -EINVAL;
+       } else if (size > params->buf_length) {
+               /*
+                * Allocate 4K work area. So if the user requests > 4K,
+                * resize the buffer length.
+                */
+               size = params->buf_length;
+       }
+
+       fwrc = rtas_ibm_platform_dump(params,
+                       rtas_work_area_phys(params->work_area),
+                       size);
+       if (fwrc < 0)
+               return fwrc;
+
+       total_bytes = (u64) (((u64)params->bytes_ret_hi << 32) |
+                       params->bytes_ret_lo);
+
+       /*
+        * Kernel or firmware bug, do not continue.
+        */
+       if (WARN(total_bytes > size, "possible write beyond end of work area"))
+               return -EFAULT;
+
+       if (copy_to_user(buf, rtas_work_area_raw_buf(params->work_area),
+                       total_bytes))
+               return -EFAULT;
+
+       return total_bytes;
+}
+
+static int papr_platform_dump_handle_release(struct inode *inode,
+                                       struct file *file)
+{
+       struct ibm_platform_dump_params *params = file->private_data;
+
+       if (params->work_area)
+               rtas_work_area_free(params->work_area);
+
+       mutex_lock(&platform_dump_list_mutex);
+       list_del(¶ms->list);
+       mutex_unlock(&platform_dump_list_mutex);
+
+       kfree(params);
+       file->private_data = NULL;
+       return 0;
+}
+
+/*
+ * This ioctl is used to invalidate the dump assuming the user space
+ * issue this ioctl after obtain the complete dump.
+ * Issue the last RTAS call with NULL buffer to invalidate the dump
+ * which means dump will be freed in the hypervisor.
+ */
+static long papr_platform_dump_invalidate_ioctl(struct file *file,
+                               unsigned int ioctl, unsigned long arg)
+{
+       struct ibm_platform_dump_params *params;
+       u64 __user *argp = (void __user *)arg;
+       u64 param_dump_tag, dump_tag;
+
+       if (ioctl != PAPR_PLATFORM_DUMP_IOC_INVALIDATE)
+               return -ENOIOCTLCMD;
+
+       if (get_user(dump_tag, argp))
+               return -EFAULT;
+
+       /*
+        * private_data is freeded during release(), so should not
+        * happen.
+        */
+       if (!file->private_data) {
+               pr_err("No valid FD to invalidate dump for the ID(%llu)\n",
+                               dump_tag);
+               return -EINVAL;
+       }
+
+       params = file->private_data;
+       param_dump_tag = (u64) (((u64)params->dump_tag_hi << 32) |
+                               params->dump_tag_lo);
+       if (dump_tag != param_dump_tag) {
+               pr_err("Invalid dump ID(%llu) to invalidate dump\n",
+                               dump_tag);
+               return -EINVAL;
+       }
+
+       if (params->status != RTAS_IBM_PLATFORM_DUMP_COMPLETE) {
+               pr_err("Platform dump is not complete, but requested "
+                       "to invalidate dump for ID(%llu)\n",
+                       dump_tag);
+               return -EINPROGRESS;
+       }
+
+       return rtas_ibm_platform_dump(params, 0, 0);
+}
+
+static const struct file_operations papr_platform_dump_handle_ops = {
+       .read = papr_platform_dump_handle_read,
+       .release = papr_platform_dump_handle_release,
+       .unlocked_ioctl = papr_platform_dump_invalidate_ioctl,
+};
+
+/**
+ * papr_platform_dump_create_handle() - Create a fd-based handle for
+ * reading platform dump
+ *
+ * Handler for PAPR_PLATFORM_DUMP_IOC_CREATE_HANDLE ioctl command
+ * Allocates RTAS parameter struct and work area and attached to the
+ * file descriptor for reading by user space with the multiple RTAS
+ * calls until the dump is completed. This memory allocation is freed
+ * when the file is released.
+ *
+ * Multiple dump requests with different IDs are allowed at the same
+ * time, but not with the same dump ID. So if the user space is
+ * already opened file descriptor for the specific dump ID, return
+ * -EALREADY for the next request.
+ *
+ * @dump_tag: Dump ID for the dump requested to retrieve from the
+ *             hypervisor
+ *
+ * Return: The installed fd number if successful, -ve errno otherwise.
+ */
+static long papr_platform_dump_create_handle(u64 dump_tag)
+{
+       struct ibm_platform_dump_params *params;
+       u64 param_dump_tag;
+       struct file *file;
+       long err;
+       int fd;
+
+       /*
+        * Return failure if the user space is already opened FD for
+        * the specific dump ID. This check will prevent multiple dump
+        * requests for the same dump ID at the same time. Generally
+        * should not expect this, but in case.
+        */
+       list_for_each_entry(params, &platform_dump_list, list) {
+               param_dump_tag = (u64) (((u64)params->dump_tag_hi << 32) |
+                                       params->dump_tag_lo);
+               if (dump_tag == param_dump_tag) {
+                       pr_err("Platform dump for ID(%llu) is already in progress\n",
+                                       dump_tag);
+                       return -EALREADY;
+               }
+       }
+
+       params =  kzalloc(sizeof(struct ibm_platform_dump_params),
+                       GFP_KERNEL_ACCOUNT);
+       if (!params)
+               return -ENOMEM;
+
+       params->work_area = rtas_work_area_alloc(SZ_4K);
+       params->buf_length = SZ_4K;
+       params->dump_tag_hi = (u32)(dump_tag >> 32);
+       params->dump_tag_lo = (u32)(dump_tag & 0x00000000ffffffffULL);
+       params->status = RTAS_IBM_PLATFORM_DUMP_START;
+
+       fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC);
+       if (fd < 0) {
+               err = fd;
+               goto free_area;
+       }
+
+       file = anon_inode_getfile_fmode("[papr-platform-dump]",
+                               &papr_platform_dump_handle_ops,
+                               (void *)params, O_RDONLY,
+                               FMODE_LSEEK | FMODE_PREAD);
+       if (IS_ERR(file)) {
+               err = PTR_ERR(file);
+               goto put_fd;
+       }
+
+       fd_install(fd, file);
+
+       list_add(¶ms->list, &platform_dump_list);
+
+       pr_info("%s (%d) initiated platform dump for dump tag %llu\n",
+               current->comm, current->pid, dump_tag);
+       return fd;
+put_fd:
+       put_unused_fd(fd);
+free_area:
+       rtas_work_area_free(params->work_area);
+       kfree(params);
+       return err;
+}
+
+/*
+ * Top-level ioctl handler for /dev/papr-platform-dump.
+ */
+static long papr_platform_dump_dev_ioctl(struct file *filp,
+                                       unsigned int ioctl,
+                                       unsigned long arg)
+{
+       u64 __user *argp = (void __user *)arg;
+       u64 dump_tag;
+       long ret;
+
+       if (get_user(dump_tag, argp))
+               return -EFAULT;
+
+       switch (ioctl) {
+       case PAPR_PLATFORM_DUMP_IOC_CREATE_HANDLE:
+               mutex_lock(&platform_dump_list_mutex);
+               ret = papr_platform_dump_create_handle(dump_tag);
+               mutex_unlock(&platform_dump_list_mutex);
+               break;
+       default:
+               ret = -ENOIOCTLCMD;
+               break;
+       }
+       return ret;
+}
+
+static const struct file_operations papr_platform_dump_ops = {
+       .unlocked_ioctl = papr_platform_dump_dev_ioctl,
+};
+
+static struct miscdevice papr_platform_dump_dev = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = "papr-platform-dump",
+       .fops = &papr_platform_dump_ops,
+};
+
+static __init int papr_platform_dump_init(void)
+{
+       if (!rtas_function_implemented(RTAS_FN_IBM_PLATFORM_DUMP))
+               return -ENODEV;
+
+       return misc_register(&papr_platform_dump_dev);
+}
+machine_device_initcall(pseries, papr_platform_dump_init);