Commit | Line | Data |
---|---|---|
d1de6d6c BA |
1 | /* |
2 | * Copyright (c) 2017 Linaro Ltd. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 and | |
6 | * only version 2 as published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | */ | |
13 | ||
14 | #include <linux/kernel.h> | |
15 | #include <linux/cdev.h> | |
16 | #include <linux/err.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/of.h> | |
20 | #include <linux/of_reserved_mem.h> | |
21 | #include <linux/dma-mapping.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/uaccess.h> | |
24 | #include <linux/io.h> | |
25 | #include <linux/qcom_scm.h> | |
26 | ||
27 | #define QCOM_RMTFS_MEM_DEV_MAX (MINORMASK + 1) | |
28 | ||
29 | static dev_t qcom_rmtfs_mem_major; | |
30 | ||
31 | struct qcom_rmtfs_mem { | |
32 | struct device dev; | |
33 | struct cdev cdev; | |
34 | ||
35 | void *base; | |
36 | phys_addr_t addr; | |
37 | phys_addr_t size; | |
38 | ||
39 | unsigned int client_id; | |
fa65f804 BA |
40 | |
41 | unsigned int perms; | |
d1de6d6c BA |
42 | }; |
43 | ||
44 | static ssize_t qcom_rmtfs_mem_show(struct device *dev, | |
45 | struct device_attribute *attr, | |
46 | char *buf); | |
47 | ||
f58b0f9d EG |
48 | static DEVICE_ATTR(phys_addr, 0444, qcom_rmtfs_mem_show, NULL); |
49 | static DEVICE_ATTR(size, 0444, qcom_rmtfs_mem_show, NULL); | |
50 | static DEVICE_ATTR(client_id, 0444, qcom_rmtfs_mem_show, NULL); | |
d1de6d6c BA |
51 | |
52 | static ssize_t qcom_rmtfs_mem_show(struct device *dev, | |
53 | struct device_attribute *attr, | |
54 | char *buf) | |
55 | { | |
56 | struct qcom_rmtfs_mem *rmtfs_mem = container_of(dev, | |
57 | struct qcom_rmtfs_mem, | |
58 | dev); | |
59 | ||
60 | if (attr == &dev_attr_phys_addr) | |
61 | return sprintf(buf, "%pa\n", &rmtfs_mem->addr); | |
62 | if (attr == &dev_attr_size) | |
63 | return sprintf(buf, "%pa\n", &rmtfs_mem->size); | |
64 | if (attr == &dev_attr_client_id) | |
65 | return sprintf(buf, "%d\n", rmtfs_mem->client_id); | |
66 | ||
67 | return -EINVAL; | |
68 | } | |
69 | ||
70 | static struct attribute *qcom_rmtfs_mem_attrs[] = { | |
71 | &dev_attr_phys_addr.attr, | |
72 | &dev_attr_size.attr, | |
73 | &dev_attr_client_id.attr, | |
74 | NULL | |
75 | }; | |
76 | ATTRIBUTE_GROUPS(qcom_rmtfs_mem); | |
77 | ||
78 | static int qcom_rmtfs_mem_open(struct inode *inode, struct file *filp) | |
79 | { | |
80 | struct qcom_rmtfs_mem *rmtfs_mem = container_of(inode->i_cdev, | |
81 | struct qcom_rmtfs_mem, | |
82 | cdev); | |
83 | ||
84 | get_device(&rmtfs_mem->dev); | |
85 | filp->private_data = rmtfs_mem; | |
86 | ||
87 | return 0; | |
88 | } | |
89 | static ssize_t qcom_rmtfs_mem_read(struct file *filp, | |
90 | char __user *buf, size_t count, loff_t *f_pos) | |
91 | { | |
92 | struct qcom_rmtfs_mem *rmtfs_mem = filp->private_data; | |
93 | ||
94 | if (*f_pos >= rmtfs_mem->size) | |
95 | return 0; | |
96 | ||
97 | if (*f_pos + count >= rmtfs_mem->size) | |
98 | count = rmtfs_mem->size - *f_pos; | |
99 | ||
100 | if (copy_to_user(buf, rmtfs_mem->base + *f_pos, count)) | |
101 | return -EFAULT; | |
102 | ||
103 | *f_pos += count; | |
104 | return count; | |
105 | } | |
106 | ||
107 | static ssize_t qcom_rmtfs_mem_write(struct file *filp, | |
108 | const char __user *buf, size_t count, | |
109 | loff_t *f_pos) | |
110 | { | |
111 | struct qcom_rmtfs_mem *rmtfs_mem = filp->private_data; | |
112 | ||
113 | if (*f_pos >= rmtfs_mem->size) | |
114 | return 0; | |
115 | ||
116 | if (*f_pos + count >= rmtfs_mem->size) | |
117 | count = rmtfs_mem->size - *f_pos; | |
118 | ||
119 | if (copy_from_user(rmtfs_mem->base + *f_pos, buf, count)) | |
120 | return -EFAULT; | |
121 | ||
122 | *f_pos += count; | |
123 | return count; | |
124 | } | |
125 | ||
126 | static int qcom_rmtfs_mem_release(struct inode *inode, struct file *filp) | |
127 | { | |
128 | struct qcom_rmtfs_mem *rmtfs_mem = filp->private_data; | |
129 | ||
130 | put_device(&rmtfs_mem->dev); | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
b4aa93bc EG |
135 | static struct class rmtfs_class = { |
136 | .owner = THIS_MODULE, | |
137 | .name = "rmtfs", | |
138 | }; | |
139 | ||
8da3daaa AJ |
140 | static int qcom_rmtfs_mem_mmap(struct file *filep, struct vm_area_struct *vma) |
141 | { | |
142 | struct qcom_rmtfs_mem *rmtfs_mem = filep->private_data; | |
143 | ||
144 | if (vma->vm_end - vma->vm_start > rmtfs_mem->size) { | |
145 | dev_dbg(&rmtfs_mem->dev, | |
146 | "vm_end[%lu] - vm_start[%lu] [%lu] > mem->size[%pa]\n", | |
147 | vma->vm_end, vma->vm_start, | |
148 | (vma->vm_end - vma->vm_start), &rmtfs_mem->size); | |
149 | return -EINVAL; | |
150 | } | |
151 | ||
152 | vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); | |
153 | return remap_pfn_range(vma, | |
154 | vma->vm_start, | |
155 | rmtfs_mem->addr >> PAGE_SHIFT, | |
156 | vma->vm_end - vma->vm_start, | |
157 | vma->vm_page_prot); | |
158 | } | |
159 | ||
d1de6d6c BA |
160 | static const struct file_operations qcom_rmtfs_mem_fops = { |
161 | .owner = THIS_MODULE, | |
162 | .open = qcom_rmtfs_mem_open, | |
163 | .read = qcom_rmtfs_mem_read, | |
164 | .write = qcom_rmtfs_mem_write, | |
165 | .release = qcom_rmtfs_mem_release, | |
166 | .llseek = default_llseek, | |
8da3daaa | 167 | .mmap = qcom_rmtfs_mem_mmap, |
d1de6d6c BA |
168 | }; |
169 | ||
170 | static void qcom_rmtfs_mem_release_device(struct device *dev) | |
171 | { | |
172 | struct qcom_rmtfs_mem *rmtfs_mem = container_of(dev, | |
173 | struct qcom_rmtfs_mem, | |
174 | dev); | |
175 | ||
176 | kfree(rmtfs_mem); | |
177 | } | |
178 | ||
179 | static int qcom_rmtfs_mem_probe(struct platform_device *pdev) | |
180 | { | |
181 | struct device_node *node = pdev->dev.of_node; | |
fa65f804 | 182 | struct qcom_scm_vmperm perms[2]; |
d1de6d6c BA |
183 | struct reserved_mem *rmem; |
184 | struct qcom_rmtfs_mem *rmtfs_mem; | |
185 | u32 client_id; | |
fa65f804 | 186 | u32 vmid; |
d1de6d6c BA |
187 | int ret; |
188 | ||
189 | rmem = of_reserved_mem_lookup(node); | |
190 | if (!rmem) { | |
191 | dev_err(&pdev->dev, "failed to acquire memory region\n"); | |
192 | return -EINVAL; | |
193 | } | |
194 | ||
195 | ret = of_property_read_u32(node, "qcom,client-id", &client_id); | |
196 | if (ret) { | |
197 | dev_err(&pdev->dev, "failed to parse \"qcom,client-id\"\n"); | |
198 | return ret; | |
199 | ||
200 | } | |
201 | ||
202 | rmtfs_mem = kzalloc(sizeof(*rmtfs_mem), GFP_KERNEL); | |
203 | if (!rmtfs_mem) | |
204 | return -ENOMEM; | |
205 | ||
206 | rmtfs_mem->addr = rmem->base; | |
207 | rmtfs_mem->client_id = client_id; | |
208 | rmtfs_mem->size = rmem->size; | |
209 | ||
210 | device_initialize(&rmtfs_mem->dev); | |
211 | rmtfs_mem->dev.parent = &pdev->dev; | |
212 | rmtfs_mem->dev.groups = qcom_rmtfs_mem_groups; | |
78ee559d | 213 | rmtfs_mem->dev.release = qcom_rmtfs_mem_release_device; |
d1de6d6c BA |
214 | |
215 | rmtfs_mem->base = devm_memremap(&rmtfs_mem->dev, rmtfs_mem->addr, | |
216 | rmtfs_mem->size, MEMREMAP_WC); | |
217 | if (IS_ERR(rmtfs_mem->base)) { | |
218 | dev_err(&pdev->dev, "failed to remap rmtfs_mem region\n"); | |
219 | ret = PTR_ERR(rmtfs_mem->base); | |
220 | goto put_device; | |
221 | } | |
222 | ||
223 | cdev_init(&rmtfs_mem->cdev, &qcom_rmtfs_mem_fops); | |
224 | rmtfs_mem->cdev.owner = THIS_MODULE; | |
225 | ||
226 | dev_set_name(&rmtfs_mem->dev, "qcom_rmtfs_mem%d", client_id); | |
227 | rmtfs_mem->dev.id = client_id; | |
b4aa93bc | 228 | rmtfs_mem->dev.class = &rmtfs_class; |
d1de6d6c BA |
229 | rmtfs_mem->dev.devt = MKDEV(MAJOR(qcom_rmtfs_mem_major), client_id); |
230 | ||
231 | ret = cdev_device_add(&rmtfs_mem->cdev, &rmtfs_mem->dev); | |
232 | if (ret) { | |
233 | dev_err(&pdev->dev, "failed to add cdev: %d\n", ret); | |
234 | goto put_device; | |
235 | } | |
236 | ||
fa65f804 BA |
237 | ret = of_property_read_u32(node, "qcom,vmid", &vmid); |
238 | if (ret < 0 && ret != -EINVAL) { | |
239 | dev_err(&pdev->dev, "failed to parse qcom,vmid\n"); | |
240 | goto remove_cdev; | |
241 | } else if (!ret) { | |
137dc584 BA |
242 | if (!qcom_scm_is_available()) { |
243 | ret = -EPROBE_DEFER; | |
244 | goto remove_cdev; | |
245 | } | |
246 | ||
fa65f804 BA |
247 | perms[0].vmid = QCOM_SCM_VMID_HLOS; |
248 | perms[0].perm = QCOM_SCM_PERM_RW; | |
249 | perms[1].vmid = vmid; | |
250 | perms[1].perm = QCOM_SCM_PERM_RW; | |
251 | ||
252 | rmtfs_mem->perms = BIT(QCOM_SCM_VMID_HLOS); | |
253 | ret = qcom_scm_assign_mem(rmtfs_mem->addr, rmtfs_mem->size, | |
254 | &rmtfs_mem->perms, perms, 2); | |
255 | if (ret < 0) { | |
256 | dev_err(&pdev->dev, "assign memory failed\n"); | |
257 | goto remove_cdev; | |
258 | } | |
259 | } | |
260 | ||
d1de6d6c BA |
261 | dev_set_drvdata(&pdev->dev, rmtfs_mem); |
262 | ||
263 | return 0; | |
264 | ||
fa65f804 BA |
265 | remove_cdev: |
266 | cdev_device_del(&rmtfs_mem->cdev, &rmtfs_mem->dev); | |
d1de6d6c BA |
267 | put_device: |
268 | put_device(&rmtfs_mem->dev); | |
269 | ||
270 | return ret; | |
271 | } | |
272 | ||
273 | static int qcom_rmtfs_mem_remove(struct platform_device *pdev) | |
274 | { | |
275 | struct qcom_rmtfs_mem *rmtfs_mem = dev_get_drvdata(&pdev->dev); | |
fa65f804 BA |
276 | struct qcom_scm_vmperm perm; |
277 | ||
278 | if (rmtfs_mem->perms) { | |
279 | perm.vmid = QCOM_SCM_VMID_HLOS; | |
280 | perm.perm = QCOM_SCM_PERM_RW; | |
281 | ||
282 | qcom_scm_assign_mem(rmtfs_mem->addr, rmtfs_mem->size, | |
283 | &rmtfs_mem->perms, &perm, 1); | |
284 | } | |
d1de6d6c BA |
285 | |
286 | cdev_device_del(&rmtfs_mem->cdev, &rmtfs_mem->dev); | |
287 | put_device(&rmtfs_mem->dev); | |
288 | ||
289 | return 0; | |
290 | } | |
291 | ||
292 | static const struct of_device_id qcom_rmtfs_mem_of_match[] = { | |
293 | { .compatible = "qcom,rmtfs-mem" }, | |
294 | {} | |
295 | }; | |
296 | MODULE_DEVICE_TABLE(of, qcom_rmtfs_mem_of_match); | |
297 | ||
298 | static struct platform_driver qcom_rmtfs_mem_driver = { | |
299 | .probe = qcom_rmtfs_mem_probe, | |
300 | .remove = qcom_rmtfs_mem_remove, | |
301 | .driver = { | |
302 | .name = "qcom_rmtfs_mem", | |
303 | .of_match_table = qcom_rmtfs_mem_of_match, | |
304 | }, | |
305 | }; | |
306 | ||
b4aa93bc | 307 | static int __init qcom_rmtfs_mem_init(void) |
d1de6d6c BA |
308 | { |
309 | int ret; | |
310 | ||
b4aa93bc EG |
311 | ret = class_register(&rmtfs_class); |
312 | if (ret) | |
313 | return ret; | |
314 | ||
d1de6d6c BA |
315 | ret = alloc_chrdev_region(&qcom_rmtfs_mem_major, 0, |
316 | QCOM_RMTFS_MEM_DEV_MAX, "qcom_rmtfs_mem"); | |
317 | if (ret < 0) { | |
318 | pr_err("qcom_rmtfs_mem: failed to allocate char dev region\n"); | |
b4aa93bc | 319 | goto unregister_class; |
d1de6d6c BA |
320 | } |
321 | ||
322 | ret = platform_driver_register(&qcom_rmtfs_mem_driver); | |
323 | if (ret < 0) { | |
324 | pr_err("qcom_rmtfs_mem: failed to register rmtfs_mem driver\n"); | |
b4aa93bc | 325 | goto unregister_chrdev; |
d1de6d6c BA |
326 | } |
327 | ||
b4aa93bc EG |
328 | return 0; |
329 | ||
330 | unregister_chrdev: | |
331 | unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX); | |
332 | unregister_class: | |
333 | class_unregister(&rmtfs_class); | |
d1de6d6c BA |
334 | return ret; |
335 | } | |
336 | module_init(qcom_rmtfs_mem_init); | |
337 | ||
b4aa93bc | 338 | static void __exit qcom_rmtfs_mem_exit(void) |
d1de6d6c BA |
339 | { |
340 | platform_driver_unregister(&qcom_rmtfs_mem_driver); | |
341 | unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX); | |
b4aa93bc | 342 | class_unregister(&rmtfs_class); |
d1de6d6c BA |
343 | } |
344 | module_exit(qcom_rmtfs_mem_exit); | |
3b229bdb JC |
345 | |
346 | MODULE_AUTHOR("Linaro Ltd"); | |
347 | MODULE_DESCRIPTION("Qualcomm Remote Filesystem memory driver"); | |
348 | MODULE_LICENSE("GPL v2"); |