1/*
2 * Access to PCI I/O memory from user space programs.
3 *
4 * Copyright IBM Corp. 2014
5 * Author(s): Alexey Ishchuk <aishchuk@linux.vnet.ibm.com>
6 */
7#include <linux/kernel.h>
8#include <linux/syscalls.h>
9#include <linux/init.h>
10#include <linux/mm.h>
11#include <linux/errno.h>
12#include <linux/pci.h>
13
14static long get_pfn(unsigned long user_addr, unsigned long access,
15		    unsigned long *pfn)
16{
17	struct vm_area_struct *vma;
18	long ret;
19
20	down_read(&current->mm->mmap_sem);
21	ret = -EINVAL;
22	vma = find_vma(current->mm, user_addr);
23	if (!vma)
24		goto out;
25	ret = -EACCES;
26	if (!(vma->vm_flags & access))
27		goto out;
28	ret = follow_pfn(vma, user_addr, pfn);
29out:
30	up_read(&current->mm->mmap_sem);
31	return ret;
32}
33
34SYSCALL_DEFINE3(s390_pci_mmio_write, unsigned long, mmio_addr,
35		const void __user *, user_buffer, size_t, length)
36{
37	u8 local_buf[64];
38	void __iomem *io_addr;
39	void *buf;
40	unsigned long pfn;
41	long ret;
42
43	if (!zpci_is_enabled())
44		return -ENODEV;
45
46	if (length <= 0 || PAGE_SIZE - (mmio_addr & ~PAGE_MASK) < length)
47		return -EINVAL;
48	if (length > 64) {
49		buf = kmalloc(length, GFP_KERNEL);
50		if (!buf)
51			return -ENOMEM;
52	} else
53		buf = local_buf;
54
55	ret = get_pfn(mmio_addr, VM_WRITE, &pfn);
56	if (ret)
57		goto out;
58	io_addr = (void __iomem *)((pfn << PAGE_SHIFT) | (mmio_addr & ~PAGE_MASK));
59
60	ret = -EFAULT;
61	if ((unsigned long) io_addr < ZPCI_IOMAP_ADDR_BASE)
62		goto out;
63
64	if (copy_from_user(buf, user_buffer, length))
65		goto out;
66
67	ret = zpci_memcpy_toio(io_addr, buf, length);
68out:
69	if (buf != local_buf)
70		kfree(buf);
71	return ret;
72}
73
74SYSCALL_DEFINE3(s390_pci_mmio_read, unsigned long, mmio_addr,
75		void __user *, user_buffer, size_t, length)
76{
77	u8 local_buf[64];
78	void __iomem *io_addr;
79	void *buf;
80	unsigned long pfn;
81	long ret;
82
83	if (!zpci_is_enabled())
84		return -ENODEV;
85
86	if (length <= 0 || PAGE_SIZE - (mmio_addr & ~PAGE_MASK) < length)
87		return -EINVAL;
88	if (length > 64) {
89		buf = kmalloc(length, GFP_KERNEL);
90		if (!buf)
91			return -ENOMEM;
92	} else
93		buf = local_buf;
94
95	ret = get_pfn(mmio_addr, VM_READ, &pfn);
96	if (ret)
97		goto out;
98	io_addr = (void __iomem *)((pfn << PAGE_SHIFT) | (mmio_addr & ~PAGE_MASK));
99
100	if ((unsigned long) io_addr < ZPCI_IOMAP_ADDR_BASE) {
101		ret = -EFAULT;
102		goto out;
103	}
104	ret = zpci_memcpy_fromio(buf, io_addr, length);
105	if (ret)
106		goto out;
107	if (copy_to_user(user_buffer, buf, length))
108		ret = -EFAULT;
109
110out:
111	if (buf != local_buf)
112		kfree(buf);
113	return ret;
114}
115