[PATCH 3/5] char: implement companion-char driver
From: Mark Jonas
Date: Tue Jun 05 2018 - 14:46:15 EST
From: Zhu Yi <yi.zhu5@xxxxxxxxxxxx>
The upper level companion-char driver provides character device
interface to userspace for communicate IO messages with the
companion processor.
Signed-off-by: Zhu Yi <yi.zhu5@xxxxxxxxxxxx>
Signed-off-by: Mark Jonas <mark.jonas@xxxxxxxxxxxx>
---
drivers/char/Kconfig | 7 +
drivers/char/Makefile | 2 +
drivers/char/companion-char.c | 367 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 376 insertions(+)
create mode 100644 drivers/char/companion-char.c
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index c28dca0..e878d56 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -588,5 +588,12 @@ config TILE_SROM
source "drivers/char/xillybus/Kconfig"
+config COMPANION_CHAR
+ tristate "Character device for companion communication (Bosch)"
+ depends on COMPANION_SPI
+ help
+ The character device allows the userspace to exchange IO messages
+ with the Bosch companion processor via the companion SPI driver.
+
endmenu
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 7dc3abe..dfe4fc1 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -60,3 +60,5 @@ js-rtc-y = rtc.o
obj-$(CONFIG_TILE_SROM) += tile-srom.o
obj-$(CONFIG_XILLYBUS) += xillybus/
obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o
+
+obj-$(CONFIG_COMPANION_CHAR) += companion-char.o
diff --git a/drivers/char/companion-char.c b/drivers/char/companion-char.c
new file mode 100644
index 0000000..3c198f2
--- /dev/null
+++ b/drivers/char/companion-char.c
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Companion upper level character device
+ *
+ * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/cdev.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/companion.h>
+
+#define DRIVER_NAME "bosch,companion-char"
+
+/*TODO: get from protocol.h*/
+#define COMPANION_PACKET_SIZE 16
+
+static struct class *companion_char_class;
+static dev_t devt;
+
+/**
+ * struct companion_char_minor - companion-char minor structure
+ * @dev: address of the associated device
+ * @writelock: mutex to protect write
+ * @readlock: mutex to protect read
+ * @writewait: wait queue head of write
+ * @readwait: wait queue head of read
+ */
+struct companion_char_minor {
+ struct device *dev;
+ struct mutex writelock;
+ struct mutex readlock;
+ wait_queue_head_t writewait;
+ wait_queue_head_t readwait;
+};
+
+/**
+ * struct companion_char_priv - companion-char private data structure
+ * @cdev: char device
+ * @parent: address of the associated parent device
+ * @minors: address of the companion-char minor
+ */
+struct companion_char_priv {
+ struct cdev cdev;
+ struct device *parent;
+ struct companion_char_minor *minors;
+};
+
+/**
+ * companion_char_read() - read callback
+ * @filp: address of the associated virtual file
+ * @buf: address of the user space buffer to receive
+ * @count: number of bytes to read
+ * @offset: address of the read offset
+ */
+static ssize_t companion_char_read(struct file *filp,
+ char __user *buf,
+ size_t count,
+ loff_t *offset)
+{
+ unsigned int number = MINOR(file_inode(filp)->i_rdev);
+ struct companion_char_priv *priv = filp->private_data;
+ struct companion_char_minor *minor = &priv->minors[number];
+ int status;
+
+ if (count != COMPANION_PACKET_SIZE)
+ return -EMSGSIZE;
+
+ if (mutex_lock_interruptible(&minor->readlock))
+ return -ERESTARTSYS;
+
+ while (companion_io_rxq_is_empty(priv->parent)) {
+ mutex_unlock(&minor->readlock);
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (wait_event_interruptible(minor->readwait,
+ !companion_io_rxq_is_empty(priv->parent)))
+ return -ERESTARTSYS;
+ if (mutex_lock_interruptible(&minor->readlock))
+ return -ERESTARTSYS;
+ }
+
+ status = companion_do_io_rx(priv->parent, buf, count);
+ mutex_unlock(&minor->readlock);
+ return status;
+}
+
+/**
+ * companion_char_write() - write callback
+ * @filp: address of the associated virtual file
+ * @buf: address of the user space buffer to transfer
+ * @count: number of bytes to write
+ * @offset: address of the write offset
+ */
+static ssize_t companion_char_write(struct file *filp,
+ const char __user *buf,
+ size_t count,
+ loff_t *offset)
+{
+ unsigned int number = MINOR(file_inode(filp)->i_rdev);
+ struct companion_char_priv *priv = filp->private_data;
+ struct companion_char_minor *minor = &priv->minors[number];
+ int status;
+
+ if (count != COMPANION_PACKET_SIZE)
+ return -EMSGSIZE;
+
+ if (mutex_lock_interruptible(&minor->writelock))
+ return -ERESTARTSYS;
+
+ while (companion_io_txq_is_full(priv->parent)) {
+ mutex_unlock(&minor->writelock);
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (wait_event_interruptible(minor->writewait,
+ !companion_io_txq_is_full(priv->parent)))
+ return -ERESTARTSYS;
+ if (mutex_lock_interruptible(&minor->writelock))
+ return -ERESTARTSYS;
+ }
+
+ status = companion_do_io_tx(priv->parent, buf, count);
+ mutex_unlock(&minor->writelock);
+ return status;
+}
+
+/**
+ * companion_char_poll() - poll callback
+ * @filp: address of the associated virtual file
+ * @wait: address of the associated poll table
+ */
+static unsigned int companion_char_poll(struct file *filp, poll_table *wait)
+{
+ unsigned int number = MINOR(file_inode(filp)->i_rdev);
+ struct companion_char_priv *priv = filp->private_data;
+ struct companion_char_minor *minor = &priv->minors[number];
+ unsigned int mask = 0;
+
+ poll_wait(filp, &minor->writewait, wait);
+ poll_wait(filp, &minor->readwait, wait);
+
+ mutex_lock(&minor->writelock);
+ if (!companion_io_txq_is_full(priv->parent))
+ mask |= POLLOUT | POLLWRNORM;
+ mutex_unlock(&minor->writelock);
+
+ mutex_lock(&minor->readlock);
+ if (!companion_io_rxq_is_empty(priv->parent))
+ mask |= POLLIN | POLLRDNORM;
+ mutex_unlock(&minor->readlock);
+
+ return mask;
+}
+
+/**
+ * companion_char_open() - open callback
+ * @inode: address of the associated inode
+ * @filp: address of the associated virtual file
+ */
+static int companion_char_open(struct inode *inode, struct file *filp)
+{
+ struct companion_char_priv *priv = container_of(
+ inode->i_cdev,
+ struct companion_char_priv,
+ cdev);
+ filp->private_data = priv;
+ nonseekable_open(inode, filp);
+ return 0;
+}
+
+/**
+ * companion_char_release() - release callback
+ * @inode: address of the associated inode
+ * @filp: address of the associated virtual file
+ */
+static int companion_char_release(struct inode *inode, struct file *filp)
+{
+ filp->private_data = NULL;
+ return 0;
+}
+
+static const struct file_operations companion_char_ops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = companion_char_read,
+ .write = companion_char_write,
+ .poll = companion_char_poll,
+ .open = companion_char_open,
+ .release = companion_char_release,
+};
+
+/**
+ * companion_char_on_tx_done() - tx done callback
+ * @data: address of user supplied callback data
+ */
+static void companion_char_on_tx_done(void *data)
+{
+ struct companion_char_priv *priv = data;
+ struct companion_char_minor *minor = &priv->minors[0];
+
+ wake_up_interruptible(&minor->writewait);
+}
+
+/**
+ * companion_char_on_rx_done() - rx done callback
+ * @data: address of user supplied callback data
+ */
+static void companion_char_on_rx_done(void *data)
+{
+ struct companion_char_priv *priv = data;
+ struct companion_char_minor *minor = &priv->minors[0];
+
+ wake_up_interruptible(&minor->readwait);
+}
+
+static struct companion_io_ops companion_char_io_ops = {
+ .on_tx_done = companion_char_on_tx_done,
+ .on_rx_done = companion_char_on_rx_done,
+};
+
+/**
+ * companion_char_probe() - probe callback
+ * @pdev: address of the platform device
+ */
+static int companion_char_probe(struct platform_device *pdev)
+{
+ struct companion_char_priv *priv;
+ struct companion_char_minor *minors;
+ int err;
+
+ if (!pdev->dev.parent) {
+ dev_err(&pdev->dev, "no parent device found\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->parent = pdev->dev.parent;
+
+ minors = devm_kzalloc(&pdev->dev, sizeof(*minors), GFP_KERNEL);
+ if (!minors)
+ return -ENOMEM;
+
+ minors->dev = device_create(companion_char_class,
+ &pdev->dev,
+ MKDEV(MAJOR(devt), 0),
+ priv,
+ "companion%d",
+ 0);
+ if (IS_ERR_OR_NULL(minors->dev))
+ return PTR_ERR_OR_ZERO(minors->dev);
+ priv->minors = minors;
+
+ mutex_init(&minors->writelock);
+ mutex_init(&minors->readlock);
+ init_waitqueue_head(&minors->writewait);
+ init_waitqueue_head(&minors->readwait);
+
+ cdev_init(&priv->cdev, &companion_char_ops);
+ err = cdev_add(&priv->cdev, MKDEV(MAJOR(devt), 0), 1);
+ if (err) {
+ dev_err(&pdev->dev, "cdev_add() failed: %d\n", err);
+ goto on_error;
+ }
+
+ dev_set_drvdata(&pdev->dev, priv);
+
+ err = companion_io_ops_register(priv->parent,
+ &companion_char_io_ops,
+ priv);
+ if (err) {
+ dev_err(&pdev->dev, "companion_io_ops_register() failed: %d\n",
+ err);
+ goto on_error;
+ }
+ return 0;
+
+on_error:
+ device_destroy(companion_char_class, MKDEV(MAJOR(devt), 0));
+ cdev_del(&priv->cdev);
+ return err;
+
+}
+
+/**
+ * companion_char_remove() - remove callback
+ * @pdev: address of the platform device
+ */
+static int companion_char_remove(struct platform_device *pdev)
+{
+ struct companion_char_priv *priv = dev_get_drvdata(&pdev->dev);
+
+ companion_io_ops_unregister(priv->parent);
+ device_destroy(companion_char_class, MKDEV(MAJOR(devt), 0));
+ cdev_del(&priv->cdev);
+ return 0;
+}
+
+static const struct of_device_id companion_char_of_match[] = {
+ { .compatible = DRIVER_NAME, .data = NULL, },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, companion_char_of_match);
+
+static struct platform_driver companion_char_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(companion_char_of_match),
+ },
+ .probe = companion_char_probe,
+ .remove = companion_char_remove,
+};
+
+/**
+ * companion_char_init() - module init
+ */
+static int __init companion_char_init(void)
+{
+ int err;
+
+ companion_char_class = class_create(THIS_MODULE, DRIVER_NAME);
+ if (IS_ERR_OR_NULL(companion_char_class))
+ return PTR_ERR_OR_ZERO(companion_char_class);
+
+ err = alloc_chrdev_region(&devt, 0, 1, DRIVER_NAME);
+ if (err) {
+ printk("companion:alloc_chrdev_region() failed: %d\n", err);
+ class_destroy(companion_char_class);
+ return err;
+ }
+
+ err = platform_driver_register(&companion_char_driver);
+ if (err) {
+ printk("companion:platform_driver_register() failed: %d\n",
+ err);
+ class_destroy(companion_char_class);
+ unregister_chrdev_region(devt, 1);
+ }
+
+ return err;
+}
+
+/**
+ * companion_char_exit() - module exit
+ */
+static void __exit companion_char_exit(void)
+{
+ platform_driver_unregister(&companion_char_driver);
+ class_destroy(companion_char_class);
+ unregister_chrdev_region(devt, 1);
+}
+
+module_init(companion_char_init);
+module_exit(companion_char_exit);
+
+MODULE_AUTHOR("Zhu Yi <yi.zhu5@xxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Companion upper level character device");
+MODULE_LICENSE("GPL v2");
--
2.7.4