| /* file.c |
| * |
| * Copyright (C) 2010 - 2013 UNISYS CORPORATION |
| * All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or (at |
| * your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or |
| * NON INFRINGEMENT. See the GNU General Public License for more |
| * details. |
| */ |
| |
| /* This contains the implementation that allows a usermode program to |
| * communicate with the visorchipset driver using a device/file interface. |
| */ |
| |
| #include "globals.h" |
| #include "visorchannel.h" |
| #include <linux/mm.h> |
| #include <linux/fs.h> |
| #include "uisutils.h" |
| #include "file.h" |
| |
| #define CURRENT_FILE_PC VISOR_CHIPSET_PC_file_c |
| |
| static struct cdev file_cdev; |
| static struct visorchannel **file_controlvm_channel; |
| |
| void |
| visorchipset_file_cleanup(dev_t major_dev) |
| { |
| if (file_cdev.ops != NULL) |
| cdev_del(&file_cdev); |
| file_cdev.ops = NULL; |
| unregister_chrdev_region(major_dev, 1); |
| } |
| |
| static int |
| visorchipset_open(struct inode *inode, struct file *file) |
| { |
| unsigned minor_number = iminor(inode); |
| |
| if (minor_number != 0) |
| return -ENODEV; |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static int |
| visorchipset_release(struct inode *inode, struct file *file) |
| { |
| return 0; |
| } |
| |
| static int |
| visorchipset_mmap(struct file *file, struct vm_area_struct *vma) |
| { |
| ulong physaddr = 0; |
| ulong offset = vma->vm_pgoff << PAGE_SHIFT; |
| GUEST_PHYSICAL_ADDRESS addr = 0; |
| |
| /* sv_enable_dfp(); */ |
| if (offset & (PAGE_SIZE - 1)) |
| return -ENXIO; /* need aligned offsets */ |
| |
| switch (offset) { |
| case VISORCHIPSET_MMAP_CONTROLCHANOFFSET: |
| vma->vm_flags |= VM_IO; |
| if (*file_controlvm_channel == NULL) { |
| return -ENXIO; |
| } |
| visorchannel_read(*file_controlvm_channel, |
| offsetof(struct spar_controlvm_channel_protocol, |
| gp_control_channel), |
| &addr, sizeof(addr)); |
| if (addr == 0) { |
| return -ENXIO; |
| } |
| physaddr = (ulong)addr; |
| if (remap_pfn_range(vma, vma->vm_start, |
| physaddr >> PAGE_SHIFT, |
| vma->vm_end - vma->vm_start, |
| /*pgprot_noncached */ |
| (vma->vm_page_prot))) { |
| return -EAGAIN; |
| } |
| break; |
| default: |
| return -ENOSYS; |
| } |
| return 0; |
| } |
| |
| static long visorchipset_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| s64 adjustment; |
| s64 vrtc_offset; |
| |
| switch (cmd) { |
| case VMCALL_QUERY_GUEST_VIRTUAL_TIME_OFFSET: |
| /* get the physical rtc offset */ |
| vrtc_offset = issue_vmcall_query_guest_virtual_time_offset(); |
| if (copy_to_user |
| ((void __user *)arg, &vrtc_offset, sizeof(vrtc_offset))) { |
| return -EFAULT; |
| } |
| return SUCCESS; |
| case VMCALL_UPDATE_PHYSICAL_TIME: |
| if (copy_from_user |
| (&adjustment, (void __user *)arg, sizeof(adjustment))) { |
| return -EFAULT; |
| } |
| return issue_vmcall_update_physical_time(adjustment); |
| default: |
| return -EFAULT; |
| } |
| } |
| |
| static const struct file_operations visorchipset_fops = { |
| .owner = THIS_MODULE, |
| .open = visorchipset_open, |
| .read = NULL, |
| .write = NULL, |
| .unlocked_ioctl = visorchipset_ioctl, |
| .release = visorchipset_release, |
| .mmap = visorchipset_mmap, |
| }; |
| |
| int |
| visorchipset_file_init(dev_t major_dev, struct visorchannel **controlvm_channel) |
| { |
| int rc = 0; |
| |
| file_controlvm_channel = controlvm_channel; |
| cdev_init(&file_cdev, &visorchipset_fops); |
| file_cdev.owner = THIS_MODULE; |
| if (MAJOR(major_dev) == 0) { |
| rc = alloc_chrdev_region(&major_dev, 0, 1, MYDRVNAME); |
| /* dynamic major device number registration required */ |
| if (rc < 0) |
| return rc; |
| } else { |
| /* static major device number registration required */ |
| rc = register_chrdev_region(major_dev, 1, MYDRVNAME); |
| if (rc < 0) |
| return rc; |
| } |
| rc = cdev_add(&file_cdev, MKDEV(MAJOR(major_dev), 0), 1); |
| if (rc < 0) { |
| unregister_chrdev_region(major_dev, 1); |
| return rc; |
| } |
| return 0; |
| } |