// SPDX-License-Identifier: GPL-2.0-only
/*
* fs/proc/vmcore.c Interface for accessing the crash
* dump from the system's previous life.
* Heavily borrowed from fs/proc/kcore.c
* Created by: Hariprasad Nellitheertha (hari@in.ibm.com)
* Copyright (C) IBM Corporation, 2004. All rights reserved
*
*/
#include <linux/mm.h>
#include <linux/kcore.h>
#include <linux/user.h>
#include <linux/elf.h>
#include <linux/elfcore.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/highmem.h>
#include <linux/printk.h>
#include <linux/memblock.h>
#include <linux/init.h>
#include <linux/crash_dump.h>
#include <linux/list.h>
#include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
#include <linux/pagemap.h>
#include <linux/uio.h>
#include <linux/cc_platform.h>
#include <asm/io.h>
#include "internal.h"
/* List representing chunks of contiguous memory areas and their offsets in
* vmcore file.
*/
static LIST_HEAD(vmcore_list);
/* Stores the pointer to the buffer containing kernel elf core headers. */
static char *elfcorebuf;
static size_t elfcorebuf_sz;
static size_t elfcorebuf_sz_orig;
static char *elfnotes_buf;
static size_t elfnotes_sz;
/* Size of all notes minus the device dump notes */
static size_t elfnotes_orig_sz;
/* Total size of vmcore file. */
static u64 vmcore_size;
static struct proc_dir_entry *proc_vmcore;
#ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP
/* Device Dump list and mutex to synchronize access to list */
static LIST_HEAD(vmcoredd_list);
static DEFINE_MUTEX(vmcoredd_mutex);
static bool vmcoredd_disabled;
core_param(novmcoredd, vmcoredd_disabled, bool, 0);
#endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */
/* Device Dump Size */
static size_t vmcoredd_orig_sz;
static DEFINE_SPINLOCK(vmcore_cb_lock);
DEFINE_STATIC_SRCU(vmcore_cb_srcu);
/* List of registered vmcore callbacks. */
static LIST_HEAD(vmcore_cb_list);
/* Whether the vmcore has been opened once. */
static bool vmcore_opened;
void register_vmcore_cb(struct vmcore_cb *cb)
{
INIT_LIST_HEAD(&cb->next);
spin_lock(&vmcore_cb_lock);
list_add_tail(&cb->next, &vmcore_cb_list);
/*
* Registering a vmcore callback after the vmcore was opened is
* very unusual (e.g., manual driver loading).
*/
if (vmcore_opened)
pr_warn_once("Unexpected vmcore callback registration\n");
spin_unlock(&vmcore_cb_lock);
}
EXPORT_SYMBOL_GPL(register_vmcore_cb);
void unregister_vmcore_cb(struct vmcore_cb *cb)
{
spin_lock(&vmcore_cb_lock);
list_del_rcu(&cb->next);
/*
* Unregistering a vmcore callback after the vmcore was opened is
* very unusual (e.g., forced driver removal), but we cannot stop
* unregistering.
*/
if (vmcore_opened)
pr_warn_once("Unexpected vmcore callback unregistration\n");
spin_unlock(&vmcore_cb_lock);
synchronize_srcu(&vmcore_cb_srcu);
}
EXPORT_SYMBOL_GPL(unregister_vmcore_cb);
static bool pfn_is_ram(unsigned long pfn)
{
struct vmcore_cb *cb;
bool ret = true;
list_for_each_entry_srcu(cb, &vmcore_cb_list, next,
srcu_read_lock_held(&vmcore_cb_srcu)) {
if (unlikely(!cb->pfn_is_ram))
continue;
ret = cb->pfn_is_ram(cb, pfn);
if (!ret)
break;
}
return ret;
}
static int open_vmcore(struct inode *inode, struct file *file)
{
spin_lock(&vmcore_cb_lock);
vmcore_opened = true;
spin_unlock(&vmcore_cb_lock);
return 0;
}
/* Reads a page from the oldmem device from given offset. */
ssize_t read_from_oldmem(struct iov_iter *iter, size_t count,
u64 *ppos, bool encrypted)
{
unsigned long pfn, offset;
ssize_t nr_bytes;
ssize_t read = 0, tmp;
int idx;
if (!count)
return 0;
offset = (unsigned long)(*ppos % PAGE_SIZE);
pfn = (unsigned long)(*ppos / PAGE_SIZE);
idx = srcu_read_lock(&vmcore_cb_srcu);
do {
if (count > (PAGE_SIZE - offset))
nr_bytes = PAGE_SIZE - offset;
else
nr_bytes = count;
/* If pfn is not ram, return zeros for sparse dump files */
if (!pfn_is_ram(pfn)) {
tmp = iov_iter_zero(nr_bytes, iter);
} else {
if (encrypted)
tmp = copy_oldmem_page_encrypted(iter, pfn,
nr_bytes,
offset);
else
tmp = copy_oldmem_page(iter, pfn, nr_bytes,
offset);
}
if (tmp < nr_bytes) {
srcu_read_unlock(&vmcore_cb_srcu, idx);
return -EFAULT;
}
*ppos += nr_bytes;
count -= nr_bytes;
read += nr_bytes;
++pfn;
offset = 0;
} while (count);
srcu_read_unlock(&vmcore_cb_srcu, idx);
return read;
}
/*
* Architectures may override this function to allocate ELF header in 2nd kernel
*/
int __weak elfcorehdr_alloc(unsigned long long *addr, unsigned long long *size)
{
return 0;
}
/*
* Architectures may override this function to free header
*/
void __weak elfcorehdr_free(unsigned long long addr)
{}
/*
* Architectures may override this function to read from ELF header
*/
ssize_t __weak elfcorehdr_read(char *buf, size_t count, u64 *ppos)
{
struct kvec kvec = { .iov_base = buf, .iov_len = count };
struct iov_iter iter;
iov_iter_kvec(&iter, ITER_DEST, &kvec, 1, count);
return read_from_oldmem(&iter, count, ppos, false);
}
/*
* Architectures may override this function to
|