arch/s390/pci/pci_mmio.c

Source file repositories/reference/linux-study-clean/arch/s390/pci/pci_mmio.c

File Facts

System
Linux kernel
Corpus path
arch/s390/pci/pci_mmio.c
Extension
.c
Size
9066 bytes
Lines
364
Domain
Architecture Layer
Bucket
arch/s390
Inferred role
Architecture Layer: syscall or user/kernel boundary
Status
core implementation candidate

Why This File Exists

CPU and platform-specific kernel glue: boot entry, traps, syscall entry, interrupts, page tables, context switch, and low-level barriers.

Dependency Surface

Detected Declarations

Annotated Snippet

SYSCALL_DEFINE3(s390_pci_mmio_write, unsigned long, mmio_addr,
		const void __user *, user_buffer, size_t, length)
{
	struct follow_pfnmap_args args = { };
	u8 local_buf[64];
	void __iomem *io_addr;
	void *buf;
	struct vm_area_struct *vma;
	long ret;

	if (!zpci_is_enabled())
		return -ENODEV;

	if (length <= 0 || PAGE_SIZE - (mmio_addr & ~PAGE_MASK) < length)
		return -EINVAL;

	/*
	 * We only support write access to MIO capable devices if we are on
	 * a MIO enabled system. Otherwise we would have to check for every
	 * address if it is a special ZPCI_ADDR and would have to do
	 * a pfn lookup which we don't need for MIO capable devices.  Currently
	 * ISM devices are the only devices without MIO support and there is no
	 * known need for accessing these from userspace.
	 */
	if (static_branch_likely(&have_mio)) {
		ret = __memcpy_toio_inuser((void  __iomem *) mmio_addr,
					user_buffer,
					length);
		return ret;
	}

	if (length > 64) {
		buf = kmalloc(length, GFP_KERNEL);
		if (!buf)
			return -ENOMEM;
	} else
		buf = local_buf;

	ret = -EFAULT;
	if (copy_from_user(buf, user_buffer, length))
		goto out_free;

	mmap_read_lock(current->mm);
	ret = -EINVAL;
	vma = vma_lookup(current->mm, mmio_addr);
	if (!vma)
		goto out_unlock_mmap;
	if (!(vma->vm_flags & (VM_IO | VM_PFNMAP)))
		goto out_unlock_mmap;
	ret = -EACCES;
	if (!(vma->vm_flags & VM_WRITE))
		goto out_unlock_mmap;

	args.address = mmio_addr;
	args.vma = vma;
	ret = follow_pfnmap_start(&args);
	if (ret) {
		fixup_user_fault(current->mm, mmio_addr, FAULT_FLAG_WRITE, NULL);
		ret = follow_pfnmap_start(&args);
		if (ret)
			goto out_unlock_mmap;
	}

	io_addr = (void __iomem *)((args.pfn << PAGE_SHIFT) |
			(mmio_addr & ~PAGE_MASK));

	if ((unsigned long) io_addr < ZPCI_IOMAP_ADDR_BASE)
		goto out_unlock_pt;

	ret = zpci_memcpy_toio(io_addr, buf, length);
out_unlock_pt:
	follow_pfnmap_end(&args);
out_unlock_mmap:
	mmap_read_unlock(current->mm);
out_free:
	if (buf != local_buf)
		kfree(buf);
	return ret;
}

static inline int __pcilg_mio_inuser(
		void __user *dst, const void __iomem *ioaddr,
		u64 ulen, u8 *status)
{
	union register_pair ioaddr_len = {.even = (u64 __force)ioaddr, .odd = ulen};
	bool sacf_flag;
	u64 cnt = ulen;
	int shift = ulen * 8;
	int cc, exception;
	u64 val, tmp;

Annotation

Implementation Notes