xref: /linux/drivers/char/mem.c (revision 8f72c31f45a575d156cfe964099b4cfcc02e03eb)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  *  linux/drivers/char/mem.c
41da177e4SLinus Torvalds  *
51da177e4SLinus Torvalds  *  Copyright (C) 1991, 1992  Linus Torvalds
61da177e4SLinus Torvalds  *
71da177e4SLinus Torvalds  *  Added devfs support.
81da177e4SLinus Torvalds  *    Jan-11-1998, C. Scott Ananian <cananian@alumni.princeton.edu>
9af901ca1SAndré Goddard Rosa  *  Shared /dev/zero mmapping support, Feb 2000, Kanoj Sarcar <kanoj@sgi.com>
101da177e4SLinus Torvalds  */
111da177e4SLinus Torvalds 
121da177e4SLinus Torvalds #include <linux/mm.h>
131da177e4SLinus Torvalds #include <linux/miscdevice.h>
141da177e4SLinus Torvalds #include <linux/slab.h>
151da177e4SLinus Torvalds #include <linux/vmalloc.h>
161da177e4SLinus Torvalds #include <linux/mman.h>
171da177e4SLinus Torvalds #include <linux/random.h>
181da177e4SLinus Torvalds #include <linux/init.h>
191da177e4SLinus Torvalds #include <linux/tty.h>
201da177e4SLinus Torvalds #include <linux/capability.h>
211da177e4SLinus Torvalds #include <linux/ptrace.h>
221da177e4SLinus Torvalds #include <linux/device.h>
2350b1fdbdSVivek Goyal #include <linux/highmem.h>
241da177e4SLinus Torvalds #include <linux/backing-dev.h>
25c01d5b30SHugh Dickins #include <linux/shmem_fs.h>
26d6b29d7cSJens Axboe #include <linux/splice.h>
27b8a3ad5bSLinus Torvalds #include <linux/pfn.h>
2866300e66SPaul Gortmaker #include <linux/export.h>
29e1612de9SHaren Myneni #include <linux/io.h>
30e2e40f2cSChristoph Hellwig #include <linux/uio.h>
3135b6c7e4SRob Ward #include <linux/uaccess.h>
329b9d8ddaSMatthew Garrett #include <linux/security.h>
331da177e4SLinus Torvalds 
343234ac66SDan Williams #define DEVMEM_MINOR	1
35e1612de9SHaren Myneni #define DEVPORT_MINOR	4
36e1612de9SHaren Myneni 
size_inside_page(unsigned long start,unsigned long size)37f222318eSWu Fengguang static inline unsigned long size_inside_page(unsigned long start,
38f222318eSWu Fengguang 					     unsigned long size)
39f222318eSWu Fengguang {
40f222318eSWu Fengguang 	unsigned long sz;
41f222318eSWu Fengguang 
427fabadddSWu Fengguang 	sz = PAGE_SIZE - (start & (PAGE_SIZE - 1));
43f222318eSWu Fengguang 
447fabadddSWu Fengguang 	return min(sz, size);
45f222318eSWu Fengguang }
46f222318eSWu Fengguang 
471da177e4SLinus Torvalds #ifndef ARCH_HAS_VALID_PHYS_ADDR_RANGE
valid_phys_addr_range(phys_addr_t addr,size_t count)487e6735c3SCyril Chemparathy static inline int valid_phys_addr_range(phys_addr_t addr, size_t count)
491da177e4SLinus Torvalds {
50cfaf346cSChangli Gao 	return addr + count <= __pa(high_memory);
511da177e4SLinus Torvalds }
5280851ef2SBjorn Helgaas 
valid_mmap_phys_addr_range(unsigned long pfn,size_t size)5306c67befSLennert Buytenhek static inline int valid_mmap_phys_addr_range(unsigned long pfn, size_t size)
5480851ef2SBjorn Helgaas {
5580851ef2SBjorn Helgaas 	return 1;
5680851ef2SBjorn Helgaas }
571da177e4SLinus Torvalds #endif
581da177e4SLinus Torvalds 
59d092633bSIngo Molnar #ifdef CONFIG_STRICT_DEVMEM
page_is_allowed(unsigned long pfn)60a4866aa8SKees Cook static inline int page_is_allowed(unsigned long pfn)
61a4866aa8SKees Cook {
62a4866aa8SKees Cook 	return devmem_is_allowed(pfn);
63a4866aa8SKees Cook }
range_is_allowed(unsigned long pfn,unsigned long size)64e2beb3eaSVenki Pallipadi static inline int range_is_allowed(unsigned long pfn, unsigned long size)
65ae531c26SArjan van de Ven {
66e2beb3eaSVenki Pallipadi 	u64 from = ((u64)pfn) << PAGE_SHIFT;
67e2beb3eaSVenki Pallipadi 	u64 to = from + size;
68e2beb3eaSVenki Pallipadi 	u64 cursor = from;
69ae531c26SArjan van de Ven 
70e2beb3eaSVenki Pallipadi 	while (cursor < to) {
7139380b80SJiri Kosina 		if (!devmem_is_allowed(pfn))
72ae531c26SArjan van de Ven 			return 0;
73e2beb3eaSVenki Pallipadi 		cursor += PAGE_SIZE;
74e2beb3eaSVenki Pallipadi 		pfn++;
75ae531c26SArjan van de Ven 	}
76ae531c26SArjan van de Ven 	return 1;
77ae531c26SArjan van de Ven }
78ae531c26SArjan van de Ven #else
page_is_allowed(unsigned long pfn)79a4866aa8SKees Cook static inline int page_is_allowed(unsigned long pfn)
80a4866aa8SKees Cook {
81a4866aa8SKees Cook 	return 1;
82a4866aa8SKees Cook }
range_is_allowed(unsigned long pfn,unsigned long size)83e2beb3eaSVenki Pallipadi static inline int range_is_allowed(unsigned long pfn, unsigned long size)
84ae531c26SArjan van de Ven {
85ae531c26SArjan van de Ven 	return 1;
86ae531c26SArjan van de Ven }
87ae531c26SArjan van de Ven #endif
88ae531c26SArjan van de Ven 
should_stop_iteration(void)898619e5bdSTetsuo Handa static inline bool should_stop_iteration(void)
908619e5bdSTetsuo Handa {
918619e5bdSTetsuo Handa 	if (need_resched())
928619e5bdSTetsuo Handa 		cond_resched();
93830a4e5cSJason A. Donenfeld 	return signal_pending(current);
948619e5bdSTetsuo Handa }
958619e5bdSTetsuo Handa 
961da177e4SLinus Torvalds /*
971da177e4SLinus Torvalds  * This funcion reads the *physical* memory. The f_pos points directly to the
981da177e4SLinus Torvalds  * memory location.
991da177e4SLinus Torvalds  */
read_mem(struct file * file,char __user * buf,size_t count,loff_t * ppos)1001da177e4SLinus Torvalds static ssize_t read_mem(struct file *file, char __user *buf,
1011da177e4SLinus Torvalds 			size_t count, loff_t *ppos)
1021da177e4SLinus Torvalds {
1037e6735c3SCyril Chemparathy 	phys_addr_t p = *ppos;
1041da177e4SLinus Torvalds 	ssize_t read, sz;
1054707a341SThierry Reding 	void *ptr;
10622ec1a2aSKees Cook 	char *bounce;
10722ec1a2aSKees Cook 	int err;
1081da177e4SLinus Torvalds 
10908d2d00bSPetr Tesarik 	if (p != *ppos)
11008d2d00bSPetr Tesarik 		return 0;
11108d2d00bSPetr Tesarik 
112136939a2SBjorn Helgaas 	if (!valid_phys_addr_range(p, count))
1131da177e4SLinus Torvalds 		return -EFAULT;
1141da177e4SLinus Torvalds 	read = 0;
1151da177e4SLinus Torvalds #ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
1161da177e4SLinus Torvalds 	/* we don't have page 0 mapped on sparc and m68k.. */
1171da177e4SLinus Torvalds 	if (p < PAGE_SIZE) {
1187fabadddSWu Fengguang 		sz = size_inside_page(p, count);
1191da177e4SLinus Torvalds 		if (sz > 0) {
1201da177e4SLinus Torvalds 			if (clear_user(buf, sz))
1211da177e4SLinus Torvalds 				return -EFAULT;
1221da177e4SLinus Torvalds 			buf += sz;
1231da177e4SLinus Torvalds 			p += sz;
1241da177e4SLinus Torvalds 			count -= sz;
1251da177e4SLinus Torvalds 			read += sz;
1261da177e4SLinus Torvalds 		}
1271da177e4SLinus Torvalds 	}
1281da177e4SLinus Torvalds #endif
1291da177e4SLinus Torvalds 
13022ec1a2aSKees Cook 	bounce = kmalloc(PAGE_SIZE, GFP_KERNEL);
13122ec1a2aSKees Cook 	if (!bounce)
13222ec1a2aSKees Cook 		return -ENOMEM;
13322ec1a2aSKees Cook 
1341da177e4SLinus Torvalds 	while (count > 0) {
135fa29e97bSWu Fengguang 		unsigned long remaining;
136b5b38200SKees Cook 		int allowed, probe;
137fa29e97bSWu Fengguang 
138f222318eSWu Fengguang 		sz = size_inside_page(p, count);
1391da177e4SLinus Torvalds 
14022ec1a2aSKees Cook 		err = -EPERM;
141a4866aa8SKees Cook 		allowed = page_is_allowed(p >> PAGE_SHIFT);
142a4866aa8SKees Cook 		if (!allowed)
14322ec1a2aSKees Cook 			goto failed;
14422ec1a2aSKees Cook 
14522ec1a2aSKees Cook 		err = -EFAULT;
146a4866aa8SKees Cook 		if (allowed == 2) {
147a4866aa8SKees Cook 			/* Show zeros for restricted memory. */
148a4866aa8SKees Cook 			remaining = clear_user(buf, sz);
149a4866aa8SKees Cook 		} else {
1501da177e4SLinus Torvalds 			/*
151a4866aa8SKees Cook 			 * On ia64 if a page has been mapped somewhere as
152a4866aa8SKees Cook 			 * uncached, then it must also be accessed uncached
153a4866aa8SKees Cook 			 * by the kernel or data corruption may occur.
1541da177e4SLinus Torvalds 			 */
1551da177e4SLinus Torvalds 			ptr = xlate_dev_mem_ptr(p);
156e045fb2aSvenkatesh.pallipadi@intel.com 			if (!ptr)
15722ec1a2aSKees Cook 				goto failed;
158e045fb2aSvenkatesh.pallipadi@intel.com 
159fe557319SChristoph Hellwig 			probe = copy_from_kernel_nofault(bounce, ptr, sz);
160e045fb2aSvenkatesh.pallipadi@intel.com 			unxlate_dev_mem_ptr(p, ptr);
161b5b38200SKees Cook 			if (probe)
16222ec1a2aSKees Cook 				goto failed;
16322ec1a2aSKees Cook 
16422ec1a2aSKees Cook 			remaining = copy_to_user(buf, bounce, sz);
165a4866aa8SKees Cook 		}
166a4866aa8SKees Cook 
167fa29e97bSWu Fengguang 		if (remaining)
16822ec1a2aSKees Cook 			goto failed;
169e045fb2aSvenkatesh.pallipadi@intel.com 
1701da177e4SLinus Torvalds 		buf += sz;
1711da177e4SLinus Torvalds 		p += sz;
1721da177e4SLinus Torvalds 		count -= sz;
1731da177e4SLinus Torvalds 		read += sz;
1748619e5bdSTetsuo Handa 		if (should_stop_iteration())
1758619e5bdSTetsuo Handa 			break;
1761da177e4SLinus Torvalds 	}
17722ec1a2aSKees Cook 	kfree(bounce);
1781da177e4SLinus Torvalds 
1791da177e4SLinus Torvalds 	*ppos += read;
1801da177e4SLinus Torvalds 	return read;
18122ec1a2aSKees Cook 
18222ec1a2aSKees Cook failed:
18322ec1a2aSKees Cook 	kfree(bounce);
18422ec1a2aSKees Cook 	return err;
1851da177e4SLinus Torvalds }
1861da177e4SLinus Torvalds 
write_mem(struct file * file,const char __user * buf,size_t count,loff_t * ppos)1871da177e4SLinus Torvalds static ssize_t write_mem(struct file *file, const char __user *buf,
1881da177e4SLinus Torvalds 			 size_t count, loff_t *ppos)
1891da177e4SLinus Torvalds {
1907e6735c3SCyril Chemparathy 	phys_addr_t p = *ppos;
1911da177e4SLinus Torvalds 	ssize_t written, sz;
1921da177e4SLinus Torvalds 	unsigned long copied;
1931da177e4SLinus Torvalds 	void *ptr;
1941da177e4SLinus Torvalds 
19508d2d00bSPetr Tesarik 	if (p != *ppos)
19608d2d00bSPetr Tesarik 		return -EFBIG;
19708d2d00bSPetr Tesarik 
198136939a2SBjorn Helgaas 	if (!valid_phys_addr_range(p, count))
1991da177e4SLinus Torvalds 		return -EFAULT;
2001da177e4SLinus Torvalds 
2011da177e4SLinus Torvalds 	written = 0;
2021da177e4SLinus Torvalds 
2031da177e4SLinus Torvalds #ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
2041da177e4SLinus Torvalds 	/* we don't have page 0 mapped on sparc and m68k.. */
2051da177e4SLinus Torvalds 	if (p < PAGE_SIZE) {
2067fabadddSWu Fengguang 		sz = size_inside_page(p, count);
2071da177e4SLinus Torvalds 		/* Hmm. Do something? */
2081da177e4SLinus Torvalds 		buf += sz;
2091da177e4SLinus Torvalds 		p += sz;
2101da177e4SLinus Torvalds 		count -= sz;
2111da177e4SLinus Torvalds 		written += sz;
2121da177e4SLinus Torvalds 	}
2131da177e4SLinus Torvalds #endif
2141da177e4SLinus Torvalds 
2151da177e4SLinus Torvalds 	while (count > 0) {
216a4866aa8SKees Cook 		int allowed;
217a4866aa8SKees Cook 
218f222318eSWu Fengguang 		sz = size_inside_page(p, count);
2191da177e4SLinus Torvalds 
220a4866aa8SKees Cook 		allowed = page_is_allowed(p >> PAGE_SHIFT);
221a4866aa8SKees Cook 		if (!allowed)
222e045fb2aSvenkatesh.pallipadi@intel.com 			return -EPERM;
223e045fb2aSvenkatesh.pallipadi@intel.com 
224a4866aa8SKees Cook 		/* Skip actual writing when a page is marked as restricted. */
225a4866aa8SKees Cook 		if (allowed == 1) {
2261da177e4SLinus Torvalds 			/*
227a4866aa8SKees Cook 			 * On ia64 if a page has been mapped somewhere as
228a4866aa8SKees Cook 			 * uncached, then it must also be accessed uncached
229a4866aa8SKees Cook 			 * by the kernel or data corruption may occur.
2301da177e4SLinus Torvalds 			 */
2311da177e4SLinus Torvalds 			ptr = xlate_dev_mem_ptr(p);
232e045fb2aSvenkatesh.pallipadi@intel.com 			if (!ptr) {
233c654d60eSJan Beulich 				if (written)
234c654d60eSJan Beulich 					break;
2351da177e4SLinus Torvalds 				return -EFAULT;
2361da177e4SLinus Torvalds 			}
237e045fb2aSvenkatesh.pallipadi@intel.com 
238e045fb2aSvenkatesh.pallipadi@intel.com 			copied = copy_from_user(ptr, buf, sz);
239fa29e97bSWu Fengguang 			unxlate_dev_mem_ptr(p, ptr);
240e045fb2aSvenkatesh.pallipadi@intel.com 			if (copied) {
241e045fb2aSvenkatesh.pallipadi@intel.com 				written += sz - copied;
242e045fb2aSvenkatesh.pallipadi@intel.com 				if (written)
243e045fb2aSvenkatesh.pallipadi@intel.com 					break;
244e045fb2aSvenkatesh.pallipadi@intel.com 				return -EFAULT;
245e045fb2aSvenkatesh.pallipadi@intel.com 			}
246a4866aa8SKees Cook 		}
247e045fb2aSvenkatesh.pallipadi@intel.com 
2481da177e4SLinus Torvalds 		buf += sz;
2491da177e4SLinus Torvalds 		p += sz;
2501da177e4SLinus Torvalds 		count -= sz;
2511da177e4SLinus Torvalds 		written += sz;
2528619e5bdSTetsuo Handa 		if (should_stop_iteration())
2538619e5bdSTetsuo Handa 			break;
2541da177e4SLinus Torvalds 	}
2551da177e4SLinus Torvalds 
2561da177e4SLinus Torvalds 	*ppos += written;
2571da177e4SLinus Torvalds 	return written;
2581da177e4SLinus Torvalds }
2591da177e4SLinus Torvalds 
phys_mem_access_prot_allowed(struct file * file,unsigned long pfn,unsigned long size,pgprot_t * vma_prot)260d7d4d849SAndrew Morton int __weak phys_mem_access_prot_allowed(struct file *file,
261f0970c13Svenkatesh.pallipadi@intel.com 	unsigned long pfn, unsigned long size, pgprot_t *vma_prot)
262f0970c13Svenkatesh.pallipadi@intel.com {
263f0970c13Svenkatesh.pallipadi@intel.com 	return 1;
264f0970c13Svenkatesh.pallipadi@intel.com }
265f0970c13Svenkatesh.pallipadi@intel.com 
26644ac8413SBjorn Helgaas #ifndef __HAVE_PHYS_MEM_ACCESS_PROT
267d7d4d849SAndrew Morton 
268d7d4d849SAndrew Morton /*
269d7d4d849SAndrew Morton  * Architectures vary in how they handle caching for addresses
270d7d4d849SAndrew Morton  * outside of main memory.
271d7d4d849SAndrew Morton  *
272d7d4d849SAndrew Morton  */
273ea56f411SDavid Howells #ifdef pgprot_noncached
uncached_access(struct file * file,phys_addr_t addr)2747e6735c3SCyril Chemparathy static int uncached_access(struct file *file, phys_addr_t addr)
275d7d4d849SAndrew Morton {
276d7d4d849SAndrew Morton 	/*
277d7d4d849SAndrew Morton 	 * Accessing memory above the top the kernel knows about or through a
278d7d4d849SAndrew Morton 	 * file pointer
279d7d4d849SAndrew Morton 	 * that was marked O_DSYNC will be done non-cached.
280d7d4d849SAndrew Morton 	 */
281d7d4d849SAndrew Morton 	if (file->f_flags & O_DSYNC)
282d7d4d849SAndrew Morton 		return 1;
283d7d4d849SAndrew Morton 	return addr >= __pa(high_memory);
284d7d4d849SAndrew Morton }
285ea56f411SDavid Howells #endif
286d7d4d849SAndrew Morton 
phys_mem_access_prot(struct file * file,unsigned long pfn,unsigned long size,pgprot_t vma_prot)28744ac8413SBjorn Helgaas static pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn,
28844ac8413SBjorn Helgaas 				     unsigned long size, pgprot_t vma_prot)
28944ac8413SBjorn Helgaas {
29044ac8413SBjorn Helgaas #ifdef pgprot_noncached
2917e6735c3SCyril Chemparathy 	phys_addr_t offset = pfn << PAGE_SHIFT;
29244ac8413SBjorn Helgaas 
29344ac8413SBjorn Helgaas 	if (uncached_access(file, offset))
29444ac8413SBjorn Helgaas 		return pgprot_noncached(vma_prot);
29544ac8413SBjorn Helgaas #endif
29644ac8413SBjorn Helgaas 	return vma_prot;
29744ac8413SBjorn Helgaas }
29844ac8413SBjorn Helgaas #endif
29944ac8413SBjorn Helgaas 
3005da6185bSDavid Howells #ifndef CONFIG_MMU
get_unmapped_area_mem(struct file * file,unsigned long addr,unsigned long len,unsigned long pgoff,unsigned long flags)3015da6185bSDavid Howells static unsigned long get_unmapped_area_mem(struct file *file,
3025da6185bSDavid Howells 					   unsigned long addr,
3035da6185bSDavid Howells 					   unsigned long len,
3045da6185bSDavid Howells 					   unsigned long pgoff,
3055da6185bSDavid Howells 					   unsigned long flags)
3065da6185bSDavid Howells {
3075da6185bSDavid Howells 	if (!valid_mmap_phys_addr_range(pgoff, len))
3085da6185bSDavid Howells 		return (unsigned long) -EINVAL;
3098a93258cSBenjamin Herrenschmidt 	return pgoff << PAGE_SHIFT;
3105da6185bSDavid Howells }
3115da6185bSDavid Howells 
312b4caecd4SChristoph Hellwig /* permit direct mmap, for read, write or exec */
memory_mmap_capabilities(struct file * file)313b4caecd4SChristoph Hellwig static unsigned memory_mmap_capabilities(struct file *file)
314b4caecd4SChristoph Hellwig {
315b4caecd4SChristoph Hellwig 	return NOMMU_MAP_DIRECT |
316b4caecd4SChristoph Hellwig 		NOMMU_MAP_READ | NOMMU_MAP_WRITE | NOMMU_MAP_EXEC;
317b4caecd4SChristoph Hellwig }
318b4caecd4SChristoph Hellwig 
zero_mmap_capabilities(struct file * file)319b4caecd4SChristoph Hellwig static unsigned zero_mmap_capabilities(struct file *file)
320b4caecd4SChristoph Hellwig {
321b4caecd4SChristoph Hellwig 	return NOMMU_MAP_COPY;
322b4caecd4SChristoph Hellwig }
323b4caecd4SChristoph Hellwig 
3245da6185bSDavid Howells /* can't do an in-place private mapping if there's no MMU */
private_mapping_ok(struct vm_area_struct * vma)3255da6185bSDavid Howells static inline int private_mapping_ok(struct vm_area_struct *vma)
3265da6185bSDavid Howells {
327fc4f4be9SDavid Hildenbrand 	return is_nommu_shared_mapping(vma->vm_flags);
3285da6185bSDavid Howells }
3295da6185bSDavid Howells #else
3305da6185bSDavid Howells 
private_mapping_ok(struct vm_area_struct * vma)3315da6185bSDavid Howells static inline int private_mapping_ok(struct vm_area_struct *vma)
3325da6185bSDavid Howells {
3335da6185bSDavid Howells 	return 1;
3345da6185bSDavid Howells }
3355da6185bSDavid Howells #endif
3365da6185bSDavid Howells 
337f0f37e2fSAlexey Dobriyan static const struct vm_operations_struct mmap_mem_ops = {
3387ae8ed50SRik van Riel #ifdef CONFIG_HAVE_IOREMAP_PROT
3397ae8ed50SRik van Riel 	.access = generic_access_phys
3407ae8ed50SRik van Riel #endif
341e7f260a2Svenkatesh.pallipadi@intel.com };
342e7f260a2Svenkatesh.pallipadi@intel.com 
mmap_mem(struct file * file,struct vm_area_struct * vma)3431da177e4SLinus Torvalds static int mmap_mem(struct file *file, struct vm_area_struct *vma)
3441da177e4SLinus Torvalds {
34580851ef2SBjorn Helgaas 	size_t size = vma->vm_end - vma->vm_start;
346b299cde2SJulius Werner 	phys_addr_t offset = (phys_addr_t)vma->vm_pgoff << PAGE_SHIFT;
347b299cde2SJulius Werner 
348be62a320SCraig Bergstrom 	/* Does it even fit in phys_addr_t? */
349be62a320SCraig Bergstrom 	if (offset >> PAGE_SHIFT != vma->vm_pgoff)
350be62a320SCraig Bergstrom 		return -EINVAL;
351be62a320SCraig Bergstrom 
352b299cde2SJulius Werner 	/* It's illegal to wrap around the end of the physical address space. */
35332829da5SJulius Werner 	if (offset + (phys_addr_t)size - 1 < offset)
354b299cde2SJulius Werner 		return -EINVAL;
35580851ef2SBjorn Helgaas 
35606c67befSLennert Buytenhek 	if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size))
35780851ef2SBjorn Helgaas 		return -EINVAL;
35880851ef2SBjorn Helgaas 
3595da6185bSDavid Howells 	if (!private_mapping_ok(vma))
3605da6185bSDavid Howells 		return -ENOSYS;
3615da6185bSDavid Howells 
362e2beb3eaSVenki Pallipadi 	if (!range_is_allowed(vma->vm_pgoff, size))
363e2beb3eaSVenki Pallipadi 		return -EPERM;
364e2beb3eaSVenki Pallipadi 
365f0970c13Svenkatesh.pallipadi@intel.com 	if (!phys_mem_access_prot_allowed(file, vma->vm_pgoff, size,
366f0970c13Svenkatesh.pallipadi@intel.com 						&vma->vm_page_prot))
367f0970c13Svenkatesh.pallipadi@intel.com 		return -EINVAL;
368f0970c13Svenkatesh.pallipadi@intel.com 
3698b150478SRoland Dreier 	vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
37080851ef2SBjorn Helgaas 						 size,
3711da177e4SLinus Torvalds 						 vma->vm_page_prot);
3721da177e4SLinus Torvalds 
373e7f260a2Svenkatesh.pallipadi@intel.com 	vma->vm_ops = &mmap_mem_ops;
374e7f260a2Svenkatesh.pallipadi@intel.com 
375314e51b9SKonstantin Khlebnikov 	/* Remap-pfn-range will mark the range VM_IO */
3761da177e4SLinus Torvalds 	if (remap_pfn_range(vma,
3771da177e4SLinus Torvalds 			    vma->vm_start,
3781da177e4SLinus Torvalds 			    vma->vm_pgoff,
37980851ef2SBjorn Helgaas 			    size,
380e7f260a2Svenkatesh.pallipadi@intel.com 			    vma->vm_page_prot)) {
3811da177e4SLinus Torvalds 		return -EAGAIN;
382e7f260a2Svenkatesh.pallipadi@intel.com 	}
3831da177e4SLinus Torvalds 	return 0;
3841da177e4SLinus Torvalds }
3851da177e4SLinus Torvalds 
386f0a816fbSNiklas Schnelle #ifdef CONFIG_DEVPORT
read_port(struct file * file,char __user * buf,size_t count,loff_t * ppos)3871da177e4SLinus Torvalds static ssize_t read_port(struct file *file, char __user *buf,
3881da177e4SLinus Torvalds 			 size_t count, loff_t *ppos)
3891da177e4SLinus Torvalds {
3901da177e4SLinus Torvalds 	unsigned long i = *ppos;
3911da177e4SLinus Torvalds 	char __user *tmp = buf;
3921da177e4SLinus Torvalds 
39396d4f267SLinus Torvalds 	if (!access_ok(buf, count))
3941da177e4SLinus Torvalds 		return -EFAULT;
3951da177e4SLinus Torvalds 	while (count-- > 0 && i < 65536) {
3961da177e4SLinus Torvalds 		if (__put_user(inb(i), tmp) < 0)
3971da177e4SLinus Torvalds 			return -EFAULT;
3981da177e4SLinus Torvalds 		i++;
3991da177e4SLinus Torvalds 		tmp++;
4001da177e4SLinus Torvalds 	}
4011da177e4SLinus Torvalds 	*ppos = i;
4021da177e4SLinus Torvalds 	return tmp-buf;
4031da177e4SLinus Torvalds }
4041da177e4SLinus Torvalds 
write_port(struct file * file,const char __user * buf,size_t count,loff_t * ppos)4051da177e4SLinus Torvalds static ssize_t write_port(struct file *file, const char __user *buf,
4061da177e4SLinus Torvalds 			  size_t count, loff_t *ppos)
4071da177e4SLinus Torvalds {
4081da177e4SLinus Torvalds 	unsigned long i = *ppos;
4091da177e4SLinus Torvalds 	const char __user *tmp = buf;
4101da177e4SLinus Torvalds 
41196d4f267SLinus Torvalds 	if (!access_ok(buf, count))
4121da177e4SLinus Torvalds 		return -EFAULT;
4131da177e4SLinus Torvalds 	while (count-- > 0 && i < 65536) {
4141da177e4SLinus Torvalds 		char c;
4156a0061baSRob Ward 
416c654d60eSJan Beulich 		if (__get_user(c, tmp)) {
417c654d60eSJan Beulich 			if (tmp > buf)
418c654d60eSJan Beulich 				break;
4191da177e4SLinus Torvalds 			return -EFAULT;
420c654d60eSJan Beulich 		}
4211da177e4SLinus Torvalds 		outb(c, i);
4221da177e4SLinus Torvalds 		i++;
4231da177e4SLinus Torvalds 		tmp++;
4241da177e4SLinus Torvalds 	}
4251da177e4SLinus Torvalds 	*ppos = i;
4261da177e4SLinus Torvalds 	return tmp-buf;
4271da177e4SLinus Torvalds }
428f0a816fbSNiklas Schnelle #endif
4291da177e4SLinus Torvalds 
read_null(struct file * file,char __user * buf,size_t count,loff_t * ppos)4301da177e4SLinus Torvalds static ssize_t read_null(struct file *file, char __user *buf,
4311da177e4SLinus Torvalds 			 size_t count, loff_t *ppos)
4321da177e4SLinus Torvalds {
4331da177e4SLinus Torvalds 	return 0;
4341da177e4SLinus Torvalds }
4351da177e4SLinus Torvalds 
write_null(struct file * file,const char __user * buf,size_t count,loff_t * ppos)4361da177e4SLinus Torvalds static ssize_t write_null(struct file *file, const char __user *buf,
4371da177e4SLinus Torvalds 			  size_t count, loff_t *ppos)
4381da177e4SLinus Torvalds {
4391da177e4SLinus Torvalds 	return count;
4401da177e4SLinus Torvalds }
4411da177e4SLinus Torvalds 
read_iter_null(struct kiocb * iocb,struct iov_iter * to)442cd28e28dSAl Viro static ssize_t read_iter_null(struct kiocb *iocb, struct iov_iter *to)
443162934deSZach Brown {
444162934deSZach Brown 	return 0;
445162934deSZach Brown }
446162934deSZach Brown 
write_iter_null(struct kiocb * iocb,struct iov_iter * from)447cd28e28dSAl Viro static ssize_t write_iter_null(struct kiocb *iocb, struct iov_iter *from)
448162934deSZach Brown {
449cd28e28dSAl Viro 	size_t count = iov_iter_count(from);
450cd28e28dSAl Viro 	iov_iter_advance(from, count);
451cd28e28dSAl Viro 	return count;
452162934deSZach Brown }
453162934deSZach Brown 
pipe_to_null(struct pipe_inode_info * info,struct pipe_buffer * buf,struct splice_desc * sd)4541ebd32fcSJens Axboe static int pipe_to_null(struct pipe_inode_info *info, struct pipe_buffer *buf,
4551ebd32fcSJens Axboe 			struct splice_desc *sd)
4561ebd32fcSJens Axboe {
4571ebd32fcSJens Axboe 	return sd->len;
4581ebd32fcSJens Axboe }
4591ebd32fcSJens Axboe 
splice_write_null(struct pipe_inode_info * pipe,struct file * out,loff_t * ppos,size_t len,unsigned int flags)4601ebd32fcSJens Axboe static ssize_t splice_write_null(struct pipe_inode_info *pipe, struct file *out,
4611ebd32fcSJens Axboe 				 loff_t *ppos, size_t len, unsigned int flags)
4621ebd32fcSJens Axboe {
4631ebd32fcSJens Axboe 	return splice_from_pipe(pipe, out, ppos, len, flags, pipe_to_null);
4641ebd32fcSJens Axboe }
4651ebd32fcSJens Axboe 
uring_cmd_null(struct io_uring_cmd * ioucmd,unsigned int issue_flags)46670752795SPaul Moore static int uring_cmd_null(struct io_uring_cmd *ioucmd, unsigned int issue_flags)
46770752795SPaul Moore {
46870752795SPaul Moore 	return 0;
46970752795SPaul Moore }
47070752795SPaul Moore 
read_iter_zero(struct kiocb * iocb,struct iov_iter * iter)47113ba33e8SAl Viro static ssize_t read_iter_zero(struct kiocb *iocb, struct iov_iter *iter)
472162934deSZach Brown {
473162934deSZach Brown 	size_t written = 0;
474162934deSZach Brown 
47513ba33e8SAl Viro 	while (iov_iter_count(iter)) {
47613ba33e8SAl Viro 		size_t chunk = iov_iter_count(iter), n;
4776a0061baSRob Ward 
47813ba33e8SAl Viro 		if (chunk > PAGE_SIZE)
47913ba33e8SAl Viro 			chunk = PAGE_SIZE;	/* Just for latency reasons */
48013ba33e8SAl Viro 		n = iov_iter_zero(chunk, iter);
48113ba33e8SAl Viro 		if (!n && iov_iter_count(iter))
482162934deSZach Brown 			return written ? written : -EFAULT;
48313ba33e8SAl Viro 		written += n;
48413ba33e8SAl Viro 		if (signal_pending(current))
48513ba33e8SAl Viro 			return written ? written : -ERESTARTSYS;
486e5f71d60SPavel Begunkov 		if (!need_resched())
487e5f71d60SPavel Begunkov 			continue;
488e5f71d60SPavel Begunkov 		if (iocb->ki_flags & IOCB_NOWAIT)
489e5f71d60SPavel Begunkov 			return written ? written : -EAGAIN;
49013ba33e8SAl Viro 		cond_resched();
49113ba33e8SAl Viro 	}
49213ba33e8SAl Viro 	return written;
493162934deSZach Brown }
494162934deSZach Brown 
read_zero(struct file * file,char __user * buf,size_t count,loff_t * ppos)49599f66735SChristoph Hellwig static ssize_t read_zero(struct file *file, char __user *buf,
49699f66735SChristoph Hellwig 			 size_t count, loff_t *ppos)
49799f66735SChristoph Hellwig {
49899f66735SChristoph Hellwig 	size_t cleared = 0;
49999f66735SChristoph Hellwig 
50099f66735SChristoph Hellwig 	while (count) {
50199f66735SChristoph Hellwig 		size_t chunk = min_t(size_t, count, PAGE_SIZE);
502ab04de8eSChristoph Hellwig 		size_t left;
50399f66735SChristoph Hellwig 
504ab04de8eSChristoph Hellwig 		left = clear_user(buf + cleared, chunk);
505ab04de8eSChristoph Hellwig 		if (unlikely(left)) {
506ab04de8eSChristoph Hellwig 			cleared += (chunk - left);
507ab04de8eSChristoph Hellwig 			if (!cleared)
508ab04de8eSChristoph Hellwig 				return -EFAULT;
509ab04de8eSChristoph Hellwig 			break;
510ab04de8eSChristoph Hellwig 		}
51199f66735SChristoph Hellwig 		cleared += chunk;
51299f66735SChristoph Hellwig 		count -= chunk;
51399f66735SChristoph Hellwig 
51499f66735SChristoph Hellwig 		if (signal_pending(current))
515ab04de8eSChristoph Hellwig 			break;
51699f66735SChristoph Hellwig 		cond_resched();
51799f66735SChristoph Hellwig 	}
51899f66735SChristoph Hellwig 
51999f66735SChristoph Hellwig 	return cleared;
52099f66735SChristoph Hellwig }
52199f66735SChristoph Hellwig 
mmap_zero(struct file * file,struct vm_area_struct * vma)5221da177e4SLinus Torvalds static int mmap_zero(struct file *file, struct vm_area_struct *vma)
5231da177e4SLinus Torvalds {
524557ed1faSNick Piggin #ifndef CONFIG_MMU
525557ed1faSNick Piggin 	return -ENOSYS;
526557ed1faSNick Piggin #endif
5271da177e4SLinus Torvalds 	if (vma->vm_flags & VM_SHARED)
5281da177e4SLinus Torvalds 		return shmem_zero_setup(vma);
529bfd40eafSKirill A. Shutemov 	vma_set_anonymous(vma);
530557ed1faSNick Piggin 	return 0;
5311da177e4SLinus Torvalds }
5321da177e4SLinus Torvalds 
get_unmapped_area_zero(struct file * file,unsigned long addr,unsigned long len,unsigned long pgoff,unsigned long flags)533c01d5b30SHugh Dickins static unsigned long get_unmapped_area_zero(struct file *file,
534c01d5b30SHugh Dickins 				unsigned long addr, unsigned long len,
535c01d5b30SHugh Dickins 				unsigned long pgoff, unsigned long flags)
536c01d5b30SHugh Dickins {
537c01d5b30SHugh Dickins #ifdef CONFIG_MMU
538c01d5b30SHugh Dickins 	if (flags & MAP_SHARED) {
539c01d5b30SHugh Dickins 		/*
540c01d5b30SHugh Dickins 		 * mmap_zero() will call shmem_zero_setup() to create a file,
541c01d5b30SHugh Dickins 		 * so use shmem's get_unmapped_area in case it can be huge;
542c01d5b30SHugh Dickins 		 * and pass NULL for file as in mmap.c's get_unmapped_area(),
543c01d5b30SHugh Dickins 		 * so as not to confuse shmem with our handle on "/dev/zero".
544c01d5b30SHugh Dickins 		 */
545c01d5b30SHugh Dickins 		return shmem_get_unmapped_area(NULL, addr, len, pgoff, flags);
546c01d5b30SHugh Dickins 	}
547c01d5b30SHugh Dickins 
548c01d5b30SHugh Dickins 	/* Otherwise flags & MAP_PRIVATE: with no shmem object beneath it */
549529ce23aSRick Edgecombe 	return mm_get_unmapped_area(current->mm, file, addr, len, pgoff, flags);
550c01d5b30SHugh Dickins #else
551c01d5b30SHugh Dickins 	return -ENOSYS;
552c01d5b30SHugh Dickins #endif
553c01d5b30SHugh Dickins }
554c01d5b30SHugh Dickins 
write_full(struct file * file,const char __user * buf,size_t count,loff_t * ppos)5551da177e4SLinus Torvalds static ssize_t write_full(struct file *file, const char __user *buf,
5561da177e4SLinus Torvalds 			  size_t count, loff_t *ppos)
5571da177e4SLinus Torvalds {
5581da177e4SLinus Torvalds 	return -ENOSPC;
5591da177e4SLinus Torvalds }
5601da177e4SLinus Torvalds 
5611da177e4SLinus Torvalds /*
5621da177e4SLinus Torvalds  * Special lseek() function for /dev/null and /dev/zero.  Most notably, you
5631da177e4SLinus Torvalds  * can fopen() both devices with "a" now.  This was previously impossible.
5641da177e4SLinus Torvalds  * -- SRB.
5651da177e4SLinus Torvalds  */
null_lseek(struct file * file,loff_t offset,int orig)5661da177e4SLinus Torvalds static loff_t null_lseek(struct file *file, loff_t offset, int orig)
5671da177e4SLinus Torvalds {
5681da177e4SLinus Torvalds 	return file->f_pos = 0;
5691da177e4SLinus Torvalds }
5701da177e4SLinus Torvalds 
5711da177e4SLinus Torvalds /*
5721da177e4SLinus Torvalds  * The memory devices use the full 32/64 bits of the offset, and so we cannot
5731da177e4SLinus Torvalds  * check against negative addresses: they are ok. The return value is weird,
5741da177e4SLinus Torvalds  * though, in that case (0).
5751da177e4SLinus Torvalds  *
5761da177e4SLinus Torvalds  * also note that seeking relative to the "end of file" isn't supported:
5771da177e4SLinus Torvalds  * it has no meaning, so it returns -EINVAL.
5781da177e4SLinus Torvalds  */
memory_lseek(struct file * file,loff_t offset,int orig)5791da177e4SLinus Torvalds static loff_t memory_lseek(struct file *file, loff_t offset, int orig)
5801da177e4SLinus Torvalds {
5811da177e4SLinus Torvalds 	loff_t ret;
5821da177e4SLinus Torvalds 
5835955102cSAl Viro 	inode_lock(file_inode(file));
5841da177e4SLinus Torvalds 	switch (orig) {
585dcefafb6SWu Fengguang 	case SEEK_CUR:
586dcefafb6SWu Fengguang 		offset += file->f_pos;
587df561f66SGustavo A. R. Silva 		fallthrough;
588dcefafb6SWu Fengguang 	case SEEK_SET:
589dcefafb6SWu Fengguang 		/* to avoid userland mistaking f_pos=-9 as -EBADF=-9 */
590ecb63a1bSAndrzej Hajda 		if ((unsigned long long)offset >= -MAX_ERRNO) {
591dcefafb6SWu Fengguang 			ret = -EOVERFLOW;
592dcefafb6SWu Fengguang 			break;
593dcefafb6SWu Fengguang 		}
594dcefafb6SWu Fengguang 		file->f_pos = offset;
5951da177e4SLinus Torvalds 		ret = file->f_pos;
5961da177e4SLinus Torvalds 		force_successful_syscall_return();
5971da177e4SLinus Torvalds 		break;
5981da177e4SLinus Torvalds 	default:
5991da177e4SLinus Torvalds 		ret = -EINVAL;
6001da177e4SLinus Torvalds 	}
6015955102cSAl Viro 	inode_unlock(file_inode(file));
6021da177e4SLinus Torvalds 	return ret;
6031da177e4SLinus Torvalds }
6041da177e4SLinus Torvalds 
open_port(struct inode * inode,struct file * filp)6051da177e4SLinus Torvalds static int open_port(struct inode *inode, struct file *filp)
6061da177e4SLinus Torvalds {
6073234ac66SDan Williams 	int rc;
6083234ac66SDan Williams 
6099b9d8ddaSMatthew Garrett 	if (!capable(CAP_SYS_RAWIO))
6109b9d8ddaSMatthew Garrett 		return -EPERM;
6119b9d8ddaSMatthew Garrett 
6123234ac66SDan Williams 	rc = security_locked_down(LOCKDOWN_DEV_MEM);
6133234ac66SDan Williams 	if (rc)
6143234ac66SDan Williams 		return rc;
6153234ac66SDan Williams 
6163234ac66SDan Williams 	if (iminor(inode) != DEVMEM_MINOR)
6173234ac66SDan Williams 		return 0;
6183234ac66SDan Williams 
6193234ac66SDan Williams 	/*
6203234ac66SDan Williams 	 * Use a unified address space to have a single point to manage
6213234ac66SDan Williams 	 * revocations when drivers want to take over a /dev/mem mapped
6223234ac66SDan Williams 	 * range.
6233234ac66SDan Williams 	 */
62471a1d8edSDaniel Vetter 	filp->f_mapping = iomem_get_mapping();
6253234ac66SDan Williams 
6263234ac66SDan Williams 	return 0;
6271da177e4SLinus Torvalds }
6281da177e4SLinus Torvalds 
6291da177e4SLinus Torvalds #define zero_lseek	null_lseek
6301da177e4SLinus Torvalds #define full_lseek      null_lseek
6311da177e4SLinus Torvalds #define write_zero	write_null
632cd28e28dSAl Viro #define write_iter_zero	write_iter_null
6331b057bd8SMax Kellermann #define splice_write_zero	splice_write_null
6341da177e4SLinus Torvalds #define open_mem	open_port
6351da177e4SLinus Torvalds 
63673f0718eSRob Ward static const struct file_operations __maybe_unused mem_fops = {
6371da177e4SLinus Torvalds 	.llseek		= memory_lseek,
6381da177e4SLinus Torvalds 	.read		= read_mem,
6391da177e4SLinus Torvalds 	.write		= write_mem,
6401da177e4SLinus Torvalds 	.mmap		= mmap_mem,
6411da177e4SLinus Torvalds 	.open		= open_mem,
642b4caecd4SChristoph Hellwig #ifndef CONFIG_MMU
6435da6185bSDavid Howells 	.get_unmapped_area = get_unmapped_area_mem,
644b4caecd4SChristoph Hellwig 	.mmap_capabilities = memory_mmap_capabilities,
645b4caecd4SChristoph Hellwig #endif
646*641bb439SChristian Brauner 	.fop_flags	= FOP_UNSIGNED_OFFSET,
6471da177e4SLinus Torvalds };
6481da177e4SLinus Torvalds 
64962322d25SArjan van de Ven static const struct file_operations null_fops = {
6501da177e4SLinus Torvalds 	.llseek		= null_lseek,
6511da177e4SLinus Torvalds 	.read		= read_null,
6521da177e4SLinus Torvalds 	.write		= write_null,
653cd28e28dSAl Viro 	.read_iter	= read_iter_null,
654cd28e28dSAl Viro 	.write_iter	= write_iter_null,
6551ebd32fcSJens Axboe 	.splice_write	= splice_write_null,
65670752795SPaul Moore 	.uring_cmd	= uring_cmd_null,
6571da177e4SLinus Torvalds };
6581da177e4SLinus Torvalds 
659f0a816fbSNiklas Schnelle #ifdef CONFIG_DEVPORT
660f0a816fbSNiklas Schnelle static const struct file_operations port_fops = {
6611da177e4SLinus Torvalds 	.llseek		= memory_lseek,
6621da177e4SLinus Torvalds 	.read		= read_port,
6631da177e4SLinus Torvalds 	.write		= write_port,
6641da177e4SLinus Torvalds 	.open		= open_port,
6651da177e4SLinus Torvalds };
666f0a816fbSNiklas Schnelle #endif
6671da177e4SLinus Torvalds 
66862322d25SArjan van de Ven static const struct file_operations zero_fops = {
6691da177e4SLinus Torvalds 	.llseek		= zero_lseek,
6701da177e4SLinus Torvalds 	.write		= write_zero,
67113ba33e8SAl Viro 	.read_iter	= read_iter_zero,
67299f66735SChristoph Hellwig 	.read		= read_zero,
673cd28e28dSAl Viro 	.write_iter	= write_iter_zero,
6741b057bd8SMax Kellermann 	.splice_read	= copy_splice_read,
6751b057bd8SMax Kellermann 	.splice_write	= splice_write_zero,
6761da177e4SLinus Torvalds 	.mmap		= mmap_zero,
677c01d5b30SHugh Dickins 	.get_unmapped_area = get_unmapped_area_zero,
678b4caecd4SChristoph Hellwig #ifndef CONFIG_MMU
679b4caecd4SChristoph Hellwig 	.mmap_capabilities = zero_mmap_capabilities,
680b4caecd4SChristoph Hellwig #endif
6811da177e4SLinus Torvalds };
6821da177e4SLinus Torvalds 
68362322d25SArjan van de Ven static const struct file_operations full_fops = {
6841da177e4SLinus Torvalds 	.llseek		= full_lseek,
68513ba33e8SAl Viro 	.read_iter	= read_iter_zero,
6861da177e4SLinus Torvalds 	.write		= write_full,
6871b057bd8SMax Kellermann 	.splice_read	= copy_splice_read,
6881da177e4SLinus Torvalds };
6891da177e4SLinus Torvalds 
690389e0cb9SKay Sievers static const struct memdev {
691389e0cb9SKay Sievers 	const char *name;
692d6f47befSAdriano dos Santos Fernandes 	const struct file_operations *fops;
693b4caecd4SChristoph Hellwig 	fmode_t fmode;
694ed1af26cSAlexey Dobriyan 	umode_t mode;
695389e0cb9SKay Sievers } devlist[] = {
69673f0718eSRob Ward #ifdef CONFIG_DEVMEM
697*641bb439SChristian Brauner 	[DEVMEM_MINOR] = { "mem", &mem_fops, 0, 0 },
69873f0718eSRob Ward #endif
699ed1af26cSAlexey Dobriyan 	[3] = { "null", &null_fops, FMODE_NOWAIT, 0666 },
700d6f47befSAdriano dos Santos Fernandes #ifdef CONFIG_DEVPORT
701ed1af26cSAlexey Dobriyan 	[4] = { "port", &port_fops, 0, 0 },
702d6f47befSAdriano dos Santos Fernandes #endif
703ed1af26cSAlexey Dobriyan 	[5] = { "zero", &zero_fops, FMODE_NOWAIT, 0666 },
704ed1af26cSAlexey Dobriyan 	[7] = { "full", &full_fops, 0, 0666 },
705ed1af26cSAlexey Dobriyan 	[8] = { "random", &random_fops, FMODE_NOWAIT, 0666 },
706ed1af26cSAlexey Dobriyan 	[9] = { "urandom", &urandom_fops, FMODE_NOWAIT, 0666 },
7077f3a781dSKay Sievers #ifdef CONFIG_PRINTK
708ed1af26cSAlexey Dobriyan 	[11] = { "kmsg", &kmsg_fops, 0, 0644 },
7097f3a781dSKay Sievers #endif
710d6f47befSAdriano dos Santos Fernandes };
711d6f47befSAdriano dos Santos Fernandes 
memory_open(struct inode * inode,struct file * filp)7121da177e4SLinus Torvalds static int memory_open(struct inode *inode, struct file *filp)
7131da177e4SLinus Torvalds {
714389e0cb9SKay Sievers 	int minor;
715389e0cb9SKay Sievers 	const struct memdev *dev;
716d6f47befSAdriano dos Santos Fernandes 
717389e0cb9SKay Sievers 	minor = iminor(inode);
718389e0cb9SKay Sievers 	if (minor >= ARRAY_SIZE(devlist))
719205153aaSFrederic Weisbecker 		return -ENXIO;
720d6f47befSAdriano dos Santos Fernandes 
721389e0cb9SKay Sievers 	dev = &devlist[minor];
722389e0cb9SKay Sievers 	if (!dev->fops)
723205153aaSFrederic Weisbecker 		return -ENXIO;
724d6f47befSAdriano dos Santos Fernandes 
725389e0cb9SKay Sievers 	filp->f_op = dev->fops;
726b4caecd4SChristoph Hellwig 	filp->f_mode |= dev->fmode;
7274a3956c7SKAMEZAWA Hiroyuki 
728389e0cb9SKay Sievers 	if (dev->fops->open)
729205153aaSFrederic Weisbecker 		return dev->fops->open(inode, filp);
730205153aaSFrederic Weisbecker 
731205153aaSFrederic Weisbecker 	return 0;
7321da177e4SLinus Torvalds }
7331da177e4SLinus Torvalds 
73462322d25SArjan van de Ven static const struct file_operations memory_fops = {
735389e0cb9SKay Sievers 	.open = memory_open,
7366038f373SArnd Bergmann 	.llseek = noop_llseek,
7371da177e4SLinus Torvalds };
7381da177e4SLinus Torvalds 
mem_devnode(const struct device * dev,umode_t * mode)739ff62b8e6SGreg Kroah-Hartman static char *mem_devnode(const struct device *dev, umode_t *mode)
740e454cea2SKay Sievers {
741e454cea2SKay Sievers 	if (mode && devlist[MINOR(dev->devt)].mode)
742e454cea2SKay Sievers 		*mode = devlist[MINOR(dev->devt)].mode;
743e454cea2SKay Sievers 	return NULL;
744e454cea2SKay Sievers }
745e454cea2SKay Sievers 
7467671284bSIvan Orlov static const struct class mem_class = {
7477671284bSIvan Orlov 	.name		= "mem",
7487671284bSIvan Orlov 	.devnode	= mem_devnode,
7497671284bSIvan Orlov };
7501da177e4SLinus Torvalds 
chr_dev_init(void)7511da177e4SLinus Torvalds static int __init chr_dev_init(void)
7521da177e4SLinus Torvalds {
7537671284bSIvan Orlov 	int retval;
754389e0cb9SKay Sievers 	int minor;
7551da177e4SLinus Torvalds 
7561da177e4SLinus Torvalds 	if (register_chrdev(MEM_MAJOR, "mem", &memory_fops))
7571da177e4SLinus Torvalds 		printk("unable to get major %d for memory devs\n", MEM_MAJOR);
7581da177e4SLinus Torvalds 
7597671284bSIvan Orlov 	retval = class_register(&mem_class);
7607671284bSIvan Orlov 	if (retval)
7617671284bSIvan Orlov 		return retval;
7626e191f7bSAnton Blanchard 
763389e0cb9SKay Sievers 	for (minor = 1; minor < ARRAY_SIZE(devlist); minor++) {
764389e0cb9SKay Sievers 		if (!devlist[minor].name)
765389e0cb9SKay Sievers 			continue;
766e1612de9SHaren Myneni 
767e1612de9SHaren Myneni 		/*
768e1612de9SHaren Myneni 		 * Create /dev/port?
769e1612de9SHaren Myneni 		 */
770e1612de9SHaren Myneni 		if ((minor == DEVPORT_MINOR) && !arch_has_dev_port())
771e1612de9SHaren Myneni 			continue;
772e1612de9SHaren Myneni 
7737671284bSIvan Orlov 		device_create(&mem_class, NULL, MKDEV(MEM_MAJOR, minor),
774389e0cb9SKay Sievers 			      NULL, devlist[minor].name);
775389e0cb9SKay Sievers 	}
7761da177e4SLinus Torvalds 
77731d1d48eSDavid Howells 	return tty_init();
7781da177e4SLinus Torvalds }
7791da177e4SLinus Torvalds 
7801da177e4SLinus Torvalds fs_initcall(chr_dev_init);
781