build/patch/kernel/archive/sunxi-6.5/patches.megous/besdbg-Add-a-debug-driver-for-controlling-the-wifi-chip.patch

499 lines
11 KiB
Diff
Raw Normal View History

From 7ef962f774ceb6e3b3d21961721a63b09f5bd12d Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megi@xff.cz>
Date: Mon, 14 Aug 2023 08:22:40 +0200
Subject: [PATCH 425/464] besdbg: Add a debug driver for controlling the wifi
chip
With this driver, it's possible to perform any test of the wifi
firmware from userspace without having to recompile the kernel.
Signed-off-by: Ondrej Jirman <megi@xff.cz>
---
drivers/net/wireless/st/cw1200/Makefile | 2 +
drivers/net/wireless/st/cw1200/besdbg.c | 463 ++++++++++++++++++++++++
2 files changed, 465 insertions(+)
create mode 100644 drivers/net/wireless/st/cw1200/besdbg.c
diff --git a/drivers/net/wireless/st/cw1200/Makefile b/drivers/net/wireless/st/cw1200/Makefile
index 725db5e56524..c9125b010881 100644
--- a/drivers/net/wireless/st/cw1200/Makefile
+++ b/drivers/net/wireless/st/cw1200/Makefile
@@ -21,3 +21,5 @@ cw1200_wlan_spi-y := cw1200_spi.o
obj-$(CONFIG_CW1200) += cw1200_core.o
obj-$(CONFIG_CW1200_WLAN_SDIO) += cw1200_wlan_sdio.o
obj-$(CONFIG_CW1200_WLAN_SPI) += cw1200_wlan_spi.o
+
+obj-m += besdbg.o
diff --git a/drivers/net/wireless/st/cw1200/besdbg.c b/drivers/net/wireless/st/cw1200/besdbg.c
new file mode 100644
index 000000000000..3a4e44181c96
--- /dev/null
+++ b/drivers/net/wireless/st/cw1200/besdbg.c
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/cdev.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_net.h>
+#include <net/mac80211.h>
+
+#include "cw1200.h"
+#include "hwbus.h"
+#include "hwio.h"
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+struct besdbg_data {
+ __u32 reg;
+ __u32 len;
+ __u64 data;
+};
+
+#define BESDBG_MAGIC 0xEE
+
+#define BESDBG_IOCTL_RESET _IO(BESDBG_MAGIC, 0x10)
+#define BESDBG_IOCTL_REG_READ _IOR(BESDBG_MAGIC, 0x11, struct besdbg_data)
+#define BESDBG_IOCTL_REG_WRITE _IOW(BESDBG_MAGIC, 0x12, struct besdbg_data)
+#define BESDBG_IOCTL_MEM_READ _IOR(BESDBG_MAGIC, 0x13, struct besdbg_data)
+#define BESDBG_IOCTL_MEM_WRITE _IOW(BESDBG_MAGIC, 0x14, struct besdbg_data)
+
+#define SDIO_BLOCK_SIZE (512)
+
+static struct class *besdbg_class;
+
+struct besdbg_priv {
+ struct sdio_func *func;
+ struct gpio_desc *wakeup_device_gpio;
+ struct cdev cdev;
+ dev_t major;
+};
+
+/* bes sdio slave regs can only be accessed by command52
+ * if a WORD or DWORD reg wants to be accessed,
+ * please combine the results of multiple command52
+ */
+static int bes2600_sdio_reg_read(struct besdbg_priv *self, u32 reg,
+ void *dst, int count)
+{
+ int ret = 0;
+ if (count <= 0 || !dst)
+ return -EINVAL;
+
+ while (count && !ret) {
+ *(u8 *)dst = sdio_readb(self->func, reg, &ret);
+
+ dst++;
+ reg++;
+ count--;
+ }
+
+ return ret;
+}
+
+static int bes2600_sdio_reg_write(struct besdbg_priv *self, u32 reg,
+ const void *src, int count)
+{
+ int ret = 0;
+ if (count <= 0 || !src)
+ return -EINVAL;
+
+ while (count && !ret) {
+ sdio_writeb(self->func, *(u8 *)src, reg, &ret);
+
+ src++;
+ reg++;
+ count--;
+ }
+
+ return ret;
+}
+
+#if 0
+static int bes2600_sdio_mem_helper(struct besdbg_priv *self, u8 *data, int count, int write)
+{
+ int off = 0;
+ int ret;
+
+ while (off < count) {
+ int block = min(count - off, );
+
+ if (write)
+ ret = sdio_memcpy_toio(func, block, data + off, block);
+ else
+ ret = sdio_memcpy_fromio(func, data + off, block, block);
+ if (ret)
+ return ret;
+
+ off += size;
+ }
+
+ return 0;
+}
+#endif
+
+#if 0
+static unsigned int besdbg_poll(struct file *fp, poll_table *wait)
+{
+ struct besdbg_priv* besdbg = fp->private_data;
+
+ poll_wait(fp, &besdbg->wait, wait);
+
+ if (!kfifo_is_empty(&besdbg->kfifo))
+ return EPOLLIN | EPOLLRDNORM;
+
+ return 0;
+}
+#endif
+
+static int besdbg_open(struct inode *ip, struct file *fp)
+{
+ struct besdbg_priv* besdbg = container_of(ip->i_cdev,
+ struct besdbg_priv, cdev);
+
+ fp->private_data = besdbg;
+
+ nonseekable_open(ip, fp);
+ return 0;
+}
+
+static int besdbg_release(struct inode *ip, struct file *fp)
+{
+// struct besdbg_priv* besdbg = fp->private_data;
+
+ return 0;
+}
+
+long besdbg_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ struct besdbg_priv* self = fp->private_data;
+ struct device *dev = &self->func->dev;
+ void __user *argp = (void __user *)arg;
+ long ret;
+
+ switch (cmd) {
+ case BESDBG_IOCTL_RESET:
+ /* Disable the card */
+ gpiod_direction_output(self->wakeup_device_gpio, 0);
+
+ sdio_claim_host(self->func);
+
+ ret = mmc_hw_reset(self->func->card);
+ if (ret)
+ dev_warn(dev, "unable to reset sdio: %ld\n", ret);
+
+ gpiod_direction_output(self->wakeup_device_gpio, 1);
+ msleep(10);
+
+ ret = sdio_enable_func(self->func);
+ if (ret) {
+ sdio_release_host(self->func);
+ return ret;
+ }
+
+ sdio_release_host(self->func);
+
+ return 0;
+
+ case BESDBG_IOCTL_REG_READ: {
+ struct besdbg_data r;
+
+ if (copy_from_user(&r, argp, sizeof(r)))
+ return -EFAULT;
+
+ if (r.len > 32 || r.len == 0 || !r.data)
+ return -EINVAL;
+
+ u8 *data = kmalloc(r.len, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ sdio_claim_host(self->func);
+ ret = bes2600_sdio_reg_read(self, r.reg, data, r.len);
+ sdio_release_host(self->func);
+
+ if (ret) {
+ dev_err(dev, "read failed\n");
+ kfree(data);
+ return ret;
+ }
+
+ if (copy_to_user((void __user *)r.data, data, r.len)) {
+ kfree(data);
+ return -EFAULT;
+ }
+
+ kfree(data);
+ return 0;
+ }
+
+ case BESDBG_IOCTL_REG_WRITE: {
+ struct besdbg_data r;
+
+ if (copy_from_user(&r, argp, sizeof(r)))
+ return -EFAULT;
+
+ if (r.len > 64 * 1024 || r.len == 0 || !r.data)
+ return -EINVAL;
+
+ u8 *data = kmalloc(r.len, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ if (copy_from_user(data, (void __user *)r.data, r.len))
+ return -EFAULT;
+
+ sdio_claim_host(self->func);
+ ret = bes2600_sdio_reg_write(self, r.reg, data, r.len);
+ sdio_release_host(self->func);
+
+ kfree(data);
+
+ if (ret) {
+ dev_err(dev, "read failed\n");
+ return ret;
+ }
+
+ return 0;
+ }
+
+ case BESDBG_IOCTL_MEM_READ: {
+ struct besdbg_data r;
+
+ if (copy_from_user(&r, argp, sizeof(r)))
+ return -EFAULT;
+
+ if (r.len > 1024 * 64 || r.len == 0 || !r.data)
+ return -EINVAL;
+
+ u8 *data = kmalloc(r.len, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ sdio_claim_host(self->func);
+ //ret = bes2600_sdio_mem_helper(self, data, r.len, 0);
+ ret = sdio_memcpy_fromio(self->func, data, r.reg, r.len);
+ sdio_release_host(self->func);
+
+ if (ret) {
+ dev_err(dev, "read failed\n");
+ kfree(data);
+ return ret;
+ }
+
+ if (copy_to_user((void __user *)r.data, data, r.len)) {
+ kfree(data);
+ return -EFAULT;
+ }
+
+ kfree(data);
+ return 0;
+ }
+
+ case BESDBG_IOCTL_MEM_WRITE: {
+ struct besdbg_data r;
+
+ if (copy_from_user(&r, argp, sizeof(r)))
+ return -EFAULT;
+
+ if (r.len > 64 * 1024 || r.len == 0 || !r.data)
+ return -EINVAL;
+
+ u8 *data = kmalloc(r.len, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ if (copy_from_user(data, (void __user *)r.data, r.len))
+ return -EFAULT;
+
+ sdio_claim_host(self->func);
+ //ret = bes2600_sdio_mem_helper(self, data, r.len, 1);
+ ret = sdio_memcpy_toio(self->func, r.reg, data, r.len);
+ sdio_release_host(self->func);
+
+ kfree(data);
+
+ if (ret) {
+ dev_err(dev, "read failed\n");
+ return ret;
+ }
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct file_operations besdbg_fops = {
+ .owner = THIS_MODULE,
+ .open = besdbg_open,
+ .release = besdbg_release,
+ .unlocked_ioctl = besdbg_ioctl,
+ .llseek = noop_llseek,
+ //.read = besdbg_read,
+ //.poll = besdbg_poll,
+};
+
+static int besdbg_probe(struct sdio_func *func, const struct sdio_device_id *id)
+{
+ struct device *dev = &func->dev;
+ struct besdbg_priv *self;
+ int ret;
+
+ if (func->num != 0x01)
+ return -ENODEV;
+
+ dev_info(dev, "probe start\n");
+
+ if (!of_device_is_compatible(dev->of_node, "bestechnic,bes2600")) {
+ dev_err(dev, "OF node for function 1 is missing\n");
+ return -ENODEV;
+ }
+
+ self = devm_kzalloc(dev, sizeof(*self), GFP_KERNEL);
+ if (!self)
+ return -ENOMEM;
+
+ self->func = func;
+
+ func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
+ func->card->quirks |= MMC_QUIRK_BROKEN_BYTE_MODE_512;
+
+ self->wakeup_device_gpio = devm_gpiod_get(dev, "device-wakeup", GPIOD_OUT_LOW);
+ if (IS_ERR(self->wakeup_device_gpio))
+ return dev_err_probe(dev, PTR_ERR(self->wakeup_device_gpio),
+ "can't get device-wakeup gpio\n");
+
+ if (self->wakeup_device_gpio) {
+ gpiod_direction_output(self->wakeup_device_gpio, 1);
+ msleep(10);
+ }
+
+ ret = alloc_chrdev_region(&self->major, 0, 1, "besdbg");
+ if (ret) {
+ dev_err(dev, "can't allocate chrdev region");
+ goto err_sleep;
+ }
+
+ cdev_init(&self->cdev, &besdbg_fops);
+ self->cdev.owner = THIS_MODULE;
+ ret = cdev_add(&self->cdev, self->major, 1);
+ if (ret) {
+ dev_err(dev, "can't add cdev");
+ goto err_unreg_chrev_region;
+ }
+
+ struct device *sdev = device_create(besdbg_class, dev, self->major, self, "besdbg");
+ if (IS_ERR(sdev)) {
+ ret = PTR_ERR(sdev);
+ goto err_cdev;
+ }
+
+/*
+ irq = irq_of_parse_and_map(np, 0);
+ if (!irq) {
+ dev_warn(dev, "No irq in platform data\n");
+ } else {
+ global_plat_data->irq = irq;
+ }
+*/
+
+ sdio_set_drvdata(func, self);
+ sdio_claim_host(func);
+ ret = sdio_enable_func(func);
+ if (ret)
+ dev_warn(dev, "can't enable func %d\n", ret);
+ sdio_release_host(func);
+
+ dev_info(dev, "probe success\n");
+
+ return 0;
+
+err_cdev:
+ cdev_del(&self->cdev);
+err_unreg_chrev_region:
+ unregister_chrdev(self->major, "besdbg");
+err_sleep:
+ gpiod_direction_output(self->wakeup_device_gpio, 0);
+
+ return ret;
+}
+
+static void besdbg_disconnect(struct sdio_func *func)
+{
+ struct besdbg_priv *self = sdio_get_drvdata(func);
+
+ if (!self)
+ return;
+
+ cdev_del(&self->cdev);
+ unregister_chrdev(self->major, "besdbg");
+ device_destroy(besdbg_class, self->major);
+
+ sdio_claim_host(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+
+ sdio_set_drvdata(func, NULL);
+
+ gpiod_direction_output(self->wakeup_device_gpio, 0);
+}
+
+static const struct sdio_device_id besdbg_ids[] = {
+ { SDIO_DEVICE(0xbe57, 0x2002), },
+ { },
+};
+
+static struct sdio_driver besdbg_sdio_driver = {
+ .name = "besdbg_sdio",
+ .id_table = besdbg_ids,
+ .probe = besdbg_probe,
+ .remove = besdbg_disconnect,
+};
+
+//module_sdio_driver(besdbg_sdio_driver);
+
+static int __init besdbg_driver_init(void)
+{
+ int ret;
+
+ besdbg_class = class_create("besdbg");
+ if (IS_ERR(besdbg_class))
+ return PTR_ERR(besdbg_class);
+
+ ret = sdio_register_driver(&besdbg_sdio_driver);
+ if (ret) {
+ class_destroy(besdbg_class);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit besdbg_driver_exit(void)
+{
+ sdio_unregister_driver(&besdbg_sdio_driver);
+ class_destroy(besdbg_class);
+}
+
+module_init(besdbg_driver_init);
+module_exit(besdbg_driver_exit);
+
+MODULE_AUTHOR("Ondrej Jirman <megi@xff.cz>");
+MODULE_DESCRIPTION("BES2600 SDIO debug driver");
+MODULE_LICENSE("GPL");
--
2.34.1