Mike Rapoport (Microsoft)mm: rename my_zero_pfn() to zero_pfn()9a1d0c79 months agoHistory
1756 lines · 45.2 KB · C
RawDownload
// 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 * */ #define pr_fmt(fmt) "vmcore: " fmt #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_DUMPstruct vmcoredd_node {	struct list_head list;	/* List of dumps */	void *buf;		/* Buffer containing device's dump */	unsigned int size;	/* Size of the buffer */}; /* Device Dump list and mutex to synchronize access to list */static LIST_HEAD(vmcoredd_list); 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_MUTEX(vmcore_mutex); 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;/* Whether the vmcore is currently open. */static unsigned int vmcore_open; static void vmcore_process_device_ram(struct vmcore_cb *cb); void register_vmcore_cb(struct vmcore_cb *cb){	INIT_LIST_HEAD(&cb->next);	mutex_lock(&vmcore_mutex);	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");	if (!vmcore_open && cb->get_device_ram)		vmcore_process_device_ram(cb);	mutex_unlock(&vmcore_mutex);}EXPORT_SYMBOL_GPL(register_vmcore_cb); void unregister_vmcore_cb(struct vmcore_cb *cb){	mutex_lock(&vmcore_mutex);	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");	mutex_unlock(&vmcore_mutex); 	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){	mutex_lock(&vmcore_mutex);	vmcore_opened = true;	if (vmcore_open + 1 == 0) {		mutex_unlock(&vmcore_mutex);		return -EBUSY;	}	vmcore_open++;	mutex_unlock(&vmcore_mutex); 	return 0;} static int release_vmcore(struct inode *inode, struct file *file){	mutex_lock(&vmcore_mutex);	vmcore_open--;	mutex_unlock(&vmcore_mutex); 	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 read from notes sections */ssize_t __weak elfcorehdr_read_notes(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,			cc_platform_has(CC_ATTR_MEM_ENCRYPT));} /* * Architectures may override this function to map oldmem */int __weak remap_oldmem_pfn_range(struct vm_area_struct *vma,				  unsigned long from, unsigned long pfn,				  unsigned long size, pgprot_t prot){	prot = pgprot_encrypted(prot);	return remap_pfn_range(vma, from, pfn, size, prot);} /* * Architectures which support memory encryption override this. */ssize_t __weak copy_oldmem_page_encrypted(struct iov_iter *iter,		unsigned long pfn, size_t csize, unsigned long offset){	return copy_oldmem_page(iter, pfn, csize, offset);} #ifdef CONFIG_PROC_VMCORE_DEVICE_DUMPstatic int vmcoredd_copy_dumps(struct iov_iter *iter, u64 start, size_t size){	struct vmcoredd_node *dump;	u64 offset = 0;	size_t tsz;	char *buf; 	list_for_each_entry(dump, &vmcoredd_list, list) {		if (start < offset + dump->size) {			tsz = min(offset + (u64)dump->size - start, (u64)size);			buf = dump->buf + start - offset;			if (copy_to_iter(buf, tsz, iter) < tsz)				return -EFAULT; 			size -= tsz;			start += tsz; 			/* Leave now if buffer filled already */			if (!size)				return 0;		}		offset += dump->size;	} 	return 0;} #ifdef CONFIG_MMUstatic int vmcoredd_mmap_dumps(struct vm_area_struct *vma, unsigned long dst,			       u64 start, size_t size){	struct vmcoredd_node *dump;	u64 offset = 0;	size_t tsz;	char *buf; 	list_for_each_entry(dump, &vmcoredd_list, list) {		if (start < offset + dump->size) {			tsz = min(offset + (u64)dump->size - start, (u64)size);			buf = dump->buf + start - offset;			if (remap_vmalloc_range_partial(vma, dst, buf, 0,							tsz))				return -EFAULT; 			size -= tsz;			start += tsz;			dst += tsz; 			/* Leave now if buffer filled already */			if (!size)				return 0;		}		offset += dump->size;	} 	return 0;}#endif /* CONFIG_MMU */#endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ /* Read from the ELF header and then the crash dump. On error, negative value is * returned otherwise number of bytes read are returned. */static ssize_t __read_vmcore(struct iov_iter *iter, loff_t *fpos){	struct vmcore_range *m = NULL;	ssize_t acc = 0, tmp;	size_t tsz;	u64 start; 	if (!iov_iter_count(iter) || *fpos >= vmcore_size)		return 0; 	iov_iter_truncate(iter, vmcore_size - *fpos); 	/* Read ELF core header */	if (*fpos < elfcorebuf_sz) {		tsz = min(elfcorebuf_sz - (size_t)*fpos, iov_iter_count(iter));		if (copy_to_iter(elfcorebuf + *fpos, tsz, iter) < tsz)			return -EFAULT;		*fpos += tsz;		acc += tsz; 		/* leave now if filled buffer already */		if (!iov_iter_count(iter))			return acc;	} 	/* Read ELF note segment */	if (*fpos < elfcorebuf_sz + elfnotes_sz) {		void *kaddr; 		/* We add device dumps before other elf notes because the		 * other elf notes may not fill the elf notes buffer		 * completely and we will end up with zero-filled data		 * between the elf notes and the device dumps. Tools will		 * then try to decode this zero-filled data as valid notes		 * and we don't want that. Hence, adding device dumps before		 * the other elf notes ensure that zero-filled data can be		 * avoided.		 */#ifdef CONFIG_PROC_VMCORE_DEVICE_DUMP		/* Read device dumps */		if (*fpos < elfcorebuf_sz + vmcoredd_orig_sz) {			tsz = min(elfcorebuf_sz + vmcoredd_orig_sz -				  (size_t)*fpos, iov_iter_count(iter));			start = *fpos - elfcorebuf_sz;			if (vmcoredd_copy_dumps(iter, start, tsz))				return -EFAULT; 			*fpos += tsz;			acc += tsz; 			/* leave now if filled buffer already */			if (!iov_iter_count(iter))				return acc;		}#endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ 		/* Read remaining elf notes */		tsz = min(elfcorebuf_sz + elfnotes_sz - (size_t)*fpos,			  iov_iter_count(iter));		kaddr = elfnotes_buf + *fpos - elfcorebuf_sz - vmcoredd_orig_sz;		if (copy_to_iter(kaddr, tsz, iter) < tsz)			return -EFAULT; 		*fpos += tsz;		acc += tsz; 		/* leave now if filled buffer already */		if (!iov_iter_count(iter))			return acc; 		cond_resched();	} 	list_for_each_entry(m, &vmcore_list, list) {		if (*fpos < m->offset + m->size) {			tsz = (size_t)min_t(unsigned long long,					    m->offset + m->size - *fpos,					    iov_iter_count(iter));			start = m->paddr + *fpos - m->offset;			tmp = read_from_oldmem(iter, tsz, &start,					cc_platform_has(CC_ATTR_MEM_ENCRYPT));			if (tmp < 0)				return tmp;			*fpos += tsz;			acc += tsz; 			/* leave now if filled buffer already */			if (!iov_iter_count(iter))				return acc;		} 		cond_resched();	} 	return acc;} static ssize_t read_vmcore(struct kiocb *iocb, struct iov_iter *iter){	return __read_vmcore(iter, &iocb->ki_pos);} /** * vmcore_alloc_buf - allocate buffer in vmalloc memory * @size: size of buffer * * If CONFIG_MMU is defined, use vmalloc_user() to allow users to mmap * the buffer to user-space by means of remap_vmalloc_range(). * * If CONFIG_MMU is not defined, use vzalloc() since mmap_vmcore() is * disabled and there's no need to allow users to mmap the buffer. */static inline char *vmcore_alloc_buf(size_t size){#ifdef CONFIG_MMU	return vmalloc_user(size);#else	return vzalloc(size);#endif} /* * Disable mmap_vmcore() if CONFIG_MMU is not defined. MMU is * essential for mmap_vmcore() in order to map physically * non-contiguous objects (ELF header, ELF note segment and memory * regions in the 1st kernel pointed to by PT_LOAD entries) into * virtually contiguous user-space in ELF layout. */#ifdef CONFIG_MMU /* * The vmcore fault handler uses the page cache and fills data using the * standard __read_vmcore() function. * * On s390 the fault handler is used for memory regions that can't be mapped * directly with remap_pfn_range(). */static vm_fault_t mmap_vmcore_fault(struct vm_fault *vmf){#ifdef CONFIG_S390	struct address_space *mapping = vmf->vma->vm_file->f_mapping;	pgoff_t index = vmf->pgoff;	struct iov_iter iter;	struct kvec kvec;	struct page *page;	loff_t offset;	int rc; 	page = find_or_create_page(mapping, index, GFP_KERNEL);	if (!page)		return VM_FAULT_OOM;	if (!PageUptodate(page)) {		offset = (loff_t) index << PAGE_SHIFT;		kvec.iov_base = page_address(page);		kvec.iov_len = PAGE_SIZE;		iov_iter_kvec(&iter, ITER_DEST, &kvec, 1, PAGE_SIZE); 		rc = __read_vmcore(&iter, &offset);		if (rc < 0) {			unlock_page(page);			put_page(page);			return vmf_error(rc);		}		SetPageUptodate(page);	}	unlock_page(page);	vmf->page = page;	return 0;

Lines 1–500 of 1756.