File mudaq_fops.h
File List > midas_fe > mudaq-dkms > mudaq_fops.h
Go to the documentation of this file
//
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0)
#define fallthrough \
do { \
} while (0)
#endif
static int wrap_ring(int int1, int int2, int wrap, int divisor) {
int result = 0;
if ((int1 - int2) > 0) {
result = (int1 - int2) / divisor;
} else if ((int1 - int2) < 0) {
result = wrap + (int1 - int2) / divisor;
} else if ((int1 - int2) == 0) {
result = 0;
}
return result;
}
static ssize_t mudaq_fops_read(struct file* filp, char __user* buf, size_t count, loff_t* f_pos) {
struct mudaq* mu = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
size_t retval;
u32 new_event_count;
u32* dma_buf_ctrl;
int n_interrupts = 0;
if (!mu->irq)
return -EIO;
if (count != sizeof(mu->to_user))
return -EINVAL;
add_wait_queue(&mu->wait, &wait); // add read process to wait queue
do {
set_current_state(TASK_INTERRUPTIBLE); // mark process as being asleep but interruptible
new_event_count = atomic_read(&mu->event);
if (new_event_count != mu->to_user[0]) { // interrupt occured
mu->to_user[0] = new_event_count; // pass interrupt number
dma_buf_ctrl = mu->mem->internal_addr[4];
/* How many DMA blocks were transfered (in units of interrupts (64 blocks)
* )? Offset in ring buffer comes in bytes from FPGA, transform to
* uint32_t words here
*/
M_DEBUG("interrupt number: %d, address: %x%x\n", new_event_count, (int)dma_buf_ctrl[1],
(int)dma_buf_ctrl[0]);
n_interrupts = wrap_ring((int)dma_buf_ctrl[3] >> 2, (int)mu->to_user[1], N_BUFFERS,
PAGES_PER_INTERRUPT * PAGE_SIZE / 4);
if (n_interrupts != 1) {
M_DEBUG("ctrl buffer: %x, previous: %x\n", (int)dma_buf_ctrl[3] >> 2,
(int)mu->to_user[1]);
M_INFO("Missed %d interrupt(s)\n", n_interrupts);
}
mu->to_user[1] = (u32)dma_buf_ctrl[3] >>
2; // position in ring buffer last written to + 1 (in words)
if (copy_to_user(buf, &(mu->to_user), sizeof(mu->to_user))) {
retval = -EFAULT;
} else {
mu->to_user[0] = new_event_count; // save event_count in case copying to
// user space fails
retval = count;
}
break;
}
if (filp->f_flags & O_NONBLOCK) { // check if user requested non-blocking I/O
retval = -EAGAIN;
break;
}
if (signal_pending(current)) { // restart system when receiving interrupt
retval = -ERESTARTSYS;
break;
}
schedule(); // check whether interrupt occured while going through above
// operations
} while (1);
__set_current_state(TASK_RUNNING); // process is able to run
remove_wait_queue(&mu->wait, &wait); // remove process from wait queue so that
// it can only be awakened once
return retval;
}
static ssize_t mudaq_fops_write(struct file* filp, const char __user* buf, size_t count,
loff_t* f_pos) {
struct mudaq* mu = filp->private_data;
s32 irq_on;
ssize_t retval;
if (!mu->irq)
return -EIO;
if (count != sizeof(s32))
return -EINVAL;
if (copy_from_user(&irq_on, buf, count) != 0) {
return -EFAULT;
}
retval = mudaq_interrupt_control(mu, irq_on);
if (retval != 0) {
return -EIO;
}
return sizeof(s32);
}
static int mudaq_fops_mmap(struct file* filp, struct vm_area_struct* vma) {
struct mudaq* mu = filp->private_data;
int index = (int)vma->vm_pgoff;
unsigned long requested_pages = 0, actual_pages;
int rv = 0;
M_DEBUG("Mapping for index %d\n", index);
/* use the requested page offset as an index to select the pci region that
should be mapped. readout board has 4 accessible memory regions
registers rw, registers ro, memory rw and memory ro
in addition, we have one dma ctrl buffer */
if ((index < 0) || (index > 4)) {
M_DEBUG("invalid mmap memory index %d\n", index);
return -EINVAL;
}
/* only allow mapping of the whole selected memory.
WARNING actual size must not be page-aligned
But minimum virtual address space of vma
is one page */
actual_pages = PAGE_ALIGN(mu->mem->phys_size[index]) >> PAGE_SHIFT;
requested_pages = vma_pages(vma);
if (requested_pages != actual_pages) {
M_ERR("invalid mmap pages requested. requested %lu actual %lu\n", requested_pages,
actual_pages);
rv = -EINVAL;
goto out;
}
if (mu->mem->phys_addr[index] + mu->mem->phys_size[index] < mu->mem->phys_addr[index]) {
M_ERR("invalid memory settings. phys_addr %pad size %u\n", &mu->mem->phys_addr[index],
mu->mem->phys_size[index]);
rv = -EINVAL;
goto out;
}
/* only the read/write registers and the read/write memory need to be
writable. everything else can be forced to be read-only */
vm_flags_set(vma, VM_READ);
vm_flags_clear(vma, VM_WRITE | VM_EXEC);
/* dma_mmap_* and vm_iomap_memory use pgoff as additional offset inside
the buffer, but we always want to map the whole area. */
vma->vm_pgoff = 0;
switch (index) {
case 0: // rw registers
case 2: // rw memory
vm_flags_set(vma, VM_WRITE);
fallthrough;
case 1: // ro registers
case 3: // ro memory
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return io_remap_pfn_range(vma, vma->vm_start, PHYS_PFN(mu->mem->phys_addr[index]),
vma->vm_end - vma->vm_start, vma->vm_page_prot);
case 4: // dma control buffer
return dma_mmap_coherent(mu->misc_mudaq.this_device, vma, mu->mem->internal_addr[index],
mu->mem->bus_addr_ctrl, mu->mem->phys_size[index]);
default:
/* this should NEVER happen */
M_ERR("invalid index\n");
rv = -EINVAL;
break;
}
out:
return rv;
}
static int mudaq_fops_release(struct inode* inode, struct file* filp) {
struct mudaq* mu = filp->private_data;
mudaq_deactivate(mu);
return 0;
}
static int mudaq_fops_open(struct inode* inode, struct file* file) {
struct mudaq* mudaq;
if (file->private_data == NULL)
return -EFAULT;
mudaq = container_of(file->private_data, struct mudaq, misc_mudaq);
file->private_data = mudaq;
return 0;
}
static long mudaq_fops_ioctl(struct file* filp,
unsigned int cmd, /* magic and sequential number for ioctl */
unsigned long ioctl_param) {
int retval = 0;
struct mudaq* mu = filp->private_data;
int err = 0;
u32 new_event_count;
void __user* user_buffer = (void __user*)ioctl_param;
if (_IOC_TYPE(cmd) != MUDAQ_IOC_TYPE) {
retval = -ENOTTY;
goto fail;
}
if (_IOC_NR(cmd) > MUDAQ_IOC_NR) {
retval = -ENOTTY;
goto fail;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) // remove `type` argument
err = !access_ok(user_buffer, _IOC_SIZE(cmd));
#else
if (_IOC_DIR(cmd) & _IOC_READ) {
err = !access_ok(VERIFY_WRITE, user_buffer, _IOC_SIZE(cmd));
} else if (_IOC_DIR(cmd) & _IOC_WRITE) {
err = !access_ok(VERIFY_READ, user_buffer, _IOC_SIZE(cmd));
}
#endif
if (err) {
retval = -EFAULT;
goto fail;
}
/*
* Switch according to the ioctl called
*/
switch (cmd) {
case REQUEST_INTERRUPT_COUNTER: /* Send current interrupt counter to user
space */
new_event_count = atomic_read(&mu->event);
retval = copy_to_user(user_buffer, &new_event_count, sizeof(new_event_count));
if (retval > 0) {
M_ERR("copy_to_user failed with error %d \n", retval);
goto fail;
}
break;
default:
return -EINVAL;
}
return 0;
fail:
mudaq_free_dma(mu);
return retval;
}
static const struct file_operations mudaq_fops = {
.owner = THIS_MODULE,
.read = mudaq_fops_read,
.write = mudaq_fops_write,
.mmap = mudaq_fops_mmap,
.open = mudaq_fops_open,
.unlocked_ioctl = mudaq_fops_ioctl,
.release = mudaq_fops_release,
};