blob: fa19cf98dcdb312e17467856041a86fa22347def [file] [log] [blame]
/* Copyright (c) 2014-2020, The Linux Foundation. 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/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/sched.h>
#include <linux/sched/task.h>
#include <linux/ratelimit.h>
#include <linux/workqueue.h>
#include <linux/diagchar.h>
#include <linux/delay.h>
#include <linux/kmemleak.h>
#include <linux/uaccess.h>
#include "diagchar.h"
#include "diag_memorydevice.h"
#include "diagfwd_bridge.h"
#include "diag_mux.h"
#include "diagmem.h"
#include "diagfwd.h"
#include "diagfwd_peripheral.h"
#include "diag_ipc_logging.h"
struct diag_md_info diag_md[NUM_DIAG_MD_DEV] = {
{
.id = DIAG_MD_LOCAL,
.ctx = 0,
.mempool = POOL_TYPE_MUX_APPS,
.num_tbl_entries = 0,
.md_info_inited = 0,
.tbl = NULL,
.ops = NULL,
},
#ifdef CONFIG_DIAGFWD_BRIDGE_CODE
{
.id = DIAG_MD_MDM,
.ctx = 0,
.mempool = POOL_TYPE_MDM_MUX,
.num_tbl_entries = 0,
.md_info_inited = 0,
.tbl = NULL,
.ops = NULL,
},
{
.id = DIAG_MD_MDM2,
.ctx = 0,
.mempool = POOL_TYPE_MDM2_MUX,
.num_tbl_entries = 0,
.md_info_inited = 0,
.tbl = NULL,
.ops = NULL,
}
#endif
};
int diag_md_register(int id, int ctx, struct diag_mux_ops *ops)
{
if (id < 0 || id >= NUM_DIAG_MD_DEV || !ops)
return -EINVAL;
diag_md[id].ops = ops;
diag_md[id].ctx = ctx;
return 0;
}
void diag_md_open_all(void)
{
int i;
struct diag_md_info *ch = NULL;
for (i = 0; i < NUM_DIAG_MD_DEV; i++) {
ch = &diag_md[i];
if (!ch->md_info_inited)
continue;
if (ch->ops && ch->ops->open)
ch->ops->open(ch->ctx, DIAG_MEMORY_DEVICE_MODE);
}
}
void diag_md_open_device(int id)
{
struct diag_md_info *ch = NULL;
ch = &diag_md[id];
if (!ch->md_info_inited)
return;
if (ch->ops && ch->ops->open)
ch->ops->open(ch->ctx, DIAG_MEMORY_DEVICE_MODE);
}
void diag_md_close_all(void)
{
int i, j;
unsigned long flags;
struct diag_md_info *ch = NULL;
struct diag_buf_tbl_t *entry = NULL;
for (i = 0; i < NUM_DIAG_MD_DEV; i++) {
ch = &diag_md[i];
if (!ch->md_info_inited)
continue;
if (ch->ops && ch->ops->close)
ch->ops->close(ch->ctx, DIAG_MEMORY_DEVICE_MODE);
/*
* When we close the Memory device mode, make sure we flush the
* internal buffers in the table so that there are no stale
* entries.
*/
spin_lock_irqsave(&ch->lock, flags);
for (j = 0; j < ch->num_tbl_entries; j++) {
entry = &ch->tbl[j];
if (entry->len <= 0)
continue;
if (ch->ops && ch->ops->write_done)
ch->ops->write_done(entry->buf, entry->len,
entry->ctx,
DIAG_MEMORY_DEVICE_MODE);
entry->buf = NULL;
entry->len = 0;
entry->ctx = 0;
}
spin_unlock_irqrestore(&ch->lock, flags);
}
diag_ws_reset(DIAG_WS_MUX);
}
void diag_md_close_device(int id)
{
int j;
unsigned long flags;
struct diag_md_info *ch = NULL;
struct diag_buf_tbl_t *entry = NULL;
ch = &diag_md[id];
if (!ch->md_info_inited)
return;
if (ch->ops && ch->ops->close)
ch->ops->close(ch->ctx, DIAG_MEMORY_DEVICE_MODE);
/*
* When we close the Memory device mode, make sure we flush the
* internal buffers in the table so that there are no stale
* entries.
* Give Write_done notifications to buffers with packets
* indicated valid length.
*/
spin_lock_irqsave(&ch->lock, flags);
for (j = 0; j < ch->num_tbl_entries; j++) {
entry = &ch->tbl[j];
if (entry->len <= 0)
continue;
if (ch->ops && ch->ops->write_done)
ch->ops->write_done(entry->buf, entry->len,
entry->ctx,
DIAG_MEMORY_DEVICE_MODE);
entry->buf = NULL;
entry->len = 0;
entry->ctx = 0;
}
spin_unlock_irqrestore(&ch->lock, flags);
diag_ws_reset(DIAG_WS_MUX);
}
void diag_md_clear_tbl_entries(int id)
{
int j;
unsigned long flags;
struct diag_md_info *ch = NULL;
struct diag_buf_tbl_t *entry = NULL;
ch = &diag_md[id];
if (!ch || !ch->md_info_inited)
return;
/*
* When we close the Memory device mode, make sure we flush the
* internal buffers in the table so that there are no stale
* entries.
*/
spin_lock_irqsave(&ch->lock, flags);
for (j = 0; j < ch->num_tbl_entries; j++) {
entry = &ch->tbl[j];
entry->buf = NULL;
entry->len = 0;
entry->ctx = 0;
}
spin_unlock_irqrestore(&ch->lock, flags);
diag_ws_reset(DIAG_WS_MUX);
}
int diag_md_write(int id, unsigned char *buf, int len, int ctx)
{
int i, peripheral, pid = 0;
uint8_t found = 0;
unsigned long flags, flags_sec;
struct diag_md_info *ch = NULL;
struct diag_md_session_t *session_info = NULL;
flags_sec = 0;
if (id < 0 || id >= NUM_DIAG_MD_DEV || id >= DIAG_NUM_PROC)
return -EINVAL;
if (!buf || len < 0)
return -EINVAL;
if (id == DIAG_LOCAL_PROC) {
peripheral = diag_md_get_peripheral(ctx);
if (peripheral < 0)
return -EINVAL;
} else {
peripheral = 0;
}
mutex_lock(&driver->md_session_lock);
session_info = diag_md_session_get_peripheral(id, peripheral);
if (!session_info) {
mutex_unlock(&driver->md_session_lock);
return -EIO;
}
pid = session_info->pid;
ch = &diag_md[id];
if (!ch || !ch->md_info_inited) {
mutex_unlock(&driver->md_session_lock);
return -EINVAL;
}
spin_lock_irqsave(&ch->lock, flags);
if (peripheral == APPS_DATA) {
spin_lock_irqsave(&driver->diagmem_lock, flags_sec);
if (!hdlc_data.allocated && !non_hdlc_data.allocated) {
spin_unlock_irqrestore(&driver->diagmem_lock,
flags_sec);
spin_unlock_irqrestore(&ch->lock, flags);
mutex_unlock(&driver->md_session_lock);
return -EINVAL;
}
}
for (i = 0; i < ch->num_tbl_entries && !found; i++) {
if (ch->tbl[i].buf != buf)
continue;
found = 1;
pr_err_ratelimited("diag: trying to write the same buffer buf: %pK, len: %d, back to the table for p: %d, t: %d, buf_num: %d, proc: %d, i: %d\n",
buf, ch->tbl[i].len, GET_BUF_PERIPHERAL(ctx),
GET_BUF_TYPE(ctx), GET_BUF_NUM(ctx), id, i);
ch->tbl[i].buf = NULL;
ch->tbl[i].len = 0;
ch->tbl[i].ctx = 0;
}
if (found) {
if (peripheral == APPS_DATA)
spin_unlock_irqrestore(&driver->diagmem_lock,
flags_sec);
spin_unlock_irqrestore(&ch->lock, flags);
mutex_unlock(&driver->md_session_lock);
return -ENOMEM;
}
for (i = 0; i < ch->num_tbl_entries && !found; i++) {
if (ch->tbl[i].len == 0) {
ch->tbl[i].buf = buf;
ch->tbl[i].len = len;
ch->tbl[i].ctx = ctx;
found = 1;
diag_ws_on_read(DIAG_WS_MUX, len);
}
}
if (peripheral == APPS_DATA)
spin_unlock_irqrestore(&driver->diagmem_lock, flags_sec);
spin_unlock_irqrestore(&ch->lock, flags);
mutex_unlock(&driver->md_session_lock);
if (!found) {
pr_err_ratelimited("diag: Unable to find an empty space in table, please reduce logging rate, proc: %d\n",
id);
return -ENOMEM;
}
found = 0;
mutex_lock(&driver->diagchar_mutex);
for (i = 0; i < driver->num_clients && !found; i++) {
if ((driver->client_map[i].pid != pid) ||
(driver->client_map[i].pid == 0))
continue;
found = 1;
if (!(driver->data_ready[i] & USER_SPACE_DATA_TYPE)) {
driver->data_ready[i] |= USER_SPACE_DATA_TYPE;
atomic_inc(&driver->data_ready_notif[i]);
}
pr_debug("diag: wake up logging process\n");
wake_up_interruptible(&driver->wait_q);
}
mutex_unlock(&driver->diagchar_mutex);
if (!found)
return -EINVAL;
return 0;
}
int diag_md_copy_to_user(char __user *buf, int *pret, size_t buf_size,
struct diag_md_session_t *info)
{
int i, j;
int err = 0;
int ret = *pret;
int num_data = 0;
int remote_token;
unsigned long flags;
struct diag_md_info *ch = NULL;
struct diag_buf_tbl_t *entry = NULL;
uint8_t drain_again = 0;
int peripheral = 0;
struct diag_md_session_t *session_info = NULL;
struct pid *pid_struct = NULL;
struct task_struct *task_s = NULL;
if (!info)
return -EINVAL;
for (i = 0; i < NUM_DIAG_MD_DEV && !err; i++) {
ch = &diag_md[i];
if (!ch->md_info_inited)
continue;
for (j = 0; j < ch->num_tbl_entries && !err; j++) {
spin_lock_irqsave(&ch->lock, flags);
entry = &ch->tbl[j];
if (entry->len <= 0 || entry->buf == NULL) {
spin_unlock_irqrestore(&ch->lock, flags);
continue;
}
peripheral = diag_md_get_peripheral(entry->ctx);
if (peripheral < 0) {
spin_unlock_irqrestore(&ch->lock, flags);
goto drop_data;
}
spin_unlock_irqrestore(&ch->lock, flags);
session_info =
diag_md_session_get_peripheral(i, peripheral);
if (!session_info)
goto drop_data;
if (session_info &&
(session_info->pid != info->pid))
continue;
if ((info->peripheral_mask[i] &
MD_PERIPHERAL_MASK(peripheral)) == 0)
goto drop_data;
pid_struct = find_get_pid(session_info->pid);
if (!pid_struct) {
err = -ESRCH;
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"diag: No such md_session_map[%d] with pid = %d err=%d exists..\n",
peripheral, session_info->pid, err);
goto drop_data;
}
/*
* If the data is from remote processor, copy the remote
* token first
*/
if (i > 0) {
if ((ret + (3 * sizeof(int)) + entry->len) >=
buf_size) {
drain_again = 1;
break;
}
} else {
if ((ret + (2 * sizeof(int)) + entry->len) >=
buf_size) {
drain_again = 1;
break;
}
}
if (i > 0) {
remote_token = diag_get_remote(i);
task_s = get_pid_task(pid_struct, PIDTYPE_PID);
if (task_s) {
err = copy_to_user(buf + ret,
&remote_token,
sizeof(int));
if (err) {
put_task_struct(task_s);
goto drop_data;
}
ret += sizeof(int);
put_task_struct(task_s);
}
}
task_s = get_pid_task(pid_struct, PIDTYPE_PID);
if (task_s) {
/* Copy the length of data being passed */
err = copy_to_user(buf + ret,
(void *)&(entry->len),
sizeof(int));
if (err) {
put_task_struct(task_s);
goto drop_data;
}
ret += sizeof(int);
/* Copy the actual data being passed */
err = copy_to_user(buf + ret,
(void *)entry->buf,
entry->len);
if (err) {
put_task_struct(task_s);
goto drop_data;
}
ret += entry->len;
put_task_struct(task_s);
}
/*
* The data is now copied to the user space client,
* Notify that the write is complete and delete its
* entry from the table
*/
num_data++;
drop_data:
spin_lock_irqsave(&ch->lock, flags);
if (ch->ops && ch->ops->write_done)
ch->ops->write_done(entry->buf, entry->len,
entry->ctx,
DIAG_MEMORY_DEVICE_MODE);
diag_ws_on_copy(DIAG_WS_MUX);
entry->buf = NULL;
entry->len = 0;
entry->ctx = 0;
spin_unlock_irqrestore(&ch->lock, flags);
put_pid(pid_struct);
}
}
*pret = ret;
pid_struct = find_get_pid(info->pid);
if (pid_struct) {
task_s = get_pid_task(pid_struct, PIDTYPE_PID);
if (task_s) {
err = copy_to_user(buf + sizeof(int),
(void *)&num_data,
sizeof(int));
put_task_struct(task_s);
}
put_pid(pid_struct);
}
diag_ws_on_copy_complete(DIAG_WS_MUX);
if (drain_again)
chk_logging_wakeup();
return err;
}
int diag_md_close_peripheral(int id, uint8_t peripheral)
{
int i;
uint8_t found = 0;
unsigned long flags;
struct diag_md_info *ch = NULL;
struct diag_buf_tbl_t *entry = NULL;
if (id < 0 || id >= NUM_DIAG_MD_DEV || id >= DIAG_NUM_PROC)
return -EINVAL;
ch = &diag_md[id];
if (!ch || !ch->md_info_inited)
return -EINVAL;
spin_lock_irqsave(&ch->lock, flags);
for (i = 0; i < ch->num_tbl_entries && !found; i++) {
entry = &ch->tbl[i];
if (peripheral > NUM_PERIPHERALS) {
if (GET_PD_CTXT(entry->ctx) != peripheral)
continue;
} else {
if (GET_BUF_PERIPHERAL(entry->ctx) !=
peripheral)
continue;
}
found = 1;
if (ch->ops && ch->ops->write_done) {
ch->ops->write_done(entry->buf, entry->len,
entry->ctx,
DIAG_MEMORY_DEVICE_MODE);
entry->buf = NULL;
entry->len = 0;
entry->ctx = 0;
}
}
spin_unlock_irqrestore(&ch->lock, flags);
return 0;
}
int diag_md_init(void)
{
int i, j;
struct diag_md_info *ch = NULL;
for (i = 0; i < DIAG_MD_LOCAL_LAST; i++) {
ch = &diag_md[i];
ch->num_tbl_entries = diag_mempools[ch->mempool].poolsize;
ch->tbl = kcalloc(ch->num_tbl_entries,
sizeof(struct diag_buf_tbl_t), GFP_KERNEL);
if (!ch->tbl)
goto fail;
for (j = 0; j < ch->num_tbl_entries; j++) {
ch->tbl[j].buf = NULL;
ch->tbl[j].len = 0;
ch->tbl[j].ctx = 0;
}
spin_lock_init(&(ch->lock));
ch->md_info_inited = 1;
}
return 0;
fail:
diag_md_exit();
return -ENOMEM;
}
int diag_md_mdm_init(void)
{
int i, j;
struct diag_md_info *ch = NULL;
for (i = DIAG_MD_BRIDGE_BASE; i < NUM_DIAG_MD_DEV; i++) {
ch = &diag_md[i];
ch->num_tbl_entries = diag_mempools[ch->mempool].poolsize;
ch->tbl = kcalloc(ch->num_tbl_entries, sizeof(*ch->tbl),
GFP_KERNEL);
if (!ch->tbl)
goto fail;
for (j = 0; j < ch->num_tbl_entries; j++) {
ch->tbl[j].buf = NULL;
ch->tbl[j].len = 0;
ch->tbl[j].ctx = 0;
}
spin_lock_init(&(ch->lock));
ch->md_info_inited = 1;
}
return 0;
fail:
diag_md_mdm_exit();
return -ENOMEM;
}
void diag_md_exit(void)
{
int i;
struct diag_md_info *ch = NULL;
for (i = 0; i < DIAG_MD_LOCAL_LAST; i++) {
ch = &diag_md[i];
kfree(ch->tbl);
ch->num_tbl_entries = 0;
ch->ops = NULL;
}
}
void diag_md_mdm_exit(void)
{
int i;
struct diag_md_info *ch = NULL;
for (i = DIAG_MD_BRIDGE_BASE; i < NUM_DIAG_MD_DEV; i++) {
ch = &diag_md[i];
kfree(ch->tbl);
ch->num_tbl_entries = 0;
ch->ops = NULL;
}
}