18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Freescale Hypervisor Management Driver 38c2ecf20Sopenharmony_ci 48c2ecf20Sopenharmony_ci * Copyright (C) 2008-2011 Freescale Semiconductor, Inc. 58c2ecf20Sopenharmony_ci * Author: Timur Tabi <timur@freescale.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public License 88c2ecf20Sopenharmony_ci * version 2. This program is licensed "as is" without any warranty of any 98c2ecf20Sopenharmony_ci * kind, whether express or implied. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * The Freescale hypervisor management driver provides several services to 128c2ecf20Sopenharmony_ci * drivers and applications related to the Freescale hypervisor: 138c2ecf20Sopenharmony_ci * 148c2ecf20Sopenharmony_ci * 1. An ioctl interface for querying and managing partitions. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * 2. A file interface to reading incoming doorbells. 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * 3. An interrupt handler for shutting down the partition upon receiving the 198c2ecf20Sopenharmony_ci * shutdown doorbell from a manager partition. 208c2ecf20Sopenharmony_ci * 218c2ecf20Sopenharmony_ci * 4. A kernel interface for receiving callbacks when a managed partition 228c2ecf20Sopenharmony_ci * shuts down. 238c2ecf20Sopenharmony_ci */ 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#include <linux/kernel.h> 268c2ecf20Sopenharmony_ci#include <linux/module.h> 278c2ecf20Sopenharmony_ci#include <linux/init.h> 288c2ecf20Sopenharmony_ci#include <linux/types.h> 298c2ecf20Sopenharmony_ci#include <linux/err.h> 308c2ecf20Sopenharmony_ci#include <linux/fs.h> 318c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 328c2ecf20Sopenharmony_ci#include <linux/mm.h> 338c2ecf20Sopenharmony_ci#include <linux/pagemap.h> 348c2ecf20Sopenharmony_ci#include <linux/slab.h> 358c2ecf20Sopenharmony_ci#include <linux/poll.h> 368c2ecf20Sopenharmony_ci#include <linux/of.h> 378c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 388c2ecf20Sopenharmony_ci#include <linux/reboot.h> 398c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 408c2ecf20Sopenharmony_ci#include <linux/notifier.h> 418c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#include <linux/io.h> 448c2ecf20Sopenharmony_ci#include <asm/fsl_hcalls.h> 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#include <linux/fsl_hypervisor.h> 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic BLOCKING_NOTIFIER_HEAD(failover_subscribers); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/* 518c2ecf20Sopenharmony_ci * Ioctl interface for FSL_HV_IOCTL_PARTITION_RESTART 528c2ecf20Sopenharmony_ci * 538c2ecf20Sopenharmony_ci * Restart a running partition 548c2ecf20Sopenharmony_ci */ 558c2ecf20Sopenharmony_cistatic long ioctl_restart(struct fsl_hv_ioctl_restart __user *p) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci struct fsl_hv_ioctl_restart param; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci /* Get the parameters from the user */ 608c2ecf20Sopenharmony_ci if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_restart))) 618c2ecf20Sopenharmony_ci return -EFAULT; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci param.ret = fh_partition_restart(param.partition); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) 668c2ecf20Sopenharmony_ci return -EFAULT; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci return 0; 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci/* 728c2ecf20Sopenharmony_ci * Ioctl interface for FSL_HV_IOCTL_PARTITION_STATUS 738c2ecf20Sopenharmony_ci * 748c2ecf20Sopenharmony_ci * Query the status of a partition 758c2ecf20Sopenharmony_ci */ 768c2ecf20Sopenharmony_cistatic long ioctl_status(struct fsl_hv_ioctl_status __user *p) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct fsl_hv_ioctl_status param; 798c2ecf20Sopenharmony_ci u32 status; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci /* Get the parameters from the user */ 828c2ecf20Sopenharmony_ci if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_status))) 838c2ecf20Sopenharmony_ci return -EFAULT; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci param.ret = fh_partition_get_status(param.partition, &status); 868c2ecf20Sopenharmony_ci if (!param.ret) 878c2ecf20Sopenharmony_ci param.status = status; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (copy_to_user(p, ¶m, sizeof(struct fsl_hv_ioctl_status))) 908c2ecf20Sopenharmony_ci return -EFAULT; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci return 0; 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci/* 968c2ecf20Sopenharmony_ci * Ioctl interface for FSL_HV_IOCTL_PARTITION_START 978c2ecf20Sopenharmony_ci * 988c2ecf20Sopenharmony_ci * Start a stopped partition. 998c2ecf20Sopenharmony_ci */ 1008c2ecf20Sopenharmony_cistatic long ioctl_start(struct fsl_hv_ioctl_start __user *p) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct fsl_hv_ioctl_start param; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* Get the parameters from the user */ 1058c2ecf20Sopenharmony_ci if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_start))) 1068c2ecf20Sopenharmony_ci return -EFAULT; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci param.ret = fh_partition_start(param.partition, param.entry_point, 1098c2ecf20Sopenharmony_ci param.load); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) 1128c2ecf20Sopenharmony_ci return -EFAULT; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return 0; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci/* 1188c2ecf20Sopenharmony_ci * Ioctl interface for FSL_HV_IOCTL_PARTITION_STOP 1198c2ecf20Sopenharmony_ci * 1208c2ecf20Sopenharmony_ci * Stop a running partition 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_cistatic long ioctl_stop(struct fsl_hv_ioctl_stop __user *p) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci struct fsl_hv_ioctl_stop param; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci /* Get the parameters from the user */ 1278c2ecf20Sopenharmony_ci if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_stop))) 1288c2ecf20Sopenharmony_ci return -EFAULT; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci param.ret = fh_partition_stop(param.partition); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) 1338c2ecf20Sopenharmony_ci return -EFAULT; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return 0; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci/* 1398c2ecf20Sopenharmony_ci * Ioctl interface for FSL_HV_IOCTL_MEMCPY 1408c2ecf20Sopenharmony_ci * 1418c2ecf20Sopenharmony_ci * The FH_MEMCPY hypercall takes an array of address/address/size structures 1428c2ecf20Sopenharmony_ci * to represent the data being copied. As a convenience to the user, this 1438c2ecf20Sopenharmony_ci * ioctl takes a user-create buffer and a pointer to a guest physically 1448c2ecf20Sopenharmony_ci * contiguous buffer in the remote partition, and creates the 1458c2ecf20Sopenharmony_ci * address/address/size array for the hypercall. 1468c2ecf20Sopenharmony_ci */ 1478c2ecf20Sopenharmony_cistatic long ioctl_memcpy(struct fsl_hv_ioctl_memcpy __user *p) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci struct fsl_hv_ioctl_memcpy param; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci struct page **pages = NULL; 1528c2ecf20Sopenharmony_ci void *sg_list_unaligned = NULL; 1538c2ecf20Sopenharmony_ci struct fh_sg_list *sg_list = NULL; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci unsigned int num_pages; 1568c2ecf20Sopenharmony_ci unsigned long lb_offset; /* Offset within a page of the local buffer */ 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci unsigned int i; 1598c2ecf20Sopenharmony_ci long ret = 0; 1608c2ecf20Sopenharmony_ci int num_pinned = 0; /* return value from get_user_pages_fast() */ 1618c2ecf20Sopenharmony_ci phys_addr_t remote_paddr; /* The next address in the remote buffer */ 1628c2ecf20Sopenharmony_ci uint32_t count; /* The number of bytes left to copy */ 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* Get the parameters from the user */ 1658c2ecf20Sopenharmony_ci if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_memcpy))) 1668c2ecf20Sopenharmony_ci return -EFAULT; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* 1698c2ecf20Sopenharmony_ci * One partition must be local, the other must be remote. In other 1708c2ecf20Sopenharmony_ci * words, if source and target are both -1, or are both not -1, then 1718c2ecf20Sopenharmony_ci * return an error. 1728c2ecf20Sopenharmony_ci */ 1738c2ecf20Sopenharmony_ci if ((param.source == -1) == (param.target == -1)) 1748c2ecf20Sopenharmony_ci return -EINVAL; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* 1778c2ecf20Sopenharmony_ci * The array of pages returned by get_user_pages_fast() covers only 1788c2ecf20Sopenharmony_ci * page-aligned memory. Since the user buffer is probably not 1798c2ecf20Sopenharmony_ci * page-aligned, we need to handle the discrepancy. 1808c2ecf20Sopenharmony_ci * 1818c2ecf20Sopenharmony_ci * We calculate the offset within a page of the S/G list, and make 1828c2ecf20Sopenharmony_ci * adjustments accordingly. This will result in a page list that looks 1838c2ecf20Sopenharmony_ci * like this: 1848c2ecf20Sopenharmony_ci * 1858c2ecf20Sopenharmony_ci * ---- <-- first page starts before the buffer 1868c2ecf20Sopenharmony_ci * | | 1878c2ecf20Sopenharmony_ci * |////|-> ---- 1888c2ecf20Sopenharmony_ci * |////| | | 1898c2ecf20Sopenharmony_ci * ---- | | 1908c2ecf20Sopenharmony_ci * | | 1918c2ecf20Sopenharmony_ci * ---- | | 1928c2ecf20Sopenharmony_ci * |////| | | 1938c2ecf20Sopenharmony_ci * |////| | | 1948c2ecf20Sopenharmony_ci * |////| | | 1958c2ecf20Sopenharmony_ci * ---- | | 1968c2ecf20Sopenharmony_ci * | | 1978c2ecf20Sopenharmony_ci * ---- | | 1988c2ecf20Sopenharmony_ci * |////| | | 1998c2ecf20Sopenharmony_ci * |////| | | 2008c2ecf20Sopenharmony_ci * |////| | | 2018c2ecf20Sopenharmony_ci * ---- | | 2028c2ecf20Sopenharmony_ci * | | 2038c2ecf20Sopenharmony_ci * ---- | | 2048c2ecf20Sopenharmony_ci * |////| | | 2058c2ecf20Sopenharmony_ci * |////|-> ---- 2068c2ecf20Sopenharmony_ci * | | <-- last page ends after the buffer 2078c2ecf20Sopenharmony_ci * ---- 2088c2ecf20Sopenharmony_ci * 2098c2ecf20Sopenharmony_ci * The distance between the start of the first page and the start of the 2108c2ecf20Sopenharmony_ci * buffer is lb_offset. The hashed (///) areas are the parts of the 2118c2ecf20Sopenharmony_ci * page list that contain the actual buffer. 2128c2ecf20Sopenharmony_ci * 2138c2ecf20Sopenharmony_ci * The advantage of this approach is that the number of pages is 2148c2ecf20Sopenharmony_ci * equal to the number of entries in the S/G list that we give to the 2158c2ecf20Sopenharmony_ci * hypervisor. 2168c2ecf20Sopenharmony_ci */ 2178c2ecf20Sopenharmony_ci lb_offset = param.local_vaddr & (PAGE_SIZE - 1); 2188c2ecf20Sopenharmony_ci if (param.count == 0 || 2198c2ecf20Sopenharmony_ci param.count > U64_MAX - lb_offset - PAGE_SIZE + 1) 2208c2ecf20Sopenharmony_ci return -EINVAL; 2218c2ecf20Sopenharmony_ci num_pages = (param.count + lb_offset + PAGE_SIZE - 1) >> PAGE_SHIFT; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci /* Allocate the buffers we need */ 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci /* 2268c2ecf20Sopenharmony_ci * 'pages' is an array of struct page pointers that's initialized by 2278c2ecf20Sopenharmony_ci * get_user_pages_fast(). 2288c2ecf20Sopenharmony_ci */ 2298c2ecf20Sopenharmony_ci pages = kcalloc(num_pages, sizeof(struct page *), GFP_KERNEL); 2308c2ecf20Sopenharmony_ci if (!pages) { 2318c2ecf20Sopenharmony_ci pr_debug("fsl-hv: could not allocate page list\n"); 2328c2ecf20Sopenharmony_ci return -ENOMEM; 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* 2368c2ecf20Sopenharmony_ci * sg_list is the list of fh_sg_list objects that we pass to the 2378c2ecf20Sopenharmony_ci * hypervisor. 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_ci sg_list_unaligned = kmalloc(num_pages * sizeof(struct fh_sg_list) + 2408c2ecf20Sopenharmony_ci sizeof(struct fh_sg_list) - 1, GFP_KERNEL); 2418c2ecf20Sopenharmony_ci if (!sg_list_unaligned) { 2428c2ecf20Sopenharmony_ci pr_debug("fsl-hv: could not allocate S/G list\n"); 2438c2ecf20Sopenharmony_ci ret = -ENOMEM; 2448c2ecf20Sopenharmony_ci goto free_pages; 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci sg_list = PTR_ALIGN(sg_list_unaligned, sizeof(struct fh_sg_list)); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci /* Get the physical addresses of the source buffer */ 2498c2ecf20Sopenharmony_ci num_pinned = get_user_pages_fast(param.local_vaddr - lb_offset, 2508c2ecf20Sopenharmony_ci num_pages, param.source != -1 ? FOLL_WRITE : 0, pages); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (num_pinned != num_pages) { 2538c2ecf20Sopenharmony_ci pr_debug("fsl-hv: could not lock source buffer\n"); 2548c2ecf20Sopenharmony_ci ret = (num_pinned < 0) ? num_pinned : -EFAULT; 2558c2ecf20Sopenharmony_ci goto exit; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci /* 2598c2ecf20Sopenharmony_ci * Build the fh_sg_list[] array. The first page is special 2608c2ecf20Sopenharmony_ci * because it's misaligned. 2618c2ecf20Sopenharmony_ci */ 2628c2ecf20Sopenharmony_ci if (param.source == -1) { 2638c2ecf20Sopenharmony_ci sg_list[0].source = page_to_phys(pages[0]) + lb_offset; 2648c2ecf20Sopenharmony_ci sg_list[0].target = param.remote_paddr; 2658c2ecf20Sopenharmony_ci } else { 2668c2ecf20Sopenharmony_ci sg_list[0].source = param.remote_paddr; 2678c2ecf20Sopenharmony_ci sg_list[0].target = page_to_phys(pages[0]) + lb_offset; 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci sg_list[0].size = min_t(uint64_t, param.count, PAGE_SIZE - lb_offset); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci remote_paddr = param.remote_paddr + sg_list[0].size; 2728c2ecf20Sopenharmony_ci count = param.count - sg_list[0].size; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci for (i = 1; i < num_pages; i++) { 2758c2ecf20Sopenharmony_ci if (param.source == -1) { 2768c2ecf20Sopenharmony_ci /* local to remote */ 2778c2ecf20Sopenharmony_ci sg_list[i].source = page_to_phys(pages[i]); 2788c2ecf20Sopenharmony_ci sg_list[i].target = remote_paddr; 2798c2ecf20Sopenharmony_ci } else { 2808c2ecf20Sopenharmony_ci /* remote to local */ 2818c2ecf20Sopenharmony_ci sg_list[i].source = remote_paddr; 2828c2ecf20Sopenharmony_ci sg_list[i].target = page_to_phys(pages[i]); 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci sg_list[i].size = min_t(uint64_t, count, PAGE_SIZE); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci remote_paddr += sg_list[i].size; 2878c2ecf20Sopenharmony_ci count -= sg_list[i].size; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci param.ret = fh_partition_memcpy(param.source, param.target, 2918c2ecf20Sopenharmony_ci virt_to_phys(sg_list), num_pages); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ciexit: 2948c2ecf20Sopenharmony_ci if (pages && (num_pinned > 0)) { 2958c2ecf20Sopenharmony_ci for (i = 0; i < num_pinned; i++) 2968c2ecf20Sopenharmony_ci put_page(pages[i]); 2978c2ecf20Sopenharmony_ci } 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci kfree(sg_list_unaligned); 3008c2ecf20Sopenharmony_cifree_pages: 3018c2ecf20Sopenharmony_ci kfree(pages); 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci if (!ret) 3048c2ecf20Sopenharmony_ci if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) 3058c2ecf20Sopenharmony_ci return -EFAULT; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return ret; 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci/* 3118c2ecf20Sopenharmony_ci * Ioctl interface for FSL_HV_IOCTL_DOORBELL 3128c2ecf20Sopenharmony_ci * 3138c2ecf20Sopenharmony_ci * Ring a doorbell 3148c2ecf20Sopenharmony_ci */ 3158c2ecf20Sopenharmony_cistatic long ioctl_doorbell(struct fsl_hv_ioctl_doorbell __user *p) 3168c2ecf20Sopenharmony_ci{ 3178c2ecf20Sopenharmony_ci struct fsl_hv_ioctl_doorbell param; 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci /* Get the parameters from the user. */ 3208c2ecf20Sopenharmony_ci if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_doorbell))) 3218c2ecf20Sopenharmony_ci return -EFAULT; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci param.ret = ev_doorbell_send(param.doorbell); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci if (copy_to_user(&p->ret, ¶m.ret, sizeof(__u32))) 3268c2ecf20Sopenharmony_ci return -EFAULT; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci return 0; 3298c2ecf20Sopenharmony_ci} 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_cistatic long ioctl_dtprop(struct fsl_hv_ioctl_prop __user *p, int set) 3328c2ecf20Sopenharmony_ci{ 3338c2ecf20Sopenharmony_ci struct fsl_hv_ioctl_prop param; 3348c2ecf20Sopenharmony_ci char __user *upath, *upropname; 3358c2ecf20Sopenharmony_ci void __user *upropval; 3368c2ecf20Sopenharmony_ci char *path, *propname; 3378c2ecf20Sopenharmony_ci void *propval; 3388c2ecf20Sopenharmony_ci int ret = 0; 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci /* Get the parameters from the user. */ 3418c2ecf20Sopenharmony_ci if (copy_from_user(¶m, p, sizeof(struct fsl_hv_ioctl_prop))) 3428c2ecf20Sopenharmony_ci return -EFAULT; 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci upath = (char __user *)(uintptr_t)param.path; 3458c2ecf20Sopenharmony_ci upropname = (char __user *)(uintptr_t)param.propname; 3468c2ecf20Sopenharmony_ci upropval = (void __user *)(uintptr_t)param.propval; 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci path = strndup_user(upath, FH_DTPROP_MAX_PATHLEN); 3498c2ecf20Sopenharmony_ci if (IS_ERR(path)) 3508c2ecf20Sopenharmony_ci return PTR_ERR(path); 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci propname = strndup_user(upropname, FH_DTPROP_MAX_PATHLEN); 3538c2ecf20Sopenharmony_ci if (IS_ERR(propname)) { 3548c2ecf20Sopenharmony_ci ret = PTR_ERR(propname); 3558c2ecf20Sopenharmony_ci goto err_free_path; 3568c2ecf20Sopenharmony_ci } 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci if (param.proplen > FH_DTPROP_MAX_PROPLEN) { 3598c2ecf20Sopenharmony_ci ret = -EINVAL; 3608c2ecf20Sopenharmony_ci goto err_free_propname; 3618c2ecf20Sopenharmony_ci } 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci propval = kmalloc(param.proplen, GFP_KERNEL); 3648c2ecf20Sopenharmony_ci if (!propval) { 3658c2ecf20Sopenharmony_ci ret = -ENOMEM; 3668c2ecf20Sopenharmony_ci goto err_free_propname; 3678c2ecf20Sopenharmony_ci } 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci if (set) { 3708c2ecf20Sopenharmony_ci if (copy_from_user(propval, upropval, param.proplen)) { 3718c2ecf20Sopenharmony_ci ret = -EFAULT; 3728c2ecf20Sopenharmony_ci goto err_free_propval; 3738c2ecf20Sopenharmony_ci } 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci param.ret = fh_partition_set_dtprop(param.handle, 3768c2ecf20Sopenharmony_ci virt_to_phys(path), 3778c2ecf20Sopenharmony_ci virt_to_phys(propname), 3788c2ecf20Sopenharmony_ci virt_to_phys(propval), 3798c2ecf20Sopenharmony_ci param.proplen); 3808c2ecf20Sopenharmony_ci } else { 3818c2ecf20Sopenharmony_ci param.ret = fh_partition_get_dtprop(param.handle, 3828c2ecf20Sopenharmony_ci virt_to_phys(path), 3838c2ecf20Sopenharmony_ci virt_to_phys(propname), 3848c2ecf20Sopenharmony_ci virt_to_phys(propval), 3858c2ecf20Sopenharmony_ci ¶m.proplen); 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci if (param.ret == 0) { 3888c2ecf20Sopenharmony_ci if (copy_to_user(upropval, propval, param.proplen) || 3898c2ecf20Sopenharmony_ci put_user(param.proplen, &p->proplen)) { 3908c2ecf20Sopenharmony_ci ret = -EFAULT; 3918c2ecf20Sopenharmony_ci goto err_free_propval; 3928c2ecf20Sopenharmony_ci } 3938c2ecf20Sopenharmony_ci } 3948c2ecf20Sopenharmony_ci } 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci if (put_user(param.ret, &p->ret)) 3978c2ecf20Sopenharmony_ci ret = -EFAULT; 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_cierr_free_propval: 4008c2ecf20Sopenharmony_ci kfree(propval); 4018c2ecf20Sopenharmony_cierr_free_propname: 4028c2ecf20Sopenharmony_ci kfree(propname); 4038c2ecf20Sopenharmony_cierr_free_path: 4048c2ecf20Sopenharmony_ci kfree(path); 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci return ret; 4078c2ecf20Sopenharmony_ci} 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci/* 4108c2ecf20Sopenharmony_ci * Ioctl main entry point 4118c2ecf20Sopenharmony_ci */ 4128c2ecf20Sopenharmony_cistatic long fsl_hv_ioctl(struct file *file, unsigned int cmd, 4138c2ecf20Sopenharmony_ci unsigned long argaddr) 4148c2ecf20Sopenharmony_ci{ 4158c2ecf20Sopenharmony_ci void __user *arg = (void __user *)argaddr; 4168c2ecf20Sopenharmony_ci long ret; 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci switch (cmd) { 4198c2ecf20Sopenharmony_ci case FSL_HV_IOCTL_PARTITION_RESTART: 4208c2ecf20Sopenharmony_ci ret = ioctl_restart(arg); 4218c2ecf20Sopenharmony_ci break; 4228c2ecf20Sopenharmony_ci case FSL_HV_IOCTL_PARTITION_GET_STATUS: 4238c2ecf20Sopenharmony_ci ret = ioctl_status(arg); 4248c2ecf20Sopenharmony_ci break; 4258c2ecf20Sopenharmony_ci case FSL_HV_IOCTL_PARTITION_START: 4268c2ecf20Sopenharmony_ci ret = ioctl_start(arg); 4278c2ecf20Sopenharmony_ci break; 4288c2ecf20Sopenharmony_ci case FSL_HV_IOCTL_PARTITION_STOP: 4298c2ecf20Sopenharmony_ci ret = ioctl_stop(arg); 4308c2ecf20Sopenharmony_ci break; 4318c2ecf20Sopenharmony_ci case FSL_HV_IOCTL_MEMCPY: 4328c2ecf20Sopenharmony_ci ret = ioctl_memcpy(arg); 4338c2ecf20Sopenharmony_ci break; 4348c2ecf20Sopenharmony_ci case FSL_HV_IOCTL_DOORBELL: 4358c2ecf20Sopenharmony_ci ret = ioctl_doorbell(arg); 4368c2ecf20Sopenharmony_ci break; 4378c2ecf20Sopenharmony_ci case FSL_HV_IOCTL_GETPROP: 4388c2ecf20Sopenharmony_ci ret = ioctl_dtprop(arg, 0); 4398c2ecf20Sopenharmony_ci break; 4408c2ecf20Sopenharmony_ci case FSL_HV_IOCTL_SETPROP: 4418c2ecf20Sopenharmony_ci ret = ioctl_dtprop(arg, 1); 4428c2ecf20Sopenharmony_ci break; 4438c2ecf20Sopenharmony_ci default: 4448c2ecf20Sopenharmony_ci pr_debug("fsl-hv: bad ioctl dir=%u type=%u cmd=%u size=%u\n", 4458c2ecf20Sopenharmony_ci _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), 4468c2ecf20Sopenharmony_ci _IOC_SIZE(cmd)); 4478c2ecf20Sopenharmony_ci return -ENOTTY; 4488c2ecf20Sopenharmony_ci } 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci return ret; 4518c2ecf20Sopenharmony_ci} 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci/* Linked list of processes that have us open */ 4548c2ecf20Sopenharmony_cistatic struct list_head db_list; 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci/* spinlock for db_list */ 4578c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(db_list_lock); 4588c2ecf20Sopenharmony_ci 4598c2ecf20Sopenharmony_ci/* The size of the doorbell event queue. This must be a power of two. */ 4608c2ecf20Sopenharmony_ci#define QSIZE 16 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci/* Returns the next head/tail pointer, wrapping around the queue if necessary */ 4638c2ecf20Sopenharmony_ci#define nextp(x) (((x) + 1) & (QSIZE - 1)) 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci/* Per-open data structure */ 4668c2ecf20Sopenharmony_cistruct doorbell_queue { 4678c2ecf20Sopenharmony_ci struct list_head list; 4688c2ecf20Sopenharmony_ci spinlock_t lock; 4698c2ecf20Sopenharmony_ci wait_queue_head_t wait; 4708c2ecf20Sopenharmony_ci unsigned int head; 4718c2ecf20Sopenharmony_ci unsigned int tail; 4728c2ecf20Sopenharmony_ci uint32_t q[QSIZE]; 4738c2ecf20Sopenharmony_ci}; 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci/* Linked list of ISRs that we registered */ 4768c2ecf20Sopenharmony_cistruct list_head isr_list; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci/* Per-ISR data structure */ 4798c2ecf20Sopenharmony_cistruct doorbell_isr { 4808c2ecf20Sopenharmony_ci struct list_head list; 4818c2ecf20Sopenharmony_ci unsigned int irq; 4828c2ecf20Sopenharmony_ci uint32_t doorbell; /* The doorbell handle */ 4838c2ecf20Sopenharmony_ci uint32_t partition; /* The partition handle, if used */ 4848c2ecf20Sopenharmony_ci}; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci/* 4878c2ecf20Sopenharmony_ci * Add a doorbell to all of the doorbell queues 4888c2ecf20Sopenharmony_ci */ 4898c2ecf20Sopenharmony_cistatic void fsl_hv_queue_doorbell(uint32_t doorbell) 4908c2ecf20Sopenharmony_ci{ 4918c2ecf20Sopenharmony_ci struct doorbell_queue *dbq; 4928c2ecf20Sopenharmony_ci unsigned long flags; 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci /* Prevent another core from modifying db_list */ 4958c2ecf20Sopenharmony_ci spin_lock_irqsave(&db_list_lock, flags); 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci list_for_each_entry(dbq, &db_list, list) { 4988c2ecf20Sopenharmony_ci if (dbq->head != nextp(dbq->tail)) { 4998c2ecf20Sopenharmony_ci dbq->q[dbq->tail] = doorbell; 5008c2ecf20Sopenharmony_ci /* 5018c2ecf20Sopenharmony_ci * This memory barrier eliminates the need to grab 5028c2ecf20Sopenharmony_ci * the spinlock for dbq. 5038c2ecf20Sopenharmony_ci */ 5048c2ecf20Sopenharmony_ci smp_wmb(); 5058c2ecf20Sopenharmony_ci dbq->tail = nextp(dbq->tail); 5068c2ecf20Sopenharmony_ci wake_up_interruptible(&dbq->wait); 5078c2ecf20Sopenharmony_ci } 5088c2ecf20Sopenharmony_ci } 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&db_list_lock, flags); 5118c2ecf20Sopenharmony_ci} 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci/* 5148c2ecf20Sopenharmony_ci * Interrupt handler for all doorbells 5158c2ecf20Sopenharmony_ci * 5168c2ecf20Sopenharmony_ci * We use the same interrupt handler for all doorbells. Whenever a doorbell 5178c2ecf20Sopenharmony_ci * is rung, and we receive an interrupt, we just put the handle for that 5188c2ecf20Sopenharmony_ci * doorbell (passed to us as *data) into all of the queues. 5198c2ecf20Sopenharmony_ci */ 5208c2ecf20Sopenharmony_cistatic irqreturn_t fsl_hv_isr(int irq, void *data) 5218c2ecf20Sopenharmony_ci{ 5228c2ecf20Sopenharmony_ci fsl_hv_queue_doorbell((uintptr_t) data); 5238c2ecf20Sopenharmony_ci 5248c2ecf20Sopenharmony_ci return IRQ_HANDLED; 5258c2ecf20Sopenharmony_ci} 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ci/* 5288c2ecf20Sopenharmony_ci * State change thread function 5298c2ecf20Sopenharmony_ci * 5308c2ecf20Sopenharmony_ci * The state change notification arrives in an interrupt, but we can't call 5318c2ecf20Sopenharmony_ci * blocking_notifier_call_chain() in an interrupt handler. We could call 5328c2ecf20Sopenharmony_ci * atomic_notifier_call_chain(), but that would require the clients' call-back 5338c2ecf20Sopenharmony_ci * function to run in interrupt context. Since we don't want to impose that 5348c2ecf20Sopenharmony_ci * restriction on the clients, we use a threaded IRQ to process the 5358c2ecf20Sopenharmony_ci * notification in kernel context. 5368c2ecf20Sopenharmony_ci */ 5378c2ecf20Sopenharmony_cistatic irqreturn_t fsl_hv_state_change_thread(int irq, void *data) 5388c2ecf20Sopenharmony_ci{ 5398c2ecf20Sopenharmony_ci struct doorbell_isr *dbisr = data; 5408c2ecf20Sopenharmony_ci 5418c2ecf20Sopenharmony_ci blocking_notifier_call_chain(&failover_subscribers, dbisr->partition, 5428c2ecf20Sopenharmony_ci NULL); 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci return IRQ_HANDLED; 5458c2ecf20Sopenharmony_ci} 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci/* 5488c2ecf20Sopenharmony_ci * Interrupt handler for state-change doorbells 5498c2ecf20Sopenharmony_ci */ 5508c2ecf20Sopenharmony_cistatic irqreturn_t fsl_hv_state_change_isr(int irq, void *data) 5518c2ecf20Sopenharmony_ci{ 5528c2ecf20Sopenharmony_ci unsigned int status; 5538c2ecf20Sopenharmony_ci struct doorbell_isr *dbisr = data; 5548c2ecf20Sopenharmony_ci int ret; 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_ci /* It's still a doorbell, so add it to all the queues. */ 5578c2ecf20Sopenharmony_ci fsl_hv_queue_doorbell(dbisr->doorbell); 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci /* Determine the new state, and if it's stopped, notify the clients. */ 5608c2ecf20Sopenharmony_ci ret = fh_partition_get_status(dbisr->partition, &status); 5618c2ecf20Sopenharmony_ci if (!ret && (status == FH_PARTITION_STOPPED)) 5628c2ecf20Sopenharmony_ci return IRQ_WAKE_THREAD; 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci return IRQ_HANDLED; 5658c2ecf20Sopenharmony_ci} 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_ci/* 5688c2ecf20Sopenharmony_ci * Returns a bitmask indicating whether a read will block 5698c2ecf20Sopenharmony_ci */ 5708c2ecf20Sopenharmony_cistatic __poll_t fsl_hv_poll(struct file *filp, struct poll_table_struct *p) 5718c2ecf20Sopenharmony_ci{ 5728c2ecf20Sopenharmony_ci struct doorbell_queue *dbq = filp->private_data; 5738c2ecf20Sopenharmony_ci unsigned long flags; 5748c2ecf20Sopenharmony_ci __poll_t mask; 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci spin_lock_irqsave(&dbq->lock, flags); 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci poll_wait(filp, &dbq->wait, p); 5798c2ecf20Sopenharmony_ci mask = (dbq->head == dbq->tail) ? 0 : (EPOLLIN | EPOLLRDNORM); 5808c2ecf20Sopenharmony_ci 5818c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&dbq->lock, flags); 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_ci return mask; 5848c2ecf20Sopenharmony_ci} 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_ci/* 5878c2ecf20Sopenharmony_ci * Return the handles for any incoming doorbells 5888c2ecf20Sopenharmony_ci * 5898c2ecf20Sopenharmony_ci * If there are doorbell handles in the queue for this open instance, then 5908c2ecf20Sopenharmony_ci * return them to the caller as an array of 32-bit integers. Otherwise, 5918c2ecf20Sopenharmony_ci * block until there is at least one handle to return. 5928c2ecf20Sopenharmony_ci */ 5938c2ecf20Sopenharmony_cistatic ssize_t fsl_hv_read(struct file *filp, char __user *buf, size_t len, 5948c2ecf20Sopenharmony_ci loff_t *off) 5958c2ecf20Sopenharmony_ci{ 5968c2ecf20Sopenharmony_ci struct doorbell_queue *dbq = filp->private_data; 5978c2ecf20Sopenharmony_ci uint32_t __user *p = (uint32_t __user *) buf; /* for put_user() */ 5988c2ecf20Sopenharmony_ci unsigned long flags; 5998c2ecf20Sopenharmony_ci ssize_t count = 0; 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ci /* Make sure we stop when the user buffer is full. */ 6028c2ecf20Sopenharmony_ci while (len >= sizeof(uint32_t)) { 6038c2ecf20Sopenharmony_ci uint32_t dbell; /* Local copy of doorbell queue data */ 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_ci spin_lock_irqsave(&dbq->lock, flags); 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci /* 6088c2ecf20Sopenharmony_ci * If the queue is empty, then either we're done or we need 6098c2ecf20Sopenharmony_ci * to block. If the application specified O_NONBLOCK, then 6108c2ecf20Sopenharmony_ci * we return the appropriate error code. 6118c2ecf20Sopenharmony_ci */ 6128c2ecf20Sopenharmony_ci if (dbq->head == dbq->tail) { 6138c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&dbq->lock, flags); 6148c2ecf20Sopenharmony_ci if (count) 6158c2ecf20Sopenharmony_ci break; 6168c2ecf20Sopenharmony_ci if (filp->f_flags & O_NONBLOCK) 6178c2ecf20Sopenharmony_ci return -EAGAIN; 6188c2ecf20Sopenharmony_ci if (wait_event_interruptible(dbq->wait, 6198c2ecf20Sopenharmony_ci dbq->head != dbq->tail)) 6208c2ecf20Sopenharmony_ci return -ERESTARTSYS; 6218c2ecf20Sopenharmony_ci continue; 6228c2ecf20Sopenharmony_ci } 6238c2ecf20Sopenharmony_ci 6248c2ecf20Sopenharmony_ci /* 6258c2ecf20Sopenharmony_ci * Even though we have an smp_wmb() in the ISR, the core 6268c2ecf20Sopenharmony_ci * might speculatively execute the "dbell = ..." below while 6278c2ecf20Sopenharmony_ci * it's evaluating the if-statement above. In that case, the 6288c2ecf20Sopenharmony_ci * value put into dbell could be stale if the core accepts the 6298c2ecf20Sopenharmony_ci * speculation. To prevent that, we need a read memory barrier 6308c2ecf20Sopenharmony_ci * here as well. 6318c2ecf20Sopenharmony_ci */ 6328c2ecf20Sopenharmony_ci smp_rmb(); 6338c2ecf20Sopenharmony_ci 6348c2ecf20Sopenharmony_ci /* Copy the data to a temporary local buffer, because 6358c2ecf20Sopenharmony_ci * we can't call copy_to_user() from inside a spinlock 6368c2ecf20Sopenharmony_ci */ 6378c2ecf20Sopenharmony_ci dbell = dbq->q[dbq->head]; 6388c2ecf20Sopenharmony_ci dbq->head = nextp(dbq->head); 6398c2ecf20Sopenharmony_ci 6408c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&dbq->lock, flags); 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci if (put_user(dbell, p)) 6438c2ecf20Sopenharmony_ci return -EFAULT; 6448c2ecf20Sopenharmony_ci p++; 6458c2ecf20Sopenharmony_ci count += sizeof(uint32_t); 6468c2ecf20Sopenharmony_ci len -= sizeof(uint32_t); 6478c2ecf20Sopenharmony_ci } 6488c2ecf20Sopenharmony_ci 6498c2ecf20Sopenharmony_ci return count; 6508c2ecf20Sopenharmony_ci} 6518c2ecf20Sopenharmony_ci 6528c2ecf20Sopenharmony_ci/* 6538c2ecf20Sopenharmony_ci * Open the driver and prepare for reading doorbells. 6548c2ecf20Sopenharmony_ci * 6558c2ecf20Sopenharmony_ci * Every time an application opens the driver, we create a doorbell queue 6568c2ecf20Sopenharmony_ci * for that file handle. This queue is used for any incoming doorbells. 6578c2ecf20Sopenharmony_ci */ 6588c2ecf20Sopenharmony_cistatic int fsl_hv_open(struct inode *inode, struct file *filp) 6598c2ecf20Sopenharmony_ci{ 6608c2ecf20Sopenharmony_ci struct doorbell_queue *dbq; 6618c2ecf20Sopenharmony_ci unsigned long flags; 6628c2ecf20Sopenharmony_ci int ret = 0; 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_ci dbq = kzalloc(sizeof(struct doorbell_queue), GFP_KERNEL); 6658c2ecf20Sopenharmony_ci if (!dbq) { 6668c2ecf20Sopenharmony_ci pr_err("fsl-hv: out of memory\n"); 6678c2ecf20Sopenharmony_ci return -ENOMEM; 6688c2ecf20Sopenharmony_ci } 6698c2ecf20Sopenharmony_ci 6708c2ecf20Sopenharmony_ci spin_lock_init(&dbq->lock); 6718c2ecf20Sopenharmony_ci init_waitqueue_head(&dbq->wait); 6728c2ecf20Sopenharmony_ci 6738c2ecf20Sopenharmony_ci spin_lock_irqsave(&db_list_lock, flags); 6748c2ecf20Sopenharmony_ci list_add(&dbq->list, &db_list); 6758c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&db_list_lock, flags); 6768c2ecf20Sopenharmony_ci 6778c2ecf20Sopenharmony_ci filp->private_data = dbq; 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci return ret; 6808c2ecf20Sopenharmony_ci} 6818c2ecf20Sopenharmony_ci 6828c2ecf20Sopenharmony_ci/* 6838c2ecf20Sopenharmony_ci * Close the driver 6848c2ecf20Sopenharmony_ci */ 6858c2ecf20Sopenharmony_cistatic int fsl_hv_close(struct inode *inode, struct file *filp) 6868c2ecf20Sopenharmony_ci{ 6878c2ecf20Sopenharmony_ci struct doorbell_queue *dbq = filp->private_data; 6888c2ecf20Sopenharmony_ci unsigned long flags; 6898c2ecf20Sopenharmony_ci 6908c2ecf20Sopenharmony_ci int ret = 0; 6918c2ecf20Sopenharmony_ci 6928c2ecf20Sopenharmony_ci spin_lock_irqsave(&db_list_lock, flags); 6938c2ecf20Sopenharmony_ci list_del(&dbq->list); 6948c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&db_list_lock, flags); 6958c2ecf20Sopenharmony_ci 6968c2ecf20Sopenharmony_ci kfree(dbq); 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci return ret; 6998c2ecf20Sopenharmony_ci} 7008c2ecf20Sopenharmony_ci 7018c2ecf20Sopenharmony_cistatic const struct file_operations fsl_hv_fops = { 7028c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 7038c2ecf20Sopenharmony_ci .open = fsl_hv_open, 7048c2ecf20Sopenharmony_ci .release = fsl_hv_close, 7058c2ecf20Sopenharmony_ci .poll = fsl_hv_poll, 7068c2ecf20Sopenharmony_ci .read = fsl_hv_read, 7078c2ecf20Sopenharmony_ci .unlocked_ioctl = fsl_hv_ioctl, 7088c2ecf20Sopenharmony_ci .compat_ioctl = compat_ptr_ioctl, 7098c2ecf20Sopenharmony_ci}; 7108c2ecf20Sopenharmony_ci 7118c2ecf20Sopenharmony_cistatic struct miscdevice fsl_hv_misc_dev = { 7128c2ecf20Sopenharmony_ci MISC_DYNAMIC_MINOR, 7138c2ecf20Sopenharmony_ci "fsl-hv", 7148c2ecf20Sopenharmony_ci &fsl_hv_fops 7158c2ecf20Sopenharmony_ci}; 7168c2ecf20Sopenharmony_ci 7178c2ecf20Sopenharmony_cistatic irqreturn_t fsl_hv_shutdown_isr(int irq, void *data) 7188c2ecf20Sopenharmony_ci{ 7198c2ecf20Sopenharmony_ci orderly_poweroff(false); 7208c2ecf20Sopenharmony_ci 7218c2ecf20Sopenharmony_ci return IRQ_HANDLED; 7228c2ecf20Sopenharmony_ci} 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ci/* 7258c2ecf20Sopenharmony_ci * Returns the handle of the parent of the given node 7268c2ecf20Sopenharmony_ci * 7278c2ecf20Sopenharmony_ci * The handle is the value of the 'hv-handle' property 7288c2ecf20Sopenharmony_ci */ 7298c2ecf20Sopenharmony_cistatic int get_parent_handle(struct device_node *np) 7308c2ecf20Sopenharmony_ci{ 7318c2ecf20Sopenharmony_ci struct device_node *parent; 7328c2ecf20Sopenharmony_ci const uint32_t *prop; 7338c2ecf20Sopenharmony_ci uint32_t handle; 7348c2ecf20Sopenharmony_ci int len; 7358c2ecf20Sopenharmony_ci 7368c2ecf20Sopenharmony_ci parent = of_get_parent(np); 7378c2ecf20Sopenharmony_ci if (!parent) 7388c2ecf20Sopenharmony_ci /* It's not really possible for this to fail */ 7398c2ecf20Sopenharmony_ci return -ENODEV; 7408c2ecf20Sopenharmony_ci 7418c2ecf20Sopenharmony_ci /* 7428c2ecf20Sopenharmony_ci * The proper name for the handle property is "hv-handle", but some 7438c2ecf20Sopenharmony_ci * older versions of the hypervisor used "reg". 7448c2ecf20Sopenharmony_ci */ 7458c2ecf20Sopenharmony_ci prop = of_get_property(parent, "hv-handle", &len); 7468c2ecf20Sopenharmony_ci if (!prop) 7478c2ecf20Sopenharmony_ci prop = of_get_property(parent, "reg", &len); 7488c2ecf20Sopenharmony_ci 7498c2ecf20Sopenharmony_ci if (!prop || (len != sizeof(uint32_t))) { 7508c2ecf20Sopenharmony_ci /* This can happen only if the node is malformed */ 7518c2ecf20Sopenharmony_ci of_node_put(parent); 7528c2ecf20Sopenharmony_ci return -ENODEV; 7538c2ecf20Sopenharmony_ci } 7548c2ecf20Sopenharmony_ci 7558c2ecf20Sopenharmony_ci handle = be32_to_cpup(prop); 7568c2ecf20Sopenharmony_ci of_node_put(parent); 7578c2ecf20Sopenharmony_ci 7588c2ecf20Sopenharmony_ci return handle; 7598c2ecf20Sopenharmony_ci} 7608c2ecf20Sopenharmony_ci 7618c2ecf20Sopenharmony_ci/* 7628c2ecf20Sopenharmony_ci * Register a callback for failover events 7638c2ecf20Sopenharmony_ci * 7648c2ecf20Sopenharmony_ci * This function is called by device drivers to register their callback 7658c2ecf20Sopenharmony_ci * functions for fail-over events. 7668c2ecf20Sopenharmony_ci */ 7678c2ecf20Sopenharmony_ciint fsl_hv_failover_register(struct notifier_block *nb) 7688c2ecf20Sopenharmony_ci{ 7698c2ecf20Sopenharmony_ci return blocking_notifier_chain_register(&failover_subscribers, nb); 7708c2ecf20Sopenharmony_ci} 7718c2ecf20Sopenharmony_ciEXPORT_SYMBOL(fsl_hv_failover_register); 7728c2ecf20Sopenharmony_ci 7738c2ecf20Sopenharmony_ci/* 7748c2ecf20Sopenharmony_ci * Unregister a callback for failover events 7758c2ecf20Sopenharmony_ci */ 7768c2ecf20Sopenharmony_ciint fsl_hv_failover_unregister(struct notifier_block *nb) 7778c2ecf20Sopenharmony_ci{ 7788c2ecf20Sopenharmony_ci return blocking_notifier_chain_unregister(&failover_subscribers, nb); 7798c2ecf20Sopenharmony_ci} 7808c2ecf20Sopenharmony_ciEXPORT_SYMBOL(fsl_hv_failover_unregister); 7818c2ecf20Sopenharmony_ci 7828c2ecf20Sopenharmony_ci/* 7838c2ecf20Sopenharmony_ci * Return TRUE if we're running under FSL hypervisor 7848c2ecf20Sopenharmony_ci * 7858c2ecf20Sopenharmony_ci * This function checks to see if we're running under the Freescale 7868c2ecf20Sopenharmony_ci * hypervisor, and returns zero if we're not, or non-zero if we are. 7878c2ecf20Sopenharmony_ci * 7888c2ecf20Sopenharmony_ci * First, it checks if MSR[GS]==1, which means we're running under some 7898c2ecf20Sopenharmony_ci * hypervisor. Then it checks if there is a hypervisor node in the device 7908c2ecf20Sopenharmony_ci * tree. Currently, that means there needs to be a node in the root called 7918c2ecf20Sopenharmony_ci * "hypervisor" and which has a property named "fsl,hv-version". 7928c2ecf20Sopenharmony_ci */ 7938c2ecf20Sopenharmony_cistatic int has_fsl_hypervisor(void) 7948c2ecf20Sopenharmony_ci{ 7958c2ecf20Sopenharmony_ci struct device_node *node; 7968c2ecf20Sopenharmony_ci int ret; 7978c2ecf20Sopenharmony_ci 7988c2ecf20Sopenharmony_ci node = of_find_node_by_path("/hypervisor"); 7998c2ecf20Sopenharmony_ci if (!node) 8008c2ecf20Sopenharmony_ci return 0; 8018c2ecf20Sopenharmony_ci 8028c2ecf20Sopenharmony_ci ret = of_find_property(node, "fsl,hv-version", NULL) != NULL; 8038c2ecf20Sopenharmony_ci 8048c2ecf20Sopenharmony_ci of_node_put(node); 8058c2ecf20Sopenharmony_ci 8068c2ecf20Sopenharmony_ci return ret; 8078c2ecf20Sopenharmony_ci} 8088c2ecf20Sopenharmony_ci 8098c2ecf20Sopenharmony_ci/* 8108c2ecf20Sopenharmony_ci * Freescale hypervisor management driver init 8118c2ecf20Sopenharmony_ci * 8128c2ecf20Sopenharmony_ci * This function is called when this module is loaded. 8138c2ecf20Sopenharmony_ci * 8148c2ecf20Sopenharmony_ci * Register ourselves as a miscellaneous driver. This will register the 8158c2ecf20Sopenharmony_ci * fops structure and create the right sysfs entries for udev. 8168c2ecf20Sopenharmony_ci */ 8178c2ecf20Sopenharmony_cistatic int __init fsl_hypervisor_init(void) 8188c2ecf20Sopenharmony_ci{ 8198c2ecf20Sopenharmony_ci struct device_node *np; 8208c2ecf20Sopenharmony_ci struct doorbell_isr *dbisr, *n; 8218c2ecf20Sopenharmony_ci int ret; 8228c2ecf20Sopenharmony_ci 8238c2ecf20Sopenharmony_ci pr_info("Freescale hypervisor management driver\n"); 8248c2ecf20Sopenharmony_ci 8258c2ecf20Sopenharmony_ci if (!has_fsl_hypervisor()) { 8268c2ecf20Sopenharmony_ci pr_info("fsl-hv: no hypervisor found\n"); 8278c2ecf20Sopenharmony_ci return -ENODEV; 8288c2ecf20Sopenharmony_ci } 8298c2ecf20Sopenharmony_ci 8308c2ecf20Sopenharmony_ci ret = misc_register(&fsl_hv_misc_dev); 8318c2ecf20Sopenharmony_ci if (ret) { 8328c2ecf20Sopenharmony_ci pr_err("fsl-hv: cannot register device\n"); 8338c2ecf20Sopenharmony_ci return ret; 8348c2ecf20Sopenharmony_ci } 8358c2ecf20Sopenharmony_ci 8368c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&db_list); 8378c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&isr_list); 8388c2ecf20Sopenharmony_ci 8398c2ecf20Sopenharmony_ci for_each_compatible_node(np, NULL, "epapr,hv-receive-doorbell") { 8408c2ecf20Sopenharmony_ci unsigned int irq; 8418c2ecf20Sopenharmony_ci const uint32_t *handle; 8428c2ecf20Sopenharmony_ci 8438c2ecf20Sopenharmony_ci handle = of_get_property(np, "interrupts", NULL); 8448c2ecf20Sopenharmony_ci irq = irq_of_parse_and_map(np, 0); 8458c2ecf20Sopenharmony_ci if (!handle || (irq == NO_IRQ)) { 8468c2ecf20Sopenharmony_ci pr_err("fsl-hv: no 'interrupts' property in %pOF node\n", 8478c2ecf20Sopenharmony_ci np); 8488c2ecf20Sopenharmony_ci continue; 8498c2ecf20Sopenharmony_ci } 8508c2ecf20Sopenharmony_ci 8518c2ecf20Sopenharmony_ci dbisr = kzalloc(sizeof(*dbisr), GFP_KERNEL); 8528c2ecf20Sopenharmony_ci if (!dbisr) 8538c2ecf20Sopenharmony_ci goto out_of_memory; 8548c2ecf20Sopenharmony_ci 8558c2ecf20Sopenharmony_ci dbisr->irq = irq; 8568c2ecf20Sopenharmony_ci dbisr->doorbell = be32_to_cpup(handle); 8578c2ecf20Sopenharmony_ci 8588c2ecf20Sopenharmony_ci if (of_device_is_compatible(np, "fsl,hv-shutdown-doorbell")) { 8598c2ecf20Sopenharmony_ci /* The shutdown doorbell gets its own ISR */ 8608c2ecf20Sopenharmony_ci ret = request_irq(irq, fsl_hv_shutdown_isr, 0, 8618c2ecf20Sopenharmony_ci np->name, NULL); 8628c2ecf20Sopenharmony_ci } else if (of_device_is_compatible(np, 8638c2ecf20Sopenharmony_ci "fsl,hv-state-change-doorbell")) { 8648c2ecf20Sopenharmony_ci /* 8658c2ecf20Sopenharmony_ci * The state change doorbell triggers a notification if 8668c2ecf20Sopenharmony_ci * the state of the managed partition changes to 8678c2ecf20Sopenharmony_ci * "stopped". We need a separate interrupt handler for 8688c2ecf20Sopenharmony_ci * that, and we also need to know the handle of the 8698c2ecf20Sopenharmony_ci * target partition, not just the handle of the 8708c2ecf20Sopenharmony_ci * doorbell. 8718c2ecf20Sopenharmony_ci */ 8728c2ecf20Sopenharmony_ci dbisr->partition = ret = get_parent_handle(np); 8738c2ecf20Sopenharmony_ci if (ret < 0) { 8748c2ecf20Sopenharmony_ci pr_err("fsl-hv: node %pOF has missing or " 8758c2ecf20Sopenharmony_ci "malformed parent\n", np); 8768c2ecf20Sopenharmony_ci kfree(dbisr); 8778c2ecf20Sopenharmony_ci continue; 8788c2ecf20Sopenharmony_ci } 8798c2ecf20Sopenharmony_ci ret = request_threaded_irq(irq, fsl_hv_state_change_isr, 8808c2ecf20Sopenharmony_ci fsl_hv_state_change_thread, 8818c2ecf20Sopenharmony_ci 0, np->name, dbisr); 8828c2ecf20Sopenharmony_ci } else 8838c2ecf20Sopenharmony_ci ret = request_irq(irq, fsl_hv_isr, 0, np->name, dbisr); 8848c2ecf20Sopenharmony_ci 8858c2ecf20Sopenharmony_ci if (ret < 0) { 8868c2ecf20Sopenharmony_ci pr_err("fsl-hv: could not request irq %u for node %pOF\n", 8878c2ecf20Sopenharmony_ci irq, np); 8888c2ecf20Sopenharmony_ci kfree(dbisr); 8898c2ecf20Sopenharmony_ci continue; 8908c2ecf20Sopenharmony_ci } 8918c2ecf20Sopenharmony_ci 8928c2ecf20Sopenharmony_ci list_add(&dbisr->list, &isr_list); 8938c2ecf20Sopenharmony_ci 8948c2ecf20Sopenharmony_ci pr_info("fsl-hv: registered handler for doorbell %u\n", 8958c2ecf20Sopenharmony_ci dbisr->doorbell); 8968c2ecf20Sopenharmony_ci } 8978c2ecf20Sopenharmony_ci 8988c2ecf20Sopenharmony_ci return 0; 8998c2ecf20Sopenharmony_ci 9008c2ecf20Sopenharmony_ciout_of_memory: 9018c2ecf20Sopenharmony_ci list_for_each_entry_safe(dbisr, n, &isr_list, list) { 9028c2ecf20Sopenharmony_ci free_irq(dbisr->irq, dbisr); 9038c2ecf20Sopenharmony_ci list_del(&dbisr->list); 9048c2ecf20Sopenharmony_ci kfree(dbisr); 9058c2ecf20Sopenharmony_ci } 9068c2ecf20Sopenharmony_ci 9078c2ecf20Sopenharmony_ci misc_deregister(&fsl_hv_misc_dev); 9088c2ecf20Sopenharmony_ci 9098c2ecf20Sopenharmony_ci return -ENOMEM; 9108c2ecf20Sopenharmony_ci} 9118c2ecf20Sopenharmony_ci 9128c2ecf20Sopenharmony_ci/* 9138c2ecf20Sopenharmony_ci * Freescale hypervisor management driver termination 9148c2ecf20Sopenharmony_ci * 9158c2ecf20Sopenharmony_ci * This function is called when this driver is unloaded. 9168c2ecf20Sopenharmony_ci */ 9178c2ecf20Sopenharmony_cistatic void __exit fsl_hypervisor_exit(void) 9188c2ecf20Sopenharmony_ci{ 9198c2ecf20Sopenharmony_ci struct doorbell_isr *dbisr, *n; 9208c2ecf20Sopenharmony_ci 9218c2ecf20Sopenharmony_ci list_for_each_entry_safe(dbisr, n, &isr_list, list) { 9228c2ecf20Sopenharmony_ci free_irq(dbisr->irq, dbisr); 9238c2ecf20Sopenharmony_ci list_del(&dbisr->list); 9248c2ecf20Sopenharmony_ci kfree(dbisr); 9258c2ecf20Sopenharmony_ci } 9268c2ecf20Sopenharmony_ci 9278c2ecf20Sopenharmony_ci misc_deregister(&fsl_hv_misc_dev); 9288c2ecf20Sopenharmony_ci} 9298c2ecf20Sopenharmony_ci 9308c2ecf20Sopenharmony_cimodule_init(fsl_hypervisor_init); 9318c2ecf20Sopenharmony_cimodule_exit(fsl_hypervisor_exit); 9328c2ecf20Sopenharmony_ci 9338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); 9348c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Freescale hypervisor management driver"); 9358c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 936