Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | // SPDX-License-Identifier: GPL-2.0-only /* * Character device interface driver for Remoteproc framework. * * Copyright (c) 2020, The Linux Foundation. All rights reserved. */ #include <linux/cdev.h> #include <linux/compat.h> #include <linux/fs.h> #include <linux/module.h> #include <linux/remoteproc.h> #include <linux/uaccess.h> #include <uapi/linux/remoteproc_cdev.h> #include "remoteproc_internal.h" #define NUM_RPROC_DEVICES 64 static dev_t rproc_major; static ssize_t rproc_cdev_write(struct file *filp, const char __user *buf, size_t len, loff_t *pos) { struct rproc *rproc = container_of(filp->f_inode->i_cdev, struct rproc, cdev); int ret = 0; char cmd[10]; if (!len || len > sizeof(cmd)) return -EINVAL; ret = copy_from_user(cmd, buf, len); if (ret) return -EFAULT; if (!strncmp(cmd, "start", len)) { ret = rproc_boot(rproc); } else if (!strncmp(cmd, "stop", len)) { ret = rproc_shutdown(rproc); } else if (!strncmp(cmd, "detach", len)) { ret = rproc_detach(rproc); } else { dev_err(&rproc->dev, "Unrecognized option\n"); ret = -EINVAL; } return ret ? ret : len; } static long rproc_device_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) { struct rproc *rproc = container_of(filp->f_inode->i_cdev, struct rproc, cdev); void __user *argp = (void __user *)arg; s32 param; switch (ioctl) { case RPROC_SET_SHUTDOWN_ON_RELEASE: if (copy_from_user(¶m, argp, sizeof(s32))) return -EFAULT; rproc->cdev_put_on_release = !!param; break; case RPROC_GET_SHUTDOWN_ON_RELEASE: param = (s32)rproc->cdev_put_on_release; if (copy_to_user(argp, ¶m, sizeof(s32))) return -EFAULT; break; default: dev_err(&rproc->dev, "Unsupported ioctl\n"); return -EINVAL; } return 0; } static int rproc_cdev_release(struct inode *inode, struct file *filp) { struct rproc *rproc = container_of(inode->i_cdev, struct rproc, cdev); int ret = 0; if (!rproc->cdev_put_on_release) return 0; if (rproc->state == RPROC_RUNNING) rproc_shutdown(rproc); else if (rproc->state == RPROC_ATTACHED) ret = rproc_detach(rproc); return ret; } static const struct file_operations rproc_fops = { .write = rproc_cdev_write, .unlocked_ioctl = rproc_device_ioctl, .compat_ioctl = compat_ptr_ioctl, .release = rproc_cdev_release, }; int rproc_char_device_add(struct rproc *rproc) { int ret; cdev_init(&rproc->cdev, &rproc_fops); rproc->cdev.owner = THIS_MODULE; rproc->dev.devt = MKDEV(MAJOR(rproc_major), rproc->index); cdev_set_parent(&rproc->cdev, &rproc->dev.kobj); ret = cdev_add(&rproc->cdev, rproc->dev.devt, 1); if (ret < 0) dev_err(&rproc->dev, "Failed to add char dev for %s\n", rproc->name); return ret; } void rproc_char_device_remove(struct rproc *rproc) { cdev_del(&rproc->cdev); } void __init rproc_init_cdev(void) { int ret; ret = alloc_chrdev_region(&rproc_major, 0, NUM_RPROC_DEVICES, "remoteproc"); if (ret < 0) pr_err("Failed to alloc rproc_cdev region, err %d\n", ret); } |