Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 734dfd57 authored by Ravi Aravamudhan's avatar Ravi Aravamudhan
Browse files

diag: Prevent suspend while reading data from SMD and HSIC



Diag driver can let apps processor go to suspend when receiving
data from remote processors and peripherals. This can result in
diag data being dropped on the apps processor. This patch ensures
that the apps processor is awake till the data is copied to the
user space.

Change-Id: Id3c46c66675d3b5632721dede5e59420b69c568e
Signed-off-by: default avatarRavi Aravamudhan <aravamud@codeaurora.org>
parent 760e6e62
Loading
Loading
Loading
Loading
+27 −23
Original line number Diff line number Diff line
@@ -190,6 +190,12 @@ static void dci_add_buffer_to_list(struct diag_dci_client_tbl *client,

	mutex_lock(&client->write_buf_mutex);
	list_add_tail(&buf->buf_track, &client->list_write_buf);
	/*
	 * In the case of DCI, there can be multiple packets in one read. To
	 * calculate the wakeup source reference count, we must account for each
	 * packet in a single read.
	 */
	diag_ws_on_read(DIAG_WS_DCI, buf->data_len);
	mutex_lock(&buf->data_mutex);
	buf->in_busy = 1;
	buf->in_list = 1;
@@ -470,7 +476,6 @@ end:
	/* wake up all sleeping DCI clients which have some data */
	diag_dci_wakeup_clients();
	dci_check_drain_timer();
	diag_dci_try_deactivate_wakeup_source();
	return 0;
}

@@ -491,7 +496,7 @@ int diag_process_smd_dci_read_data(struct diag_smd_info *smd_info, void *buf,
	 * process DCI data
	 */
	if (driver->num_dci_client == 0) {
		diag_dci_try_deactivate_wakeup_source();
		diag_ws_reset(DIAG_WS_DCI);
		return 0;
	}

@@ -511,7 +516,7 @@ int diag_process_smd_dci_read_data(struct diag_smd_info *smd_info, void *buf,
		if ((dci_pkt_len + 5) > (recd_bytes - read_bytes)) {
			pr_err("diag: Invalid length in %s, len: %d, dci_pkt_len: %d",
				__func__, recd_bytes, dci_pkt_len);
			diag_dci_try_deactivate_wakeup_source();
			diag_ws_release();
			return 0;
		}
		/*
@@ -520,8 +525,10 @@ int diag_process_smd_dci_read_data(struct diag_smd_info *smd_info, void *buf,
		 */
		err = diag_process_single_dci_pkt(buf + 4, dci_pkt_len,
					smd_info->peripheral, DCI_LOCAL_PROC);
		if (err)
		if (err) {
			diag_ws_release();
			break;
		}
		read_bytes += 5 + dci_pkt_len;
		buf += 5 + dci_pkt_len; /* advance to next DCI pkt */
	}
@@ -529,7 +536,6 @@ int diag_process_smd_dci_read_data(struct diag_smd_info *smd_info, void *buf,
	/* wake up all sleeping DCI clients which have some data */
	diag_dci_wakeup_clients();
	dci_check_drain_timer();
	diag_dci_try_deactivate_wakeup_source();
	return 0;
}

@@ -723,6 +729,7 @@ void extract_dci_ctrl_pkt(unsigned char *buf, int len, int token)
		return;
	}

	diag_ws_on_read(DIAG_WS_DCI, len);
	header = (struct diag_ctrl_dci_status *)temp;
	temp += sizeof(struct diag_ctrl_dci_status);
	read_len += sizeof(struct diag_ctrl_dci_status);
@@ -731,7 +738,7 @@ void extract_dci_ctrl_pkt(unsigned char *buf, int len, int token)
		if (read_len > len) {
			pr_err("diag: Invalid length len: %d in %s\n", len,
								__func__);
			return;
			goto err;
		}

		switch (*(uint8_t *)temp) {
@@ -747,7 +754,7 @@ void extract_dci_ctrl_pkt(unsigned char *buf, int len, int token)
		default:
			pr_err("diag: In %s, unknown peripheral, peripheral: %d\n",
				__func__, *(uint8_t *)temp);
			return;
			goto err;
		}
		temp += sizeof(uint8_t);
		read_len += sizeof(uint8_t);
@@ -758,6 +765,13 @@ void extract_dci_ctrl_pkt(unsigned char *buf, int len, int token)
		read_len += sizeof(uint8_t);
		diag_dci_notify_client(peripheral_mask, status, token);
	}
err:
	/*
	 * DCI control packets are not consumed by the clients. Mimic client
	 * consumption by setting and clearing the wakeup source copy_count
	 * explicitly.
	 */
	diag_ws_on_copy_fail(DIAG_WS_DCI);
}

void extract_dci_pkt_rsp(unsigned char *buf, int len, int data_source,
@@ -2574,21 +2588,6 @@ int diag_dci_set_real_time(struct diag_dci_client_tbl *entry, uint8_t real_time)
	return 1;
}

void diag_dci_try_activate_wakeup_source()
{
	spin_lock_irqsave(&ws_lock, ws_lock_flags);
	pm_wakeup_event(driver->diag_dev, DCI_WAKEUP_TIMEOUT);
	pm_stay_awake(driver->diag_dev);
	spin_unlock_irqrestore(&ws_lock, ws_lock_flags);
}

void diag_dci_try_deactivate_wakeup_source()
{
	spin_lock_irqsave(&ws_lock, ws_lock_flags);
	pm_relax(driver->diag_dev);
	spin_unlock_irqrestore(&ws_lock, ws_lock_flags);
}

int diag_dci_register_client(struct diag_dci_reg_tbl_t *reg_entry)
{
	int i, err = 0;
@@ -2810,7 +2809,12 @@ int diag_dci_deinit_client(struct diag_dci_client_tbl *entry)
			smd_info->in_busy_1 = 0;
			mutex_unlock(&buf_entry->data_mutex);
		}
		diag_dci_try_deactivate_wakeup_source();
		/*
		 * These are buffers that can't be written to the client which
		 * means that the copy cannot be completed. Make sure that we
		 * remove those references in DCI wakeup source.
		 */
		diag_ws_on_copy_fail(DIAG_WS_DCI);
	}
	mutex_unlock(&entry->write_buf_mutex);

+0 −3
Original line number Diff line number Diff line
@@ -258,9 +258,6 @@ uint8_t diag_dci_get_cumulative_real_time(int token);
int diag_dci_set_real_time(struct diag_dci_client_tbl *entry,
			   uint8_t real_time);
int diag_dci_copy_health_stats(struct diag_dci_health_stats_proc *stats_proc);
/* Functions related to DCI wakeup sources */
void diag_dci_try_activate_wakeup_source(void);
void diag_dci_try_deactivate_wakeup_source(void);
int diag_dci_write_proc(int peripheral, int pkt_type, char *buf, int len);

#ifdef CONFIG_DIAGFWD_BRIDGE_CODE
+47 −0
Original line number Diff line number Diff line
@@ -288,6 +288,44 @@ static ssize_t diag_dbgfs_read_dcistats(struct file *file,
	return bytes_written;
}

static ssize_t diag_dbgfs_read_power(struct file *file, char __user *ubuf,
				     size_t count, loff_t *ppos)
{
	char *buf;
	int ret;
	unsigned int buf_size;

	buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
	if (!buf) {
		pr_err("diag: %s, Error allocating memory\n", __func__);
		return -ENOMEM;
	}

	buf_size = ksize(buf);
	ret = scnprintf(buf, buf_size,
		"DCI reference count: %d\n"
		"DCI copy count: %d\n"
		"DCI Client Count: %d\n\n"
		"Memory Device reference count: %d\n"
		"Memory Device copy count: %d\n"
		"Logging mode: %d\n\n"
		"Wakeup source active count: %lu\n"
		"Wakeup source relax count: %lu\n\n",
		driver->dci_ws.ref_count,
		driver->dci_ws.copy_count,
		driver->num_dci_client,
		driver->md_ws.ref_count,
		driver->md_ws.copy_count,
		driver->logging_mode,
		driver->diag_dev->power.wakeup->active_count,
		driver->diag_dev->power.wakeup->relax_count);

	ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret);

	kfree(buf);
	return ret;
}

static ssize_t diag_dbgfs_read_workpending(struct file *file,
				char __user *ubuf, size_t count, loff_t *ppos)
{
@@ -786,6 +824,10 @@ const struct file_operations diag_dbgfs_dcistats_ops = {
	.read = diag_dbgfs_read_dcistats,
};

const struct file_operations diag_dbgfs_power_ops = {
	.read = diag_dbgfs_read_power,
};

int diag_debugfs_init(void)
{
	struct dentry *entry = NULL;
@@ -819,6 +861,11 @@ int diag_debugfs_init(void)
	if (!entry)
		goto err;

	entry = debugfs_create_file("power", 0444, diag_dbgfs_dent, 0,
				    &diag_dbgfs_power_ops);
	if (!entry)
		goto err;

#ifdef CONFIG_DIAGFWD_BRIDGE_CODE
	entry = debugfs_create_file("bridge", 0444, diag_dbgfs_dent, 0,
				    &diag_dbgfs_bridge_ops);
+22 −10
Original line number Diff line number Diff line
@@ -191,6 +191,9 @@
#define DIAG_NUM_PROC	1
#endif

#define DIAG_WS_DCI		0
#define DIAG_WS_MD		1

/* Maximum number of pkt reg supported at initialization*/
extern int diag_max_reg;
extern int diag_threshold_reg;
@@ -254,14 +257,6 @@ struct diag_client_map {
	int pid;
};

struct diag_nrt_wake_lock {
	int enabled;
	int ref_count;
	int copy_count;
	struct wake_lock read_lock;
	spinlock_t read_spinlock;
};

struct real_time_vote_t {
	int client_id;
	uint16_t proc;
@@ -273,6 +268,12 @@ struct real_time_query_t {
	int proc;
} __packed;

struct diag_ws_ref_t {
	int ref_count;
	int copy_count;
	spinlock_t lock;
};

/* This structure is defined in USB header file */
#ifndef CONFIG_DIAG_OVER_USB
struct diag_request {
@@ -314,8 +315,6 @@ struct diag_smd_info {
	struct diag_request *write_ptr_1;
	struct diag_request *write_ptr_2;

	struct diag_nrt_wake_lock nrt_lock;

	struct workqueue_struct *wq;

	struct work_struct diag_read_smd_work;
@@ -475,6 +474,10 @@ struct diagchar_dev {
	int logging_process_id;
	struct task_struct *socket_process;
	struct task_struct *callback_process;
	/* Power related variables */
	struct diag_ws_ref_t dci_ws;
	struct diag_ws_ref_t md_ws;
	spinlock_t ws_lock;

#ifdef CONFIG_DIAGFWD_BRIDGE_CODE
	/* common for all bridges */
@@ -503,4 +506,13 @@ void diag_get_timestamp(char *time_str);
int diag_find_polling_reg(int i);
void check_drain_timer(void);

void diag_ws_init(void);
void diag_ws_on_notify(void);
void diag_ws_on_read(int type, int pkt_len);
void diag_ws_on_copy(int type);
void diag_ws_on_copy_fail(int type);
void diag_ws_on_copy_complete(int type);
void diag_ws_reset(int type);
void diag_ws_release(void);

#endif
+195 −13
Original line number Diff line number Diff line
@@ -330,6 +330,7 @@ static int diagchar_close(struct inode *inode, struct file *file)
		diag_update_proc_vote(DIAG_PROC_MEMORY_DEVICE, VOTE_DOWN,
				      ALL_PROC);
		diag_switch_logging(USB_MODE);
		diag_ws_reset(DIAG_WS_MD);
	}
#endif /* DIAG over USB */
	/* Delete the pkt response table entry for the exiting process */
@@ -492,6 +493,7 @@ int diag_copy_remote(char __user *buf, size_t count, int *pret, int *pnum_data)
	int ret = *pret;
	int num_data = *pnum_data;
	int remote_token;
	int copy_data = 0;
	unsigned long spin_lock_flags;
	struct diag_write_device hsic_buf_tbl[NUM_HSIC_BUF_TBL_ENTRIES];

@@ -523,6 +525,9 @@ int diag_copy_remote(char __user *buf, size_t count, int *pret, int *pnum_data)
					hsic_buf_tbl[i].length);
				num_data++;

				diag_ws_on_copy(DIAG_WS_MD);
				copy_data = 1;

				/* Copy the negative token */
				if (copy_to_user(buf+ret,
					&remote_token, 4)) {
@@ -579,6 +584,8 @@ drop_hsic:
		driver->in_busy_smux = 0;
	}
	exit_stat = 0;
	if (copy_data)
		diag_ws_on_copy_complete(DIAG_WS_MD);
exit:
	*pret = ret;
	*pnum_data = num_data;
@@ -620,6 +627,7 @@ static int diag_copy_dci(char __user *buf, size_t count,
				goto drop;
			ret += buf_entry->data_len;
			total_data_len += buf_entry->data_len;
			diag_ws_on_copy(DIAG_WS_DCI);
drop:
			buf_entry->in_busy = 0;
			buf_entry->data_len = 0;
@@ -657,6 +665,13 @@ drop:
		/* Copy the total data length */
		COPY_USER_SPACE_OR_EXIT(buf+8, total_data_len, 4);
		ret -= 4;
		/*
		 * Flush any read that is currently pending on DCI data and
		 * command channnels. This will ensure that the next read is not
		 * missed.
		 */
		flush_workqueue(driver->diag_dci_wq);
		diag_ws_on_copy_complete(DIAG_WS_DCI);
	} else {
		pr_debug("diag: In %s, Trying to copy ZERO bytes, total_data_len: %d\n",
			__func__, total_data_len);
@@ -1341,7 +1356,7 @@ static ssize_t diagchar_read(struct file *file, char __user *buf, size_t count,
	int num_data = 0, data_type;
	int remote_token;
	int exit_stat;
	int clear_read_wakelock;
	int copy_data = 0;
	unsigned long flags;

	for (i = 0; i < driver->num_clients; i++)
@@ -1360,7 +1375,6 @@ static ssize_t diagchar_read(struct file *file, char __user *buf, size_t count,

	mutex_lock(&driver->diagchar_mutex);

	clear_read_wakelock = 0;
	if ((driver->data_ready[index] & USER_SPACE_DATA_TYPE) && (driver->
					logging_mode == MEMORY_DEVICE_MODE)) {
		remote_token = 0;
@@ -1442,14 +1456,12 @@ drop:
				COPY_USER_SPACE_OR_EXIT(buf+ret,
					*(data->buf_in_1),
					data->write_ptr_1->length);
				if (!driver->real_time_mode) {
					process_lock_on_copy(&data->nrt_lock);
					clear_read_wakelock++;
				}
				spin_lock_irqsave(&data->in_busy_lock, flags);
				data->in_busy_1 = 0;
				spin_unlock_irqrestore(&data->in_busy_lock,
						       flags);
				diag_ws_on_copy(DIAG_WS_MD);
				copy_data = 1;
			}
			if (data->in_busy_2 == 1) {
				num_data++;
@@ -1460,14 +1472,12 @@ drop:
				COPY_USER_SPACE_OR_EXIT(buf+ret,
					*(data->buf_in_2),
					data->write_ptr_2->length);
				if (!driver->real_time_mode) {
					process_lock_on_copy(&data->nrt_lock);
					clear_read_wakelock++;
				}
				spin_lock_irqsave(&data->in_busy_lock, flags);
				data->in_busy_2 = 0;
				spin_unlock_irqrestore(&data->in_busy_lock,
						       flags);
				diag_ws_on_copy(DIAG_WS_MD);
				copy_data = 1;
			}
		}
		if (driver->supports_separate_cmdrsp) {
@@ -1634,10 +1644,15 @@ drop:
		goto exit;
	}
exit:
	if (clear_read_wakelock) {
	if (copy_data) {
		/*
		 * Flush any work that is currently pending on the data
		 * channels. This will ensure that the next read is not missed.
		 */
		for (i = 0; i < NUM_SMD_DATA_CHANNELS; i++)
			process_lock_on_copy_complete(
				&driver->smd_data[i].nrt_lock);
			flush_workqueue(driver->smd_data[i].wq);
		wake_up(&driver->smd_wait_q);
		diag_ws_on_copy_complete(DIAG_WS_MD);
	}
	mutex_unlock(&driver->diagchar_mutex);
	return ret;
@@ -2112,6 +2127,172 @@ fail_free_copy:
	return ret;
}

void diag_ws_init()
{
	driver->dci_ws.ref_count = 0;
	driver->dci_ws.copy_count = 0;
	spin_lock_init(&driver->dci_ws.lock);

	driver->md_ws.ref_count = 0;
	driver->md_ws.copy_count = 0;
	spin_lock_init(&driver->md_ws.lock);

	spin_lock_init(&driver->ws_lock);
}

void diag_ws_on_notify()
{
	/*
	 * Do not deal with reference count here as there can be spurious
	 * interrupts.
	 */
	pm_stay_awake(driver->diag_dev);
}

void diag_ws_on_read(int type, int pkt_len)
{
	unsigned long flags;
	struct diag_ws_ref_t *ws_ref = NULL;

	switch (type) {
	case DIAG_WS_DCI:
		ws_ref = &driver->dci_ws;
		break;
	case DIAG_WS_MD:
		ws_ref = &driver->md_ws;
		break;
	default:
		pr_err_ratelimited("diag: In %s, invalid type: %d\n",
				   __func__, type);
		return;
	}

	spin_lock_irqsave(&ws_ref->lock, flags);
	if (pkt_len > 0) {
		ws_ref->ref_count++;
	} else {
		if (ws_ref->ref_count < 1) {
			ws_ref->ref_count = 0;
			ws_ref->copy_count = 0;
		}
		diag_ws_release();
	}
	spin_unlock_irqrestore(&ws_ref->lock, flags);
}


void diag_ws_on_copy(int type)
{
	unsigned long flags;
	struct diag_ws_ref_t *ws_ref = NULL;

	switch (type) {
	case DIAG_WS_DCI:
		ws_ref = &driver->dci_ws;
		break;
	case DIAG_WS_MD:
		ws_ref = &driver->md_ws;
		break;
	default:
		pr_err_ratelimited("diag: In %s, invalid type: %d\n",
				   __func__, type);
		return;
	}

	spin_lock_irqsave(&ws_ref->lock, flags);
	ws_ref->copy_count++;
	spin_unlock_irqrestore(&ws_ref->lock, flags);
}

void diag_ws_on_copy_fail(int type)
{
	unsigned long flags;
	struct diag_ws_ref_t *ws_ref = NULL;

	switch (type) {
	case DIAG_WS_DCI:
		ws_ref = &driver->dci_ws;
		break;
	case DIAG_WS_MD:
		ws_ref = &driver->md_ws;
		break;
	default:
		pr_err_ratelimited("diag: In %s, invalid type: %d\n",
				   __func__, type);
		return;
	}

	spin_lock_irqsave(&ws_ref->lock, flags);
	ws_ref->ref_count--;
	spin_unlock_irqrestore(&ws_ref->lock, flags);

	diag_ws_release();
}

void diag_ws_on_copy_complete(int type)
{
	unsigned long flags;
	struct diag_ws_ref_t *ws_ref = NULL;

	switch (type) {
	case DIAG_WS_DCI:
		ws_ref = &driver->dci_ws;
		break;
	case DIAG_WS_MD:
		ws_ref = &driver->md_ws;
		break;
	default:
		pr_err_ratelimited("diag: In %s, invalid type: %d\n",
				   __func__, type);
		return;
	}

	spin_lock_irqsave(&ws_ref->lock, flags);
	ws_ref->ref_count -= ws_ref->copy_count;
		if (ws_ref->ref_count < 1)
			ws_ref->ref_count = 0;
		ws_ref->copy_count = 0;
	spin_unlock_irqrestore(&ws_ref->lock, flags);

	diag_ws_release();
}

void diag_ws_reset(int type)
{
	unsigned long flags;
	struct diag_ws_ref_t *ws_ref = NULL;

	switch (type) {
	case DIAG_WS_DCI:
		ws_ref = &driver->dci_ws;
		break;
	case DIAG_WS_MD:
		ws_ref = &driver->md_ws;
		break;
	default:
		pr_err_ratelimited("diag: In %s, invalid type: %d\n",
				   __func__, type);
		return;
	}

	spin_lock_irqsave(&ws_ref->lock, flags);
	ws_ref->ref_count = 0;
	ws_ref->copy_count = 0;
	spin_unlock_irqrestore(&ws_ref->lock, flags);

	diag_ws_release();
}

void diag_ws_release()
{
	unsigned long flags;

	spin_lock_irqsave(&driver->ws_lock, flags);
	if (driver->dci_ws.ref_count == 0 && driver->md_ws.ref_count == 0)
		pm_relax(driver->diag_dev);
	spin_unlock_irqrestore(&driver->ws_lock, flags);
}

static int diag_real_time_info_init(void)
{
	int i;
@@ -2352,6 +2533,7 @@ static int __init diagchar_init(void)
	init_waitqueue_head(&driver->wait_q);
	init_waitqueue_head(&driver->smd_wait_q);
	INIT_WORK(&(driver->diag_drain_work), diag_drain_work_fn);
	diag_ws_init();
	ret = diag_real_time_info_init();
	if (ret)
		goto fail;
Loading