Commit | Line | Data |
---|---|---|
136200f4 | 1 | // SPDX-License-Identifier: GPL-2.0 |
c0cdc19f | 2 | /* |
69265bc1 | 3 | * Copyright (C) 2022, STMicroelectronics |
c0cdc19f BA |
4 | * Copyright (c) 2016, Linaro Ltd. |
5 | * Copyright (c) 2012, Michal Simek <monstr@monstr.eu> | |
6 | * Copyright (c) 2012, PetaLogix | |
7 | * Copyright (c) 2011, Texas Instruments, Inc. | |
8 | * Copyright (c) 2011, Google, Inc. | |
9 | * | |
10 | * Based on rpmsg performance statistics driver by Michal Simek, which in turn | |
11 | * was based on TI & Google OMX rpmsg driver. | |
c0cdc19f | 12 | */ |
c5727244 AP |
13 | |
14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
15 | ||
c0cdc19f BA |
16 | #include <linux/cdev.h> |
17 | #include <linux/device.h> | |
18 | #include <linux/fs.h> | |
19 | #include <linux/idr.h> | |
20 | #include <linux/kernel.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/poll.h> | |
23 | #include <linux/rpmsg.h> | |
24 | #include <linux/skbuff.h> | |
25 | #include <linux/slab.h> | |
26 | #include <linux/uaccess.h> | |
27 | #include <uapi/linux/rpmsg.h> | |
28 | ||
69265bc1 | 29 | #include "rpmsg_char.h" |
608edd96 | 30 | #include "rpmsg_internal.h" |
69265bc1 | 31 | |
c0cdc19f BA |
32 | #define RPMSG_DEV_MAX (MINORMASK + 1) |
33 | ||
34 | static dev_t rpmsg_major; | |
c0cdc19f | 35 | |
c0cdc19f BA |
36 | static DEFINE_IDA(rpmsg_ept_ida); |
37 | static DEFINE_IDA(rpmsg_minor_ida); | |
38 | ||
39 | #define dev_to_eptdev(dev) container_of(dev, struct rpmsg_eptdev, dev) | |
40 | #define cdev_to_eptdev(i_cdev) container_of(i_cdev, struct rpmsg_eptdev, cdev) | |
41 | ||
c0cdc19f BA |
42 | /** |
43 | * struct rpmsg_eptdev - endpoint device context | |
44 | * @dev: endpoint device | |
45 | * @cdev: cdev for the endpoint device | |
46 | * @rpdev: underlaying rpmsg device | |
47 | * @chinfo: info used to open the endpoint | |
48 | * @ept_lock: synchronization of @ept modifications | |
49 | * @ept: rpmsg endpoint reference, when open | |
50 | * @queue_lock: synchronization of @queue operations | |
51 | * @queue: incoming message queue | |
52 | * @readq: wait object for incoming queue | |
bea9b79c AP |
53 | * @default_ept: set to channel default endpoint if the default endpoint should be re-used |
54 | * on device open to prevent endpoint address update. | |
5550201c CL |
55 | * remote_flow_restricted: to indicate if the remote has requested for flow to be limited |
56 | * remote_flow_updated: to indicate if the flow control has been requested | |
c0cdc19f BA |
57 | */ |
58 | struct rpmsg_eptdev { | |
59 | struct device dev; | |
60 | struct cdev cdev; | |
61 | ||
62 | struct rpmsg_device *rpdev; | |
63 | struct rpmsg_channel_info chinfo; | |
64 | ||
65 | struct mutex ept_lock; | |
66 | struct rpmsg_endpoint *ept; | |
bea9b79c | 67 | struct rpmsg_endpoint *default_ept; |
c0cdc19f BA |
68 | |
69 | spinlock_t queue_lock; | |
70 | struct sk_buff_head queue; | |
71 | wait_queue_head_t readq; | |
bea9b79c | 72 | |
5550201c CL |
73 | bool remote_flow_restricted; |
74 | bool remote_flow_updated; | |
c0cdc19f BA |
75 | }; |
76 | ||
69265bc1 | 77 | int rpmsg_chrdev_eptdev_destroy(struct device *dev, void *data) |
c0cdc19f BA |
78 | { |
79 | struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev); | |
80 | ||
81 | mutex_lock(&eptdev->ept_lock); | |
17b88a20 | 82 | eptdev->rpdev = NULL; |
c0cdc19f | 83 | if (eptdev->ept) { |
467233a4 SW |
84 | /* The default endpoint is released by the rpmsg core */ |
85 | if (!eptdev->default_ept) | |
86 | rpmsg_destroy_ept(eptdev->ept); | |
c0cdc19f BA |
87 | eptdev->ept = NULL; |
88 | } | |
89 | mutex_unlock(&eptdev->ept_lock); | |
90 | ||
91 | /* wake up any blocked readers */ | |
92 | wake_up_interruptible(&eptdev->readq); | |
93 | ||
7a534ae8 | 94 | cdev_device_del(&eptdev->cdev, &eptdev->dev); |
c0cdc19f BA |
95 | put_device(&eptdev->dev); |
96 | ||
97 | return 0; | |
98 | } | |
69265bc1 | 99 | EXPORT_SYMBOL(rpmsg_chrdev_eptdev_destroy); |
c0cdc19f BA |
100 | |
101 | static int rpmsg_ept_cb(struct rpmsg_device *rpdev, void *buf, int len, | |
102 | void *priv, u32 addr) | |
103 | { | |
104 | struct rpmsg_eptdev *eptdev = priv; | |
105 | struct sk_buff *skb; | |
106 | ||
107 | skb = alloc_skb(len, GFP_ATOMIC); | |
108 | if (!skb) | |
109 | return -ENOMEM; | |
110 | ||
59ae1d12 | 111 | skb_put_data(skb, buf, len); |
c0cdc19f BA |
112 | |
113 | spin_lock(&eptdev->queue_lock); | |
114 | skb_queue_tail(&eptdev->queue, skb); | |
115 | spin_unlock(&eptdev->queue_lock); | |
116 | ||
117 | /* wake up any blocking processes, waiting for new data */ | |
118 | wake_up_interruptible(&eptdev->readq); | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
5550201c CL |
123 | static int rpmsg_ept_flow_cb(struct rpmsg_device *rpdev, void *priv, bool enable) |
124 | { | |
125 | struct rpmsg_eptdev *eptdev = priv; | |
126 | ||
127 | eptdev->remote_flow_restricted = enable; | |
128 | eptdev->remote_flow_updated = true; | |
129 | ||
130 | wake_up_interruptible(&eptdev->readq); | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
c0cdc19f BA |
135 | static int rpmsg_eptdev_open(struct inode *inode, struct file *filp) |
136 | { | |
137 | struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev); | |
138 | struct rpmsg_endpoint *ept; | |
139 | struct rpmsg_device *rpdev = eptdev->rpdev; | |
140 | struct device *dev = &eptdev->dev; | |
141 | ||
abe13e9a SW |
142 | mutex_lock(&eptdev->ept_lock); |
143 | if (eptdev->ept) { | |
144 | mutex_unlock(&eptdev->ept_lock); | |
964e8bed | 145 | return -EBUSY; |
abe13e9a | 146 | } |
964e8bed | 147 | |
17b88a20 DKS |
148 | if (!eptdev->rpdev) { |
149 | mutex_unlock(&eptdev->ept_lock); | |
150 | return -ENETRESET; | |
151 | } | |
152 | ||
c0cdc19f BA |
153 | get_device(dev); |
154 | ||
bea9b79c AP |
155 | /* |
156 | * If the default_ept is set, the rpmsg device default endpoint is used. | |
157 | * Else a new endpoint is created on open that will be destroyed on release. | |
158 | */ | |
159 | if (eptdev->default_ept) | |
160 | ept = eptdev->default_ept; | |
161 | else | |
162 | ept = rpmsg_create_ept(rpdev, rpmsg_ept_cb, eptdev, eptdev->chinfo); | |
163 | ||
c0cdc19f BA |
164 | if (!ept) { |
165 | dev_err(dev, "failed to open %s\n", eptdev->chinfo.name); | |
166 | put_device(dev); | |
abe13e9a | 167 | mutex_unlock(&eptdev->ept_lock); |
c0cdc19f BA |
168 | return -EINVAL; |
169 | } | |
170 | ||
5550201c | 171 | ept->flow_cb = rpmsg_ept_flow_cb; |
c0cdc19f BA |
172 | eptdev->ept = ept; |
173 | filp->private_data = eptdev; | |
abe13e9a | 174 | mutex_unlock(&eptdev->ept_lock); |
c0cdc19f BA |
175 | |
176 | return 0; | |
177 | } | |
178 | ||
179 | static int rpmsg_eptdev_release(struct inode *inode, struct file *filp) | |
180 | { | |
181 | struct rpmsg_eptdev *eptdev = cdev_to_eptdev(inode->i_cdev); | |
182 | struct device *dev = &eptdev->dev; | |
c0cdc19f BA |
183 | |
184 | /* Close the endpoint, if it's not already destroyed by the parent */ | |
185 | mutex_lock(&eptdev->ept_lock); | |
186 | if (eptdev->ept) { | |
bea9b79c AP |
187 | if (!eptdev->default_ept) |
188 | rpmsg_destroy_ept(eptdev->ept); | |
c0cdc19f BA |
189 | eptdev->ept = NULL; |
190 | } | |
191 | mutex_unlock(&eptdev->ept_lock); | |
5550201c | 192 | eptdev->remote_flow_updated = false; |
c0cdc19f BA |
193 | |
194 | /* Discard all SKBs */ | |
bb06a5ce | 195 | skb_queue_purge(&eptdev->queue); |
c0cdc19f BA |
196 | |
197 | put_device(dev); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
ccf45b18 | 202 | static ssize_t rpmsg_eptdev_read_iter(struct kiocb *iocb, struct iov_iter *to) |
c0cdc19f | 203 | { |
ccf45b18 | 204 | struct file *filp = iocb->ki_filp; |
c0cdc19f BA |
205 | struct rpmsg_eptdev *eptdev = filp->private_data; |
206 | unsigned long flags; | |
207 | struct sk_buff *skb; | |
208 | int use; | |
209 | ||
210 | if (!eptdev->ept) | |
211 | return -EPIPE; | |
212 | ||
213 | spin_lock_irqsave(&eptdev->queue_lock, flags); | |
214 | ||
215 | /* Wait for data in the queue */ | |
216 | if (skb_queue_empty(&eptdev->queue)) { | |
217 | spin_unlock_irqrestore(&eptdev->queue_lock, flags); | |
218 | ||
219 | if (filp->f_flags & O_NONBLOCK) | |
220 | return -EAGAIN; | |
221 | ||
222 | /* Wait until we get data or the endpoint goes away */ | |
223 | if (wait_event_interruptible(eptdev->readq, | |
224 | !skb_queue_empty(&eptdev->queue) || | |
225 | !eptdev->ept)) | |
226 | return -ERESTARTSYS; | |
227 | ||
228 | /* We lost the endpoint while waiting */ | |
229 | if (!eptdev->ept) | |
230 | return -EPIPE; | |
231 | ||
232 | spin_lock_irqsave(&eptdev->queue_lock, flags); | |
233 | } | |
234 | ||
235 | skb = skb_dequeue(&eptdev->queue); | |
0abd6bdd | 236 | spin_unlock_irqrestore(&eptdev->queue_lock, flags); |
c0cdc19f BA |
237 | if (!skb) |
238 | return -EFAULT; | |
239 | ||
ccf45b18 BA |
240 | use = min_t(size_t, iov_iter_count(to), skb->len); |
241 | if (copy_to_iter(skb->data, use, to) != use) | |
c0cdc19f BA |
242 | use = -EFAULT; |
243 | ||
244 | kfree_skb(skb); | |
245 | ||
246 | return use; | |
247 | } | |
248 | ||
ccf45b18 BA |
249 | static ssize_t rpmsg_eptdev_write_iter(struct kiocb *iocb, |
250 | struct iov_iter *from) | |
c0cdc19f | 251 | { |
ccf45b18 | 252 | struct file *filp = iocb->ki_filp; |
c0cdc19f | 253 | struct rpmsg_eptdev *eptdev = filp->private_data; |
ccf45b18 | 254 | size_t len = iov_iter_count(from); |
c0cdc19f BA |
255 | void *kbuf; |
256 | int ret; | |
257 | ||
ccf45b18 BA |
258 | kbuf = kzalloc(len, GFP_KERNEL); |
259 | if (!kbuf) | |
260 | return -ENOMEM; | |
261 | ||
bbe692e3 NE |
262 | if (!copy_from_iter_full(kbuf, len, from)) { |
263 | ret = -EFAULT; | |
264 | goto free_kbuf; | |
265 | } | |
c0cdc19f BA |
266 | |
267 | if (mutex_lock_interruptible(&eptdev->ept_lock)) { | |
268 | ret = -ERESTARTSYS; | |
269 | goto free_kbuf; | |
270 | } | |
271 | ||
272 | if (!eptdev->ept) { | |
273 | ret = -EPIPE; | |
274 | goto unlock_eptdev; | |
275 | } | |
276 | ||
cbf58250 | 277 | if (filp->f_flags & O_NONBLOCK) { |
b4ce7e2e | 278 | ret = rpmsg_trysendto(eptdev->ept, kbuf, len, eptdev->chinfo.dst); |
cbf58250 TB |
279 | if (ret == -ENOMEM) |
280 | ret = -EAGAIN; | |
281 | } else { | |
b4ce7e2e | 282 | ret = rpmsg_sendto(eptdev->ept, kbuf, len, eptdev->chinfo.dst); |
cbf58250 | 283 | } |
c0cdc19f BA |
284 | |
285 | unlock_eptdev: | |
286 | mutex_unlock(&eptdev->ept_lock); | |
287 | ||
288 | free_kbuf: | |
289 | kfree(kbuf); | |
290 | return ret < 0 ? ret : len; | |
291 | } | |
292 | ||
afc9a42b | 293 | static __poll_t rpmsg_eptdev_poll(struct file *filp, poll_table *wait) |
c0cdc19f BA |
294 | { |
295 | struct rpmsg_eptdev *eptdev = filp->private_data; | |
afc9a42b | 296 | __poll_t mask = 0; |
c0cdc19f BA |
297 | |
298 | if (!eptdev->ept) | |
a9a08845 | 299 | return EPOLLERR; |
c0cdc19f BA |
300 | |
301 | poll_wait(filp, &eptdev->readq, wait); | |
302 | ||
303 | if (!skb_queue_empty(&eptdev->queue)) | |
a9a08845 | 304 | mask |= EPOLLIN | EPOLLRDNORM; |
c0cdc19f | 305 | |
5550201c CL |
306 | if (eptdev->remote_flow_updated) |
307 | mask |= EPOLLPRI; | |
308 | ||
17b88a20 | 309 | mutex_lock(&eptdev->ept_lock); |
c0cdc19f | 310 | mask |= rpmsg_poll(eptdev->ept, filp, wait); |
17b88a20 | 311 | mutex_unlock(&eptdev->ept_lock); |
c0cdc19f BA |
312 | |
313 | return mask; | |
314 | } | |
315 | ||
316 | static long rpmsg_eptdev_ioctl(struct file *fp, unsigned int cmd, | |
317 | unsigned long arg) | |
318 | { | |
319 | struct rpmsg_eptdev *eptdev = fp->private_data; | |
320 | ||
5550201c CL |
321 | bool set; |
322 | int ret; | |
c0cdc19f | 323 | |
5550201c CL |
324 | switch (cmd) { |
325 | case RPMSG_GET_OUTGOING_FLOWCONTROL: | |
326 | eptdev->remote_flow_updated = false; | |
327 | ret = put_user(eptdev->remote_flow_restricted, (int __user *)arg); | |
328 | break; | |
329 | case RPMSG_SET_INCOMING_FLOWCONTROL: | |
330 | if (arg > 1) { | |
331 | ret = -EINVAL; | |
332 | break; | |
333 | } | |
334 | set = !!arg; | |
335 | ret = rpmsg_set_flow_control(eptdev->ept, set, eptdev->chinfo.dst); | |
336 | break; | |
337 | case RPMSG_DESTROY_EPT_IOCTL: | |
338 | /* Don't allow to destroy a default endpoint. */ | |
339 | if (eptdev->default_ept) { | |
340 | ret = -EINVAL; | |
341 | break; | |
342 | } | |
343 | ret = rpmsg_chrdev_eptdev_destroy(&eptdev->dev, NULL); | |
344 | break; | |
345 | default: | |
346 | ret = -EINVAL; | |
347 | } | |
bea9b79c | 348 | |
5550201c | 349 | return ret; |
c0cdc19f BA |
350 | } |
351 | ||
352 | static const struct file_operations rpmsg_eptdev_fops = { | |
353 | .owner = THIS_MODULE, | |
354 | .open = rpmsg_eptdev_open, | |
355 | .release = rpmsg_eptdev_release, | |
ccf45b18 BA |
356 | .read_iter = rpmsg_eptdev_read_iter, |
357 | .write_iter = rpmsg_eptdev_write_iter, | |
c0cdc19f BA |
358 | .poll = rpmsg_eptdev_poll, |
359 | .unlocked_ioctl = rpmsg_eptdev_ioctl, | |
1832f2d8 | 360 | .compat_ioctl = compat_ptr_ioctl, |
c0cdc19f BA |
361 | }; |
362 | ||
363 | static ssize_t name_show(struct device *dev, struct device_attribute *attr, | |
364 | char *buf) | |
365 | { | |
366 | struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev); | |
367 | ||
368 | return sprintf(buf, "%s\n", eptdev->chinfo.name); | |
369 | } | |
370 | static DEVICE_ATTR_RO(name); | |
371 | ||
372 | static ssize_t src_show(struct device *dev, struct device_attribute *attr, | |
373 | char *buf) | |
374 | { | |
375 | struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev); | |
376 | ||
377 | return sprintf(buf, "%d\n", eptdev->chinfo.src); | |
378 | } | |
379 | static DEVICE_ATTR_RO(src); | |
380 | ||
381 | static ssize_t dst_show(struct device *dev, struct device_attribute *attr, | |
382 | char *buf) | |
383 | { | |
384 | struct rpmsg_eptdev *eptdev = dev_get_drvdata(dev); | |
385 | ||
386 | return sprintf(buf, "%d\n", eptdev->chinfo.dst); | |
387 | } | |
388 | static DEVICE_ATTR_RO(dst); | |
389 | ||
390 | static struct attribute *rpmsg_eptdev_attrs[] = { | |
391 | &dev_attr_name.attr, | |
392 | &dev_attr_src.attr, | |
393 | &dev_attr_dst.attr, | |
394 | NULL | |
395 | }; | |
396 | ATTRIBUTE_GROUPS(rpmsg_eptdev); | |
397 | ||
398 | static void rpmsg_eptdev_release_device(struct device *dev) | |
399 | { | |
400 | struct rpmsg_eptdev *eptdev = dev_to_eptdev(dev); | |
401 | ||
402 | ida_simple_remove(&rpmsg_ept_ida, dev->id); | |
403 | ida_simple_remove(&rpmsg_minor_ida, MINOR(eptdev->dev.devt)); | |
c0cdc19f BA |
404 | kfree(eptdev); |
405 | } | |
406 | ||
cc9da7de AP |
407 | static struct rpmsg_eptdev *rpmsg_chrdev_eptdev_alloc(struct rpmsg_device *rpdev, |
408 | struct device *parent) | |
c0cdc19f | 409 | { |
c0cdc19f BA |
410 | struct rpmsg_eptdev *eptdev; |
411 | struct device *dev; | |
c0cdc19f BA |
412 | |
413 | eptdev = kzalloc(sizeof(*eptdev), GFP_KERNEL); | |
414 | if (!eptdev) | |
cc9da7de | 415 | return ERR_PTR(-ENOMEM); |
c0cdc19f BA |
416 | |
417 | dev = &eptdev->dev; | |
418 | eptdev->rpdev = rpdev; | |
c0cdc19f BA |
419 | |
420 | mutex_init(&eptdev->ept_lock); | |
421 | spin_lock_init(&eptdev->queue_lock); | |
422 | skb_queue_head_init(&eptdev->queue); | |
423 | init_waitqueue_head(&eptdev->readq); | |
424 | ||
425 | device_initialize(dev); | |
426 | dev->class = rpmsg_class; | |
69265bc1 | 427 | dev->parent = parent; |
c0cdc19f BA |
428 | dev->groups = rpmsg_eptdev_groups; |
429 | dev_set_drvdata(dev, eptdev); | |
430 | ||
431 | cdev_init(&eptdev->cdev, &rpmsg_eptdev_fops); | |
432 | eptdev->cdev.owner = THIS_MODULE; | |
433 | ||
cc9da7de AP |
434 | return eptdev; |
435 | } | |
436 | ||
437 | static int rpmsg_chrdev_eptdev_add(struct rpmsg_eptdev *eptdev, struct rpmsg_channel_info chinfo) | |
438 | { | |
439 | struct device *dev = &eptdev->dev; | |
440 | int ret; | |
441 | ||
442 | eptdev->chinfo = chinfo; | |
443 | ||
c0cdc19f BA |
444 | ret = ida_simple_get(&rpmsg_minor_ida, 0, RPMSG_DEV_MAX, GFP_KERNEL); |
445 | if (ret < 0) | |
446 | goto free_eptdev; | |
447 | dev->devt = MKDEV(MAJOR(rpmsg_major), ret); | |
448 | ||
449 | ret = ida_simple_get(&rpmsg_ept_ida, 0, 0, GFP_KERNEL); | |
450 | if (ret < 0) | |
451 | goto free_minor_ida; | |
452 | dev->id = ret; | |
453 | dev_set_name(dev, "rpmsg%d", ret); | |
454 | ||
7a534ae8 | 455 | ret = cdev_device_add(&eptdev->cdev, &eptdev->dev); |
c0cdc19f BA |
456 | if (ret) |
457 | goto free_ept_ida; | |
458 | ||
459 | /* We can now rely on the release function for cleanup */ | |
460 | dev->release = rpmsg_eptdev_release_device; | |
461 | ||
c0cdc19f BA |
462 | return ret; |
463 | ||
464 | free_ept_ida: | |
465 | ida_simple_remove(&rpmsg_ept_ida, dev->id); | |
466 | free_minor_ida: | |
467 | ida_simple_remove(&rpmsg_minor_ida, MINOR(dev->devt)); | |
468 | free_eptdev: | |
469 | put_device(dev); | |
470 | kfree(eptdev); | |
471 | ||
472 | return ret; | |
473 | } | |
cc9da7de AP |
474 | |
475 | int rpmsg_chrdev_eptdev_create(struct rpmsg_device *rpdev, struct device *parent, | |
476 | struct rpmsg_channel_info chinfo) | |
477 | { | |
478 | struct rpmsg_eptdev *eptdev; | |
cc9da7de AP |
479 | |
480 | eptdev = rpmsg_chrdev_eptdev_alloc(rpdev, parent); | |
481 | if (IS_ERR(eptdev)) | |
482 | return PTR_ERR(eptdev); | |
483 | ||
06564be4 | 484 | return rpmsg_chrdev_eptdev_add(eptdev, chinfo); |
cc9da7de | 485 | } |
69265bc1 | 486 | EXPORT_SYMBOL(rpmsg_chrdev_eptdev_create); |
c0cdc19f | 487 | |
bc69d106 AP |
488 | static int rpmsg_chrdev_probe(struct rpmsg_device *rpdev) |
489 | { | |
490 | struct rpmsg_channel_info chinfo; | |
491 | struct rpmsg_eptdev *eptdev; | |
492 | struct device *dev = &rpdev->dev; | |
493 | ||
494 | memcpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE); | |
495 | chinfo.src = rpdev->src; | |
496 | chinfo.dst = rpdev->dst; | |
497 | ||
498 | eptdev = rpmsg_chrdev_eptdev_alloc(rpdev, dev); | |
499 | if (IS_ERR(eptdev)) | |
500 | return PTR_ERR(eptdev); | |
501 | ||
502 | /* Set the default_ept to the rpmsg device endpoint */ | |
503 | eptdev->default_ept = rpdev->ept; | |
504 | ||
505 | /* | |
506 | * The rpmsg_ept_cb uses *priv parameter to get its rpmsg_eptdev context. | |
507 | * Storedit in default_ept *priv field. | |
508 | */ | |
509 | eptdev->default_ept->priv = eptdev; | |
510 | ||
511 | return rpmsg_chrdev_eptdev_add(eptdev, chinfo); | |
512 | } | |
513 | ||
514 | static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev) | |
515 | { | |
516 | int ret; | |
517 | ||
518 | ret = device_for_each_child(&rpdev->dev, NULL, rpmsg_chrdev_eptdev_destroy); | |
519 | if (ret) | |
520 | dev_warn(&rpdev->dev, "failed to destroy endpoints: %d\n", ret); | |
521 | } | |
522 | ||
523 | static struct rpmsg_device_id rpmsg_chrdev_id_table[] = { | |
524 | { .name = "rpmsg-raw" }, | |
525 | { }, | |
526 | }; | |
527 | ||
528 | static struct rpmsg_driver rpmsg_chrdev_driver = { | |
529 | .probe = rpmsg_chrdev_probe, | |
530 | .remove = rpmsg_chrdev_remove, | |
531 | .callback = rpmsg_ept_cb, | |
532 | .id_table = rpmsg_chrdev_id_table, | |
533 | .drv.name = "rpmsg_chrdev", | |
534 | }; | |
535 | ||
60d7b22d | 536 | static int rpmsg_chrdev_init(void) |
c0cdc19f BA |
537 | { |
538 | int ret; | |
539 | ||
617d3293 | 540 | ret = alloc_chrdev_region(&rpmsg_major, 0, RPMSG_DEV_MAX, "rpmsg_char"); |
c0cdc19f | 541 | if (ret < 0) { |
c5727244 | 542 | pr_err("failed to allocate char dev region\n"); |
c0cdc19f BA |
543 | return ret; |
544 | } | |
545 | ||
bc69d106 AP |
546 | ret = register_rpmsg_driver(&rpmsg_chrdev_driver); |
547 | if (ret < 0) { | |
548 | pr_err("rpmsg: failed to register rpmsg raw driver\n"); | |
549 | goto free_region; | |
550 | } | |
551 | ||
617d3293 | 552 | return 0; |
bc69d106 AP |
553 | |
554 | free_region: | |
555 | unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX); | |
556 | ||
557 | return ret; | |
c0cdc19f | 558 | } |
60d7b22d | 559 | postcore_initcall(rpmsg_chrdev_init); |
c0cdc19f BA |
560 | |
561 | static void rpmsg_chrdev_exit(void) | |
562 | { | |
bc69d106 | 563 | unregister_rpmsg_driver(&rpmsg_chrdev_driver); |
c0cdc19f BA |
564 | unregister_chrdev_region(rpmsg_major, RPMSG_DEV_MAX); |
565 | } | |
566 | module_exit(rpmsg_chrdev_exit); | |
93dd4e73 RF |
567 | |
568 | MODULE_ALIAS("rpmsg:rpmsg_chrdev"); | |
c0cdc19f | 569 | MODULE_LICENSE("GPL v2"); |