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 <ryanl@motorola.com>
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 <kennykessler@motorola.com>
Submit-Approved: Jira Key
This commit is contained in:
Ryan Lattrel 2018-09-04 17:03:33 -05:00
commit 9b887b876c
5 changed files with 359 additions and 41 deletions

View file

@ -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

View file

@ -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 <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/module.h>
#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);
}

View file

@ -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_ */

View file

@ -23,9 +23,11 @@
#include <linux/pm.h>
#include <linux/timer.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <soc/qcom/mmi_boot_info.h>
#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);

View file

@ -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_ */