char: broadcom: Add vcio module
[platform/kernel/linux-rpi.git] / drivers / char / broadcom / vcio.c
1 /*
2  *  Copyright (C) 2010 Broadcom
3  *  Copyright (C) 2015 Noralf Trønnes
4  *  Copyright (C) 2021 Raspberry Pi (Trading) Ltd.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  */
11
12 #include <linux/cdev.h>
13 #include <linux/device.h>
14 #include <linux/fs.h>
15 #include <linux/init.h>
16 #include <linux/ioctl.h>
17 #include <linux/module.h>
18 #include <linux/slab.h>
19 #include <linux/uaccess.h>
20 #include <linux/compat.h>
21 #include <linux/miscdevice.h>
22 #include <soc/bcm2835/raspberrypi-firmware.h>
23
24 #define MODULE_NAME "vcio"
25 #define VCIO_IOC_MAGIC 100
26 #define IOCTL_MBOX_PROPERTY _IOWR(VCIO_IOC_MAGIC, 0, char *)
27 #ifdef CONFIG_COMPAT
28 #define IOCTL_MBOX_PROPERTY32 _IOWR(VCIO_IOC_MAGIC, 0, compat_uptr_t)
29 #endif
30
31 struct vcio_data {
32         struct rpi_firmware *fw;
33         struct miscdevice misc_dev;
34 };
35
36 static int vcio_user_property_list(struct vcio_data *vcio, void *user)
37 {
38         u32 *buf, size;
39         int ret;
40
41         /* The first 32-bit is the size of the buffer */
42         if (copy_from_user(&size, user, sizeof(size)))
43                 return -EFAULT;
44
45         buf = kmalloc(size, GFP_KERNEL);
46         if (!buf)
47                 return -ENOMEM;
48
49         if (copy_from_user(buf, user, size)) {
50                 kfree(buf);
51                 return -EFAULT;
52         }
53
54         /* Strip off protocol encapsulation */
55         ret = rpi_firmware_property_list(vcio->fw, &buf[2], size - 12);
56         if (ret) {
57                 kfree(buf);
58                 return ret;
59         }
60
61         buf[1] = RPI_FIRMWARE_STATUS_SUCCESS;
62         if (copy_to_user(user, buf, size))
63                 ret = -EFAULT;
64
65         kfree(buf);
66
67         return ret;
68 }
69
70 static int vcio_device_open(struct inode *inode, struct file *file)
71 {
72         try_module_get(THIS_MODULE);
73
74         return 0;
75 }
76
77 static int vcio_device_release(struct inode *inode, struct file *file)
78 {
79         module_put(THIS_MODULE);
80
81         return 0;
82 }
83
84 static long vcio_device_ioctl(struct file *file, unsigned int ioctl_num,
85                               unsigned long ioctl_param)
86 {
87         struct vcio_data *vcio = container_of(file->private_data,
88                                               struct vcio_data, misc_dev);
89
90         switch (ioctl_num) {
91         case IOCTL_MBOX_PROPERTY:
92                 return vcio_user_property_list(vcio, (void *)ioctl_param);
93         default:
94                 pr_err("unknown ioctl: %x\n", ioctl_num);
95                 return -EINVAL;
96         }
97 }
98
99 #ifdef CONFIG_COMPAT
100 static long vcio_device_compat_ioctl(struct file *file, unsigned int ioctl_num,
101                                      unsigned long ioctl_param)
102 {
103         struct vcio_data *vcio = container_of(file->private_data,
104                                               struct vcio_data, misc_dev);
105
106         switch (ioctl_num) {
107         case IOCTL_MBOX_PROPERTY32:
108                 return vcio_user_property_list(vcio, compat_ptr(ioctl_param));
109         default:
110                 pr_err("unknown ioctl: %x\n", ioctl_num);
111                 return -EINVAL;
112         }
113 }
114 #endif
115
116 const struct file_operations vcio_fops = {
117         .unlocked_ioctl = vcio_device_ioctl,
118 #ifdef CONFIG_COMPAT
119         .compat_ioctl = vcio_device_compat_ioctl,
120 #endif
121         .open = vcio_device_open,
122         .release = vcio_device_release,
123 };
124
125 static int vcio_probe(struct platform_device *pdev)
126 {
127         struct device *dev = &pdev->dev;
128         struct device_node *np = dev->of_node;
129         struct device_node *fw_node;
130         struct rpi_firmware *fw;
131         struct vcio_data *vcio;
132
133         fw_node = of_get_parent(np);
134         if (!fw_node) {
135                 dev_err(dev, "Missing firmware node\n");
136                 return -ENOENT;
137         }
138
139         fw = rpi_firmware_get(fw_node);
140         of_node_put(fw_node);
141         if (!fw)
142                 return -EPROBE_DEFER;
143
144         vcio = devm_kzalloc(dev, sizeof(struct vcio_data), GFP_KERNEL);
145         if (!vcio)
146                 return -ENOMEM;
147
148         vcio->fw = fw;
149         vcio->misc_dev.fops = &vcio_fops;
150         vcio->misc_dev.minor = MISC_DYNAMIC_MINOR;
151         vcio->misc_dev.name = "vcio";
152         vcio->misc_dev.parent = dev;
153
154         return misc_register(&vcio->misc_dev);
155 }
156
157 static int vcio_remove(struct platform_device *pdev)
158 {
159         struct device *dev = &pdev->dev;
160
161         misc_deregister(dev_get_drvdata(dev));
162         return 0;
163 }
164
165 static const struct of_device_id vcio_ids[] = {
166         { .compatible = "raspberrypi,vcio" },
167         { }
168 };
169 MODULE_DEVICE_TABLE(of, vcio_ids);
170
171 static struct platform_driver vcio_driver = {
172         .driver = {
173                 .name           = MODULE_NAME,
174                 .of_match_table = of_match_ptr(vcio_ids),
175         },
176         .probe  = vcio_probe,
177         .remove = vcio_remove,
178 };
179
180 module_platform_driver(vcio_driver);
181
182 MODULE_AUTHOR("Gray Girling");
183 MODULE_AUTHOR("Noralf Trønnes");
184 MODULE_DESCRIPTION("Mailbox userspace access");
185 MODULE_LICENSE("GPL");
186 MODULE_ALIAS("platform:rpi-vcio");