blob: 4143d5ed5e4baa32fc948764b569d5261a973146 [file] [log] [blame]
// SPDX-License-Identifier: ISC
/* Copyright (c) 2014,2017 Qualcomm Atheros, Inc.
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#include <linux/uaccess.h>
#include "wil6210.h"
#include <uapi/linux/wil6210_uapi.h>
#define wil_hex_dump_ioctl(prefix_str, buf, len) \
print_hex_dump_debug("DBG[IOC ]" prefix_str, \
DUMP_PREFIX_OFFSET, 16, 1, buf, len, true)
#define wil_dbg_ioctl(wil, fmt, arg...) wil_dbg(wil, "DBG[IOC ]" fmt, ##arg)
#define WIL_PRIV_DATA_MAX_LEN 8192
#define CMD_SET_AP_WPS_P2P_IE "SET_AP_WPS_P2P_IE"
struct wil_android_priv_data {
char *buf;
int used_len;
int total_len;
};
static void __iomem *wil_ioc_addr(struct wil6210_priv *wil, u32 addr,
u32 size, u32 op)
{
void __iomem *a;
u32 off;
switch (op & WIL_MMIO_ADDR_MASK) {
case WIL_MMIO_ADDR_LINKER:
a = wmi_buffer(wil, cpu_to_le32(addr));
break;
case WIL_MMIO_ADDR_AHB:
a = wmi_addr(wil, addr);
break;
case WIL_MMIO_ADDR_BAR:
a = wmi_addr(wil, addr + WIL6210_FW_HOST_OFF);
break;
default:
wil_err(wil, "Unsupported address mode, op = 0x%08x\n", op);
return NULL;
}
off = a - wil->csr;
if (size > wil->bar_size - off) {
wil_err(wil,
"Invalid requested block: off(0x%08x) size(0x%08x)\n",
off, size);
return NULL;
}
return a;
}
static int wil_ioc_memio_dword(struct wil6210_priv *wil, void __user *data)
{
struct wil_memio io;
void __iomem *a;
bool need_copy = false;
int rc;
if (copy_from_user(&io, data, sizeof(io)))
return -EFAULT;
wil_dbg_ioctl(wil, "IO: addr = 0x%08x val = 0x%08x op = 0x%08x\n",
io.addr, io.val, io.op);
a = wil_ioc_addr(wil, io.addr, sizeof(u32), io.op);
if (!a) {
wil_err(wil, "invalid address 0x%08x, op = 0x%08x\n", io.addr,
io.op);
return -EINVAL;
}
rc = wil_mem_access_lock(wil);
if (rc)
return rc;
/* operation */
switch (io.op & WIL_MMIO_OP_MASK) {
case WIL_MMIO_READ:
io.val = readl_relaxed(a);
need_copy = true;
break;
#if defined(CONFIG_WIL6210_WRITE_IOCTL)
case WIL_MMIO_WRITE:
writel_relaxed(io.val, a);
wmb(); /* make sure write propagated to HW */
break;
#endif
default:
wil_err(wil, "Unsupported operation, op = 0x%08x\n", io.op);
wil_mem_access_unlock(wil);
return -EINVAL;
}
wil_mem_access_unlock(wil);
if (need_copy) {
wil_dbg_ioctl(wil,
"IO done: addr(0x%08x) val(0x%08x) op(0x%08x)\n",
io.addr, io.val, io.op);
if (copy_to_user(data, &io, sizeof(io)))
return -EFAULT;
}
return 0;
}
static int wil_ioc_memio_block(struct wil6210_priv *wil, void __user *data)
{
struct wil_memio_block io;
void *block;
void __iomem *a;
int rc = 0;
if (copy_from_user(&io, data, sizeof(io)))
return -EFAULT;
wil_dbg_ioctl(wil, "IO: addr = 0x%08x size = 0x%08x op = 0x%08x\n",
io.addr, io.size, io.op);
/* size */
if (io.size > WIL6210_MAX_MEM_SIZE) {
wil_err(wil, "size is too large: 0x%08x\n", io.size);
return -EINVAL;
}
if (io.size % 4) {
wil_err(wil, "size is not multiple of 4: 0x%08x\n", io.size);
return -EINVAL;
}
a = wil_ioc_addr(wil, io.addr, io.size, io.op);
if (!a) {
wil_err(wil, "invalid address 0x%08x, op = 0x%08x\n", io.addr,
io.op);
return -EINVAL;
}
block = kmalloc(io.size, GFP_USER);
if (!block)
return -ENOMEM;
rc = wil_mem_access_lock(wil);
if (rc) {
kfree(block);
return rc;
}
/* operation */
switch (io.op & WIL_MMIO_OP_MASK) {
case WIL_MMIO_READ:
wil_memcpy_fromio_32(block, a, io.size);
wil_hex_dump_ioctl("Read ", block, io.size);
if (copy_to_user((void __user *)(uintptr_t)io.block,
block, io.size)) {
rc = -EFAULT;
goto out_unlock;
}
break;
#if defined(CONFIG_WIL6210_WRITE_IOCTL)
case WIL_MMIO_WRITE:
if (copy_from_user(block, (void __user *)(uintptr_t)io.block,
io.size)) {
rc = -EFAULT;
goto out_unlock;
}
wil_memcpy_toio_32(a, block, io.size);
wmb(); /* make sure write propagated to HW */
wil_hex_dump_ioctl("Write ", block, io.size);
break;
#endif
default:
wil_err(wil, "Unsupported operation, op = 0x%08x\n", io.op);
rc = -EINVAL;
break;
}
out_unlock:
wil_mem_access_unlock(wil);
kfree(block);
return rc;
}
static int wil_ioc_android(struct wil6210_priv *wil, void __user *data)
{
int rc = 0;
char *command;
struct wil_android_priv_data priv_data;
wil_dbg_ioctl(wil, "ioc_android\n");
if (copy_from_user(&priv_data, data, sizeof(priv_data)))
return -EFAULT;
if (priv_data.total_len <= 0 ||
priv_data.total_len >= WIL_PRIV_DATA_MAX_LEN) {
wil_err(wil, "invalid data len %d\n", priv_data.total_len);
return -EINVAL;
}
command = kmalloc(priv_data.total_len + 1, GFP_KERNEL);
if (!command)
return -ENOMEM;
if (copy_from_user(command, priv_data.buf, priv_data.total_len)) {
rc = -EFAULT;
goto out_free;
}
/* Make sure the command is NUL-terminated */
command[priv_data.total_len] = '\0';
wil_dbg_ioctl(wil, "ioc_android: command = %s\n", command);
/* P2P not supported, but WPS is (in AP mode).
* Ignore those in order not to block WPS functionality
* in non-P2P mode.
*/
if (strncasecmp(command, CMD_SET_AP_WPS_P2P_IE,
strlen(CMD_SET_AP_WPS_P2P_IE)) == 0)
rc = 0;
else
rc = -ENOIOCTLCMD;
out_free:
kfree(command);
return rc;
}
int wil_ioctl(struct wil6210_priv *wil, void __user *data, int cmd)
{
int ret;
ret = wil_pm_runtime_get(wil);
if (ret < 0)
return ret;
switch (cmd) {
case WIL_IOCTL_MEMIO:
ret = wil_ioc_memio_dword(wil, data);
break;
case WIL_IOCTL_MEMIO_BLOCK:
ret = wil_ioc_memio_block(wil, data);
break;
case (SIOCDEVPRIVATE + 1):
ret = wil_ioc_android(wil, data);
break;
default:
wil_dbg_ioctl(wil, "Unsupported IOCTL 0x%04x\n", cmd);
wil_pm_runtime_put(wil);
return -ENOIOCTLCMD;
}
wil_pm_runtime_put(wil);
wil_dbg_ioctl(wil, "ioctl(0x%04x) -> %d\n", cmd, ret);
return ret;
}