From 9b887b876cba092cd27cb7683d65ba18d5c0bff8 Mon Sep 17 00:00:00 2001 From: Ryan Lattrel Date: Tue, 4 Sep 2018 17:03:33 -0500 Subject: [PATCH] mmi_muc_uart: switch from tty to char dev Switch from a tty interface to a char device so that we can implement message based reads instead of a stream. Change-Id: I23e7cb9bedcb51d857c3159b64b81f30574c132d Signed-off-by: Ryan Lattrel Reviewed-on: https://gerrit.mot.com/1238344 SLTApproved: Slta Waiver SME-Granted: SME Approvals Granted Reviewed-by: Master Yoda Tested-by: Jira Key Reviewed-by: Kenneth Kessler Submit-Approved: Jira Key --- drivers/mmi_muc_uart/Kbuild | 2 +- drivers/mmi_muc_uart/mmi_char.c | 145 ++++++++++++++++++++++ drivers/mmi_muc_uart/mmi_char.h | 19 +++ drivers/mmi_muc_uart/muc_uart.c | 208 ++++++++++++++++++++++++++------ drivers/mmi_muc_uart/muc_uart.h | 26 ++++ 5 files changed, 359 insertions(+), 41 deletions(-) create mode 100644 drivers/mmi_muc_uart/mmi_char.c create mode 100644 drivers/mmi_muc_uart/mmi_char.h create mode 100644 drivers/mmi_muc_uart/muc_uart.h diff --git a/drivers/mmi_muc_uart/Kbuild b/drivers/mmi_muc_uart/Kbuild index 49c5747c145b..5d31b228b615 100644 --- a/drivers/mmi_muc_uart/Kbuild +++ b/drivers/mmi_muc_uart/Kbuild @@ -3,6 +3,6 @@ EXTRA_CFLAGS += -Wall EXTRA_CFLAGS += -I$(TOP)/motorola/kernel/modules/include mmi_muc_uart-objs += mmi_uart.o -mmi_muc_uart-objs += mmi_tty.o +mmi_muc_uart-objs += mmi_char.o mmi_muc_uart-objs += muc_uart.o obj-m += mmi_muc_uart.o \ No newline at end of file diff --git a/drivers/mmi_muc_uart/mmi_char.c b/drivers/mmi_muc_uart/mmi_char.c new file mode 100644 index 000000000000..71e544f4a50d --- /dev/null +++ b/drivers/mmi_muc_uart/mmi_char.c @@ -0,0 +1,145 @@ +/* Copyright (c) 2018, Motorola Mobility LLC. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include "mmi_char.h" +#include "muc_uart.h" + +#define NUM_MINORS 1 + +static struct platform_device *parent_pdev; +static struct cdev mmi_char_cdev; +static struct class *mmi_char_class; +static dev_t mmi_char_dev_num; +wait_queue_head_t mmi_char_read_wq; +static int mmi_char_major; +static int mmi_char_max_write_size; +static atomic_t mmi_char_open_excl; + +static int mmi_char_open(struct inode *inode, struct file *file) +{ + if (atomic_xchg(&mmi_char_open_excl, 1)) + return -EBUSY; + + return 0; +} + +static int mmi_char_release(struct inode *inode, struct file *file) +{ + WARN_ON(!atomic_xchg(&mmi_char_open_excl, 0)); + + return 0; +} + +static ssize_t mmi_char_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + if(count > mmi_char_max_write_size) + return -ENOSPC; + + return muc_uart_pb_write(parent_pdev, buf, count); +} + +static ssize_t mmi_char_read(struct file *file, + char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret; + + if (!(file->f_flags & O_NONBLOCK)) { + ret = wait_event_interruptible(mmi_char_read_wq, + muc_uart_pb_get_q_size(parent_pdev)); + if(ret < 0) + goto exit; + } + + /* Count gets updated to bytes read, ret will + * be 0 if ok, and negative on an error + */ + ret = muc_uart_pb_get_q(parent_pdev, buf, &count); + if(ret >= 0) + ret = count; + +exit: + return ret; +} + +static unsigned int mmi_char_poll(struct file *file, + struct poll_table_struct *pll_table) +{ + int ret = 0; + + poll_wait(file, &mmi_char_read_wq, pll_table); + if(muc_uart_pb_get_q_size(parent_pdev)) + ret |= POLLIN; + + return ret; +} + +static const struct file_operations mmi_char_fops = { + .write = mmi_char_write, + .read = mmi_char_read, + .open = mmi_char_open, + .llseek = noop_llseek, + .release = mmi_char_release, + .poll = mmi_char_poll, +}; + +int mmi_char_init(int max_write_size, struct platform_device *pdev) +{ + int ret; + mmi_char_max_write_size = max_write_size; + parent_pdev = pdev; + + ret = alloc_chrdev_region(&mmi_char_dev_num, 0, NUM_MINORS, "muc"); + if (ret < 0) + goto err_dev; + + mmi_char_major = MAJOR(mmi_char_dev_num); + mmi_char_dev_num = MKDEV(mmi_char_major, 0); + mmi_char_class = class_create(THIS_MODULE, "mmi_char_class"); + + cdev_init(&mmi_char_cdev, &mmi_char_fops); + ret = cdev_add(&mmi_char_cdev, mmi_char_dev_num, 1); + if(ret) + goto err_cdev; + + if(!device_create(mmi_char_class, NULL, + mmi_char_dev_num, NULL, "muc%d", 0)) + goto err_cdev; + + atomic_set(&mmi_char_open_excl, 0); + + init_waitqueue_head(&mmi_char_read_wq); + muc_uart_pb_register_wq(pdev, &mmi_char_read_wq); + + return 0; +err_cdev: + unregister_chrdev_region(mmi_char_dev_num, NUM_MINORS); +err_dev: + return 1; +} + +void mmi_char_exit(void) +{ + device_destroy(mmi_char_class, mmi_char_dev_num); + unregister_chrdev_region(mmi_char_dev_num, NUM_MINORS); + class_destroy(mmi_char_class); +} diff --git a/drivers/mmi_muc_uart/mmi_char.h b/drivers/mmi_muc_uart/mmi_char.h new file mode 100644 index 000000000000..f47bd8124ca7 --- /dev/null +++ b/drivers/mmi_muc_uart/mmi_char.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2018, Motorola Mobility LLC. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _DRIVERS_CHAR_MMI_H_ +#define _DRIVERS_CHAR_MMI_H_ + +int mmi_char_init(int max_write_size, struct platform_device *pdev); +void mmi_char_exit(void); + +#endif /* _DRIVERS_CHAR_MMI_H_ */ \ No newline at end of file diff --git a/drivers/mmi_muc_uart/muc_uart.c b/drivers/mmi_muc_uart/muc_uart.c index 853a8f50211c..eecd851de1b2 100644 --- a/drivers/mmi_muc_uart/muc_uart.c +++ b/drivers/mmi_muc_uart/muc_uart.c @@ -23,9 +23,11 @@ #include #include #include +#include #include +#include "muc_uart.h" #include "mmi_uart.h" -#include "mmi_tty.h" +#include "mmi_char.h" #include "muc_protocol.h" #define DRIVERNAME "muc_uart" @@ -60,6 +62,12 @@ struct write_data { uint8_t retries; }; +struct read_data { + struct list_head list; + size_t payload_length; + uint8_t *payload; +}; + struct mod_muc_data_t { struct platform_device *pdev; void *uart_data; @@ -80,6 +88,9 @@ struct mod_muc_data_t { struct workqueue_struct *write_wq; struct write_data *write_data_q; int write_q_size; + struct read_data *read_data_q; + int read_q_size; + wait_queue_head_t *read_wq; atomic_t write_credits; struct delayed_work wake_work; struct delayed_work sleep_work; @@ -100,6 +111,7 @@ struct mod_muc_data_t { #define MUC_UART_ACK_TIME 500 #define WRITE_CREDIT_INIT 100 static DEFINE_SPINLOCK(write_q_lock); +static DEFINE_SPINLOCK(read_q_lock); /* Share tx between acks and send queue */ DEFINE_MUTEX(tx_lock); @@ -378,10 +390,11 @@ static int muc_uart_send(struct mod_muc_data_t *mm_data, } /* Any message that needs an ack goes on the queue */ -static int muc_uart_queue_send(struct mod_muc_data_t *mm_data, +static int __muc_uart_queue_send(struct mod_muc_data_t *mm_data, uint16_t cmd, uint8_t *payload, - size_t payload_len) + size_t payload_len, + bool payload_persistant) { struct write_data *write_data; unsigned long flags; @@ -400,19 +413,22 @@ static int muc_uart_queue_send(struct mod_muc_data_t *mm_data, return -ENOMEM; } - write_data->payload = kmalloc(payload_len, GFP_ATOMIC); - /* TODO this should probably panic */ - if (WARN_ON(!write_data->payload)) { - atomic_inc(&mm_data->write_credits); - kfree(write_data); - return -ENOMEM; - } + if(!payload_persistant) { + write_data->payload = kmalloc(payload_len, GFP_ATOMIC); + /* TODO this should probably panic */ + if (WARN_ON(!write_data->payload)) { + atomic_inc(&mm_data->write_credits); + kfree(write_data); + return -ENOMEM; + } + memcpy(write_data->payload, payload, payload_len); + } else + write_data->payload = payload; INIT_LIST_HEAD(&write_data->list); write_data->payload_length = payload_len; write_data->cmd = cmd; write_data->retries = MUC_UART_RETRY_COUNT; - memcpy(write_data->payload, payload, payload_len); spin_lock_irqsave(&write_q_lock, flags); if (!mm_data->write_data_q) @@ -435,6 +451,15 @@ static int muc_uart_queue_send(struct mod_muc_data_t *mm_data, return payload_len; } +static int muc_uart_queue_send(struct mod_muc_data_t *mm_data, + uint16_t cmd, + uint8_t *payload, + size_t payload_len) +{ + return __muc_uart_queue_send(mm_data, cmd, payload, + payload_len, false); +} + static void muc_uart_send_work(struct work_struct *w) { struct mod_muc_data_t *mm_data = @@ -730,34 +755,138 @@ static int muc_uart_ps_notifier_cb(struct notifier_block *nb, return NOTIFY_OK; } -static int muc_uart_tty_rx_cb(struct platform_device *pdev, - int idx, - uint8_t *payload, - size_t payload_length) +int muc_uart_pb_register_wq(struct platform_device *pdev, + wait_queue_head_t *read_wq) { struct mod_muc_data_t *mm_data = platform_get_drvdata(pdev); - pr_info("muc_uart_send_packetbus: send from %d msg size %zd.\n", - idx, - payload_length); + /* Only allow the first one who registered */ + if(mm_data->read_wq) + return 1; - /* TODO idx 1 is for debug */ - if (idx == MMI_TTY_DEBUG_IDX) { - if (!mmi_tty_push_to_us(MMI_TTY_DEFAULT_IDX, - payload, payload_length)) - return payload_length; - else - return -EAGAIN; - } else { - mmi_tty_push_to_us(MMI_TTY_DEBUG_IDX, - payload, payload_length); - time_send_start = ktime_get(); - return muc_uart_queue_send(mm_data, - PACKETBUS_PROT_MSG, - payload, - payload_length); + mm_data->read_wq = read_wq; + return 0; +} + +int muc_uart_pb_get_q(struct platform_device *pdev, + char __user *buf, + size_t *count) +{ + struct mod_muc_data_t *mm_data = + platform_get_drvdata(pdev); + + unsigned long flags; + struct read_data *pb_msg = NULL; + + spin_lock_irqsave(&read_q_lock, flags); + if (mm_data->read_data_q) { + pb_msg = mm_data->read_data_q; + if(list_is_last(&pb_msg->list, + &mm_data->read_data_q->list)) + mm_data->read_data_q = NULL; + else { + mm_data->read_data_q = + list_next_entry(pb_msg, list); + list_del(&pb_msg->list); + } + mm_data->read_q_size--; } + spin_unlock_irqrestore(&read_q_lock, flags); + + if(!pb_msg) + return -ENOMSG; + + if(pb_msg->payload_length > *count) + return -ENOSPC; + + if(copy_to_user(buf, pb_msg->payload, pb_msg->payload_length)) + return -EFAULT; + + *count = pb_msg->payload_length; + + kfree(pb_msg->payload); + kfree(pb_msg); + + return 0; +} + +int muc_uart_pb_get_q_size(struct platform_device *pdev) +{ + struct mod_muc_data_t *mm_data = + platform_get_drvdata(pdev); + + return mm_data->read_q_size; +} + +static int muc_uart_pb_put_q(struct mod_muc_data_t *mm_data, + size_t payload_length, + uint8_t *payload) +{ + unsigned long flags; + struct read_data *pb_msg; + + if(!payload_length) + return 1; + + /* TODO should this list have a size limit? + * otherwise a crash of the consumer could + * potentially crash the kernel ... + */ + + /* Set up the message */ + pb_msg = kmalloc(sizeof(struct read_data), GFP_KERNEL); + if (!pb_msg) + return -ENOMEM; + + pb_msg->payload = kmalloc(payload_length, GFP_KERNEL); + if (!pb_msg->payload) { + kfree(pb_msg); + return -ENOMEM; + } + + INIT_LIST_HEAD(&pb_msg->list); + pb_msg->payload_length = payload_length; + memcpy(pb_msg->payload, payload, payload_length); + + spin_lock_irqsave(&read_q_lock, flags); + if (!mm_data->read_data_q) + mm_data->read_data_q = pb_msg; + else + list_add_tail(&pb_msg->list, + &mm_data->read_data_q->list); + mm_data->read_q_size++; + spin_unlock_irqrestore(&read_q_lock, flags); + + /* Wake up any process waiting on a message */ + if(mm_data->read_wq) + wake_up(mm_data->read_wq); + + return 0; +} + +int muc_uart_pb_write(struct platform_device *pdev, + const char __user *buf, + size_t count) +{ + uint8_t *payload; + struct mod_muc_data_t *mm_data = + platform_get_drvdata(pdev); + + pr_info("muc_uart_send_packetbus: send pb msg size %zd.\n", + count); + + /* Payload is freed after send work is done */ + payload = kmalloc(count, GFP_KERNEL); + if (!payload) + return -ENOMEM; + + if (copy_from_user(payload, buf, count)) + return -EFAULT; + + time_send_start = ktime_get(); + return __muc_uart_queue_send(mm_data, PACKETBUS_PROT_MSG, + payload,count, true); } static void muc_uart_handle_message(struct mod_muc_data_t *mm_data, @@ -788,13 +917,14 @@ static void muc_uart_handle_message(struct mod_muc_data_t *mm_data, break; } case PACKETBUS_PROT_MSG: { - if (!mmi_tty_push_to_us(MMI_TTY_DEFAULT_IDX, - payload, payload_len)) { + if (!muc_uart_pb_put_q(mm_data, + payload_len, + payload)) { muc_uart_send(mm_data, PACKETBUS_PROT_MSG|MSG_ACK_MASK, NULL, 0); } else { - pr_err("muc_uart_handle_message: Couldn't pass packet to us!"); + pr_err("muc_uart_handle_message: Couldn't add packet to pb q!"); muc_uart_send(mm_data, PACKETBUS_PROT_MSG|MSG_NACK_MASK, NULL, 0); @@ -1261,9 +1391,7 @@ static int muc_uart_probe(struct platform_device *pdev) pdev)) goto err2; - if (mmi_tty_init(muc_uart_tty_rx_cb, - (UART_MAX_MSG_SIZE - MSG_META_DATA_SIZE), - pdev)) + if (mmi_char_init(UART_MAX_MSG_SIZE - MSG_META_DATA_SIZE, pdev)) goto err3; mm_data->phone_psy = devm_power_supply_register(&pdev->dev, @@ -1328,7 +1456,7 @@ static int muc_uart_remove(struct platform_device *pdev) cancel_work_sync(&mm_data->ps_notify_work); destroy_workqueue(mm_data->write_wq); mmi_uart_exit(mm_data->uart_data); - mmi_tty_exit(); + mmi_char_exit(); sysfs_remove_groups(&pdev->dev.kobj, muc_uart_groups); kobject_uevent(&pdev->dev.kobj, KOBJ_REMOVE); platform_set_drvdata(pdev, NULL); diff --git a/drivers/mmi_muc_uart/muc_uart.h b/drivers/mmi_muc_uart/muc_uart.h new file mode 100644 index 000000000000..b6d0c36f9f45 --- /dev/null +++ b/drivers/mmi_muc_uart/muc_uart.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2018, Motorola Mobility LLC. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _DRIVERS_MUC_UART_H_ +#define _DRIVERS_MUC_UART_H_ + +int muc_uart_pb_get_q_size(struct platform_device *pdev); +int muc_uart_pb_get_q(struct platform_device *pdev, + char __user *buf, + size_t *count); +int muc_uart_pb_register_wq(struct platform_device *pdev, + wait_queue_head_t *read_wq); +int muc_uart_pb_write(struct platform_device *pdev, + const char __user *buf, + size_t count); + +#endif /* _DRIVERS_MUC_UART_H_ */