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

Commit e1262efb authored by Arkady Miasnikov's avatar Arkady Miasnikov Committed by Luciano Coelho
Browse files

wlcore: access the firmware memory via debugfs



Applications running in the user space needs access to the
memory of the chip. Examples of such access
- read/write global variables
- access to firmware log
- dump memory after firmware panic event

Arbitrary 4-bytes aligned location can be accessed by
read/write file wlcore/mem

[Check return value of wlcore_raw_read/write and wlcore_set_partition
calls as required by the recent IO changes. -- Luca]

Signed-off-by: default avatarArkady Miasnikov <a-miasnikov@ti.com>
Signed-off-by: default avatarLuciano Coelho <coelho@ti.com>
parent f1a26e63
Loading
Loading
Loading
Loading
+192 −0
Original line number Diff line number Diff line
@@ -38,6 +38,8 @@
/* ms */
#define WL1271_DEBUGFS_STATS_LIFETIME 1000

#define WLCORE_MAX_BLOCK_SIZE ((size_t)(4*PAGE_SIZE))

/* debugfs macros idea from mac80211 */
int wl1271_format_buffer(char __user *userbuf, size_t count,
			 loff_t *ppos, char *fmt, ...)
@@ -1025,6 +1027,195 @@ static const struct file_operations sleep_auth_ops = {
	.llseek = default_llseek,
};

static ssize_t dev_mem_read(struct file *file,
	     char __user *user_buf, size_t count,
	     loff_t *ppos)
{
	struct wl1271 *wl = file->private_data;
	struct wlcore_partition_set part, old_part;
	size_t bytes = count;
	int ret;
	char *buf;

	/* only requests of dword-aligned size and offset are supported */
	if (bytes % 4)
		return -EINVAL;

	if (*ppos % 4)
		return -EINVAL;

	/* function should return in reasonable time */
	bytes = min(bytes, WLCORE_MAX_BLOCK_SIZE);

	if (bytes == 0)
		return -EINVAL;

	memset(&part, 0, sizeof(part));
	part.mem.start = file->f_pos;
	part.mem.size = bytes;

	buf = kmalloc(bytes, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	mutex_lock(&wl->mutex);

	if (wl->state == WL1271_STATE_OFF) {
		ret = -EFAULT;
		goto skip_read;
	}

	ret = wl1271_ps_elp_wakeup(wl);
	if (ret < 0)
		goto skip_read;

	/* store current partition and switch partition */
	memcpy(&old_part, &wl->curr_part, sizeof(old_part));
	ret = wlcore_set_partition(wl, &part);
	if (ret < 0)
		goto part_err;

	ret = wlcore_raw_read(wl, 0, buf, bytes, false);
	if (ret < 0)
		goto read_err;

read_err:
	/* recover partition */
	ret = wlcore_set_partition(wl, &old_part);
	if (ret < 0)
		goto part_err;

part_err:
	wl1271_ps_elp_sleep(wl);

skip_read:
	mutex_unlock(&wl->mutex);

	if (ret == 0) {
		ret = copy_to_user(user_buf, buf, bytes);
		if (ret < bytes) {
			bytes -= ret;
			*ppos += bytes;
			ret = 0;
		} else {
			ret = -EFAULT;
		}
	}

	kfree(buf);

	return ((ret == 0) ? bytes : ret);
}

static ssize_t dev_mem_write(struct file *file, const char __user *user_buf,
		size_t count, loff_t *ppos)
{
	struct wl1271 *wl = file->private_data;
	struct wlcore_partition_set part, old_part;
	size_t bytes = count;
	int ret;
	char *buf;

	/* only requests of dword-aligned size and offset are supported */
	if (bytes % 4)
		return -EINVAL;

	if (*ppos % 4)
		return -EINVAL;

	/* function should return in reasonable time */
	bytes = min(bytes, WLCORE_MAX_BLOCK_SIZE);

	if (bytes == 0)
		return -EINVAL;

	memset(&part, 0, sizeof(part));
	part.mem.start = file->f_pos;
	part.mem.size = bytes;

	buf = kmalloc(bytes, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = copy_from_user(buf, user_buf, bytes);
	if (ret) {
		ret = -EFAULT;
		goto err_out;
	}

	mutex_lock(&wl->mutex);

	if (wl->state == WL1271_STATE_OFF) {
		ret = -EFAULT;
		goto skip_write;
	}

	ret = wl1271_ps_elp_wakeup(wl);
	if (ret < 0)
		goto skip_write;

	/* store current partition and switch partition */
	memcpy(&old_part, &wl->curr_part, sizeof(old_part));
	ret = wlcore_set_partition(wl, &part);
	if (ret < 0)
		goto part_err;

	ret = wlcore_raw_write(wl, 0, buf, bytes, false);
	if (ret < 0)
		goto write_err;

write_err:
	/* recover partition */
	ret = wlcore_set_partition(wl, &old_part);
	if (ret < 0)
		goto part_err;

part_err:
	wl1271_ps_elp_sleep(wl);

skip_write:
	mutex_unlock(&wl->mutex);

	if (ret == 0)
		*ppos += bytes;

err_out:
	kfree(buf);

	return ((ret == 0) ? bytes : ret);
}

static loff_t dev_mem_seek(struct file *file, loff_t offset, int orig)
{
	loff_t ret;

	/* only requests of dword-aligned size and offset are supported */
	if (offset % 4)
		return -EINVAL;

	switch (orig) {
	case SEEK_SET:
		file->f_pos = offset;
		ret = file->f_pos;
		break;
	case SEEK_CUR:
		file->f_pos += offset;
		ret = file->f_pos;
		break;
	default:
		ret = -EINVAL;
	}

	return ret;
}

static const struct file_operations dev_mem_ops = {
	.open = simple_open,
	.read = dev_mem_read,
	.write = dev_mem_write,
	.llseek = dev_mem_seek,
};

static int wl1271_debugfs_add_files(struct wl1271 *wl,
				    struct dentry *rootdir)
{
@@ -1059,6 +1250,7 @@ static int wl1271_debugfs_add_files(struct wl1271 *wl,
	DEBUGFS_ADD_PREFIX(rx_streaming, interval, streaming);
	DEBUGFS_ADD_PREFIX(rx_streaming, always, streaming);

	DEBUGFS_ADD_PREFIX(dev, mem, rootdir);

	return 0;