/* * Freescale Hypervisor Management Driver * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. * Author: Timur Tabi <timur@freescale.com> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. * * The Freescale hypervisor management driver provides several services to * drivers and applications related to the Freescale hypervisor: * * 1. An ioctl interface for querying and managing partitions. * * 2. A file interface to reading incoming doorbells. * * 3. An interrupt handler for shutting down the partition upon receiving the * shutdown doorbell from a manager partition. * * 4. A kernel interface for receiving callbacks when a managed partition * shuts down. */ #include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/types.h>#include <linux/err.h>#include <linux/fs.h>#include <linux/miscdevice.h>#include <linux/mm.h>#include <linux/pagemap.h>#include <linux/slab.h>#include <linux/poll.h>#include <linux/of.h>#include <linux/of_irq.h>#include <linux/reboot.h>#include <linux/uaccess.h>#include <linux/notifier.h>#include <linux/interrupt.h> #include <linux/io.h>#include <asm/fsl_hcalls.h> #include <linux/fsl_hypervisor.h> static BLOCKING_NOTIFIER_HEAD(failover_subscribers); /* * Ioctl interface for FSL_HV_IOCTL_PARTITION_RESTART * * Restart a running partition */static long ioctl_restart(struct fsl_hv_ioctl_restart __user *p){ struct fsl_hv_ioctl_restart param; /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_restart))) return -EFAULT; param.ret = fh_partition_restart(param.partition); if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return 0;} /* * Ioctl interface for FSL_HV_IOCTL_PARTITION_STATUS * * Query the status of a partition */static long ioctl_status(struct fsl_hv_ioctl_status __user *p){ struct fsl_hv_ioctl_status param; u32 status; /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_status))) return -EFAULT; param.ret = fh_partition_get_status(param.partition, &status); if (!param.ret) param.status = status; if (copy_to_user(p, ¶m, sizeof(struct fsl_hv_ioctl_status))) return -EFAULT; return 0;} /* * Ioctl interface for FSL_HV_IOCTL_PARTITION_START * * Start a stopped partition. */static long ioctl_start(struct fsl_hv_ioctl_start __user *p){ struct fsl_hv_ioctl_start param; /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_start))) return -EFAULT; param.ret = fh_partition_start(param.partition, param.entry_point, param.load); if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return 0;} /* * Ioctl interface for FSL_HV_IOCTL_PARTITION_STOP * * Stop a running partition */static long ioctl_stop(struct fsl_hv_ioctl_stop __user *p){ struct fsl_hv_ioctl_stop param; /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_stop))) return -EFAULT; param.ret = fh_partition_stop(param.partition); if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return 0;} /* * Ioctl interface for FSL_HV_IOCTL_MEMCPY * * The FH_MEMCPY hypercall takes an array of address/address/size structures * to represent the data being copied. As a convenience to the user, this * ioctl takes a user-create buffer and a pointer to a guest physically * contiguous buffer in the remote partition, and creates the * address/address/size array for the hypercall. */static long ioctl_memcpy(struct fsl_hv_ioctl_memcpy __user *p){ struct fsl_hv_ioctl_memcpy param; struct page **pages = NULL; void *sg_list_unaligned = NULL; struct fh_sg_list *sg_list = NULL; unsigned int num_pages; unsigned long lb_offset; /* Offset within a page of the local buffer */ unsigned int i; long ret = 0; int num_pinned; /* return value from get_user_pages() */ phys_addr_t remote_paddr; /* The next address in the remote buffer */ uint32_t count; /* The number of bytes left to copy */ /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_memcpy))) return -EFAULT; /* * One partition must be local, the other must be remote. In other * words, if source and target are both -1, or are both not -1, then * return an error. */ if ((param.source == -1) == (param.target == -1)) return -EINVAL; /* * The array of pages returned by get_user_pages() covers only * page-aligned memory. Since the user buffer is probably not * page-aligned, we need to handle the discrepancy. * * We calculate the offset within a page of the S/G list, and make * adjustments accordingly. This will result in a page list that looks * like this: * * ---- <-- first page starts before the buffer * | | * |////|-> ---- * |////| | | * ---- | | * | | * ---- | | * |////| | | * |////| | | * |////| | | * ---- | | * | | * ---- | | * |////| | | * |////| | | * |////| | | * ---- | | * | | * ---- | | * |////| | | * |////|-> ---- * | | <-- last page ends after the buffer * ---- * * The distance between the start of the first page and the start of the * buffer is lb_offset. The hashed (///) areas are the parts of the * page list that contain the actual buffer. * * The advantage of this approach is that the number of pages is * equal to the number of entries in the S/G list that we give to the * hypervisor. */ lb_offset = param.local_vaddr & (PAGE_SIZE - 1); num_pages = (param.count + lb_offset + PAGE_SIZE - 1) >> PAGE_SHIFT; /* Allocate the buffers we need */ /* * 'pages' is an array of struct page pointers that's initialized by * get_user_pages(). */ pages = kzalloc(num_pages * sizeof(struct page *), GFP_KERNEL); if (!pages) { pr_debug("fsl-hv: could not allocate page list\n"); return -ENOMEM; } /* * sg_list is the list of fh_sg_list objects that we pass to the * hypervisor. */ sg_list_unaligned = kmalloc(num_pages * sizeof(struct fh_sg_list) + sizeof(struct fh_sg_list) - 1, GFP_KERNEL); if (!sg_list_unaligned) { pr_debug("fsl-hv: could not allocate S/G list\n"); ret = -ENOMEM; goto exit; } sg_list = PTR_ALIGN(sg_list_unaligned, sizeof(struct fh_sg_list)); /* Get the physical addresses of the source buffer */ num_pinned = get_user_pages_unlocked(param.local_vaddr - lb_offset, num_pages, pages, (param.source == -1) ? 0 : FOLL_WRITE); if (num_pinned != num_pages) { /* get_user_pages() failed */ pr_debug("fsl-hv: could not lock source buffer\n"); ret = (num_pinned < 0) ? num_pinned : -EFAULT; goto exit; } /* * Build the fh_sg_list[] array. The first page is special * because it's misaligned. */ if (param.source == -1) { sg_list[0].source = page_to_phys(pages[0]) + lb_offset; sg_list[0].target = param.remote_paddr; } else { sg_list[0].source = param.remote_paddr; sg_list[0].target = page_to_phys(pages[0]) + lb_offset; } sg_list[0].size = min_t(uint64_t, param.count, PAGE_SIZE - lb_offset); remote_paddr = param.remote_paddr + sg_list[0].size; count = param.count - sg_list[0].size; for (i = 1; i < num_pages; i++) { if (param.source == -1) { /* local to remote */ sg_list[i].source = page_to_phys(pages[i]); sg_list[i].target = remote_paddr; } else { /* remote to local */ sg_list[i].source = remote_paddr; sg_list[i].target = page_to_phys(pages[i]); } sg_list[i].size = min_t(uint64_t, count, PAGE_SIZE); remote_paddr += sg_list[i].size; count -= sg_list[i].size; } param.ret = fh_partition_memcpy(param.source, param.target, virt_to_phys(sg_list), num_pages); exit: if (pages) { for (i = 0; i < num_pages; i++) if (pages[i]) put_page(pages[i]); } kfree(sg_list_unaligned); kfree(pages); if (!ret) if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return ret;} /* * Ioctl interface for FSL_HV_IOCTL_DOORBELL * * Ring a doorbell */static long ioctl_doorbell(struct fsl_hv_ioctl_doorbell __user *p){ struct fsl_hv_ioctl_doorbell param; /* Get the parameters from the user. */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_doorbell))) return -EFAULT; param.ret = ev_doorbell_send(param.doorbell); if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return 0;} static long ioctl_dtprop(struct fsl_hv_ioctl_prop __user *p, int set){ struct fsl_hv_ioctl_prop param; char __user *upath, *upropname; void __user *upropval; char *path = NULL, *propname = NULL; void *propval = NULL; int ret = 0; /* Get the parameters from the user. */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_prop))) return -EFAULT; upath = (char __user *)(uintptr_t)param.path; upropname = (char __user *)(uintptr_t)param.propname; upropval = (void __user *)(uintptr_t)param.propval; path = strndup_user(upath, FH_DTPROP_MAX_PATHLEN); if (IS_ERR(path)) { ret = PTR_ERR(path); goto out; } propname = strndup_user(upropname, FH_DTPROP_MAX_PATHLEN); if (IS_ERR(propname)) { ret = PTR_ERR(propname); goto out; } if (param.proplen > FH_DTPROP_MAX_PROPLEN) { ret = -EINVAL; goto out; } propval = kmalloc(param.proplen, GFP_KERNEL); if (!propval) { ret = -ENOMEM; goto out; } if (set) { if (copy_from_user(propval, upropval, param.proplen)) { ret = -EFAULT; goto out; } param.ret = fh_partition_set_dtprop(param.handle, virt_to_phys(path), virt_to_phys(propname), virt_to_phys(propval), param.proplen); } else { param.ret = fh_partition_get_dtprop(param.handle, virt_to_phys(path), virt_to_phys(propname), virt_to_phys(propval), ¶m.proplen); if (param.ret == 0) { if (copy_to_user(upropval, propval, param.proplen) || put_user(param.proplen, &p->proplen)) { ret = -EFAULT; goto out; } } } if (put_user(param.ret, &p->ret)) ret = -EFAULT; out: kfree(path); kfree(propval); kfree(propname); return ret;} /* * Ioctl main entry point */static long fsl_hv_ioctl(struct file *file, unsigned int cmd, unsigned long argaddr){ void __user *arg = (void __user *)argaddr; long ret; switch (cmd) { case FSL_HV_IOCTL_PARTITION_RESTART: ret = ioctl_restart(arg); break; case FSL_HV_IOCTL_PARTITION_GET_STATUS: ret = ioctl_status(arg); break; case FSL_HV_IOCTL_PARTITION_START: ret = ioctl_start(arg); break; case FSL_HV_IOCTL_PARTITION_STOP: ret = ioctl_stop(arg); break; case FSL_HV_IOCTL_MEMCPY: ret = ioctl_memcpy(arg); break; case FSL_HV_IOCTL_DOORBELL: ret = ioctl_doorbell(arg); break; case FSL_HV_IOCTL_GETPROP: ret = ioctl_dtprop(arg, 0); break; case FSL_HV_IOCTL_SETPROP: ret = ioctl_dtprop(arg, 1); break; default: pr_debug("fsl-hv: bad ioctl dir=%u type=%u cmd=%u size=%u\n", _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd)); return -ENOTTY; } return ret;} /* Linked list of processes that have us open */static struct list_head db_list; /* spinlock for db_list */static DEFINE_SPINLOCK(db_list_lock); /* The size of the doorbell event queue. This must be a power of two. */#define QSIZE 16 /* Returns the next head/tail pointer, wrapping around the queue if necessary */#define nextp(x) (((x) + 1) & (QSIZE - 1)) /* Per-open data structure */struct doorbell_queue { struct list_head list; spinlock_t lock; wait_queue_head_t wait; unsigned int head; unsigned int tail; uint32_t q[QSIZE];}; /* Linked list of ISRs that we registered */struct list_head isr_list; /* Per-ISR data structure */struct doorbell_isr { struct list_head list; unsigned int irq; uint32_t doorbell; /* The doorbell handle */ uint32_t partition; /* The partition handle, if used */}; /* * Add a doorbell to all of the doorbell queues */static void fsl_hv_queue_doorbell(uint32_t doorbell){ struct doorbell_queue *dbq; unsigned long flags; /* Prevent another core from modifying db_list */ spin_lock_irqsave(&db_list_lock, flags); list_for_each_entry(dbq, &db_list, list) { if (dbq->head != nextp(dbq->tail)) { dbq->q[dbq->tail] = doorbell; /* * This memory barrier eliminates the need to grab * the spinlock for dbq.Lines 1–500 of 933. Load more
/* * Freescale Hypervisor Management Driver * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. * Author: Timur Tabi <timur@freescale.com> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. * * The Freescale hypervisor management driver provides several services to * drivers and applications related to the Freescale hypervisor: * * 1. An ioctl interface for querying and managing partitions. * * 2. A file interface to reading incoming doorbells. * * 3. An interrupt handler for shutting down the partition upon receiving the * shutdown doorbell from a manager partition. * * 4. A kernel interface for receiving callbacks when a managed partition * shuts down. */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <linux/err.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/mm.h> #include <linux/pagemap.h> #include <linux/slab.h> #include <linux/poll.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/reboot.h> #include <linux/uaccess.h> #include <linux/notifier.h> #include <linux/interrupt.h> #include <linux/io.h> #include <asm/fsl_hcalls.h> #include <linux/fsl_hypervisor.h> static BLOCKING_NOTIFIER_HEAD(failover_subscribers); /* * Ioctl interface for FSL_HV_IOCTL_PARTITION_RESTART * * Restart a running partition */ static long ioctl_restart(struct fsl_hv_ioctl_restart __user *p) { struct fsl_hv_ioctl_restart param; /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_restart))) return -EFAULT; param.ret = fh_partition_restart(param.partition); if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return 0; } /* * Ioctl interface for FSL_HV_IOCTL_PARTITION_STATUS * * Query the status of a partition */ static long ioctl_status(struct fsl_hv_ioctl_status __user *p) { struct fsl_hv_ioctl_status param; u32 status; /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_status))) return -EFAULT; param.ret = fh_partition_get_status(param.partition, &status); if (!param.ret) param.status = status; if (copy_to_user(p, ¶m, sizeof(struct fsl_hv_ioctl_status))) return -EFAULT; return 0; } /* * Ioctl interface for FSL_HV_IOCTL_PARTITION_START * * Start a stopped partition. */ static long ioctl_start(struct fsl_hv_ioctl_start __user *p) { struct fsl_hv_ioctl_start param; /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_start))) return -EFAULT; param.ret = fh_partition_start(param.partition, param.entry_point, param.load); if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return 0; } /* * Ioctl interface for FSL_HV_IOCTL_PARTITION_STOP * * Stop a running partition */ static long ioctl_stop(struct fsl_hv_ioctl_stop __user *p) { struct fsl_hv_ioctl_stop param; /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_stop))) return -EFAULT; param.ret = fh_partition_stop(param.partition); if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return 0; } /* * Ioctl interface for FSL_HV_IOCTL_MEMCPY * * The FH_MEMCPY hypercall takes an array of address/address/size structures * to represent the data being copied. As a convenience to the user, this * ioctl takes a user-create buffer and a pointer to a guest physically * contiguous buffer in the remote partition, and creates the * address/address/size array for the hypercall. */ static long ioctl_memcpy(struct fsl_hv_ioctl_memcpy __user *p) { struct fsl_hv_ioctl_memcpy param; struct page **pages = NULL; void *sg_list_unaligned = NULL; struct fh_sg_list *sg_list = NULL; unsigned int num_pages; unsigned long lb_offset; /* Offset within a page of the local buffer */ unsigned int i; long ret = 0; int num_pinned; /* return value from get_user_pages() */ phys_addr_t remote_paddr; /* The next address in the remote buffer */ uint32_t count; /* The number of bytes left to copy */ /* Get the parameters from the user */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_memcpy))) return -EFAULT; /* * One partition must be local, the other must be remote. In other * words, if source and target are both -1, or are both not -1, then * return an error. */ if ((param.source == -1) == (param.target == -1)) return -EINVAL; /* * The array of pages returned by get_user_pages() covers only * page-aligned memory. Since the user buffer is probably not * page-aligned, we need to handle the discrepancy. * * We calculate the offset within a page of the S/G list, and make * adjustments accordingly. This will result in a page list that looks * like this: * * ---- <-- first page starts before the buffer * | | * |////|-> ---- * |////| | | * ---- | | * | | * ---- | | * |////| | | * |////| | | * |////| | | * ---- | | * | | * ---- | | * |////| | | * |////| | | * |////| | | * ---- | | * | | * ---- | | * |////| | | * |////|-> ---- * | | <-- last page ends after the buffer * ---- * * The distance between the start of the first page and the start of the * buffer is lb_offset. The hashed (///) areas are the parts of the * page list that contain the actual buffer. * * The advantage of this approach is that the number of pages is * equal to the number of entries in the S/G list that we give to the * hypervisor. */ lb_offset = param.local_vaddr & (PAGE_SIZE - 1); num_pages = (param.count + lb_offset + PAGE_SIZE - 1) >> PAGE_SHIFT; /* Allocate the buffers we need */ /* * 'pages' is an array of struct page pointers that's initialized by * get_user_pages(). */ pages = kzalloc(num_pages * sizeof(struct page *), GFP_KERNEL); if (!pages) { pr_debug("fsl-hv: could not allocate page list\n"); return -ENOMEM; } /* * sg_list is the list of fh_sg_list objects that we pass to the * hypervisor. */ sg_list_unaligned = kmalloc(num_pages * sizeof(struct fh_sg_list) + sizeof(struct fh_sg_list) - 1, GFP_KERNEL); if (!sg_list_unaligned) { pr_debug("fsl-hv: could not allocate S/G list\n"); ret = -ENOMEM; goto exit; } sg_list = PTR_ALIGN(sg_list_unaligned, sizeof(struct fh_sg_list)); /* Get the physical addresses of the source buffer */ num_pinned = get_user_pages_unlocked(param.local_vaddr - lb_offset, num_pages, pages, (param.source == -1) ? 0 : FOLL_WRITE); if (num_pinned != num_pages) { /* get_user_pages() failed */ pr_debug("fsl-hv: could not lock source buffer\n"); ret = (num_pinned < 0) ? num_pinned : -EFAULT; goto exit; } /* * Build the fh_sg_list[] array. The first page is special * because it's misaligned. */ if (param.source == -1) { sg_list[0].source = page_to_phys(pages[0]) + lb_offset; sg_list[0].target = param.remote_paddr; } else { sg_list[0].source = param.remote_paddr; sg_list[0].target = page_to_phys(pages[0]) + lb_offset; } sg_list[0].size = min_t(uint64_t, param.count, PAGE_SIZE - lb_offset); remote_paddr = param.remote_paddr + sg_list[0].size; count = param.count - sg_list[0].size; for (i = 1; i < num_pages; i++) { if (param.source == -1) { /* local to remote */ sg_list[i].source = page_to_phys(pages[i]); sg_list[i].target = remote_paddr; } else { /* remote to local */ sg_list[i].source = remote_paddr; sg_list[i].target = page_to_phys(pages[i]); } sg_list[i].size = min_t(uint64_t, count, PAGE_SIZE); remote_paddr += sg_list[i].size; count -= sg_list[i].size; } param.ret = fh_partition_memcpy(param.source, param.target, virt_to_phys(sg_list), num_pages); exit: if (pages) { for (i = 0; i < num_pages; i++) if (pages[i]) put_page(pages[i]); } kfree(sg_list_unaligned); kfree(pages); if (!ret) if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return ret; } /* * Ioctl interface for FSL_HV_IOCTL_DOORBELL * * Ring a doorbell */ static long ioctl_doorbell(struct fsl_hv_ioctl_doorbell __user *p) { struct fsl_hv_ioctl_doorbell param; /* Get the parameters from the user. */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_doorbell))) return -EFAULT; param.ret = ev_doorbell_send(param.doorbell); if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) return -EFAULT; return 0; } static long ioctl_dtprop(struct fsl_hv_ioctl_prop __user *p, int set) { struct fsl_hv_ioctl_prop param; char __user *upath, *upropname; void __user *upropval; char *path = NULL, *propname = NULL; void *propval = NULL; int ret = 0; /* Get the parameters from the user. */ if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_prop))) return -EFAULT; upath = (char __user *)(uintptr_t)param.path; upropname = (char __user *)(uintptr_t)param.propname; upropval = (void __user *)(uintptr_t)param.propval; path = strndup_user(upath, FH_DTPROP_MAX_PATHLEN); if (IS_ERR(path)) { ret = PTR_ERR(path); goto out; } propname = strndup_user(upropname, FH_DTPROP_MAX_PATHLEN); if (IS_ERR(propname)) { ret = PTR_ERR(propname); goto out; } if (param.proplen > FH_DTPROP_MAX_PROPLEN) { ret = -EINVAL; goto out; } propval = kmalloc(param.proplen, GFP_KERNEL); if (!propval) { ret = -ENOMEM; goto out; } if (set) { if (copy_from_user(propval, upropval, param.proplen)) { ret = -EFAULT; goto out; } param.ret = fh_partition_set_dtprop(param.handle, virt_to_phys(path), virt_to_phys(propname), virt_to_phys(propval), param.proplen); } else { param.ret = fh_partition_get_dtprop(param.handle, virt_to_phys(path), virt_to_phys(propname), virt_to_phys(propval), ¶m.proplen); if (param.ret == 0) { if (copy_to_user(upropval, propval, param.proplen) || put_user(param.proplen, &p->proplen)) { ret = -EFAULT; goto out; } } } if (put_user(param.ret, &p->ret)) ret = -EFAULT; out: kfree(path); kfree(propval); kfree(propname); return ret; } /* * Ioctl main entry point */ static long fsl_hv_ioctl(struct file *file, unsigned int cmd, unsigned long argaddr) { void __user *arg = (void __user *)argaddr; long ret; switch (cmd) { case FSL_HV_IOCTL_PARTITION_RESTART: ret = ioctl_restart(arg); break; case FSL_HV_IOCTL_PARTITION_GET_STATUS: ret = ioctl_status(arg); break; case FSL_HV_IOCTL_PARTITION_START: ret = ioctl_start(arg); break; case FSL_HV_IOCTL_PARTITION_STOP: ret = ioctl_stop(arg); break; case FSL_HV_IOCTL_MEMCPY: ret = ioctl_memcpy(arg); break; case FSL_HV_IOCTL_DOORBELL: ret = ioctl_doorbell(arg); break; case FSL_HV_IOCTL_GETPROP: ret = ioctl_dtprop(arg, 0); break; case FSL_HV_IOCTL_SETPROP: ret = ioctl_dtprop(arg, 1); break; default: pr_debug("fsl-hv: bad ioctl dir=%u type=%u cmd=%u size=%u\n", _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd)); return -ENOTTY; } return ret; } /* Linked list of processes that have us open */ static struct list_head db_list; /* spinlock for db_list */ static DEFINE_SPINLOCK(db_list_lock); /* The size of the doorbell event queue. This must be a power of two. */ #define QSIZE 16 /* Returns the next head/tail pointer, wrapping around the queue if necessary */ #define nextp(x) (((x) + 1) & (QSIZE - 1)) /* Per-open data structure */ struct doorbell_queue { struct list_head list; spinlock_t lock; wait_queue_head_t wait; unsigned int head; unsigned int tail; uint32_t q[QSIZE]; }; /* Linked list of ISRs that we registered */ struct list_head isr_list; /* Per-ISR data structure */ struct doorbell_isr { struct list_head list; unsigned int irq; uint32_t doorbell; /* The doorbell handle */ uint32_t partition; /* The partition handle, if used */ }; /* * Add a doorbell to all of the doorbell queues */ static void fsl_hv_queue_doorbell(uint32_t doorbell) { struct doorbell_queue *dbq; unsigned long flags; /* Prevent another core from modifying db_list */ spin_lock_irqsave(&db_list_lock, flags); list_for_each_entry(dbq, &db_list, list) { if (dbq->head != nextp(dbq->tail)) { dbq->q[dbq->tail] = doorbell; /* * This memory barrier eliminates the need to grab * the spinlock for dbq.