diff options
Diffstat (limited to 'drivers/net')
153 files changed, 60159 insertions, 4295 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 5116aec3c174..e9d5c193c773 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -428,4 +428,6 @@ config FUJITSU_ES source "drivers/net/hyperv/Kconfig" +source "drivers/net/rmnet/Kconfig" + endif # NETDEVICES diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 900b0c5320bb..3cb2c188ee3f 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -70,3 +70,4 @@ obj-$(CONFIG_HYPERV_NET) += hyperv/ obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o obj-$(CONFIG_FUJITSU_ES) += fjes/ +obj-$(CONFIG_RMNET) += rmnet/ diff --git a/drivers/net/can/spi/Kconfig b/drivers/net/can/spi/Kconfig index 148cae5871a6..e6b3862230b9 100644 --- a/drivers/net/can/spi/Kconfig +++ b/drivers/net/can/spi/Kconfig @@ -7,4 +7,21 @@ config CAN_MCP251X ---help--- Driver for the Microchip MCP251x SPI CAN controllers. +config CAN_RH850 + tristate "Renesas RH850 SPI CAN controller" + depends on HAS_DMA + ---help--- + Driver for the Renesas RH850 SPI CAN controller. + +config CAN_K61 + tristate "Freescale K61 SPI CAN controllers" + depends on SPI + ---help--- + Driver for the Freescale K61 SPI CAN controllers. + +config QTI_CAN + tristate "Unified driver for QTI CAN controllers" + depends on SPI + ---help--- + Unified driver for QTI CAN controllers. endmenu diff --git a/drivers/net/can/spi/Makefile b/drivers/net/can/spi/Makefile index 0e86040cdd8c..c1951ccc0034 100644 --- a/drivers/net/can/spi/Makefile +++ b/drivers/net/can/spi/Makefile @@ -4,3 +4,6 @@ obj-$(CONFIG_CAN_MCP251X) += mcp251x.o +obj-$(CONFIG_CAN_RH850) += rh850.o +obj-${CONFIG_CAN_K61} += k61.o +obj-$(CONFIG_QTI_CAN) += qti-can.o diff --git a/drivers/net/can/spi/k61.c b/drivers/net/can/spi/k61.c new file mode 100644 index 000000000000..7830d5badb94 --- /dev/null +++ b/drivers/net/can/spi/k61.c @@ -0,0 +1,964 @@ +/* Copyright (c) 2016-2018, 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/interrupt.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/workqueue.h> +#include <linux/spi/spi.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/can/error.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/uaccess.h> +#include <linux/pm.h> + +#define DEBUG_K61 0 +#if DEBUG_K61 == 1 +#define LOGDI(...) dev_info(&priv_data->spidev->dev, __VA_ARGS__) +#define LOGNI(...) netdev_info(netdev, __VA_ARGS__) +#else +#define LOGDI(...) +#define LOGNI(...) +#endif +#define LOGDE(...) dev_err(&priv_data->spidev->dev, __VA_ARGS__) +#define LOGNE(...) netdev_err(netdev, __VA_ARGS__) + +#define MAX_TX_BUFFERS 1 +#define XFER_BUFFER_SIZE 64 +#define K61_CLOCK 120000000 +#define K61_MAX_CHANNELS 1 +#define K61_FW_QUERY_RETRY_COUNT 3 + +struct k61_can { + struct net_device *netdev; + struct spi_device *spidev; + + struct mutex spi_lock; /* SPI device lock */ + + struct workqueue_struct *tx_wq; + char *tx_buf, *rx_buf; + int xfer_length; + atomic_t msg_seq; + + atomic_t netif_queue_stop; + struct completion response_completion; + int reset; + int wait_cmd; + int cmd_result; + int bits_per_word; + int reset_delay_msec; +}; + +struct k61_netdev_privdata { + struct can_priv can; + struct k61_can *k61_can; +}; + +struct k61_tx_work { + struct work_struct work; + struct sk_buff *skb; + struct net_device *netdev; +}; + +/* Message definitions */ +struct spi_mosi { /* TLV for MOSI line */ + u8 cmd; + u8 len; + u16 seq; + u8 data[]; +} __packed; + +struct spi_miso { /* TLV for MISO line */ + u8 cmd; + u8 len; + u16 seq; /* should match seq field from request, or 0 for unsols */ + u8 data[]; +} __packed; + +#define CMD_GET_FW_VERSION 0x81 +#define CMD_CAN_SEND_FRAME 0x82 +#define CMD_CAN_ADD_FILTER 0x83 +#define CMD_CAN_REMOVE_FILTER 0x84 +#define CMD_CAN_RECEIVE_FRAME 0x85 +#define CMD_CAN_DATA_BUFF_ADD 0x87 +#define CMD_CAN_DATA_BUFF_REMOVE 0x88 +#define CMD_CAN_RELEASE_BUFFER 0x89 +#define CMD_CAN_DATA_BUFF_REMOVE_ALL 0x8A + +#define IOCTL_RELEASE_CAN_BUFFER (SIOCDEVPRIVATE + 0) +#define IOCTL_ENABLE_BUFFERING (SIOCDEVPRIVATE + 1) +#define IOCTL_ADD_FRAME_FILTER (SIOCDEVPRIVATE + 2) +#define IOCTL_REMOVE_FRAME_FILTER (SIOCDEVPRIVATE + 3) +#define IOCTL_DISABLE_BUFFERING (SIOCDEVPRIVATE + 5) +#define IOCTL_DISABLE_ALL_BUFFERING (SIOCDEVPRIVATE + 6) + +struct can_fw_resp { + u8 maj; + u8 min; + u8 ver; +} __packed; + +struct can_write_req { + u32 ts; + u32 mid; + u8 dlc; + u8 data[]; +} __packed; + +struct can_write_resp { + u8 err; +} __packed; + +struct can_receive_frame { + u32 ts; + u32 mid; + u8 dlc; + u8 data[]; +} __packed; + +struct can_add_filter_req { + u8 can_if; + u32 mid; + u32 mask; + u8 type; +} __packed; + +static struct can_bittiming_const k61_bittiming_const = { + .name = "k61", + .tseg1_min = 4, + .tseg1_max = 16, + .tseg2_min = 2, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 4, + .brp_max = 1023, + .brp_inc = 1, +}; + +struct k61_add_can_buffer { + u8 can_if; + u32 mid; + u32 mask; +} __packed; + +struct k61_delete_can_buffer { + u8 can_if; + u32 mid; + u32 mask; +} __packed; + +static int k61_rx_message(struct k61_can *priv_data); + +static irqreturn_t k61_irq(int irq, void *priv) +{ + struct k61_can *priv_data = priv; + + LOGDI("k61_irq\n"); + k61_rx_message(priv_data); + return IRQ_HANDLED; +} + +static void k61_frame_error(struct k61_can *priv_data, + struct can_receive_frame *frame) +{ + struct can_frame *cf; + struct sk_buff *skb; + struct net_device *netdev; + + netdev = priv_data->netdev; + skb = alloc_can_err_skb(netdev, &cf); + if (!skb) { + LOGDE("skb alloc failed\n"); + return; + } + + cf->can_id |= CAN_ERR_BUSERROR; + cf->data[2] |= CAN_ERR_PROT_FORM; + netdev->stats.rx_errors++; + netif_rx(skb); + netdev->stats.rx_packets++; + netdev->stats.rx_bytes += cf->can_dlc; +} + +static void k61_receive_frame(struct k61_can *priv_data, + struct can_receive_frame *frame) +{ + struct can_frame *cf; + struct sk_buff *skb; + struct skb_shared_hwtstamps *skt; + struct timeval tv; + static int msec; + struct net_device *netdev; + int i; + + if (frame->dlc > 8) { + LOGDE("can rx frame error\n"); + k61_frame_error(priv_data, frame); + return; + } + + netdev = priv_data->netdev; + skb = alloc_can_skb(netdev, &cf); + if (!skb) { + LOGDE("skb alloc failed\n"); + return; + } + + LOGDI("rcv frame %d %x %d %x %x %x %x %x %x %x %x\n", + frame->ts, frame->mid, frame->dlc, frame->data[0], + frame->data[1], frame->data[2], frame->data[3], frame->data[4], + frame->data[5], frame->data[6], frame->data[7]); + cf->can_id = le32_to_cpu(frame->mid); + cf->can_dlc = get_can_dlc(frame->dlc); + + for (i = 0; i < cf->can_dlc; i++) + cf->data[i] = frame->data[i]; + + msec = le32_to_cpu(frame->ts); + tv.tv_sec = msec / 1000; + tv.tv_usec = (msec - tv.tv_sec * 1000) * 1000; + skt = skb_hwtstamps(skb); + skt->hwtstamp = timeval_to_ktime(tv); + LOGDI(" hwtstamp %lld\n", ktime_to_ms(skt->hwtstamp)); + skb->tstamp = timeval_to_ktime(tv); + netif_rx(skb); + netdev->stats.rx_packets++; + netdev->stats.rx_bytes += cf->can_dlc; +} + +static void k61_process_response(struct k61_can *priv_data, + struct spi_miso *resp) +{ + int ret = 0; + + LOGDI("<%x %2d [%d]\n", resp->cmd, resp->len, resp->seq); + if (resp->cmd == CMD_CAN_RECEIVE_FRAME) { + struct can_receive_frame *frame = + (struct can_receive_frame *)&resp->data; + k61_receive_frame(priv_data, frame); + } else if (resp->cmd == CMD_GET_FW_VERSION) { + struct can_fw_resp *fw_resp = (struct can_fw_resp *)resp->data; + + dev_info(&priv_data->spidev->dev, "fw %d.%d.%d", + fw_resp->maj, fw_resp->min, fw_resp->ver); + } + + if (resp->cmd == priv_data->wait_cmd) { + priv_data->cmd_result = ret; + complete(&priv_data->response_completion); + } +} + +static void k61_process_rx(struct k61_can *priv_data, char *rx_buf) +{ + struct spi_miso *resp; + int length_processed = 0, actual_length = priv_data->xfer_length; + + while (length_processed < actual_length) { + int length_left = actual_length - length_processed; + int length = 0; /* length of consumed chunk */ + void *data; + + data = rx_buf + length_processed; + resp = (struct spi_miso *)data; + + if (resp->cmd == 0) { + /* special case. ignore cmd==0 */ + length_processed += 1; + continue; + } + + LOGDI("processing. p %d -> l %d (t %d)\n", + length_processed, length_left, priv_data->xfer_length); + length = resp->len + sizeof(*resp); + + if (length <= length_left) { + k61_process_response(priv_data, resp); + length_processed += length; + } else { + /* Incomplete command */ + break; + } + } +} + +static int k61_do_spi_transaction(struct k61_can *priv_data) +{ + struct spi_device *spi; + struct spi_transfer *xfer; + struct spi_message *msg; + int ret; + + spi = priv_data->spidev; + msg = devm_kzalloc(&spi->dev, sizeof(*msg), GFP_KERNEL); + xfer = devm_kzalloc(&spi->dev, sizeof(*xfer), GFP_KERNEL); + if (xfer == 0 || msg == 0) + return -ENOMEM; + spi_message_init(msg); + + spi_message_add_tail(xfer, msg); + xfer->tx_buf = priv_data->tx_buf; + xfer->rx_buf = priv_data->rx_buf; + xfer->len = XFER_BUFFER_SIZE; + xfer->bits_per_word = priv_data->bits_per_word; + + ret = spi_sync(spi, msg); + LOGDI("spi_sync ret %d\n", ret); + + if (ret == 0) { + devm_kfree(&spi->dev, msg); + devm_kfree(&spi->dev, xfer); + k61_process_rx(priv_data, priv_data->rx_buf); + } + return ret; +} + +static int k61_rx_message(struct k61_can *priv_data) +{ + char *tx_buf, *rx_buf; + int ret; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + ret = k61_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int k61_query_firmware_version(struct k61_can *priv_data) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_GET_FW_VERSION; + req->len = 0; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + priv_data->wait_cmd = CMD_GET_FW_VERSION; + priv_data->cmd_result = -1; + reinit_completion(&priv_data->response_completion); + + ret = k61_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + if (ret == 0) { + wait_for_completion_interruptible_timeout( + &priv_data->response_completion, 0.001 * HZ); + ret = priv_data->cmd_result; + } + + return ret; +} + +static int k61_can_write(struct k61_can *priv_data, struct can_frame *cf) +{ + char *tx_buf, *rx_buf; + int ret, i; + struct spi_mosi *req; + struct can_write_req *req_d; + struct net_device *netdev; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_SEND_FRAME; + req->len = sizeof(struct can_write_req) + 8; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + req_d = (struct can_write_req *)req->data; + req_d->mid = cf->can_id; + req_d->dlc = cf->can_dlc; + for (i = 0; i < cf->can_dlc; i++) + req_d->data[i] = cf->data[i]; + + ret = k61_do_spi_transaction(priv_data); + netdev = priv_data->netdev; + netdev->stats.tx_packets++; + netdev->stats.tx_bytes += cf->can_dlc; + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int k61_netdev_open(struct net_device *netdev) +{ + int err; + + LOGNI("Open"); + err = open_candev(netdev); + if (err) + return err; + + netif_start_queue(netdev); + + return 0; +} + +static int k61_netdev_close(struct net_device *netdev) +{ + LOGNI("Close"); + + netif_stop_queue(netdev); + close_candev(netdev); + return 0; +} + +static void k61_send_can_frame(struct work_struct *ws) +{ + struct k61_tx_work *tx_work; + struct can_frame *cf; + struct k61_can *priv_data; + struct net_device *netdev; + struct k61_netdev_privdata *netdev_priv_data; + + tx_work = container_of(ws, struct k61_tx_work, work); + netdev = tx_work->netdev; + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->k61_can; + LOGDI("send_can_frame ws %p\n", ws); + LOGDI("send_can_frame tx %p\n", tx_work); + + cf = (struct can_frame *)tx_work->skb->data; + k61_can_write(priv_data, cf); + + dev_kfree_skb(tx_work->skb); + kfree(tx_work); +} + +static int k61_frame_filter(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct can_add_filter_req *add_filter; + struct can_add_filter_req *filter_request; + struct k61_can *priv_data; + struct k61_netdev_privdata *netdev_priv_data; + struct spi_device *spi; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->k61_can; + spi = priv_data->spidev; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + if (!ifr) + return -EINVAL; + + filter_request = + devm_kzalloc(&spi->dev, sizeof(struct can_add_filter_req), + GFP_KERNEL); + if (!filter_request) + return -ENOMEM; + + if (copy_from_user(filter_request, ifr->ifr_data, + sizeof(struct can_add_filter_req))) { + devm_kfree(&spi->dev, filter_request); + return -EFAULT; + } + + req = (struct spi_mosi *)tx_buf; + if (cmd == IOCTL_ADD_FRAME_FILTER) + req->cmd = CMD_CAN_ADD_FILTER; + else + req->cmd = CMD_CAN_REMOVE_FILTER; + + req->len = sizeof(struct can_add_filter_req); + req->seq = atomic_inc_return(&priv_data->msg_seq); + + add_filter = (struct can_add_filter_req *)req->data; + add_filter->can_if = filter_request->can_if; + add_filter->mid = filter_request->mid; + add_filter->mask = filter_request->mask; + + ret = k61_do_spi_transaction(priv_data); + devm_kfree(&spi->dev, filter_request); + mutex_unlock(&priv_data->spi_lock); + return ret; +} + +static netdev_tx_t k61_netdev_start_xmit( + struct sk_buff *skb, struct net_device *netdev) +{ + struct k61_netdev_privdata *netdev_priv_data = netdev_priv(netdev); + struct k61_can *priv_data = netdev_priv_data->k61_can; + struct k61_tx_work *tx_work; + + LOGNI("netdev_start_xmit"); + if (can_dropped_invalid_skb(netdev, skb)) { + LOGNE("Dropping invalid can frame\n"); + return NETDEV_TX_OK; + } + tx_work = kzalloc(sizeof(*tx_work), GFP_ATOMIC); + if (tx_work == 0) + return NETDEV_TX_OK; + INIT_WORK(&tx_work->work, k61_send_can_frame); + tx_work->netdev = netdev; + tx_work->skb = skb; + queue_work(priv_data->tx_wq, &tx_work->work); + + return NETDEV_TX_OK; +} + +static int k61_send_release_can_buffer_cmd(struct net_device *netdev) +{ + struct k61_can *priv_data; + struct k61_netdev_privdata *netdev_priv_data; + struct spi_device *spi; + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->k61_can; + spi = priv_data->spidev; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_RELEASE_BUFFER; + req->len = 0; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + ret = k61_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + return ret; +} + +static int k61_remove_all_buffering(struct net_device *netdev) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct k61_can *priv_data; + struct k61_netdev_privdata *netdev_priv_data; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->k61_can; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_DATA_BUFF_REMOVE_ALL; + req->len = 0; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + priv_data->wait_cmd = req->cmd; + priv_data->cmd_result = -1; + reinit_completion(&priv_data->response_completion); + + ret = k61_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + if (ret == 0) { + LOGDI("k61_do_blocking_ioctl ready to wait for response\n"); + /* Flash write may take some time. Hence give 2s as + * wait duration in the worst case. This wait time should + * increase if more number of frame IDs are stored in flash. + */ + ret = wait_for_completion_interruptible_timeout( + &priv_data->response_completion, 2 * HZ); + ret = priv_data->cmd_result; + } + + return ret; +} + +static int k61_convert_ioctl_cmd_to_spi_cmd(int ioctl_cmd) +{ + switch (ioctl_cmd) { + case IOCTL_ENABLE_BUFFERING: + return CMD_CAN_DATA_BUFF_ADD; + case IOCTL_DISABLE_BUFFERING: + return CMD_CAN_DATA_BUFF_REMOVE; + } + return -EINVAL; +} + +static int k61_data_buffering(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + int spi_cmd, ret; + char *tx_buf, *rx_buf; + struct k61_can *priv_data; + struct spi_mosi *req; + struct k61_netdev_privdata *netdev_priv_data; + struct k61_add_can_buffer *enable_buffering; + struct k61_add_can_buffer *add_request; + struct spi_device *spi; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->k61_can; + spi = priv_data->spidev; + + mutex_lock(&priv_data->spi_lock); + spi_cmd = k61_convert_ioctl_cmd_to_spi_cmd(cmd); + if (spi_cmd < 0) { + LOGDE("k61_do_blocking_ioctl wrong command %d\n", cmd); + return spi_cmd; + } + + if (!ifr) + return -EINVAL; + + add_request = devm_kzalloc(&spi->dev, sizeof(struct k61_add_can_buffer), + GFP_KERNEL); + if (!add_request) + return -ENOMEM; + + if (copy_from_user(add_request, ifr->ifr_data, + sizeof(struct k61_add_can_buffer))) { + devm_kfree(&spi->dev, add_request); + return -EFAULT; + } + + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = spi_cmd; + req->len = sizeof(struct k61_add_can_buffer); + req->seq = atomic_inc_return(&priv_data->msg_seq); + + enable_buffering = (struct k61_add_can_buffer *)req->data; + enable_buffering->can_if = add_request->can_if; + enable_buffering->mid = add_request->mid; + enable_buffering->mask = add_request->mask; + + priv_data->wait_cmd = spi_cmd; + priv_data->cmd_result = -1; + reinit_completion(&priv_data->response_completion); + + ret = k61_do_spi_transaction(priv_data); + devm_kfree(&spi->dev, add_request); + mutex_unlock(&priv_data->spi_lock); + + if (ret == 0) { + LOGDI("k61_do_blocking_ioctl ready to wait for response\n"); + /* Flash write may take some time. Hence give 400ms as + * wait duration in the worst case. + */ + ret = wait_for_completion_interruptible_timeout( + &priv_data->response_completion, 0.4 * HZ); + ret = priv_data->cmd_result; + } + return ret; +} + +static int k61_netdev_do_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + struct k61_can *priv_data; + struct k61_netdev_privdata *netdev_priv_data; + int ret = -EINVAL; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->k61_can; + LOGDI("k61_netdev_do_ioctl %x\n", cmd); + + switch (cmd) { + case IOCTL_ADD_FRAME_FILTER: + case IOCTL_REMOVE_FRAME_FILTER: + ret = k61_frame_filter(netdev, ifr, cmd); + break; + case IOCTL_ENABLE_BUFFERING: + case IOCTL_DISABLE_BUFFERING: + ret = k61_data_buffering(netdev, ifr, cmd); + break; + case IOCTL_DISABLE_ALL_BUFFERING: + ret = k61_remove_all_buffering(netdev); + break; + case IOCTL_RELEASE_CAN_BUFFER: + ret = k61_send_release_can_buffer_cmd(netdev); + break; + } + return ret; +} + +static const struct net_device_ops k61_netdev_ops = { + .ndo_open = k61_netdev_open, + .ndo_stop = k61_netdev_close, + .ndo_start_xmit = k61_netdev_start_xmit, + .ndo_do_ioctl = k61_netdev_do_ioctl, +}; + +static int k61_create_netdev(struct spi_device *spi, + struct k61_can *priv_data) +{ + struct net_device *netdev; + struct k61_netdev_privdata *netdev_priv_data; + + LOGDI("k61_create_netdev\n"); + netdev = alloc_candev(sizeof(*netdev_priv_data), MAX_TX_BUFFERS); + if (!netdev) { + LOGDE("Couldn't alloc candev\n"); + return -ENOMEM; + } + + netdev_priv_data = netdev_priv(netdev); + netdev_priv_data->k61_can = priv_data; + + priv_data->netdev = netdev; + + netdev->netdev_ops = &k61_netdev_ops; + SET_NETDEV_DEV(netdev, &spi->dev); + netdev_priv_data->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES | + CAN_CTRLMODE_LISTENONLY; + netdev_priv_data->can.bittiming_const = &k61_bittiming_const; + netdev_priv_data->can.clock.freq = K61_CLOCK; + + return 0; +} + +static struct k61_can *k61_create_priv_data(struct spi_device *spi) +{ + struct k61_can *priv_data; + int err; + struct device *dev; + + dev = &spi->dev; + priv_data = devm_kzalloc(dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) { + dev_err(dev, "Couldn't alloc k61_can\n"); + return 0; + } + spi_set_drvdata(spi, priv_data); + atomic_set(&priv_data->netif_queue_stop, 0); + priv_data->spidev = spi; + + priv_data->tx_wq = alloc_workqueue("k61_tx_wq", 0, 0); + if (!priv_data->tx_wq) { + dev_err(dev, "Couldn't alloc workqueue\n"); + err = -ENOMEM; + goto cleanup_privdata; + } + + priv_data->tx_buf = devm_kzalloc(dev, XFER_BUFFER_SIZE, + GFP_KERNEL); + priv_data->rx_buf = devm_kzalloc(dev, XFER_BUFFER_SIZE, + GFP_KERNEL); + if (!priv_data->tx_buf || !priv_data->rx_buf) { + dev_err(dev, "Couldn't alloc tx or rx buffers\n"); + err = -ENOMEM; + goto cleanup_privdata; + } + priv_data->xfer_length = 0; + + mutex_init(&priv_data->spi_lock); + atomic_set(&priv_data->msg_seq, 0); + init_completion(&priv_data->response_completion); + return priv_data; + +cleanup_privdata: + if (priv_data) { + if (priv_data->tx_wq) + destroy_workqueue(priv_data->tx_wq); + } + return 0; +} + +static int k61_probe(struct spi_device *spi) +{ + int err, retry = 0, query_err = -1; + struct k61_can *priv_data; + struct device *dev; + + dev = &spi->dev; + dev_dbg(dev, "k61_probe"); + + err = spi_setup(spi); + if (err) { + dev_err(dev, "spi_setup failed: %d", err); + return err; + } + + priv_data = k61_create_priv_data(spi); + if (!priv_data) { + dev_err(dev, "Failed to create k61_can priv_data\n"); + err = -ENOMEM; + return err; + } + dev_dbg(dev, "k61_probe created priv_data"); + + err = of_property_read_u32(spi->dev.of_node, "bits-per-word", + &priv_data->bits_per_word); + if (err) + priv_data->bits_per_word = 16; + + err = of_property_read_u32(spi->dev.of_node, "reset-delay-msec", + &priv_data->reset_delay_msec); + if (err) + priv_data->reset_delay_msec = 1; + + priv_data->reset = of_get_named_gpio(spi->dev.of_node, "reset-gpio", 0); + if (gpio_is_valid(priv_data->reset)) { + err = gpio_request(priv_data->reset, "k61-reset"); + if (err < 0) { + dev_err(&spi->dev, + "failed to request gpio %d: %d\n", + priv_data->reset, err); + goto cleanup_candev; + } + + gpio_direction_output(priv_data->reset, 0); + udelay(1); + gpio_direction_output(priv_data->reset, 1); + msleep(priv_data->reset_delay_msec); + } + + err = k61_create_netdev(spi, priv_data); + if (err) { + dev_err(dev, "Failed to create CAN device: %d", err); + goto cleanup_candev; + } + + err = register_candev(priv_data->netdev); + if (err) { + dev_err(dev, "Failed to register CAN device: %d", err); + goto unregister_candev; + } + + err = request_threaded_irq(spi->irq, NULL, k61_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "k61", priv_data); + if (err) { + dev_err(dev, "Failed to request irq: %d", err); + goto unregister_candev; + } + dev_dbg(dev, "Request irq %d ret %d\n", spi->irq, err); + + while ((query_err != 0) && (retry < K61_FW_QUERY_RETRY_COUNT)) { + query_err = k61_query_firmware_version(priv_data); + retry++; + } + + if (query_err) { + dev_info(dev, "K61 probe failed\n"); + err = -ENODEV; + goto free_irq; + } + return 0; + +free_irq: + free_irq(spi->irq, priv_data); +unregister_candev: + unregister_candev(priv_data->netdev); +cleanup_candev: + if (priv_data) { + if (priv_data->netdev) + free_candev(priv_data->netdev); + if (priv_data->tx_wq) + destroy_workqueue(priv_data->tx_wq); + } + return err; +} + +static int k61_remove(struct spi_device *spi) +{ + struct k61_can *priv_data = spi_get_drvdata(spi); + + LOGDI("k61_remove\n"); + unregister_candev(priv_data->netdev); + free_candev(priv_data->netdev); + destroy_workqueue(priv_data->tx_wq); + return 0; +} + +static const struct of_device_id k61_match_table[] = { + { .compatible = "fsl,k61" }, + { } +}; + +#ifdef CONFIG_PM +static int k61_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + enable_irq_wake(spi->irq); + return 0; +} + +static int k61_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct k61_can *priv_data = spi_get_drvdata(spi); + + disable_irq_wake(spi->irq); + k61_rx_message(priv_data); + return 0; +} + +static const struct dev_pm_ops k61_dev_pm_ops = { + .suspend = k61_suspend, + .resume = k61_resume, +}; +#endif + +static struct spi_driver k61_driver = { + .driver = { + .name = "k61", + .of_match_table = k61_match_table, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &k61_dev_pm_ops, +#endif + }, + .probe = k61_probe, + .remove = k61_remove, +}; +module_spi_driver(k61_driver); + +MODULE_DESCRIPTION("Freescale K61 SPI-CAN module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/spi/qti-can.c b/drivers/net/can/spi/qti-can.c new file mode 100644 index 000000000000..f7ba4510d1bc --- /dev/null +++ b/drivers/net/can/spi/qti-can.c @@ -0,0 +1,1509 @@ +/* Copyright (c) 2015-2018, 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/interrupt.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/workqueue.h> +#include <linux/spi/spi.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/can/error.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/uaccess.h> +#include <linux/pm.h> +#include <asm/div64.h> + +#define DEBUG_QTI_CAN 0 +#if DEBUG_QTI_CAN == 1 +#define LOGDI(...) dev_info(&priv_data->spidev->dev, __VA_ARGS__) +#define LOGNI(...) netdev_info(netdev, __VA_ARGS__) +#else +#define LOGDI(...) dev_dbg(&priv_data->spidev->dev, __VA_ARGS__) +#define LOGNI(...) netdev_dbg(netdev, __VA_ARGS__) +#endif +#define LOGDE(...) dev_err(&priv_data->spidev->dev, __VA_ARGS__) +#define LOGNE(...) netdev_err(netdev, __VA_ARGS__) + +#define MAX_TX_BUFFERS 1 +#define XFER_BUFFER_SIZE 64 +#define RX_ASSEMBLY_BUFFER_SIZE 128 +#define QTI_CAN_FW_QUERY_RETRY_COUNT 3 +#define DRIVER_MODE_RAW_FRAMES 0 +#define DRIVER_MODE_PROPERTIES 1 +#define DRIVER_MODE_AMB 2 +#define QUERY_FIRMWARE_TIMEOUT_MS 100 + +struct qti_can { + struct net_device **netdev; + struct spi_device *spidev; + struct mutex spi_lock; /* SPI device lock */ + struct workqueue_struct *tx_wq; + char *tx_buf, *rx_buf; + int xfer_length; + atomic_t msg_seq; + char *assembly_buffer; + u8 assembly_buffer_size; + atomic_t netif_queue_stop; + struct completion response_completion; + int wait_cmd; + int cmd_result; + int driver_mode; + int clk_freq_mhz; + int max_can_channels; + int bits_per_word; + int reset_delay_msec; + int reset; + bool support_can_fd; + bool can_fw_cmd_timeout_req; + u32 rem_all_buffering_timeout_ms; + u32 can_fw_cmd_timeout_ms; + s64 time_diff; +}; + +struct qti_can_netdev_privdata { + struct can_priv can; + struct qti_can *qti_can; + u8 netdev_index; +}; + +struct qti_can_tx_work { + struct work_struct work; + struct sk_buff *skb; + struct net_device *netdev; +}; + +/* Message definitions */ +struct spi_mosi { /* TLV for MOSI line */ + u8 cmd; + u8 len; + u16 seq; + u8 data[]; +} __packed; + +struct spi_miso { /* TLV for MISO line */ + u8 cmd; + u8 len; + u16 seq; /* should match seq field from request, or 0 for unsols */ + u8 data[]; +} __packed; + +#define CMD_GET_FW_VERSION 0x81 +#define CMD_CAN_SEND_FRAME 0x82 +#define CMD_CAN_ADD_FILTER 0x83 +#define CMD_CAN_REMOVE_FILTER 0x84 +#define CMD_CAN_RECEIVE_FRAME 0x85 +#define CMD_CAN_CONFIG_BIT_TIMING 0x86 +#define CMD_CAN_DATA_BUFF_ADD 0x87 +#define CMD_CAN_DATA_BUFF_REMOVE 0X88 +#define CMD_CAN_RELEASE_BUFFER 0x89 +#define CMD_CAN_DATA_BUFF_REMOVE_ALL 0x8A +#define CMD_PROPERTY_WRITE 0x8B +#define CMD_PROPERTY_READ 0x8C +#define CMD_GET_FW_BR_VERSION 0x95 +#define CMD_BEGIN_FIRMWARE_UPGRADE 0x96 +#define CMD_FIRMWARE_UPGRADE_DATA 0x97 +#define CMD_END_FIRMWARE_UPGRADE 0x98 +#define CMD_BEGIN_BOOT_ROM_UPGRADE 0x99 +#define CMD_BOOT_ROM_UPGRADE_DATA 0x9A +#define CMD_END_BOOT_ROM_UPGRADE 0x9B +#define CMD_END_FW_UPDATE_FILE 0x9C +#define CMD_UPDATE_TIME_INFO 0x9D +#define CMD_UPDATE_SUSPEND_EVENT 0x9E +#define CMD_UPDATE_RESUME_EVENT 0x9F + +#define IOCTL_RELEASE_CAN_BUFFER (SIOCDEVPRIVATE + 0) +#define IOCTL_ENABLE_BUFFERING (SIOCDEVPRIVATE + 1) +#define IOCTL_ADD_FRAME_FILTER (SIOCDEVPRIVATE + 2) +#define IOCTL_REMOVE_FRAME_FILTER (SIOCDEVPRIVATE + 3) +#define IOCTL_DISABLE_BUFFERING (SIOCDEVPRIVATE + 5) +#define IOCTL_DISABLE_ALL_BUFFERING (SIOCDEVPRIVATE + 6) +#define IOCTL_GET_FW_BR_VERSION (SIOCDEVPRIVATE + 7) +#define IOCTL_BEGIN_FIRMWARE_UPGRADE (SIOCDEVPRIVATE + 8) +#define IOCTL_FIRMWARE_UPGRADE_DATA (SIOCDEVPRIVATE + 9) +#define IOCTL_END_FIRMWARE_UPGRADE (SIOCDEVPRIVATE + 10) +#define IOCTL_BEGIN_BOOT_ROM_UPGRADE (SIOCDEVPRIVATE + 11) +#define IOCTL_BOOT_ROM_UPGRADE_DATA (SIOCDEVPRIVATE + 12) +#define IOCTL_END_BOOT_ROM_UPGRADE (SIOCDEVPRIVATE + 13) +#define IOCTL_END_FW_UPDATE_FILE (SIOCDEVPRIVATE + 14) + +#define IFR_DATA_OFFSET 0x100 +struct can_fw_resp { + u8 maj; + u8 min; + u8 ver[48]; +} __packed; + +struct can_write_req { + u8 can_if; + u32 mid; + u8 dlc; + u8 data[8]; +} __packed; + +struct can_write_resp { + u8 err; +} __packed; + +struct can_filter_req { + u8 can_if; + u32 mid; + u32 mask; +} __packed; + +struct can_add_filter_resp { + u8 err; +} __packed; + +struct can_receive_frame { + u8 can_if; + __le64 ts; + u32 mid; + u8 dlc; + u8 data[8]; +} __packed; + +struct can_config_bit_timing { + u8 can_if; + u32 prop_seg; + u32 phase_seg1; + u32 phase_seg2; + u32 sjw; + u32 brp; +} __packed; + +struct can_time_info { + __le64 time; +} __packed; + +static struct can_bittiming_const rh850_bittiming_const = { + .name = "qti_can", + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 16, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 70, + .brp_inc = 1, +}; + +static struct can_bittiming_const flexcan_bittiming_const = { + .name = "qti_can", + .tseg1_min = 4, + .tseg1_max = 16, + .tseg2_min = 2, + .tseg2_max = 8, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 256, + .brp_inc = 1, +}; + +static struct can_bittiming_const qti_can_bittiming_const; + +static struct can_bittiming_const qti_can_data_bittiming_const = { + .name = "qti_can", + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 16, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 70, + .brp_inc = 1, +}; + +struct vehicle_property { + int id; + u64 ts; + int zone; + int val_type; + u32 data_len; + union { + u8 bval; + int val; + int val_arr[4]; + float f_value; + float float_arr[4]; + u8 str[36]; + }; +} __packed; + +struct qti_can_release_can_buffer { + u8 enable; +} __packed; + +struct qti_can_buffer { + u8 can_if; + u32 mid; + u32 mask; +} __packed; + +struct can_fw_br_resp { + u8 maj; + u8 min; + u8 ver[32]; + u8 br_maj; + u8 br_min; + u8 curr_exec_mode; +} __packed; + +struct qti_can_ioctl_req { + u8 len; + u8 data[64]; +} __packed; + +static int qti_can_rx_message(struct qti_can *priv_data); + +static irqreturn_t qti_can_irq(int irq, void *priv) +{ + struct qti_can *priv_data = priv; + + LOGDI("qti_can_irq\n"); + qti_can_rx_message(priv_data); + return IRQ_HANDLED; +} + +static void qti_can_receive_frame(struct qti_can *priv_data, + struct can_receive_frame *frame) +{ + struct can_frame *cf; + struct sk_buff *skb; + struct skb_shared_hwtstamps *skt; + ktime_t nsec; + struct net_device *netdev; + int i; + struct device *dev; + + dev = &priv_data->spidev->dev; + if (frame->can_if >= priv_data->max_can_channels) { + LOGDE("qti_can rcv error. Channel is %d\n", frame->can_if); + return; + } + + netdev = priv_data->netdev[frame->can_if]; + skb = alloc_can_skb(netdev, &cf); + if (!skb) { + LOGDE("skb alloc failed. frame->can_if %d\n", frame->can_if); + return; + } + + LOGDI("rcv frame %d %llu %x %d %x %x %x %x %x %x %x %x\n", + frame->can_if, frame->ts, frame->mid, frame->dlc, + frame->data[0], frame->data[1], frame->data[2], frame->data[3], + frame->data[4], frame->data[5], frame->data[6], frame->data[7]); + cf->can_id = le32_to_cpu(frame->mid); + cf->can_dlc = get_can_dlc(frame->dlc); + + for (i = 0; i < cf->can_dlc; i++) + cf->data[i] = frame->data[i]; + + nsec = ms_to_ktime(le64_to_cpu(frame->ts) + priv_data->time_diff); + + skt = skb_hwtstamps(skb); + skt->hwtstamp = nsec; + LOGDI(" hwtstamp %lld\n", ktime_to_ms(skt->hwtstamp)); + skb->tstamp = nsec; + netif_rx(skb); + netdev->stats.rx_packets++; +} + +static void qti_can_receive_property(struct qti_can *priv_data, + struct vehicle_property *property) +{ + struct canfd_frame *cfd; + u8 *p; + struct sk_buff *skb; + struct skb_shared_hwtstamps *skt; + ktime_t nsec; + struct net_device *netdev; + struct device *dev; + int i; + + /* can0 as the channel with properties */ + dev = &priv_data->spidev->dev; + netdev = priv_data->netdev[0]; + skb = alloc_canfd_skb(netdev, &cfd); + if (!skb) { + LOGDE("skb alloc failed. frame->can_if %d\n", 0); + return; + } + + LOGDI("rcv property:0x%x data:%2x %2x %2x %2x", property->id, + property->str[0], property->str[1], + property->str[2], property->str[3]); + cfd->can_id = 0x00; + cfd->len = sizeof(struct vehicle_property); + + p = (u8 *)property; + for (i = 0; i < cfd->len; i++) + cfd->data[i] = p[i]; + + nsec = ns_to_ktime(le64_to_cpu(property->ts)); + skt = skb_hwtstamps(skb); + skt->hwtstamp = nsec; + LOGDI(" hwtstamp %lld\n", ktime_to_ms(skt->hwtstamp)); + skb->tstamp = nsec; + netif_rx(skb); + netdev->stats.rx_packets++; +} + +static int qti_can_process_response(struct qti_can *priv_data, + struct spi_miso *resp, int length) +{ + int ret = 0; + u64 mstime; + ktime_t ktime_now; + + LOGDI("<%x %2d [%d]\n", resp->cmd, resp->len, resp->seq); + if (resp->cmd == CMD_CAN_RECEIVE_FRAME) { + struct can_receive_frame *frame = + (struct can_receive_frame *)&resp->data; + if (resp->len > length) { + LOGDE("Error. This should never happen\n"); + LOGDE("process_response: Saving %d bytes\n", length); + memcpy(priv_data->assembly_buffer, (char *)resp, + length); + priv_data->assembly_buffer_size = length; + } else { + qti_can_receive_frame(priv_data, frame); + } + } else if (resp->cmd == CMD_PROPERTY_READ) { + struct vehicle_property *property = + (struct vehicle_property *)&resp->data; + if (resp->len > length) { + LOGDE("Error. This should never happen\n"); + LOGDE("process_response: Saving %d bytes\n", length); + memcpy(priv_data->assembly_buffer, (char *)resp, + length); + priv_data->assembly_buffer_size = length; + } else { + qti_can_receive_property(priv_data, property); + } + } else if (resp->cmd == CMD_GET_FW_VERSION) { + struct can_fw_resp *fw_resp = (struct can_fw_resp *)resp->data; + + dev_info(&priv_data->spidev->dev, "fw %d.%d", + fw_resp->maj, fw_resp->min); + dev_info(&priv_data->spidev->dev, "fw string %s", + fw_resp->ver); + } else if (resp->cmd == CMD_GET_FW_BR_VERSION) { + struct can_fw_br_resp *fw_resp = + (struct can_fw_br_resp *)resp->data; + dev_info(&priv_data->spidev->dev, "fw_can %d.%d", + fw_resp->maj, fw_resp->min); + dev_info(&priv_data->spidev->dev, "fw string %s", + fw_resp->ver); + dev_info(&priv_data->spidev->dev, "fw_br %d.%d exec_mode %d", + fw_resp->br_maj, fw_resp->br_min, + fw_resp->curr_exec_mode); + ret = fw_resp->curr_exec_mode << 28; + ret |= (fw_resp->br_maj & 0xF) << 24; + ret |= (fw_resp->br_min & 0xFF) << 16; + ret |= (fw_resp->maj & 0xF) << 8; + ret |= (fw_resp->min & 0xFF); + } else if (resp->cmd == CMD_UPDATE_TIME_INFO) { + struct can_time_info *time_data = + (struct can_time_info *)resp->data; + ktime_now = ktime_get_boottime(); + mstime = ktime_to_ms(ktime_now); + priv_data->time_diff = mstime - (le64_to_cpu(time_data->time)); + } + + if (resp->cmd == priv_data->wait_cmd) { + priv_data->cmd_result = ret; + complete(&priv_data->response_completion); + } + return ret; +} + +static int qti_can_process_rx(struct qti_can *priv_data, char *rx_buf) +{ + struct spi_miso *resp; + struct device *dev; + int length_processed = 0, actual_length = priv_data->xfer_length; + int ret = 0; + + dev = &priv_data->spidev->dev; + while (length_processed < actual_length) { + int length_left = actual_length - length_processed; + int length = 0; /* length of consumed chunk */ + void *data; + + if (priv_data->assembly_buffer_size > 0) { + LOGDI("callback: Reassembling %d bytes\n", + priv_data->assembly_buffer_size); + /* should copy just 1 byte instead, since cmd should */ + /* already been copied as being first byte */ + memcpy(priv_data->assembly_buffer + + priv_data->assembly_buffer_size, + rx_buf, 2); + data = priv_data->assembly_buffer; + resp = (struct spi_miso *)data; + length = resp->len + sizeof(*resp) + - priv_data->assembly_buffer_size; + if (length > 0) + memcpy(priv_data->assembly_buffer + + priv_data->assembly_buffer_size, + rx_buf, length); + length_left += priv_data->assembly_buffer_size; + priv_data->assembly_buffer_size = 0; + } else { + data = rx_buf + length_processed; + resp = (struct spi_miso *)data; + if (resp->cmd == 0x00 || resp->cmd == 0xFF) { + /* special case. ignore cmd==0x00, 0xFF */ + length_processed += 1; + continue; + } + length = resp->len + sizeof(struct spi_miso); + } + LOGDI("processing. p %d -> l %d (t %d)\n", + length_processed, length_left, priv_data->xfer_length); + length_processed += length; + if (length_left >= sizeof(*resp) && + resp->len + sizeof(*resp) <= length_left) { + struct spi_miso *resp = + (struct spi_miso *)data; + ret = qti_can_process_response(priv_data, resp, + length_left); + } else if (length_left > 0) { + /* Not full message. Store however much we have for */ + /* later assembly */ + LOGDI("callback: Storing %d bytes of response\n", + length_left); + memcpy(priv_data->assembly_buffer, data, length_left); + priv_data->assembly_buffer_size = length_left; + break; + } + } + return ret; +} + +static int qti_can_do_spi_transaction(struct qti_can *priv_data) +{ + struct spi_device *spi; + struct spi_transfer *xfer; + struct spi_message *msg; + struct device *dev; + int ret; + + spi = priv_data->spidev; + dev = &spi->dev; + msg = devm_kzalloc(&spi->dev, sizeof(*msg), GFP_KERNEL); + xfer = devm_kzalloc(&spi->dev, sizeof(*xfer), GFP_KERNEL); + if (!xfer || !msg) + return -ENOMEM; + LOGDI(">%x %2d [%d]\n", priv_data->tx_buf[0], + priv_data->tx_buf[1], priv_data->tx_buf[2]); + spi_message_init(msg); + spi_message_add_tail(xfer, msg); + xfer->tx_buf = priv_data->tx_buf; + xfer->rx_buf = priv_data->rx_buf; + xfer->len = priv_data->xfer_length; + xfer->bits_per_word = priv_data->bits_per_word; + ret = spi_sync(spi, msg); + LOGDI("spi_sync ret %d data %x %x %x %x %x %x %x %x\n", ret, + priv_data->rx_buf[0], priv_data->rx_buf[1], + priv_data->rx_buf[2], priv_data->rx_buf[3], + priv_data->rx_buf[4], priv_data->rx_buf[5], + priv_data->rx_buf[6], priv_data->rx_buf[7]); + + if (ret == 0) + qti_can_process_rx(priv_data, priv_data->rx_buf); + devm_kfree(&spi->dev, msg); + devm_kfree(&spi->dev, xfer); + return ret; +} + +static int qti_can_rx_message(struct qti_can *priv_data) +{ + char *tx_buf, *rx_buf; + int ret; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + ret = qti_can_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int qti_can_query_firmware_version(struct qti_can *priv_data) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_GET_FW_VERSION; + req->len = 0; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + priv_data->wait_cmd = CMD_GET_FW_VERSION; + priv_data->cmd_result = -1; + reinit_completion(&priv_data->response_completion); + + ret = qti_can_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + if (ret == 0) { + wait_for_completion_interruptible_timeout( + &priv_data->response_completion, + msecs_to_jiffies(QUERY_FIRMWARE_TIMEOUT_MS)); + ret = priv_data->cmd_result; + } + + return ret; +} + +static int qti_can_notify_power_events(struct qti_can *priv_data, u8 event_type) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = event_type; + req->len = 0; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + ret = qti_can_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int qti_can_set_bitrate(struct net_device *netdev) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct can_config_bit_timing *req_d; + struct qti_can *priv_data; + struct can_priv *priv = netdev_priv(netdev); + struct qti_can_netdev_privdata *qti_can_priv; + + qti_can_priv = netdev_priv(netdev); + priv_data = qti_can_priv->qti_can; + + netdev_info(netdev, "ch%i, bitrate setting>%i", + qti_can_priv->netdev_index, priv->bittiming.bitrate); + LOGNI("sjw>%i brp>%i ph_sg1>%i ph_sg2>%i smpl_pt>%i tq>%i pr_seg>%i", + priv->bittiming.sjw, priv->bittiming.brp, + priv->bittiming.phase_seg1, + priv->bittiming.phase_seg2, + priv->bittiming.sample_point, + priv->bittiming.tq, priv->bittiming.prop_seg); + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_CONFIG_BIT_TIMING; + req->len = sizeof(struct can_config_bit_timing); + req->seq = atomic_inc_return(&priv_data->msg_seq); + req_d = (struct can_config_bit_timing *)req->data; + req_d->can_if = qti_can_priv->netdev_index; + req_d->prop_seg = priv->bittiming.prop_seg; + req_d->phase_seg1 = priv->bittiming.phase_seg1; + req_d->phase_seg2 = priv->bittiming.phase_seg2; + req_d->sjw = priv->bittiming.sjw; + req_d->brp = priv->bittiming.brp; + ret = qti_can_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int qti_can_write(struct qti_can *priv_data, + int can_channel, struct canfd_frame *cf) +{ + char *tx_buf, *rx_buf; + int ret, i; + struct spi_mosi *req; + struct can_write_req *req_d; + struct net_device *netdev; + + if (can_channel < 0 || can_channel >= priv_data->max_can_channels) { + LOGDE("qti_can_write error. Channel is %d\n", can_channel); + return -EINVAL; + } + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + if (priv_data->driver_mode == DRIVER_MODE_RAW_FRAMES) { + req->cmd = CMD_CAN_SEND_FRAME; + req->len = sizeof(struct can_write_req) + 8; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + req_d = (struct can_write_req *)req->data; + req_d->can_if = can_channel; + req_d->mid = cf->can_id; + req_d->dlc = cf->len; + + for (i = 0; i < cf->len; i++) + req_d->data[i] = cf->data[i]; + } else if (priv_data->driver_mode == DRIVER_MODE_PROPERTIES || + priv_data->driver_mode == DRIVER_MODE_AMB) { + req->cmd = CMD_PROPERTY_WRITE; + req->len = sizeof(struct vehicle_property); + req->seq = atomic_inc_return(&priv_data->msg_seq); + for (i = 0; i < cf->len; i++) + req->data[i] = cf->data[i]; + } else { + LOGDE("qti_can_write: wrong driver mode %i", + priv_data->driver_mode); + } + + ret = qti_can_do_spi_transaction(priv_data); + netdev = priv_data->netdev[can_channel]; + netdev->stats.tx_packets++; + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int qti_can_netdev_open(struct net_device *netdev) +{ + int err; + + LOGNI("Open"); + err = open_candev(netdev); + if (err) + return err; + + netif_start_queue(netdev); + + return 0; +} + +static int qti_can_netdev_close(struct net_device *netdev) +{ + LOGNI("Close"); + + netif_stop_queue(netdev); + close_candev(netdev); + return 0; +} + +static void qti_can_send_can_frame(struct work_struct *ws) +{ + struct qti_can_tx_work *tx_work; + struct canfd_frame *cf; + struct qti_can *priv_data; + struct net_device *netdev; + struct qti_can_netdev_privdata *netdev_priv_data; + int can_channel; + + tx_work = container_of(ws, struct qti_can_tx_work, work); + netdev = tx_work->netdev; + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->qti_can; + can_channel = netdev_priv_data->netdev_index; + + LOGDI("send_can_frame ws %pK\n", ws); + LOGDI("send_can_frame tx %pK\n", tx_work); + + cf = (struct canfd_frame *)tx_work->skb->data; + qti_can_write(priv_data, can_channel, cf); + + dev_kfree_skb(tx_work->skb); + kfree(tx_work); +} + +static netdev_tx_t qti_can_netdev_start_xmit( + struct sk_buff *skb, struct net_device *netdev) +{ + struct qti_can_netdev_privdata *netdev_priv_data = netdev_priv(netdev); + struct qti_can *priv_data = netdev_priv_data->qti_can; + struct qti_can_tx_work *tx_work; + + LOGNI("netdev_start_xmit"); + if (can_dropped_invalid_skb(netdev, skb)) { + LOGNE("Dropping invalid can frame\n"); + return NETDEV_TX_OK; + } + tx_work = kzalloc(sizeof(*tx_work), GFP_ATOMIC); + if (!tx_work) + return NETDEV_TX_OK; + INIT_WORK(&tx_work->work, qti_can_send_can_frame); + tx_work->netdev = netdev; + tx_work->skb = skb; + queue_work(priv_data->tx_wq, &tx_work->work); + + return NETDEV_TX_OK; +} + +static int qti_can_send_release_can_buffer_cmd(struct net_device *netdev) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct qti_can *priv_data; + struct qti_can_netdev_privdata *netdev_priv_data; + int *mode; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->qti_can; + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_RELEASE_BUFFER; + req->len = sizeof(int); + req->seq = atomic_inc_return(&priv_data->msg_seq); + mode = (int *)req->data; + *mode = priv_data->driver_mode; + + ret = qti_can_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + return ret; +} + +static int qti_can_data_buffering(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + char *tx_buf, *rx_buf; + int ret; + u32 timeout; + struct spi_mosi *req; + struct qti_can_buffer *enable_buffering; + struct qti_can_buffer *add_request; + struct qti_can *priv_data; + struct qti_can_netdev_privdata *netdev_priv_data; + struct spi_device *spi; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->qti_can; + spi = priv_data->spidev; + timeout = priv_data->can_fw_cmd_timeout_ms; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + if (!ifr) + return -EINVAL; + add_request = devm_kzalloc(&spi->dev, + sizeof(struct qti_can_buffer), + GFP_KERNEL); + if (!add_request) + return -ENOMEM; + + if (copy_from_user(add_request, ifr->ifr_data, + sizeof(struct qti_can_buffer))) { + devm_kfree(&spi->dev, add_request); + return -EFAULT; + } + + req = (struct spi_mosi *)tx_buf; + if (cmd == IOCTL_ENABLE_BUFFERING) + req->cmd = CMD_CAN_DATA_BUFF_ADD; + else + req->cmd = CMD_CAN_DATA_BUFF_REMOVE; + req->len = sizeof(struct qti_can_buffer); + req->seq = atomic_inc_return(&priv_data->msg_seq); + + enable_buffering = (struct qti_can_buffer *)req->data; + enable_buffering->can_if = add_request->can_if; + enable_buffering->mid = add_request->mid; + enable_buffering->mask = add_request->mask; + + if (priv_data->can_fw_cmd_timeout_req) { + priv_data->wait_cmd = req->cmd; + priv_data->cmd_result = -1; + reinit_completion(&priv_data->response_completion); + } + + ret = qti_can_do_spi_transaction(priv_data); + devm_kfree(&spi->dev, add_request); + mutex_unlock(&priv_data->spi_lock); + + if (ret == 0 && priv_data->can_fw_cmd_timeout_req) { + LOGDI("qti_can_data_buffering ready to wait for response\n"); + ret = wait_for_completion_interruptible_timeout( + &priv_data->response_completion, + msecs_to_jiffies(timeout)); + ret = priv_data->cmd_result; + } + return ret; +} + +static int qti_can_remove_all_buffering(struct net_device *netdev) +{ + char *tx_buf, *rx_buf; + int ret; + u32 timeout; + struct spi_mosi *req; + struct qti_can *priv_data; + struct qti_can_netdev_privdata *netdev_priv_data; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->qti_can; + timeout = priv_data->rem_all_buffering_timeout_ms; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_DATA_BUFF_REMOVE_ALL; + req->len = 0; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + if (priv_data->can_fw_cmd_timeout_req) { + priv_data->wait_cmd = req->cmd; + priv_data->cmd_result = -1; + reinit_completion(&priv_data->response_completion); + } + + ret = qti_can_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + if (ret == 0 && priv_data->can_fw_cmd_timeout_req) { + LOGDI("qti_can_remove_all_buffering wait for response\n"); + ret = wait_for_completion_interruptible_timeout( + &priv_data->response_completion, + msecs_to_jiffies(timeout)); + ret = priv_data->cmd_result; + } + + return ret; +} + +static int qti_can_frame_filter(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct can_filter_req *add_filter; + struct can_filter_req *filter_request; + struct qti_can *priv_data; + struct qti_can_netdev_privdata *netdev_priv_data; + struct spi_device *spi; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->qti_can; + spi = priv_data->spidev; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + if (!ifr) + return -EINVAL; + + filter_request = + devm_kzalloc(&spi->dev, sizeof(struct can_filter_req), + GFP_KERNEL); + if (!filter_request) + return -ENOMEM; + + if (copy_from_user(filter_request, ifr->ifr_data, + sizeof(struct can_filter_req))) { + devm_kfree(&spi->dev, filter_request); + return -EFAULT; + } + + req = (struct spi_mosi *)tx_buf; + if (cmd == IOCTL_ADD_FRAME_FILTER) + req->cmd = CMD_CAN_ADD_FILTER; + else + req->cmd = CMD_CAN_REMOVE_FILTER; + + req->len = sizeof(struct can_filter_req); + req->seq = atomic_inc_return(&priv_data->msg_seq); + + add_filter = (struct can_filter_req *)req->data; + add_filter->can_if = filter_request->can_if; + add_filter->mid = filter_request->mid; + add_filter->mask = filter_request->mask; + + ret = qti_can_do_spi_transaction(priv_data); + devm_kfree(&spi->dev, filter_request); + mutex_unlock(&priv_data->spi_lock); + return ret; +} + +static int qti_can_send_spi_locked(struct qti_can *priv_data, int cmd, int len, + u8 *data) +{ + char *tx_buf, *rx_buf; + struct spi_mosi *req; + int ret; + + LOGDI("qti_can_send_spi_locked\n"); + + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = cmd; + req->len = len; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + if (unlikely(len > 64)) + return -EINVAL; + memcpy(req->data, data, len); + + ret = qti_can_do_spi_transaction(priv_data); + return ret; +} + +static int qti_can_convert_ioctl_cmd_to_spi_cmd(int ioctl_cmd) +{ + switch (ioctl_cmd) { + case IOCTL_GET_FW_BR_VERSION: + return CMD_GET_FW_BR_VERSION; + case IOCTL_BEGIN_FIRMWARE_UPGRADE: + return CMD_BEGIN_FIRMWARE_UPGRADE; + case IOCTL_FIRMWARE_UPGRADE_DATA: + return CMD_FIRMWARE_UPGRADE_DATA; + case IOCTL_END_FIRMWARE_UPGRADE: + return CMD_END_FIRMWARE_UPGRADE; + case IOCTL_BEGIN_BOOT_ROM_UPGRADE: + return CMD_BEGIN_BOOT_ROM_UPGRADE; + case IOCTL_BOOT_ROM_UPGRADE_DATA: + return CMD_BOOT_ROM_UPGRADE_DATA; + case IOCTL_END_BOOT_ROM_UPGRADE: + return CMD_END_BOOT_ROM_UPGRADE; + case IOCTL_END_FW_UPDATE_FILE: + return CMD_END_FW_UPDATE_FILE; + } + return -EINVAL; +} + +static int qti_can_do_blocking_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + int spi_cmd, ret; + + struct qti_can *priv_data; + struct qti_can_netdev_privdata *netdev_priv_data; + struct qti_can_ioctl_req *ioctl_data = NULL; + struct spi_device *spi; + int len = 0; + u8 *data = NULL; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->qti_can; + spi = priv_data->spidev; + + spi_cmd = qti_can_convert_ioctl_cmd_to_spi_cmd(cmd); + LOGDI("qti_can_do_blocking_ioctl spi_cmd %x\n", spi_cmd); + if (spi_cmd < 0) { + LOGDE("qti_can_do_blocking_ioctl wrong command %d\n", cmd); + return spi_cmd; + } + + if (!ifr) + return -EINVAL; + + mutex_lock(&priv_data->spi_lock); + if (spi_cmd == CMD_FIRMWARE_UPGRADE_DATA || + spi_cmd == CMD_BOOT_ROM_UPGRADE_DATA) { + ioctl_data = + devm_kzalloc(&spi->dev, + sizeof(struct qti_can_ioctl_req), + GFP_KERNEL); + if (!ioctl_data) + return -ENOMEM; + + if (copy_from_user(ioctl_data, ifr->ifr_data, + sizeof(struct qti_can_ioctl_req))) { + devm_kfree(&spi->dev, ioctl_data); + return -EFAULT; + } + + /* Regular NULL check will fail here as ioctl_data is at + * some offset + */ + if ((void *)ioctl_data > (void *)0x100) { + len = ioctl_data->len; + data = ioctl_data->data; + } + } + LOGDI("qti_can_do_blocking_ioctl len %d\n", len); + + priv_data->wait_cmd = spi_cmd; + priv_data->cmd_result = -1; + reinit_completion(&priv_data->response_completion); + + ret = qti_can_send_spi_locked(priv_data, spi_cmd, len, data); + if (ioctl_data) + devm_kfree(&spi->dev, ioctl_data); + mutex_unlock(&priv_data->spi_lock); + + if (ret == 0) { + LOGDI("qti_can_do_blocking_ioctl ready to wait for response\n"); + wait_for_completion_interruptible_timeout( + &priv_data->response_completion, + 5 * HZ); + ret = priv_data->cmd_result; + } + return ret; +} + +static int qti_can_netdev_do_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + struct qti_can *priv_data; + struct qti_can_netdev_privdata *netdev_priv_data; + int *mode; + int ret = -EINVAL; + struct spi_device *spi; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->qti_can; + spi = priv_data->spidev; + LOGDI("qti_can_netdev_do_ioctl %x\n", cmd); + + switch (cmd) { + case IOCTL_RELEASE_CAN_BUFFER: + if (!ifr) + return -EINVAL; + + /* Regular NULL check will fail here as ioctl_data is at + * some offset + */ + if (ifr->ifr_data > (void __user *)IFR_DATA_OFFSET) { + mutex_lock(&priv_data->spi_lock); + mode = devm_kzalloc(&spi->dev, sizeof(int), GFP_KERNEL); + if (!mode) + return -ENOMEM; + if (copy_from_user(mode, ifr->ifr_data, sizeof(int))) { + devm_kfree(&spi->dev, mode); + return -EFAULT; + } + priv_data->driver_mode = *mode; + LOGDE("qti_can_driver_mode %d\n", + priv_data->driver_mode); + devm_kfree(&spi->dev, mode); + mutex_unlock(&priv_data->spi_lock); + } + qti_can_send_release_can_buffer_cmd(netdev); + ret = 0; + break; + case IOCTL_ENABLE_BUFFERING: + case IOCTL_DISABLE_BUFFERING: + qti_can_data_buffering(netdev, ifr, cmd); + ret = 0; + break; + case IOCTL_DISABLE_ALL_BUFFERING: + qti_can_remove_all_buffering(netdev); + ret = 0; + break; + case IOCTL_ADD_FRAME_FILTER: + case IOCTL_REMOVE_FRAME_FILTER: + qti_can_frame_filter(netdev, ifr, cmd); + ret = 0; + break; + case IOCTL_GET_FW_BR_VERSION: + case IOCTL_BEGIN_FIRMWARE_UPGRADE: + case IOCTL_FIRMWARE_UPGRADE_DATA: + case IOCTL_END_FIRMWARE_UPGRADE: + case IOCTL_BEGIN_BOOT_ROM_UPGRADE: + case IOCTL_BOOT_ROM_UPGRADE_DATA: + case IOCTL_END_BOOT_ROM_UPGRADE: + case IOCTL_END_FW_UPDATE_FILE: + ret = qti_can_do_blocking_ioctl(netdev, ifr, cmd); + break; + } + LOGDI("qti_can_netdev_do_ioctl ret %d\n", ret); + + return ret; +} + +static const struct net_device_ops qti_can_netdev_ops = { + .ndo_open = qti_can_netdev_open, + .ndo_stop = qti_can_netdev_close, + .ndo_start_xmit = qti_can_netdev_start_xmit, + .ndo_do_ioctl = qti_can_netdev_do_ioctl, +}; + +static int qti_can_create_netdev(struct spi_device *spi, + struct qti_can *priv_data, int index) +{ + struct net_device *netdev; + struct qti_can_netdev_privdata *netdev_priv_data; + + LOGDI("qti_can_create_netdev %d\n", index); + if (index < 0 || index >= priv_data->max_can_channels) { + LOGDE("qti_can_create_netdev wrong index %d\n", index); + return -EINVAL; + } + netdev = alloc_candev(sizeof(*netdev_priv_data), MAX_TX_BUFFERS); + if (!netdev) { + LOGDE("Couldn't alloc candev\n"); + return -ENOMEM; + } + + netdev->mtu = CANFD_MTU; + + netdev_priv_data = netdev_priv(netdev); + netdev_priv_data->qti_can = priv_data; + netdev_priv_data->netdev_index = index; + + priv_data->netdev[index] = netdev; + + netdev->netdev_ops = &qti_can_netdev_ops; + SET_NETDEV_DEV(netdev, &spi->dev); + netdev_priv_data->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES | + CAN_CTRLMODE_LISTENONLY; + if (priv_data->support_can_fd) + netdev_priv_data->can.ctrlmode_supported |= CAN_CTRLMODE_FD; + netdev_priv_data->can.bittiming_const = &qti_can_bittiming_const; + netdev_priv_data->can.data_bittiming_const = + &qti_can_data_bittiming_const; + netdev_priv_data->can.clock.freq = priv_data->clk_freq_mhz; + netdev_priv_data->can.do_set_bittiming = qti_can_set_bitrate; + + return 0; +} + +static struct qti_can *qti_can_create_priv_data(struct spi_device *spi) +{ + struct qti_can *priv_data; + int err; + struct device *dev; + + dev = &spi->dev; + priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) { + err = -ENOMEM; + return NULL; + } + spi_set_drvdata(spi, priv_data); + atomic_set(&priv_data->netif_queue_stop, 0); + priv_data->spidev = spi; + priv_data->assembly_buffer = kzalloc(RX_ASSEMBLY_BUFFER_SIZE, + GFP_KERNEL); + if (!priv_data->assembly_buffer) { + err = -ENOMEM; + goto cleanup_privdata; + } + + priv_data->tx_wq = alloc_workqueue("qti_can_tx_wq", 0, 0); + if (!priv_data->tx_wq) { + LOGDE("Couldn't alloc workqueue\n"); + err = -ENOMEM; + goto cleanup_privdata; + } + + priv_data->tx_buf = kzalloc(XFER_BUFFER_SIZE, + GFP_KERNEL); + priv_data->rx_buf = kzalloc(XFER_BUFFER_SIZE, + GFP_KERNEL); + if (!priv_data->tx_buf || !priv_data->rx_buf) { + LOGDE("Couldn't alloc tx or rx buffers\n"); + err = -ENOMEM; + goto cleanup_privdata; + } + priv_data->xfer_length = 0; + priv_data->driver_mode = DRIVER_MODE_RAW_FRAMES; + + mutex_init(&priv_data->spi_lock); + atomic_set(&priv_data->msg_seq, 0); + init_completion(&priv_data->response_completion); + return priv_data; + +cleanup_privdata: + if (priv_data) { + if (priv_data->tx_wq) + destroy_workqueue(priv_data->tx_wq); + kfree(priv_data->rx_buf); + kfree(priv_data->tx_buf); + kfree(priv_data->assembly_buffer); + kfree(priv_data); + } + return NULL; +} + +static const struct of_device_id qti_can_match_table[] = { + { .compatible = "qcom,nxp,mpc5746c" }, + { } +}; + +static int qti_can_probe(struct spi_device *spi) +{ + int err, retry = 0, query_err = -1, i; + struct qti_can *priv_data = NULL; + struct device *dev; + u32 irq_type; + + dev = &spi->dev; + dev_info(dev, "qti_can_probe"); + + err = spi_setup(spi); + if (err) { + dev_err(dev, "spi_setup failed: %d", err); + return err; + } + + priv_data = qti_can_create_priv_data(spi); + if (!priv_data) { + dev_err(dev, "Failed to create qti_can priv_data\n"); + err = -ENOMEM; + return err; + } + dev_info(dev, "qti_can_probe created priv_data"); + + err = of_property_read_u32(spi->dev.of_node, "qcom,clk-freq-mhz", + &priv_data->clk_freq_mhz); + if (err) { + LOGDE("DT property: qcom,clk-freq-hz not defined\n"); + return err; + } + + err = of_property_read_u32(spi->dev.of_node, "qcom,max-can-channels", + &priv_data->max_can_channels); + if (err) { + LOGDE("DT property: qcom,max-can-channels not defined\n"); + return err; + } + + err = of_property_read_u32(spi->dev.of_node, "qcom,bits-per-word", + &priv_data->bits_per_word); + if (err) + priv_data->bits_per_word = 16; + + err = of_property_read_u32(spi->dev.of_node, "qcom,reset-delay-msec", + &priv_data->reset_delay_msec); + if (err) + priv_data->reset_delay_msec = 1; + + priv_data->can_fw_cmd_timeout_req = + of_property_read_bool(spi->dev.of_node, + "qcom,can-fw-cmd-timeout-req"); + + err = of_property_read_u32(spi->dev.of_node, + "qcom,can-fw-cmd-timeout-ms", + &priv_data->can_fw_cmd_timeout_ms); + if (err) + priv_data->can_fw_cmd_timeout_ms = 0; + + err = of_property_read_u32(spi->dev.of_node, + "qcom,rem-all-buffering-timeout-ms", + &priv_data->rem_all_buffering_timeout_ms); + if (err) + priv_data->rem_all_buffering_timeout_ms = 0; + + priv_data->reset = of_get_named_gpio(spi->dev.of_node, + "qcom,reset-gpio", 0); + + if (gpio_is_valid(priv_data->reset)) { + err = gpio_request(priv_data->reset, "qti-can-reset"); + if (err < 0) { + LOGDE("failed to request gpio %d: %d\n", + priv_data->reset, err); + return err; + } + + gpio_direction_output(priv_data->reset, 0); + udelay(1); + gpio_direction_output(priv_data->reset, 1); + msleep(priv_data->reset_delay_msec); + } + + priv_data->support_can_fd = of_property_read_bool(spi->dev.of_node, + "qcom,support-can-fd"); + + if (of_device_is_compatible(spi->dev.of_node, "qcom,nxp,mpc5746c")) + qti_can_bittiming_const = flexcan_bittiming_const; + else if (of_device_is_compatible(spi->dev.of_node, + "qcom,renesas,rh850")) + qti_can_bittiming_const = rh850_bittiming_const; + + priv_data->netdev = kzalloc(sizeof(priv_data->netdev[0]) * + priv_data->max_can_channels, + GFP_KERNEL); + if (!priv_data->netdev) { + err = -ENOMEM; + return err; + } + + for (i = 0; i < priv_data->max_can_channels; i++) { + err = qti_can_create_netdev(spi, priv_data, i); + if (err) { + LOGDE("Failed to create CAN device: %d", err); + goto cleanup_candev; + } + + err = register_candev(priv_data->netdev[i]); + if (err) { + LOGDE("Failed to register CAN device: %d", err); + goto unregister_candev; + } + } + + irq_type = irq_get_trigger_type(spi->irq); + if (irq_type == IRQ_TYPE_NONE) + irq_type = IRQ_TYPE_EDGE_FALLING; + err = request_threaded_irq(spi->irq, NULL, qti_can_irq, + irq_type | IRQF_ONESHOT, + "qti-can", priv_data); + if (err) { + LOGDE("Failed to request irq: %d", err); + goto unregister_candev; + } + dev_info(dev, "Request irq %d ret %d\n", spi->irq, err); + + while ((query_err != 0) && (retry < QTI_CAN_FW_QUERY_RETRY_COUNT)) { + query_err = qti_can_query_firmware_version(priv_data); + priv_data->assembly_buffer_size = 0; + retry++; + } + + if (query_err) { + LOGDE("QTI CAN probe failed\n"); + err = -ENODEV; + goto free_irq; + } + return 0; + +free_irq: + free_irq(spi->irq, priv_data); +unregister_candev: + for (i = 0; i < priv_data->max_can_channels; i++) + unregister_candev(priv_data->netdev[i]); +cleanup_candev: + if (priv_data) { + for (i = 0; i < priv_data->max_can_channels; i++) { + if (priv_data->netdev[i]) + free_candev(priv_data->netdev[i]); + } + if (priv_data->tx_wq) + destroy_workqueue(priv_data->tx_wq); + kfree(priv_data->rx_buf); + kfree(priv_data->tx_buf); + kfree(priv_data->assembly_buffer); + kfree(priv_data->netdev); + kfree(priv_data); + } + return err; +} + +static int qti_can_remove(struct spi_device *spi) +{ + struct qti_can *priv_data = spi_get_drvdata(spi); + int i; + + LOGDI("qti_can_remove\n"); + for (i = 0; i < priv_data->max_can_channels; i++) { + unregister_candev(priv_data->netdev[i]); + free_candev(priv_data->netdev[i]); + } + destroy_workqueue(priv_data->tx_wq); + kfree(priv_data->rx_buf); + kfree(priv_data->tx_buf); + kfree(priv_data->assembly_buffer); + kfree(priv_data->netdev); + kfree(priv_data); + return 0; +} + +#ifdef CONFIG_PM +static int qti_can_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct qti_can *priv_data = spi_get_drvdata(spi); + u8 power_event = CMD_UPDATE_SUSPEND_EVENT; + + qti_can_notify_power_events(priv_data, power_event); + + enable_irq_wake(spi->irq); + return 0; +} + +static int qti_can_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct qti_can *priv_data = spi_get_drvdata(spi); + u8 power_event = CMD_UPDATE_RESUME_EVENT; + + disable_irq_wake(spi->irq); + qti_can_notify_power_events(priv_data, power_event); + return 0; +} + +static const struct dev_pm_ops qti_can_dev_pm_ops = { + .suspend = qti_can_suspend, + .resume = qti_can_resume, +}; +#endif + +static struct spi_driver qti_can_driver = { + .driver = { + .name = "qti-can", + .of_match_table = qti_can_match_table, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &qti_can_dev_pm_ops, +#endif + }, + .probe = qti_can_probe, + .remove = qti_can_remove, +}; +module_spi_driver(qti_can_driver); + +MODULE_DESCRIPTION("QTI CAN controller module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/can/spi/rh850.c b/drivers/net/can/spi/rh850.c new file mode 100644 index 000000000000..b32ae2ddd41b --- /dev/null +++ b/drivers/net/can/spi/rh850.c @@ -0,0 +1,1208 @@ +/* Copyright (c) 2015-2018, 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/interrupt.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/workqueue.h> +#include <linux/spi/spi.h> +#include <linux/can.h> +#include <linux/can/dev.h> +#include <linux/completion.h> +#include <linux/irq.h> + +#define DEBUG_RH850 0 +#if DEBUG_RH850 == 1 +#define LOGDI(...) dev_info(&priv_data->spidev->dev, __VA_ARGS__) +#define LOGNI(...) netdev_info(netdev, __VA_ARGS__) +#else +#define LOGDI(...) +#define LOGNI(...) +#endif +#define LOGDE(...) dev_err(&priv_data->spidev->dev, __VA_ARGS__) +#define LOGNE(...) netdev_err(netdev, __VA_ARGS__) + +#define MAX_TX_BUFFERS 1 +#define XFER_BUFFER_SIZE 64 +#define RX_ASSEMBLY_BUFFER_SIZE 128 +#define RH850_CLOCK 16000000 +#define RH850_MAX_CHANNELS 4 +#define DRIVER_MODE_RAW_FRAMES 0 +#define DRIVER_MODE_PROPERTIES 1 +#define DRIVER_MODE_AMB 2 + +struct rh850_can { + struct net_device *netdev[RH850_MAX_CHANNELS]; + struct spi_device *spidev; + + struct mutex spi_lock; /* SPI device lock */ + + struct workqueue_struct *tx_wq; + char *tx_buf, *rx_buf; + int xfer_length; + atomic_t msg_seq; + + char *assembly_buffer; + u8 assembly_buffer_size; + atomic_t netif_queue_stop; + struct completion response_completion; + int wait_cmd; + int cmd_result; + int driver_mode; +}; + +struct rh850_netdev_privdata { + struct can_priv can; + struct rh850_can *rh850_can; + u8 netdev_index; +}; + +struct rh850_tx_work { + struct work_struct work; + struct sk_buff *skb; + struct net_device *netdev; +}; + +/* Message definitions */ +struct spi_mosi { /* TLV for MOSI line */ + u8 cmd; + u8 len; + u16 seq; + u8 data[]; +} __packed; + +struct spi_miso { /* TLV for MISO line */ + u8 cmd; + u8 len; + u16 seq; /* should match seq field from request, or 0 for unsols */ + u8 data[]; +} __packed; + +#define CMD_GET_FW_VERSION 0x81 +#define CMD_CAN_SEND_FRAME 0x82 +#define CMD_CAN_ADD_FILTER 0x83 +#define CMD_CAN_REMOVE_FILTER 0x84 +#define CMD_CAN_RECEIVE_FRAME 0x85 +#define CMD_CAN_CONFIG_BIT_TIMING 0x86 + +#define CMD_CAN_DATA_BUFF_ADD 0x87 +#define CMD_CAN_DATA_BUFF_REMOVE 0X88 +#define CMD_CAN_RELEASE_BUFFER 0x89 +#define CMD_CAN_DATA_BUFF_REMOVE_ALL 0x8A +#define CMD_PROPERTY_WRITE 0x8B +#define CMD_PROPERTY_READ 0x8C + +#define CMD_GET_FW_BR_VERSION 0x95 +#define CMD_BEGIN_FIRMWARE_UPGRADE 0x96 +#define CMD_FIRMWARE_UPGRADE_DATA 0x97 +#define CMD_END_FIRMWARE_UPGRADE 0x98 +#define CMD_BEGIN_BOOT_ROM_UPGRADE 0x99 +#define CMD_BOOT_ROM_UPGRADE_DATA 0x9A +#define CMD_END_BOOT_ROM_UPGRADE 0x9B + +#define IOCTL_RELEASE_CAN_BUFFER (SIOCDEVPRIVATE + 0) +#define IOCTL_ENABLE_BUFFERING (SIOCDEVPRIVATE + 1) +#define IOCTL_ADD_FRAME_FILTER (SIOCDEVPRIVATE + 2) +#define IOCTL_REMOVE_FRAME_FILTER (SIOCDEVPRIVATE + 3) +#define IOCTL_DISABLE_BUFFERING (SIOCDEVPRIVATE + 5) +#define IOCTL_DISABLE_ALL_BUFFERING (SIOCDEVPRIVATE + 6) +#define IOCTL_GET_FW_BR_VERSION (SIOCDEVPRIVATE + 7) +#define IOCTL_BEGIN_FIRMWARE_UPGRADE (SIOCDEVPRIVATE + 8) +#define IOCTL_FIRMWARE_UPGRADE_DATA (SIOCDEVPRIVATE + 9) +#define IOCTL_END_FIRMWARE_UPGRADE (SIOCDEVPRIVATE + 10) +#define IOCTL_BEGIN_BOOT_ROM_UPGRADE (SIOCDEVPRIVATE + 11) +#define IOCTL_BOOT_ROM_UPGRADE_DATA (SIOCDEVPRIVATE + 12) +#define IOCTL_END_BOOT_ROM_UPGRADE (SIOCDEVPRIVATE + 13) + +struct can_fw_resp { + u8 maj; + u8 min; + u8 ver[32]; +} __packed; + +struct can_write_req { + u8 can_if; + u32 mid; + u8 dlc; + u8 data[]; +} __packed; + +struct can_write_resp { + u8 err; +} __packed; + +struct can_add_filter_req { + u8 can_if; + u32 mid; + u32 mask; +} __packed; + +struct can_add_filter_resp { + u8 err; +} __packed; + +struct can_remove_filter_req { + u8 can_if; + u32 mid; + u32 mask; +} __packed; + +struct can_receive_frame { + u8 can_if; + u32 ts; + u32 mid; + u8 dlc; + u8 data[]; +} __packed; + +struct can_config_bit_timing { + u8 can_if; + u32 brp; + u32 tseg1; + u32 tseg2; + u32 sjw; +} __packed; + +struct vehicle_property { + int id; + u64 ts; + int zone; + int val_type; + u32 data_len; + union { + u8 bval; + int val; + int val_arr[4]; + float f_value; + float float_arr[4]; + u8 str[36]; + }; +} __packed; + +/* IOCTL messages */ +struct rh850_release_can_buffer { + u8 enable; +} __packed; + +struct rh850_add_can_buffer { + u8 can_if; + u32 mid; + u32 mask; +} __packed; + +struct rh850_delete_can_buffer { + u8 can_if; + u32 mid; + u32 mask; +} __packed; + +struct can_fw_br_resp { + u8 maj; + u8 min; + u8 ver[32]; + u8 br_maj; + u8 br_min; + u8 curr_exec_mode; +} __packed; + +struct rh850_ioctl_req { + u8 len; + u8 data[]; +} __packed; + +static struct can_bittiming_const rh850_bittiming_const = { + .name = "rh850", + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 16, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 70, + .brp_inc = 1, +}; + +static struct can_bittiming_const rh850_data_bittiming_const = { + .name = "rh850", + .tseg1_min = 1, + .tseg1_max = 16, + .tseg2_min = 1, + .tseg2_max = 16, + .sjw_max = 4, + .brp_min = 1, + .brp_max = 70, + .brp_inc = 1, +}; + +static int rh850_rx_message(struct rh850_can *priv_data); + +static irqreturn_t rh850_irq(int irq, void *priv) +{ + struct rh850_can *priv_data = priv; + + LOGDI("rh850_irq\n"); + rh850_rx_message(priv_data); + return IRQ_HANDLED; +} + +static void rh850_receive_frame(struct rh850_can *priv_data, + struct can_receive_frame *frame) +{ + struct can_frame *cf; + struct sk_buff *skb; + struct skb_shared_hwtstamps *skt; + struct timeval tv; + static int msec; + struct net_device *netdev; + int i; + if (frame->can_if >= RH850_MAX_CHANNELS) { + LOGDE("rh850 rcv error. Channel is %d\n", frame->can_if); + return; + } + netdev = priv_data->netdev[frame->can_if]; + skb = alloc_can_skb(netdev, &cf); + if (!skb) { + LOGDE("skb alloc failed. frame->can_if %d\n", frame->can_if); + return; + } + + LOGDI("rcv frame %d %d %x %d %x %x %x %x %x %x %x %x\n", + frame->can_if, frame->ts, frame->mid, frame->dlc, frame->data[0], + frame->data[1], frame->data[2], frame->data[3], frame->data[4], + frame->data[5], frame->data[6], frame->data[7]); + cf->can_id = le32_to_cpu(frame->mid); + cf->can_dlc = get_can_dlc(frame->dlc); + + for (i = 0; i < cf->can_dlc; i++) + cf->data[i] = frame->data[i]; + + msec = le32_to_cpu(frame->ts); + tv.tv_sec = msec / 1000; + tv.tv_usec = (msec - tv.tv_sec * 1000) * 1000; + skt = skb_hwtstamps(skb); + skt->hwtstamp = timeval_to_ktime(tv); + LOGDI(" hwtstamp %lld\n", ktime_to_ms(skt->hwtstamp)); + skb->tstamp = timeval_to_ktime(tv); + netif_rx(skb); + netdev->stats.rx_packets++; +} + +static void rh850_receive_property(struct rh850_can *priv_data, + struct vehicle_property *property) +{ + struct canfd_frame *cfd; + u8 *p; + struct sk_buff *skb; + struct skb_shared_hwtstamps *skt; + struct timeval tv; + static u64 nanosec; + struct net_device *netdev; + int i; + + /* can0 as the channel with properties */ + netdev = priv_data->netdev[0]; + skb = alloc_canfd_skb(netdev, &cfd); + if (!skb) { + LOGDE("skb alloc failed. frame->can_if %d\n", 0); + return; + } + + LOGDI("rcv property:0x%x data:%2x %2x %2x %2x", + property->id, property->str[0], property->str[1], + property->str[2], property->str[3]); + cfd->can_id = 0x00; + cfd->len = sizeof(struct vehicle_property); + + p = (u8 *)property; + for (i = 0; i < cfd->len; i++) + cfd->data[i] = p[i]; + + nanosec = le64_to_cpu(property->ts); + tv.tv_sec = (int)(nanosec / 1000000000); + tv.tv_usec = (int)(nanosec - (u64)tv.tv_sec * 1000000000) / 1000; + skt = skb_hwtstamps(skb); + skt->hwtstamp = timeval_to_ktime(tv); + LOGDI(" hwtstamp %lld\n", ktime_to_ms(skt->hwtstamp)); + skb->tstamp = timeval_to_ktime(tv); + netif_rx(skb); + netdev->stats.rx_packets++; +} + +static int rh850_process_response(struct rh850_can *priv_data, + struct spi_miso *resp, int length) +{ + int ret = 0; + LOGDI("<%x %2d [%d]\n", resp->cmd, resp->len, resp->seq); + if (resp->cmd == CMD_CAN_RECEIVE_FRAME) { + struct can_receive_frame *frame = + (struct can_receive_frame *)&resp->data; + if (resp->len > length) { + LOGDE("Error. This should never happen\n"); + LOGDE("process_response: Saving %d bytes\n", + length); + memcpy(priv_data->assembly_buffer, (char *)resp, + length); + priv_data->assembly_buffer_size = length; + } else { + rh850_receive_frame(priv_data, frame); + } + } else if (resp->cmd == CMD_PROPERTY_READ) { + struct vehicle_property *property = + (struct vehicle_property *)&resp->data; + if (resp->len > length) { + LOGDE("Error. This should never happen\n"); + LOGDE("process_response: Saving %d bytes\n", + length); + memcpy(priv_data->assembly_buffer, (char *)resp, + length); + priv_data->assembly_buffer_size = length; + } else { + rh850_receive_property(priv_data, property); + } + } else if (resp->cmd == CMD_GET_FW_VERSION) { + struct can_fw_resp *fw_resp = (struct can_fw_resp *)resp->data; + dev_info(&priv_data->spidev->dev, "fw %d.%d", + fw_resp->maj, fw_resp->min); + dev_info(&priv_data->spidev->dev, "fw string %s", + fw_resp->ver); + } else if (resp->cmd == CMD_GET_FW_BR_VERSION) { + struct can_fw_br_resp *fw_resp = + (struct can_fw_br_resp *)resp->data; + + dev_info(&priv_data->spidev->dev, "fw_can %d.%d", + fw_resp->maj, fw_resp->min); + dev_info(&priv_data->spidev->dev, "fw string %s", + fw_resp->ver); + dev_info(&priv_data->spidev->dev, "fw_br %d.%d exec_mode %d", + fw_resp->br_maj, fw_resp->br_min, + fw_resp->curr_exec_mode); + ret = fw_resp->curr_exec_mode << 28; + ret |= (fw_resp->br_maj & 0xF) << 24; + ret |= (fw_resp->br_min & 0xFF) << 16; + ret |= (fw_resp->maj & 0xF) << 8; + ret |= (fw_resp->min & 0xFF); + } + + if (resp->cmd == priv_data->wait_cmd) { + priv_data->cmd_result = ret; + complete(&priv_data->response_completion); + } + return ret; +} + +static int rh850_process_rx(struct rh850_can *priv_data, char *rx_buf) +{ + struct spi_miso *resp; + int length_processed = 0, actual_length = priv_data->xfer_length; + int ret = 0; + + while (length_processed < actual_length) { + int length_left = actual_length - length_processed; + int length = 0; /* length of consumed chunk */ + void *data; + + if (priv_data->assembly_buffer_size > 0) { + LOGDI("callback: Reassembling %d bytes\n", + priv_data->assembly_buffer_size); + /* should copy just 1 byte instead, since cmd should */ + /* already been copied as being first byte */ + memcpy(priv_data->assembly_buffer + + priv_data->assembly_buffer_size, + rx_buf, 2); + data = priv_data->assembly_buffer; + resp = (struct spi_miso *)data; + length = resp->len + sizeof(*resp) + - priv_data->assembly_buffer_size; + if (length > 0) + memcpy(priv_data->assembly_buffer + + priv_data->assembly_buffer_size, + rx_buf, length); + length_left += priv_data->assembly_buffer_size; + priv_data->assembly_buffer_size = 0; + } else { + data = rx_buf + length_processed; + resp = (struct spi_miso *)data; + if (resp->cmd == 0) { + /* special case. ignore cmd==0 */ + length_processed += 1; + continue; + } + length = resp->len + sizeof(struct spi_miso); + } + LOGDI("processing. p %d -> l %d (t %d)\n", + length_processed, length_left, priv_data->xfer_length); + length_processed += length; + if (length_left >= sizeof(*resp) && + resp->len + sizeof(*resp) <= length_left) { + struct spi_miso *resp = + (struct spi_miso *)data; + ret = rh850_process_response(priv_data, resp, + length_left); + } else if (length_left > 0) { + /* Not full message. Store however much we have for */ + /* later assembly */ + LOGDI("callback: Storing %d bytes of response\n", + length_left); + memcpy(priv_data->assembly_buffer, data, length_left); + priv_data->assembly_buffer_size = length_left; + break; + } + } + return ret; +} + +static int rh850_do_spi_transaction(struct rh850_can *priv_data) +{ + struct spi_device *spi; + struct spi_transfer *xfer; + struct spi_message *msg; + int ret; + + spi = priv_data->spidev; + xfer = kzalloc(sizeof(*xfer), GFP_KERNEL); + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (xfer == 0 || msg == 0) + return -ENOMEM; + LOGDI(">%x %2d [%d]\n", priv_data->tx_buf[0], + priv_data->tx_buf[1], priv_data->tx_buf[2]); + spi_message_init(msg); + spi_message_add_tail(xfer, msg); + xfer->tx_buf = priv_data->tx_buf; + xfer->rx_buf = priv_data->rx_buf; + xfer->len = priv_data->xfer_length; + ret = spi_sync(spi, msg); + LOGDI("spi_sync ret %d data %x %x %x %x %x %x %x %x\n", ret, + priv_data->rx_buf[0], priv_data->rx_buf[1], priv_data->rx_buf[2], + priv_data->rx_buf[3], priv_data->rx_buf[4], priv_data->rx_buf[5], + priv_data->rx_buf[6], priv_data->rx_buf[7]); + if (ret == 0) + ret = rh850_process_rx(priv_data, priv_data->rx_buf); + kfree(msg); + kfree(xfer); + return ret; +} + +static int rh850_rx_message(struct rh850_can *priv_data) +{ + char *tx_buf, *rx_buf; + int ret; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + ret = rh850_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int rh850_query_firmware_version(struct rh850_can *priv_data) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_GET_FW_VERSION; + req->len = 0; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + ret = rh850_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int rh850_set_bitrate(struct net_device *netdev) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct can_config_bit_timing *req_d; + struct rh850_can *priv_data; + struct can_priv *priv = netdev_priv(netdev); + struct rh850_netdev_privdata *rh850_priv; + + rh850_priv = netdev_priv(netdev); + priv_data = rh850_priv->rh850_can; + + netdev_info(netdev, "ch%i, bitrate setting>%i", + rh850_priv->netdev_index, priv->bittiming.bitrate); + LOGNI("sjw>%i brp>%i ph_sg1>%i ph_sg2>%i smpl_pt>%i tq>%i pr_seg>%i", + priv->bittiming.sjw, priv->bittiming.brp, + priv->bittiming.phase_seg1, + priv->bittiming.phase_seg2, + priv->bittiming.sample_point, + priv->bittiming.tq, priv->bittiming.prop_seg); + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_CONFIG_BIT_TIMING; + req->len = sizeof(struct can_config_bit_timing); + req->seq = atomic_inc_return(&priv_data->msg_seq); + req_d = (struct can_config_bit_timing *)req->data; + req_d->can_if = rh850_priv->netdev_index; + req_d->brp = priv->bittiming.brp; + req_d->tseg1 = priv->bittiming.phase_seg1 + priv->bittiming.prop_seg; + req_d->tseg2 = priv->bittiming.phase_seg2; + req_d->sjw = priv->bittiming.sjw; + + ret = rh850_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int rh850_can_write(struct rh850_can *priv_data, + int can_channel, struct canfd_frame *cf) +{ + char *tx_buf, *rx_buf; + int ret, i; + struct spi_mosi *req; + struct can_write_req *req_d; + struct net_device *netdev; + + if (can_channel < 0 || can_channel >= RH850_MAX_CHANNELS) { + LOGDE("rh850_can_write error. Channel is %d\n", can_channel); + return -EINVAL; + } + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + if (priv_data->driver_mode == DRIVER_MODE_RAW_FRAMES) { + req->cmd = CMD_CAN_SEND_FRAME; + req->len = sizeof(struct can_write_req) + 8; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + req_d = (struct can_write_req *)req->data; + req_d->can_if = can_channel; + req_d->mid = cf->can_id; + req_d->dlc = cf->len; + + for (i = 0; i < cf->len; i++) + req_d->data[i] = cf->data[i]; + } else if (priv_data->driver_mode == DRIVER_MODE_PROPERTIES || + priv_data->driver_mode == DRIVER_MODE_AMB) { + req->cmd = CMD_PROPERTY_WRITE; + req->len = sizeof(struct vehicle_property); + req->seq = atomic_inc_return(&priv_data->msg_seq); + for (i = 0; i < cf->len; i++) + req->data[i] = cf->data[i]; + } else { + LOGDE("rh850_can_write: wrong driver mode %i", + priv_data->driver_mode); + } + + ret = rh850_do_spi_transaction(priv_data); + netdev = priv_data->netdev[can_channel]; + netdev->stats.tx_packets++; + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int rh850_netdev_open(struct net_device *netdev) +{ + int err; + + LOGNI("Open"); + err = open_candev(netdev); + if (err) + return err; + + netif_start_queue(netdev); + + return 0; +} + +static int rh850_netdev_close(struct net_device *netdev) +{ + LOGNI("Close"); + + netif_stop_queue(netdev); + close_candev(netdev); + return 0; +} + +static void rh850_send_can_frame(struct work_struct *ws) +{ + struct rh850_tx_work *tx_work; + struct canfd_frame *cf; + struct rh850_can *priv_data; + struct net_device *netdev; + struct rh850_netdev_privdata *netdev_priv_data; + int can_channel; + + tx_work = container_of(ws, struct rh850_tx_work, work); + netdev = tx_work->netdev; + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->rh850_can; + can_channel = netdev_priv_data->netdev_index; + LOGDI("send_can_frame ws %p\n", ws); + LOGDI("send_can_frame tx %p\n", tx_work); + + cf = (struct canfd_frame *)tx_work->skb->data; + rh850_can_write(priv_data, can_channel, cf); + + dev_kfree_skb(tx_work->skb); + kfree(tx_work); +} + +static netdev_tx_t rh850_netdev_start_xmit( + struct sk_buff *skb, struct net_device *netdev) +{ + struct rh850_netdev_privdata *netdev_priv_data = netdev_priv(netdev); + struct rh850_can *priv_data = netdev_priv_data->rh850_can; + struct rh850_tx_work *tx_work; + + LOGNI("netdev_start_xmit"); + if (can_dropped_invalid_skb(netdev, skb)) { + LOGNE("Dropping invalid can frame\n"); + return NETDEV_TX_OK; + } + tx_work = kzalloc(sizeof(*tx_work), GFP_ATOMIC); + if (tx_work == 0) + return NETDEV_TX_OK; + INIT_WORK(&tx_work->work, rh850_send_can_frame); + tx_work->netdev = netdev; + tx_work->skb = skb; + queue_work(priv_data->tx_wq, &tx_work->work); + + return NETDEV_TX_OK; +} + +static int rh850_send_release_can_buffer_cmd(struct net_device *netdev) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct rh850_can *priv_data; + struct rh850_netdev_privdata *netdev_priv_data; + int *mode; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->rh850_can; + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_RELEASE_BUFFER; + req->len = sizeof(int); + req->seq = atomic_inc_return(&priv_data->msg_seq); + mode = (int *)req->data; + *mode = priv_data->driver_mode; + + ret = rh850_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int rh850_data_buffering(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct rh850_add_can_buffer *enable_buffering; + struct rh850_add_can_buffer *add_request; + struct rh850_can *priv_data; + struct rh850_netdev_privdata *netdev_priv_data; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->rh850_can; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + add_request = ifr->ifr_data; + req = (struct spi_mosi *)tx_buf; + + if (cmd == IOCTL_ENABLE_BUFFERING) + req->cmd = CMD_CAN_DATA_BUFF_ADD; + else + req->cmd = CMD_CAN_DATA_BUFF_REMOVE; + + req->len = sizeof(struct rh850_add_can_buffer); + req->seq = atomic_inc_return(&priv_data->msg_seq); + + enable_buffering = (struct rh850_add_can_buffer *)req->data; + enable_buffering->can_if = add_request->can_if; + enable_buffering->mid = add_request->mid; + enable_buffering->mask = add_request->mask; + + ret = rh850_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int rh850_remove_all_buffering(struct net_device *netdev) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct rh850_can *priv_data; + struct rh850_netdev_privdata *netdev_priv_data; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->rh850_can; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = CMD_CAN_DATA_BUFF_REMOVE_ALL; + req->len = 0; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + ret = rh850_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int rh850_frame_filter(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + char *tx_buf, *rx_buf; + int ret; + struct spi_mosi *req; + struct can_add_filter_req *add_filter; + struct can_add_filter_req *filter_request; + struct rh850_can *priv_data; + struct rh850_netdev_privdata *netdev_priv_data; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->rh850_can; + + mutex_lock(&priv_data->spi_lock); + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + filter_request = ifr->ifr_data; + req = (struct spi_mosi *)tx_buf; + + if (cmd == IOCTL_ADD_FRAME_FILTER) + req->cmd = CMD_CAN_ADD_FILTER; + else + req->cmd = CMD_CAN_REMOVE_FILTER; + + req->len = sizeof(struct can_add_filter_req); + req->seq = atomic_inc_return(&priv_data->msg_seq); + + add_filter = (struct can_add_filter_req *)req->data; + add_filter->can_if = filter_request->can_if; + add_filter->mid = filter_request->mid; + add_filter->mask = filter_request->mask; + + ret = rh850_do_spi_transaction(priv_data); + mutex_unlock(&priv_data->spi_lock); + + return ret; +} + +static int rh850_send_spi_locked(struct rh850_can *priv_data, int cmd, int len, + u8 *data) +{ + char *tx_buf, *rx_buf; + struct spi_mosi *req; + int ret; + + LOGDI("rh850_send_spi_locked\n"); + + tx_buf = priv_data->tx_buf; + rx_buf = priv_data->rx_buf; + memset(tx_buf, 0, XFER_BUFFER_SIZE); + memset(rx_buf, 0, XFER_BUFFER_SIZE); + priv_data->xfer_length = XFER_BUFFER_SIZE; + + req = (struct spi_mosi *)tx_buf; + req->cmd = cmd; + req->len = len; + req->seq = atomic_inc_return(&priv_data->msg_seq); + + if (unlikely(len > 64)) + return -EINVAL; + memcpy(req->data, data, len); + + ret = rh850_do_spi_transaction(priv_data); + return ret; +} + +static int rh850_convert_ioctl_cmd_to_spi_cmd(int ioctl_cmd) +{ + switch (ioctl_cmd) { + case IOCTL_GET_FW_BR_VERSION: + return CMD_GET_FW_BR_VERSION; + case IOCTL_BEGIN_FIRMWARE_UPGRADE: + return CMD_BEGIN_FIRMWARE_UPGRADE; + case IOCTL_FIRMWARE_UPGRADE_DATA: + return CMD_FIRMWARE_UPGRADE_DATA; + case IOCTL_END_FIRMWARE_UPGRADE: + return CMD_END_FIRMWARE_UPGRADE; + case IOCTL_BEGIN_BOOT_ROM_UPGRADE: + return CMD_BEGIN_BOOT_ROM_UPGRADE; + case IOCTL_BOOT_ROM_UPGRADE_DATA: + return CMD_BOOT_ROM_UPGRADE_DATA; + case IOCTL_END_BOOT_ROM_UPGRADE: + return CMD_END_BOOT_ROM_UPGRADE; + } + return -EINVAL; +} + +static int rh850_do_blocking_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + int spi_cmd, ret; + + struct rh850_can *priv_data; + struct rh850_netdev_privdata *netdev_priv_data; + struct rh850_ioctl_req *ioctl_data; + int len = 0; + u8 *data = NULL; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->rh850_can; + + spi_cmd = rh850_convert_ioctl_cmd_to_spi_cmd(cmd); + LOGDI("rh850_do_blocking_ioctl spi_cmd %x\n", spi_cmd); + if (spi_cmd < 0) { + LOGDE("rh850_do_blocking_ioctl wrong command %d\n", cmd); + return spi_cmd; + } + if (!ifr) + return -EINVAL; + ioctl_data = ifr->ifr_data; + /* Regular NULL check fails here as ioctl_data is at some offset */ + if ((void *)ioctl_data > (void *)0x100) { + len = ioctl_data->len; + data = ioctl_data->data; + } + LOGDI("rh850_do_blocking_ioctl len %d\n", len); + mutex_lock(&priv_data->spi_lock); + + priv_data->wait_cmd = spi_cmd; + priv_data->cmd_result = -1; + reinit_completion(&priv_data->response_completion); + + ret = rh850_send_spi_locked(priv_data, spi_cmd, len, data); + mutex_unlock(&priv_data->spi_lock); + + if (ret == 0) { + LOGDI("rh850_do_blocking_ioctl ready to wait for response\n"); + wait_for_completion_interruptible_timeout( + &priv_data->response_completion, 5 * HZ); + ret = priv_data->cmd_result; + } + return ret; +} + +static int rh850_netdev_do_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + struct rh850_can *priv_data; + struct rh850_netdev_privdata *netdev_priv_data; + int *mode; + int ret = -EINVAL; + + netdev_priv_data = netdev_priv(netdev); + priv_data = netdev_priv_data->rh850_can; + LOGDI("rh850_netdev_do_ioctl %x\n", cmd); + + switch (cmd) { + case IOCTL_RELEASE_CAN_BUFFER: + if (ifr->ifr_data > (void *)0x100) { + mode = ifr->ifr_data; + priv_data->driver_mode = *mode; + } + LOGDE("rh850_driver_mode %d\n", priv_data->driver_mode); + rh850_send_release_can_buffer_cmd(netdev); + ret = 0; + break; + case IOCTL_ENABLE_BUFFERING: + case IOCTL_DISABLE_BUFFERING: + rh850_data_buffering(netdev, ifr, cmd); + ret = 0; + break; + case IOCTL_DISABLE_ALL_BUFFERING: + rh850_remove_all_buffering(netdev); + ret = 0; + break; + case IOCTL_ADD_FRAME_FILTER: + case IOCTL_REMOVE_FRAME_FILTER: + rh850_frame_filter(netdev, ifr, cmd); + ret = 0; + break; + case IOCTL_GET_FW_BR_VERSION: + case IOCTL_BEGIN_FIRMWARE_UPGRADE: + case IOCTL_FIRMWARE_UPGRADE_DATA: + case IOCTL_END_FIRMWARE_UPGRADE: + case IOCTL_BEGIN_BOOT_ROM_UPGRADE: + case IOCTL_BOOT_ROM_UPGRADE_DATA: + case IOCTL_END_BOOT_ROM_UPGRADE: + ret = rh850_do_blocking_ioctl(netdev, ifr, cmd); + break; + } + LOGDI("rh850_netdev_do_ioctl ret %d\n", ret); + + return ret; +} + +static const struct net_device_ops rh850_netdev_ops = { + .ndo_open = rh850_netdev_open, + .ndo_stop = rh850_netdev_close, + .ndo_start_xmit = rh850_netdev_start_xmit, + .ndo_do_ioctl = rh850_netdev_do_ioctl, +}; + +static int rh850_create_netdev(struct spi_device *spi, + struct rh850_can *priv_data, int index) +{ + struct net_device *netdev; + struct rh850_netdev_privdata *netdev_priv_data; + + LOGDI("rh850_create_netdev %d\n", index); + if (index < 0 || index >= RH850_MAX_CHANNELS) { + LOGDE("rh850_create_netdev wrong index %d\n", index); + return -EINVAL; + } + netdev = alloc_candev(sizeof(*netdev_priv_data), MAX_TX_BUFFERS); + if (!netdev) { + LOGDE("Couldn't alloc candev\n"); + return -ENOMEM; + } + + netdev->mtu = CANFD_MTU; + + netdev_priv_data = netdev_priv(netdev); + netdev_priv_data->rh850_can = priv_data; + netdev_priv_data->netdev_index = index; + + priv_data->netdev[index] = netdev; + + netdev->netdev_ops = &rh850_netdev_ops; + SET_NETDEV_DEV(netdev, &spi->dev); + netdev_priv_data->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES | + CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_FD; + netdev_priv_data->can.bittiming_const = &rh850_bittiming_const; + netdev_priv_data->can.data_bittiming_const = + &rh850_data_bittiming_const; + netdev_priv_data->can.clock.freq = RH850_CLOCK; + netdev_priv_data->can.do_set_bittiming = rh850_set_bitrate; + + return 0; +} + +static struct rh850_can *rh850_create_priv_data(struct spi_device *spi) +{ + struct rh850_can *priv_data; + int err; + struct device *dev; + + dev = &spi->dev; + priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) { + dev_err(dev, "Couldn't alloc rh850_can\n"); + return 0; + } + spi_set_drvdata(spi, priv_data); + atomic_set(&priv_data->netif_queue_stop, 0); + priv_data->spidev = spi; + priv_data->assembly_buffer = kzalloc(RX_ASSEMBLY_BUFFER_SIZE, + GFP_KERNEL); + if (!priv_data->assembly_buffer) { + err = -ENOMEM; + goto cleanup_privdata; + } + + priv_data->tx_wq = alloc_workqueue("rh850_tx_wq", 0, 0); + if (!priv_data->tx_wq) { + dev_err(dev, "Couldn't alloc workqueue\n"); + err = -ENOMEM; + goto cleanup_privdata; + } + + priv_data->tx_buf = kzalloc(XFER_BUFFER_SIZE, GFP_KERNEL); + priv_data->rx_buf = kzalloc(XFER_BUFFER_SIZE, GFP_KERNEL); + if (!priv_data->tx_buf || !priv_data->rx_buf) { + dev_err(dev, "Couldn't alloc tx or rx buffers\n"); + err = -ENOMEM; + goto cleanup_privdata; + } + priv_data->xfer_length = 0; + priv_data->driver_mode = DRIVER_MODE_RAW_FRAMES; + + mutex_init(&priv_data->spi_lock); + atomic_set(&priv_data->msg_seq, 0); + init_completion(&priv_data->response_completion); + return priv_data; + +cleanup_privdata: + if (priv_data) { + if (priv_data->tx_wq) + destroy_workqueue(priv_data->tx_wq); + kfree(priv_data->rx_buf); + kfree(priv_data->tx_buf); + kfree(priv_data->assembly_buffer); + kfree(priv_data); + } + return 0; +} + +static int rh850_probe(struct spi_device *spi) +{ + int err, i; + struct rh850_can *priv_data; + struct device *dev; + u32 irq_type; + + dev = &spi->dev; + dev_info(dev, "rh850_probe"); + + err = spi_setup(spi); + if (err) { + dev_err(dev, "spi_setup failed: %d", err); + return err; + } + + priv_data = rh850_create_priv_data(spi); + if (!priv_data) { + dev_err(dev, "Failed to create rh850_can priv_data\n"); + err = -ENOMEM; + return err; + } + dev_info(dev, "rh850_probe created priv_data"); + for (i = 0; i < RH850_MAX_CHANNELS; i++) { + err = rh850_create_netdev(spi, priv_data, i); + if (err) { + dev_err(dev, "Failed to create CAN device: %d", err); + goto cleanup_candev; + } + + err = register_candev(priv_data->netdev[i]); + if (err) { + dev_err(dev, "Failed to register CAN device: %d", err); + goto unregister_candev; + } + } + + irq_type = irq_get_trigger_type(spi->irq); + if (irq_type == IRQ_TYPE_NONE) + irq_type = IRQ_TYPE_EDGE_FALLING; + err = request_threaded_irq(spi->irq, NULL, rh850_irq, + irq_type | IRQF_ONESHOT, + "rh850", priv_data); + if (err) { + dev_err(dev, "Failed to request irq: %d", err); + goto unregister_candev; + } + dev_info(dev, "Request irq %d ret %d\n", spi->irq, err); + + rh850_query_firmware_version(priv_data); + return 0; + +unregister_candev: + for (i = 0; i < RH850_MAX_CHANNELS; i++) + unregister_candev(priv_data->netdev[i]); +cleanup_candev: + if (priv_data) { + for (i = 0; i < RH850_MAX_CHANNELS; i++) { + if (priv_data->netdev[i]) + free_candev(priv_data->netdev[i]); + } + if (priv_data->tx_wq) + destroy_workqueue(priv_data->tx_wq); + kfree(priv_data->rx_buf); + kfree(priv_data->tx_buf); + kfree(priv_data->assembly_buffer); + kfree(priv_data); + } + return err; +} + +static int rh850_remove(struct spi_device *spi) +{ + struct rh850_can *priv_data = spi_get_drvdata(spi); + int i; + + LOGDI("rh850_remove\n"); + for (i = 0; i < RH850_MAX_CHANNELS; i++) { + unregister_candev(priv_data->netdev[i]); + free_candev(priv_data->netdev[i]); + } + destroy_workqueue(priv_data->tx_wq); + kfree(priv_data->assembly_buffer); + kfree(priv_data->rx_buf); + kfree(priv_data->tx_buf); + kfree(priv_data); + return 0; +} + +static const struct of_device_id rh850_match_table[] = { + { .compatible = "renesas,rh850" }, + { } +}; + +static struct spi_driver rh850_driver = { + .driver = { + .name = "rh850", + .of_match_table = rh850_match_table, + .owner = THIS_MODULE, + }, + .probe = rh850_probe, + .remove = rh850_remove, +}; +module_spi_driver(rh850_driver); + +MODULE_DESCRIPTION("RH850 SPI-CAN module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index 31c5e476fd64..0e6c8f249125 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -110,6 +110,7 @@ source "drivers/net/ethernet/mellanox/Kconfig" source "drivers/net/ethernet/micrel/Kconfig" source "drivers/net/ethernet/microchip/Kconfig" source "drivers/net/ethernet/moxa/Kconfig" +source "drivers/net/ethernet/msm/Kconfig" source "drivers/net/ethernet/myricom/Kconfig" config FEALNX diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 071f84eb6f3f..34cd0537a6df 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_NET_VENDOR_MELLANOX) += mellanox/ obj-$(CONFIG_NET_VENDOR_MICREL) += micrel/ obj-$(CONFIG_NET_VENDOR_MICROCHIP) += microchip/ obj-$(CONFIG_NET_VENDOR_MOXART) += moxa/ +obj-$(CONFIG_ARCH_QCOM) += msm/ obj-$(CONFIG_NET_VENDOR_MYRI) += myricom/ obj-$(CONFIG_FEALNX) += fealnx.o obj-$(CONFIG_NET_VENDOR_NATSEMI) += natsemi/ diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c index c08d34f618b9..3e77c3e843bb 100644 --- a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c @@ -2182,21 +2182,20 @@ static int atl1c_tx_map(struct atl1c_adapter *adapter, use_tpd->buffer_addr = cpu_to_le64(buffer_info->dma); use_tpd->buffer_len = cpu_to_le16(buffer_info->length); } - - /* The last tpd */ - use_tpd->word1 |= 1 << TPD_EOP_SHIFT; - /* The last buffer info contain the skb address, - so it will be free after unmap */ - buffer_info->skb = skb; - - return 0; - + if (use_tpd && buffer_info) { + /*The last tpd*/ + use_tpd->word1 |= 1 << TPD_EOP_SHIFT; + /*The last buffer info contain the skb address, + *so it will be free after unmap + */ + buffer_info->skb = skb; + return 0; + } err_dma: buffer_info->dma = 0; buffer_info->length = 0; return -1; } - static void atl1c_tx_queue(struct atl1c_adapter *adapter, struct sk_buff *skb, struct atl1c_tpd_desc *tpd, enum atl1c_trans_queue type) { diff --git a/drivers/net/ethernet/hisilicon/hns/hns_enet.c b/drivers/net/ethernet/hisilicon/hns/hns_enet.c index 2fa54b0b0679..7948c5a9e5fc 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_enet.c +++ b/drivers/net/ethernet/hisilicon/hns/hns_enet.c @@ -100,9 +100,9 @@ static void unfill_desc(struct hnae_ring *ring) ring_ptr_move_bw(ring, next_to_use); } -int hns_nic_net_xmit_hw(struct net_device *ndev, - struct sk_buff *skb, - struct hns_nic_ring_data *ring_data) +netdev_tx_t hns_nic_net_xmit_hw(struct net_device *ndev, + struct sk_buff *skb, + struct hns_nic_ring_data *ring_data) { struct hns_nic_priv *priv = netdev_priv(ndev); struct hnae_ring *ring = ring_data->ring; @@ -172,6 +172,10 @@ int hns_nic_net_xmit_hw(struct net_device *ndev, dev_queue = netdev_get_tx_queue(ndev, skb->queue_mapping); netdev_tx_sent_queue(dev_queue, skb->len); + ndev->trans_start = jiffies; + ndev->stats.tx_bytes += skb->len; + ndev->stats.tx_packets++; + wmb(); /* commit all data before submit */ assert(skb->queue_mapping < priv->ae_handle->q_num); hnae_queue_xmit(priv->ae_handle->qs[skb->queue_mapping], buf_num); @@ -1089,17 +1093,11 @@ static netdev_tx_t hns_nic_net_xmit(struct sk_buff *skb, struct net_device *ndev) { struct hns_nic_priv *priv = netdev_priv(ndev); - int ret; assert(skb->queue_mapping < ndev->ae_handle->q_num); - ret = hns_nic_net_xmit_hw(ndev, skb, - &tx_ring_data(priv, skb->queue_mapping)); - if (ret == NETDEV_TX_OK) { - ndev->trans_start = jiffies; - ndev->stats.tx_bytes += skb->len; - ndev->stats.tx_packets++; - } - return (netdev_tx_t)ret; + + return hns_nic_net_xmit_hw(ndev, skb, + &tx_ring_data(priv, skb->queue_mapping)); } static int hns_nic_change_mtu(struct net_device *ndev, int new_mtu) diff --git a/drivers/net/ethernet/hisilicon/hns/hns_enet.h b/drivers/net/ethernet/hisilicon/hns/hns_enet.h index dae0ed19ac6d..b4489ffdb4de 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_enet.h +++ b/drivers/net/ethernet/hisilicon/hns/hns_enet.h @@ -77,8 +77,8 @@ void hns_ethtool_set_ops(struct net_device *ndev); void hns_nic_net_reset(struct net_device *ndev); void hns_nic_net_reinit(struct net_device *netdev); int hns_nic_init_phy(struct net_device *ndev, struct hnae_handle *h); -int hns_nic_net_xmit_hw(struct net_device *ndev, - struct sk_buff *skb, - struct hns_nic_ring_data *ring_data); +netdev_tx_t hns_nic_net_xmit_hw(struct net_device *ndev, + struct sk_buff *skb, + struct hns_nic_ring_data *ring_data); #endif /**__HNS_ENET_H */ diff --git a/drivers/net/ethernet/msm/Kconfig b/drivers/net/ethernet/msm/Kconfig new file mode 100644 index 000000000000..b0fe6a6d7115 --- /dev/null +++ b/drivers/net/ethernet/msm/Kconfig @@ -0,0 +1,31 @@ +# +# msm network device configuration +# + +config MSM_RMNET_MHI + bool "RMNET MHI Driver" + depends on MSM_MHI + help + Implements RMNET over MHI interface. + RMNET provides a virtual ethernet interface + for routing IP packets within the MSM using + BAM as a physical transport. + +config ECM_IPA + tristate "STD ECM LAN Driver support" + depends on IPA || IPA3 + help + Enables LAN between applications processor and a tethered + host using the STD ECM protocol. + This Network interface is aimed to allow data path go through + IPA core while using STD ECM protocol. + +config RNDIS_IPA + tristate "RNDIS_IPA Network Interface Driver support" + depends on IPA || IPA3 + help + Enables LAN between applications processor and a tethered + host using the RNDIS protocol. + This Network interface is aimed to allow data path go through + IPA core while using RNDIS protocol. + diff --git a/drivers/net/ethernet/msm/Makefile b/drivers/net/ethernet/msm/Makefile new file mode 100644 index 000000000000..5bf934e82fce --- /dev/null +++ b/drivers/net/ethernet/msm/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the msm networking support. +# + +obj-$(CONFIG_MSM_RMNET_MHI) += msm_rmnet_mhi.o +obj-$(CONFIG_ECM_IPA) += ecm_ipa.o +obj-$(CONFIG_RNDIS_IPA) += rndis_ipa.o diff --git a/drivers/net/ethernet/msm/ecm_ipa.c b/drivers/net/ethernet/msm/ecm_ipa.c new file mode 100644 index 000000000000..a89face0e891 --- /dev/null +++ b/drivers/net/ethernet/msm/ecm_ipa.c @@ -0,0 +1,1436 @@ +/* Copyright (c) 2013-2017, 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/debugfs.h> +#include <linux/errno.h> +#include <linux/etherdevice.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/sched.h> +#include <linux/atomic.h> +#include <linux/ecm_ipa.h> + +#define DRIVER_NAME "ecm_ipa" +#define ECM_IPA_IPV4_HDR_NAME "ecm_eth_ipv4" +#define ECM_IPA_IPV6_HDR_NAME "ecm_eth_ipv6" +#define INACTIVITY_MSEC_DELAY 100 +#define DEFAULT_OUTSTANDING_HIGH 64 +#define DEFAULT_OUTSTANDING_LOW 32 +#define DEBUGFS_TEMP_BUF_SIZE 4 +#define TX_TIMEOUT (5 * HZ) + + +#define ECM_IPA_DEBUG(fmt, args...) \ + pr_debug("ctx:%s: "\ + fmt, current->comm, ## args) + +#define ECM_IPA_INFO(fmt, args...) \ + pr_err(DRIVER_NAME "@%s@%d@ctx:%s: "\ + fmt, __func__, __LINE__, current->comm, ## args) + +#define ECM_IPA_ERROR(fmt, args...) \ + pr_err(DRIVER_NAME "@%s@%d@ctx:%s: "\ + fmt, __func__, __LINE__, current->comm, ## args) + +#define NULL_CHECK(ptr) \ + do { \ + if (!(ptr)) { \ + ECM_IPA_ERROR("null pointer #ptr\n"); \ + return -EINVAL; \ + } \ + } \ + while (0) + +#define ECM_IPA_LOG_ENTRY() ECM_IPA_DEBUG("begin\n") +#define ECM_IPA_LOG_EXIT() ECM_IPA_DEBUG("end\n") + +/** + * enum ecm_ipa_state - specify the current driver internal state + * which is guarded by a state machine. + * + * The driver internal state changes due to its external API usage. + * The driver saves its internal state to guard from caller illegal + * call sequence. + * states: + * UNLOADED is the first state which is the default one and is also the state + * after the driver gets unloaded(cleanup). + * INITIALIZED is the driver state once it finished registering + * the network device and all internal data struct were initialized + * CONNECTED is the driver state once the USB pipes were connected to IPA + * UP is the driver state after the interface mode was set to UP but the + * pipes are not connected yet - this state is meta-stable state. + * CONNECTED_AND_UP is the driver state when the pipe were connected and + * the interface got UP request from the network stack. this is the driver + * idle operation state which allows it to transmit/receive data. + * INVALID is a state which is not allowed. + */ +enum ecm_ipa_state { + ECM_IPA_UNLOADED = 0, + ECM_IPA_INITIALIZED, + ECM_IPA_CONNECTED, + ECM_IPA_UP, + ECM_IPA_CONNECTED_AND_UP, + ECM_IPA_INVALID, +}; + +/** + * enum ecm_ipa_operation - enumerations used to descibe the API operation + * + * Those enums are used as input for the driver state machine. + */ +enum ecm_ipa_operation { + ECM_IPA_INITIALIZE, + ECM_IPA_CONNECT, + ECM_IPA_OPEN, + ECM_IPA_STOP, + ECM_IPA_DISCONNECT, + ECM_IPA_CLEANUP, +}; + +#define ECM_IPA_STATE_DEBUG(ecm_ipa_ctx) \ + ECM_IPA_DEBUG("Driver state - %s\n",\ + ecm_ipa_state_string(ecm_ipa_ctx->state)); + +/** + * struct ecm_ipa_dev - main driver context parameters + * @net: network interface struct implemented by this driver + * @directory: debugfs directory for various debuging switches + * @eth_ipv4_hdr_hdl: saved handle for ipv4 header-insertion table + * @eth_ipv6_hdr_hdl: saved handle for ipv6 header-insertion table + * @usb_to_ipa_hdl: save handle for IPA pipe operations + * @ipa_to_usb_hdl: save handle for IPA pipe operations + * @outstanding_pkts: number of packets sent to IPA without TX complete ACKed + * @outstanding_high: number of outstanding packets allowed + * @outstanding_low: number of outstanding packets which shall cause + * to netdev queue start (after stopped due to outstanding_high reached) + * @state: current state of ecm_ipa driver + * @device_ready_notify: callback supplied by USB core driver + * This callback shall be called by the Netdev once the Netdev internal + * state is changed to RNDIS_IPA_CONNECTED_AND_UP + * @ipa_to_usb_client: consumer client + * @usb_to_ipa_client: producer client + * @ipa_rm_resource_name_prod: IPA resource manager producer resource + * @ipa_rm_resource_name_cons: IPA resource manager consumer resource + */ +struct ecm_ipa_dev { + struct net_device *net; + struct dentry *directory; + uint32_t eth_ipv4_hdr_hdl; + uint32_t eth_ipv6_hdr_hdl; + u32 usb_to_ipa_hdl; + u32 ipa_to_usb_hdl; + atomic_t outstanding_pkts; + u8 outstanding_high; + u8 outstanding_low; + enum ecm_ipa_state state; + void (*device_ready_notify)(void); + enum ipa_client_type ipa_to_usb_client; + enum ipa_client_type usb_to_ipa_client; + enum ipa_rm_resource_name ipa_rm_resource_name_prod; + enum ipa_rm_resource_name ipa_rm_resource_name_cons; +}; + +static int ecm_ipa_open(struct net_device *net); +static void ecm_ipa_packet_receive_notify(void *priv, + enum ipa_dp_evt_type evt, unsigned long data); +static void ecm_ipa_tx_complete_notify(void *priv, + enum ipa_dp_evt_type evt, unsigned long data); +static void ecm_ipa_tx_timeout(struct net_device *net); +static int ecm_ipa_stop(struct net_device *net); +static void ecm_ipa_enable_data_path(struct ecm_ipa_dev *ecm_ipa_ctx); +static int ecm_ipa_rules_cfg(struct ecm_ipa_dev *ecm_ipa_ctx, + const void *dst_mac, const void *src_mac); +static void ecm_ipa_rules_destroy(struct ecm_ipa_dev *ecm_ipa_ctx); +static int ecm_ipa_register_properties(struct ecm_ipa_dev *ecm_ipa_ctx); +static void ecm_ipa_deregister_properties(void); +static void ecm_ipa_rm_notify(void *user_data, enum ipa_rm_event event, + unsigned long data); +static struct net_device_stats *ecm_ipa_get_stats(struct net_device *net); +static int ecm_ipa_create_rm_resource(struct ecm_ipa_dev *ecm_ipa_ctx); +static void ecm_ipa_destory_rm_resource(struct ecm_ipa_dev *ecm_ipa_ctx); +static int resource_request(struct ecm_ipa_dev *ecm_ipa_ctx); +static void resource_release(struct ecm_ipa_dev *ecm_ipa_ctx); +static netdev_tx_t ecm_ipa_start_xmit(struct sk_buff *skb, + struct net_device *net); +static int ecm_ipa_debugfs_atomic_open(struct inode *inode, struct file *file); +static ssize_t ecm_ipa_debugfs_atomic_read(struct file *file, + char __user *ubuf, size_t count, loff_t *ppos); +static void ecm_ipa_debugfs_init(struct ecm_ipa_dev *ecm_ipa_ctx); +static void ecm_ipa_debugfs_destroy(struct ecm_ipa_dev *ecm_ipa_ctx); +static int ecm_ipa_ep_registers_cfg(u32 usb_to_ipa_hdl, u32 ipa_to_usb_hdl); +static int ecm_ipa_set_device_ethernet_addr(u8 *dev_ethaddr, + u8 device_ethaddr[]); +static enum ecm_ipa_state ecm_ipa_next_state(enum ecm_ipa_state current_state, + enum ecm_ipa_operation operation); +static const char *ecm_ipa_state_string(enum ecm_ipa_state state); +static int ecm_ipa_init_module(void); +static void ecm_ipa_cleanup_module(void); + +static const struct net_device_ops ecm_ipa_netdev_ops = { + .ndo_open = ecm_ipa_open, + .ndo_stop = ecm_ipa_stop, + .ndo_start_xmit = ecm_ipa_start_xmit, + .ndo_set_mac_address = eth_mac_addr, + .ndo_tx_timeout = ecm_ipa_tx_timeout, + .ndo_get_stats = ecm_ipa_get_stats, +}; + +const struct file_operations ecm_ipa_debugfs_atomic_ops = { + .open = ecm_ipa_debugfs_atomic_open, + .read = ecm_ipa_debugfs_atomic_read, +}; + +static void ecm_ipa_msg_free_cb(void *buff, u32 len, u32 type) +{ + kfree(buff); +} + +/** + * ecm_ipa_init() - create network device and initializes internal + * data structures + * @params: in/out parameters required for ecm_ipa initialization + * + * Shall be called prior to pipe connection. + * The out parameters (the callbacks) shall be supplied to ipa_connect. + * Detailed description: + * - allocate the network device + * - set default values for driver internals + * - create debugfs folder and files + * - create IPA resource manager client + * - add header insertion rules for IPA driver (based on host/device + * Ethernet addresses given in input params) + * - register tx/rx properties to IPA driver (will be later used + * by IPA configuration manager to configure reset of the IPA rules) + * - set the carrier state to "off" (until ecm_ipa_connect is called) + * - register the network device + * - set the out parameters + * + * Returns negative errno, or zero on success + */ +int ecm_ipa_init(struct ecm_ipa_params *params) +{ + int result = 0; + struct net_device *net; + struct ecm_ipa_dev *ecm_ipa_ctx; + + ECM_IPA_LOG_ENTRY(); + + ECM_IPA_DEBUG("%s initializing\n", DRIVER_NAME); + NULL_CHECK(params); + + ECM_IPA_DEBUG("host_ethaddr=%pM, device_ethaddr=%pM\n", + params->host_ethaddr, + params->device_ethaddr); + + net = alloc_etherdev(sizeof(struct ecm_ipa_dev)); + if (!net) { + result = -ENOMEM; + ECM_IPA_ERROR("fail to allocate etherdev\n"); + goto fail_alloc_etherdev; + } + ECM_IPA_DEBUG("network device was successfully allocated\n"); + + ecm_ipa_ctx = netdev_priv(net); + if (!ecm_ipa_ctx) { + ECM_IPA_ERROR("fail to extract netdev priv\n"); + result = -ENOMEM; + goto fail_netdev_priv; + } + memset(ecm_ipa_ctx, 0, sizeof(*ecm_ipa_ctx)); + ECM_IPA_DEBUG("ecm_ipa_ctx (private) = %p\n", ecm_ipa_ctx); + + ecm_ipa_ctx->net = net; + ecm_ipa_ctx->outstanding_high = DEFAULT_OUTSTANDING_HIGH; + ecm_ipa_ctx->outstanding_low = DEFAULT_OUTSTANDING_LOW; + atomic_set(&ecm_ipa_ctx->outstanding_pkts, 0); + snprintf(net->name, sizeof(net->name), "%s%%d", "ecm"); + net->netdev_ops = &ecm_ipa_netdev_ops; + net->watchdog_timeo = TX_TIMEOUT; + ECM_IPA_DEBUG("internal data structures were intialized\n"); + + if (!params->device_ready_notify) + ECM_IPA_DEBUG("device_ready_notify() was not supplied"); + ecm_ipa_ctx->device_ready_notify = params->device_ready_notify; + + ecm_ipa_debugfs_init(ecm_ipa_ctx); + + result = ecm_ipa_set_device_ethernet_addr(net->dev_addr, + params->device_ethaddr); + if (result) { + ECM_IPA_ERROR("set device MAC failed\n"); + goto fail_set_device_ethernet; + } + ECM_IPA_DEBUG("Device Ethernet address set %pM\n", net->dev_addr); + + result = ecm_ipa_rules_cfg(ecm_ipa_ctx, params->host_ethaddr, + params->device_ethaddr); + if (result) { + ECM_IPA_ERROR("fail on ipa rules set\n"); + goto fail_rules_cfg; + } + ECM_IPA_DEBUG("Ethernet header insertion set\n"); + + netif_carrier_off(net); + ECM_IPA_DEBUG("netif_carrier_off() was called\n"); + + netif_stop_queue(ecm_ipa_ctx->net); + ECM_IPA_DEBUG("netif_stop_queue() was called"); + + result = register_netdev(net); + if (result) { + ECM_IPA_ERROR("register_netdev failed: %d\n", result); + goto fail_register_netdev; + } + ECM_IPA_DEBUG("register_netdev succeeded\n"); + + params->ecm_ipa_rx_dp_notify = ecm_ipa_packet_receive_notify; + params->ecm_ipa_tx_dp_notify = ecm_ipa_tx_complete_notify; + params->private = (void *)ecm_ipa_ctx; + params->skip_ep_cfg = false; + ecm_ipa_ctx->state = ECM_IPA_INITIALIZED; + ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); + + ECM_IPA_INFO("ECM_IPA was initialized successfully\n"); + + ECM_IPA_LOG_EXIT(); + + return 0; + +fail_register_netdev: + ecm_ipa_rules_destroy(ecm_ipa_ctx); +fail_set_device_ethernet: +fail_rules_cfg: + ecm_ipa_debugfs_destroy(ecm_ipa_ctx); +fail_netdev_priv: + free_netdev(net); +fail_alloc_etherdev: + return result; +} +EXPORT_SYMBOL(ecm_ipa_init); + +/** + * ecm_ipa_connect() - notify ecm_ipa for IPA<->USB pipes connection + * @usb_to_ipa_hdl: handle of IPA driver client for USB->IPA + * @ipa_to_usb_hdl: handle of IPA driver client for IPA->USB + * @priv: same value that was set by ecm_ipa_init(), this + * parameter holds the network device pointer. + * + * Once USB driver finishes the pipe connection between IPA core + * and USB core this method shall be called in order to + * allow ecm_ipa complete the data path configurations. + * Caller should make sure that it is calling this function + * from a context that allows it to handle device_ready_notify(). + * Detailed description: + * - configure the IPA end-points register + * - notify the Linux kernel for "carrier_on" + * After this function is done the driver state changes to "Connected". + * This API is expected to be called after ecm_ipa_init() or + * after a call to ecm_ipa_disconnect. + */ +int ecm_ipa_connect(u32 usb_to_ipa_hdl, u32 ipa_to_usb_hdl, void *priv) +{ + struct ecm_ipa_dev *ecm_ipa_ctx = priv; + int next_state; + struct ipa_ecm_msg *ecm_msg; + struct ipa_msg_meta msg_meta; + int retval; + + ECM_IPA_LOG_ENTRY(); + NULL_CHECK(priv); + ECM_IPA_DEBUG("usb_to_ipa_hdl = %d, ipa_to_usb_hdl = %d, priv=0x%p\n", + usb_to_ipa_hdl, ipa_to_usb_hdl, priv); + + next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_CONNECT); + if (next_state == ECM_IPA_INVALID) { + ECM_IPA_ERROR("can't call connect before calling initialize\n"); + return -EPERM; + } + ecm_ipa_ctx->state = next_state; + ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); + + if (!ipa_is_client_handle_valid(usb_to_ipa_hdl)) { + ECM_IPA_ERROR("usb_to_ipa_hdl(%d) is not a valid ipa handle\n", + usb_to_ipa_hdl); + return -EINVAL; + } + if (!ipa_is_client_handle_valid(ipa_to_usb_hdl)) { + ECM_IPA_ERROR("ipa_to_usb_hdl(%d) is not a valid ipa handle\n", + ipa_to_usb_hdl); + return -EINVAL; + } + + ecm_ipa_ctx->ipa_to_usb_hdl = ipa_to_usb_hdl; + ecm_ipa_ctx->usb_to_ipa_hdl = usb_to_ipa_hdl; + + ecm_ipa_ctx->ipa_to_usb_client = ipa_get_client_mapping(ipa_to_usb_hdl); + if (ecm_ipa_ctx->ipa_to_usb_client < 0) { + ECM_IPA_ERROR( + "Error getting IPA->USB client from handle %d\n", + ecm_ipa_ctx->ipa_to_usb_client); + return -EINVAL; + } + ECM_IPA_DEBUG("ipa_to_usb_client = %d\n", + ecm_ipa_ctx->ipa_to_usb_client); + + ecm_ipa_ctx->usb_to_ipa_client = ipa_get_client_mapping(usb_to_ipa_hdl); + if (ecm_ipa_ctx->usb_to_ipa_client < 0) { + ECM_IPA_ERROR( + "Error getting USB->IPA client from handle %d\n", + ecm_ipa_ctx->usb_to_ipa_client); + return -EINVAL; + } + ECM_IPA_DEBUG("usb_to_ipa_client = %d\n", + ecm_ipa_ctx->usb_to_ipa_client); + + ecm_ipa_ctx->ipa_rm_resource_name_cons = + ipa_get_rm_resource_from_ep(ipa_to_usb_hdl); + if (ecm_ipa_ctx->ipa_rm_resource_name_cons < 0) { + ECM_IPA_ERROR("Error getting CONS RM resource from handle %d\n", + ecm_ipa_ctx->ipa_rm_resource_name_cons); + return -EINVAL; + } + ECM_IPA_DEBUG("ipa_rm_resource_name_cons = %d\n", + ecm_ipa_ctx->ipa_rm_resource_name_cons); + + ecm_ipa_ctx->ipa_rm_resource_name_prod = + ipa_get_rm_resource_from_ep(usb_to_ipa_hdl); + if (ecm_ipa_ctx->ipa_rm_resource_name_prod < 0) { + ECM_IPA_ERROR("Error getting PROD RM resource from handle %d\n", + ecm_ipa_ctx->ipa_rm_resource_name_prod); + return -EINVAL; + } + ECM_IPA_DEBUG("ipa_rm_resource_name_prod = %d\n", + ecm_ipa_ctx->ipa_rm_resource_name_prod); + + retval = ecm_ipa_create_rm_resource(ecm_ipa_ctx); + if (retval) { + ECM_IPA_ERROR("fail on RM create\n"); + goto fail_create_rm; + } + ECM_IPA_DEBUG("RM resource was created\n"); + + retval = ecm_ipa_register_properties(ecm_ipa_ctx); + if (retval) { + ECM_IPA_ERROR("fail on properties set\n"); + goto fail_create_rm; + } + ECM_IPA_DEBUG("ecm_ipa 2 Tx and 2 Rx properties were registered\n"); + + retval = ecm_ipa_ep_registers_cfg(usb_to_ipa_hdl, ipa_to_usb_hdl); + if (retval) { + ECM_IPA_ERROR("fail on ep cfg\n"); + goto fail; + } + ECM_IPA_DEBUG("end-point configured\n"); + + netif_carrier_on(ecm_ipa_ctx->net); + + ecm_msg = kzalloc(sizeof(struct ipa_ecm_msg), GFP_KERNEL); + if (!ecm_msg) { + ECM_IPA_ERROR("can't alloc msg mem\n"); + retval = -ENOMEM; + goto fail; + } + + memset(&msg_meta, 0, sizeof(struct ipa_msg_meta)); + msg_meta.msg_type = ECM_CONNECT; + msg_meta.msg_len = sizeof(struct ipa_ecm_msg); + strlcpy(ecm_msg->name, ecm_ipa_ctx->net->name, + IPA_RESOURCE_NAME_MAX); + ecm_msg->ifindex = ecm_ipa_ctx->net->ifindex; + + retval = ipa_send_msg(&msg_meta, ecm_msg, ecm_ipa_msg_free_cb); + if (retval) { + ECM_IPA_ERROR("fail to send ECM_CONNECT message\n"); + kfree(ecm_msg); + goto fail; + } + + if (!netif_carrier_ok(ecm_ipa_ctx->net)) { + ECM_IPA_ERROR("netif_carrier_ok error\n"); + retval = -EBUSY; + goto fail; + } + ECM_IPA_DEBUG("carrier_on notified\n"); + + if (ecm_ipa_ctx->state == ECM_IPA_CONNECTED_AND_UP) + ecm_ipa_enable_data_path(ecm_ipa_ctx); + else + ECM_IPA_DEBUG("data path was not enabled yet\n"); + + ECM_IPA_INFO("ECM_IPA was connected successfully\n"); + + ECM_IPA_LOG_EXIT(); + + return 0; + +fail: + ecm_ipa_deregister_properties(); +fail_create_rm: + ecm_ipa_destory_rm_resource(ecm_ipa_ctx); + return retval; +} +EXPORT_SYMBOL(ecm_ipa_connect); + +/** + * ecm_ipa_open() - notify Linux network stack to start sending packets + * @net: the network interface supplied by the network stack + * + * Linux uses this API to notify the driver that the network interface + * transitions to the up state. + * The driver will instruct the Linux network stack to start + * delivering data packets. + */ +static int ecm_ipa_open(struct net_device *net) +{ + struct ecm_ipa_dev *ecm_ipa_ctx; + int next_state; + + ECM_IPA_LOG_ENTRY(); + + ecm_ipa_ctx = netdev_priv(net); + + next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_OPEN); + if (next_state == ECM_IPA_INVALID) { + ECM_IPA_ERROR("can't bring driver up before initialize\n"); + return -EPERM; + } + ecm_ipa_ctx->state = next_state; + ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); + + if (ecm_ipa_ctx->state == ECM_IPA_CONNECTED_AND_UP) + ecm_ipa_enable_data_path(ecm_ipa_ctx); + else + ECM_IPA_DEBUG("data path was not enabled yet\n"); + + ECM_IPA_LOG_EXIT(); + + return 0; +} + +/** + * ecm_ipa_start_xmit() - send data from APPs to USB core via IPA core + * @skb: packet received from Linux network stack + * @net: the network device being used to send this packet + * + * Several conditions needed in order to send the packet to IPA: + * - Transmit queue for the network driver is currently + * in "send" state + * - The driver internal state is in "UP" state. + * - Filter Tx switch is turned off + * - The IPA resource manager state for the driver producer client + * is "Granted" which implies that all the resources in the dependency + * graph are valid for data flow. + * - outstanding high boundary did not reach. + * + * In case all of the above conditions are met, the network driver will + * send the packet by using the IPA API for Tx. + * In case the outstanding packet high boundary is reached, the driver will + * stop the send queue until enough packet were proceeded by the IPA core. + */ +static netdev_tx_t ecm_ipa_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + int ret; + netdev_tx_t status = NETDEV_TX_BUSY; + struct ecm_ipa_dev *ecm_ipa_ctx = netdev_priv(net); + + net->trans_start = jiffies; + + ECM_IPA_DEBUG("Tx, len=%d, skb->protocol=%d, outstanding=%d\n", + skb->len, skb->protocol, + atomic_read(&ecm_ipa_ctx->outstanding_pkts)); + + if (unlikely(netif_queue_stopped(net))) { + ECM_IPA_ERROR("interface queue is stopped\n"); + goto out; + } + + if (unlikely(ecm_ipa_ctx->state != ECM_IPA_CONNECTED_AND_UP)) { + ECM_IPA_ERROR("Missing pipe connected and/or iface up\n"); + return NETDEV_TX_BUSY; + } + + ret = resource_request(ecm_ipa_ctx); + if (ret) { + ECM_IPA_DEBUG("Waiting to resource\n"); + netif_stop_queue(net); + goto resource_busy; + } + + if (atomic_read(&ecm_ipa_ctx->outstanding_pkts) >= + ecm_ipa_ctx->outstanding_high) { + ECM_IPA_DEBUG("outstanding high (%d)- stopping\n", + ecm_ipa_ctx->outstanding_high); + netif_stop_queue(net); + status = NETDEV_TX_BUSY; + goto out; + } + + ret = ipa_tx_dp(ecm_ipa_ctx->ipa_to_usb_client, skb, NULL); + if (ret) { + ECM_IPA_ERROR("ipa transmit failed (%d)\n", ret); + goto fail_tx_packet; + } + + atomic_inc(&ecm_ipa_ctx->outstanding_pkts); + + status = NETDEV_TX_OK; + goto out; + +fail_tx_packet: +out: + resource_release(ecm_ipa_ctx); +resource_busy: + return status; +} + +/** + * ecm_ipa_packet_receive_notify() - Rx notify + * + * @priv: ecm driver context + * @evt: event type + * @data: data provided with event + * + * IPA will pass a packet to the Linux network stack with skb->data pointing + * to Ethernet packet frame. + */ +static void ecm_ipa_packet_receive_notify(void *priv, + enum ipa_dp_evt_type evt, + unsigned long data) +{ + struct sk_buff *skb = (struct sk_buff *)data; + struct ecm_ipa_dev *ecm_ipa_ctx = priv; + int result; + unsigned int packet_len; + + if (!skb) { + ECM_IPA_ERROR("Bad SKB received from IPA driver\n"); + return; + } + + packet_len = skb->len; + ECM_IPA_DEBUG("packet RX, len=%d\n", skb->len); + + if (unlikely(ecm_ipa_ctx->state != ECM_IPA_CONNECTED_AND_UP)) { + ECM_IPA_DEBUG("Missing pipe connected and/or iface up\n"); + return; + } + + if (evt != IPA_RECEIVE) { + ECM_IPA_ERROR("A none IPA_RECEIVE event in ecm_ipa_receive\n"); + return; + } + + skb->dev = ecm_ipa_ctx->net; + skb->protocol = eth_type_trans(skb, ecm_ipa_ctx->net); + + result = netif_rx(skb); + if (result) + ECM_IPA_ERROR("fail on netif_rx\n"); + ecm_ipa_ctx->net->stats.rx_packets++; + ecm_ipa_ctx->net->stats.rx_bytes += packet_len; + + return; +} + +/** ecm_ipa_stop() - called when network device transitions to the down + * state. + * @net: the network device being stopped. + * + * This API is used by Linux network stack to notify the network driver that + * its state was changed to "down" + * The driver will stop the "send" queue and change its internal + * state to "Connected". + */ +static int ecm_ipa_stop(struct net_device *net) +{ + struct ecm_ipa_dev *ecm_ipa_ctx = netdev_priv(net); + int next_state; + + ECM_IPA_LOG_ENTRY(); + + next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_STOP); + if (next_state == ECM_IPA_INVALID) { + ECM_IPA_ERROR("can't do network interface down without up\n"); + return -EPERM; + } + ecm_ipa_ctx->state = next_state; + ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); + + netif_stop_queue(net); + ECM_IPA_DEBUG("network device stopped\n"); + + ECM_IPA_LOG_EXIT(); + return 0; +} + +/** ecm_ipa_disconnect() - called when the USB cable is unplugged. + * @priv: same value that was set by ecm_ipa_init(), this + * parameter holds the network device pointer. + * + * Once the USB cable is unplugged the USB driver will notify the network + * interface driver. + * The internal driver state will returned to its initialized state and + * Linux network stack will be informed for carrier off and the send queue + * will be stopped. + */ +int ecm_ipa_disconnect(void *priv) +{ + struct ecm_ipa_dev *ecm_ipa_ctx = priv; + int next_state; + struct ipa_ecm_msg *ecm_msg; + struct ipa_msg_meta msg_meta; + int retval; + int outstanding_dropped_pkts; + + ECM_IPA_LOG_ENTRY(); + NULL_CHECK(ecm_ipa_ctx); + ECM_IPA_DEBUG("priv=0x%p\n", priv); + + next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_DISCONNECT); + if (next_state == ECM_IPA_INVALID) { + ECM_IPA_ERROR("can't disconnect before connect\n"); + return -EPERM; + } + ecm_ipa_ctx->state = next_state; + ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); + + netif_carrier_off(ecm_ipa_ctx->net); + ECM_IPA_DEBUG("carrier_off notifcation was sent\n"); + + ecm_msg = kzalloc(sizeof(struct ipa_ecm_msg), GFP_KERNEL); + if (!ecm_msg) { + ECM_IPA_ERROR("can't alloc msg mem\n"); + return -ENOMEM; + } + + memset(&msg_meta, 0, sizeof(struct ipa_msg_meta)); + msg_meta.msg_type = ECM_DISCONNECT; + msg_meta.msg_len = sizeof(struct ipa_ecm_msg); + strlcpy(ecm_msg->name, ecm_ipa_ctx->net->name, + IPA_RESOURCE_NAME_MAX); + ecm_msg->ifindex = ecm_ipa_ctx->net->ifindex; + + retval = ipa_send_msg(&msg_meta, ecm_msg, ecm_ipa_msg_free_cb); + if (retval) { + ECM_IPA_ERROR("fail to send ECM_DISCONNECT message\n"); + kfree(ecm_msg); + return -EPERM; + } + + netif_stop_queue(ecm_ipa_ctx->net); + ECM_IPA_DEBUG("queue stopped\n"); + + ecm_ipa_destory_rm_resource(ecm_ipa_ctx); + + outstanding_dropped_pkts = + atomic_read(&ecm_ipa_ctx->outstanding_pkts); + ecm_ipa_ctx->net->stats.tx_errors += outstanding_dropped_pkts; + atomic_set(&ecm_ipa_ctx->outstanding_pkts, 0); + + ECM_IPA_INFO("ECM_IPA was disconnected successfully\n"); + + ECM_IPA_LOG_EXIT(); + + return 0; +} +EXPORT_SYMBOL(ecm_ipa_disconnect); + + +/** + * ecm_ipa_cleanup() - unregister the network interface driver and free + * internal data structs. + * @priv: same value that was set by ecm_ipa_init(), this + * parameter holds the network device pointer. + * + * This function shall be called once the network interface is not + * needed anymore, e.g: when the USB composition does not support ECM. + * This function shall be called after the pipes were disconnected. + * Detailed description: + * - delete the driver dependency defined for IPA resource manager and + * destroy the producer resource. + * - remove the debugfs entries + * - deregister the network interface from Linux network stack + * - free all internal data structs + */ +void ecm_ipa_cleanup(void *priv) +{ + struct ecm_ipa_dev *ecm_ipa_ctx = priv; + int next_state; + + ECM_IPA_LOG_ENTRY(); + + ECM_IPA_DEBUG("priv=0x%p\n", priv); + + if (!ecm_ipa_ctx) { + ECM_IPA_ERROR("ecm_ipa_ctx NULL pointer\n"); + return; + } + + next_state = ecm_ipa_next_state(ecm_ipa_ctx->state, ECM_IPA_CLEANUP); + if (next_state == ECM_IPA_INVALID) { + ECM_IPA_ERROR("can't clean driver without cable disconnect\n"); + return; + } + ecm_ipa_ctx->state = next_state; + ECM_IPA_STATE_DEBUG(ecm_ipa_ctx); + + ecm_ipa_rules_destroy(ecm_ipa_ctx); + ecm_ipa_debugfs_destroy(ecm_ipa_ctx); + + unregister_netdev(ecm_ipa_ctx->net); + free_netdev(ecm_ipa_ctx->net); + + ECM_IPA_INFO("ECM_IPA was destroyed successfully\n"); + + ECM_IPA_LOG_EXIT(); + + return; +} +EXPORT_SYMBOL(ecm_ipa_cleanup); + +static void ecm_ipa_enable_data_path(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + if (ecm_ipa_ctx->device_ready_notify) { + ecm_ipa_ctx->device_ready_notify(); + ECM_IPA_DEBUG("USB device_ready_notify() was called\n"); + } else { + ECM_IPA_DEBUG("device_ready_notify() not supplied\n"); + } + + netif_start_queue(ecm_ipa_ctx->net); + ECM_IPA_DEBUG("queue started\n"); +} + +/** + * ecm_ipa_rules_cfg() - set header insertion and register Tx/Rx properties + * Headers will be commited to HW + * @ecm_ipa_ctx: main driver context parameters + * @dst_mac: destination MAC address + * @src_mac: source MAC address + * + * Returns negative errno, or zero on success + */ +static int ecm_ipa_rules_cfg(struct ecm_ipa_dev *ecm_ipa_ctx, + const void *dst_mac, const void *src_mac) +{ + struct ipa_ioc_add_hdr *hdrs; + struct ipa_hdr_add *ipv4_hdr; + struct ipa_hdr_add *ipv6_hdr; + struct ethhdr *eth_ipv4; + struct ethhdr *eth_ipv6; + int result = 0; + + ECM_IPA_LOG_ENTRY(); + hdrs = kzalloc(sizeof(*hdrs) + sizeof(*ipv4_hdr) + sizeof(*ipv6_hdr), + GFP_KERNEL); + if (!hdrs) { + result = -ENOMEM; + goto out; + } + ipv4_hdr = &hdrs->hdr[0]; + eth_ipv4 = (struct ethhdr *)ipv4_hdr->hdr; + ipv6_hdr = &hdrs->hdr[1]; + eth_ipv6 = (struct ethhdr *)ipv6_hdr->hdr; + strlcpy(ipv4_hdr->name, ECM_IPA_IPV4_HDR_NAME, IPA_RESOURCE_NAME_MAX); + memcpy(eth_ipv4->h_dest, dst_mac, ETH_ALEN); + memcpy(eth_ipv4->h_source, src_mac, ETH_ALEN); + eth_ipv4->h_proto = htons(ETH_P_IP); + ipv4_hdr->hdr_len = ETH_HLEN; + ipv4_hdr->is_partial = 0; + ipv4_hdr->is_eth2_ofst_valid = true; + ipv4_hdr->eth2_ofst = 0; + ipv4_hdr->type = IPA_HDR_L2_ETHERNET_II; + strlcpy(ipv6_hdr->name, ECM_IPA_IPV6_HDR_NAME, IPA_RESOURCE_NAME_MAX); + memcpy(eth_ipv6->h_dest, dst_mac, ETH_ALEN); + memcpy(eth_ipv6->h_source, src_mac, ETH_ALEN); + eth_ipv6->h_proto = htons(ETH_P_IPV6); + ipv6_hdr->hdr_len = ETH_HLEN; + ipv6_hdr->is_partial = 0; + ipv6_hdr->is_eth2_ofst_valid = true; + ipv6_hdr->eth2_ofst = 0; + ipv6_hdr->type = IPA_HDR_L2_ETHERNET_II; + hdrs->commit = 1; + hdrs->num_hdrs = 2; + result = ipa_add_hdr(hdrs); + if (result) { + ECM_IPA_ERROR("Fail on Header-Insertion(%d)\n", result); + goto out_free_mem; + } + if (ipv4_hdr->status) { + ECM_IPA_ERROR("Fail on Header-Insertion ipv4(%d)\n", + ipv4_hdr->status); + result = ipv4_hdr->status; + goto out_free_mem; + } + if (ipv6_hdr->status) { + ECM_IPA_ERROR("Fail on Header-Insertion ipv6(%d)\n", + ipv6_hdr->status); + result = ipv6_hdr->status; + goto out_free_mem; + } + ecm_ipa_ctx->eth_ipv4_hdr_hdl = ipv4_hdr->hdr_hdl; + ecm_ipa_ctx->eth_ipv6_hdr_hdl = ipv6_hdr->hdr_hdl; + ECM_IPA_LOG_EXIT(); +out_free_mem: + kfree(hdrs); +out: + return result; +} + +/** + * ecm_ipa_rules_destroy() - remove the IPA core configuration done for + * the driver data path. + * @ecm_ipa_ctx: the driver context + * + * Revert the work done on ecm_ipa_rules_cfg. + */ +static void ecm_ipa_rules_destroy(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + struct ipa_ioc_del_hdr *del_hdr; + struct ipa_hdr_del *ipv4; + struct ipa_hdr_del *ipv6; + int result; + del_hdr = kzalloc(sizeof(*del_hdr) + sizeof(*ipv4) + + sizeof(*ipv6), GFP_KERNEL); + if (!del_hdr) + return; + del_hdr->commit = 1; + del_hdr->num_hdls = 2; + ipv4 = &del_hdr->hdl[0]; + ipv4->hdl = ecm_ipa_ctx->eth_ipv4_hdr_hdl; + ipv6 = &del_hdr->hdl[1]; + ipv6->hdl = ecm_ipa_ctx->eth_ipv6_hdr_hdl; + result = ipa_del_hdr(del_hdr); + if (result || ipv4->status || ipv6->status) + ECM_IPA_ERROR("ipa_del_hdr failed\n"); + kfree(del_hdr); +} + +/* ecm_ipa_register_properties() - set Tx/Rx properties for ipacm + * + * Register ecm0 interface with 2 Tx properties and 2 Rx properties: + * The 2 Tx properties are for data flowing from IPA to USB, they + * have Header-Insertion properties both for Ipv4 and Ipv6 Ethernet framing. + * The 2 Rx properties are for data flowing from USB to IPA, they have + * simple rule which always "hit". + * + */ +static int ecm_ipa_register_properties(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + struct ipa_tx_intf tx_properties = {0}; + struct ipa_ioc_tx_intf_prop properties[2] = { {0}, {0} }; + struct ipa_ioc_tx_intf_prop *ipv4_property; + struct ipa_ioc_tx_intf_prop *ipv6_property; + struct ipa_ioc_rx_intf_prop rx_ioc_properties[2] = { {0}, {0} }; + struct ipa_rx_intf rx_properties = {0}; + struct ipa_ioc_rx_intf_prop *rx_ipv4_property; + struct ipa_ioc_rx_intf_prop *rx_ipv6_property; + int result = 0; + + ECM_IPA_LOG_ENTRY(); + + tx_properties.prop = properties; + ipv4_property = &tx_properties.prop[0]; + ipv4_property->ip = IPA_IP_v4; + ipv4_property->dst_pipe = ecm_ipa_ctx->ipa_to_usb_client; + strlcpy(ipv4_property->hdr_name, ECM_IPA_IPV4_HDR_NAME, + IPA_RESOURCE_NAME_MAX); + ipv4_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; + ipv6_property = &tx_properties.prop[1]; + ipv6_property->ip = IPA_IP_v6; + ipv6_property->dst_pipe = ecm_ipa_ctx->ipa_to_usb_client; + ipv6_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; + strlcpy(ipv6_property->hdr_name, ECM_IPA_IPV6_HDR_NAME, + IPA_RESOURCE_NAME_MAX); + tx_properties.num_props = 2; + + rx_properties.prop = rx_ioc_properties; + rx_ipv4_property = &rx_properties.prop[0]; + rx_ipv4_property->ip = IPA_IP_v4; + rx_ipv4_property->attrib.attrib_mask = 0; + rx_ipv4_property->src_pipe = ecm_ipa_ctx->usb_to_ipa_client; + rx_ipv4_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; + rx_ipv6_property = &rx_properties.prop[1]; + rx_ipv6_property->ip = IPA_IP_v6; + rx_ipv6_property->attrib.attrib_mask = 0; + rx_ipv6_property->src_pipe = ecm_ipa_ctx->usb_to_ipa_client; + rx_ipv6_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; + rx_properties.num_props = 2; + + result = ipa_register_intf("ecm0", &tx_properties, &rx_properties); + if (result) + ECM_IPA_ERROR("fail on Tx/Rx properties registration\n"); + + ECM_IPA_LOG_EXIT(); + + return result; +} + +static void ecm_ipa_deregister_properties(void) +{ + int result; + ECM_IPA_LOG_ENTRY(); + result = ipa_deregister_intf("ecm0"); + if (result) + ECM_IPA_DEBUG("Fail on Tx prop deregister\n"); + ECM_IPA_LOG_EXIT(); + return; +} + +/** + * ecm_ipa_configure() - make IPA core end-point specific configuration + * @usb_to_ipa_hdl: handle of usb_to_ipa end-point for IPA driver + * @ipa_to_usb_hdl: handle of ipa_to_usb end-point for IPA driver + * @host_ethaddr: host Ethernet address in network order + * @device_ethaddr: device Ethernet address in network order + * + * Configure the usb_to_ipa and ipa_to_usb end-point registers + * - USB->IPA end-point: disable de-aggregation, enable link layer + * header removal (Ethernet removal), source NATing and default routing. + * - IPA->USB end-point: disable aggregation, add link layer header (Ethernet) + * - allocate Ethernet device + * - register to Linux network stack + * + * Returns negative errno, or zero on success + */ + + +static void ecm_ipa_rm_notify(void *user_data, enum ipa_rm_event event, + unsigned long data) +{ + struct ecm_ipa_dev *ecm_ipa_ctx = user_data; + ECM_IPA_LOG_ENTRY(); + if (event == IPA_RM_RESOURCE_GRANTED && + netif_queue_stopped(ecm_ipa_ctx->net)) { + ECM_IPA_DEBUG("Resource Granted - starting queue\n"); + netif_start_queue(ecm_ipa_ctx->net); + } else { + ECM_IPA_DEBUG("Resource released\n"); + } + ECM_IPA_LOG_EXIT(); +} + +static struct net_device_stats *ecm_ipa_get_stats(struct net_device *net) +{ + return &net->stats; +} + + +static int ecm_ipa_create_rm_resource(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + struct ipa_rm_create_params create_params = {0}; + struct ipa_rm_perf_profile profile; + int result; + ECM_IPA_LOG_ENTRY(); + create_params.name = IPA_RM_RESOURCE_STD_ECM_PROD; + create_params.reg_params.user_data = ecm_ipa_ctx; + create_params.reg_params.notify_cb = ecm_ipa_rm_notify; + result = ipa_rm_create_resource(&create_params); + if (result) { + ECM_IPA_ERROR("Fail on ipa_rm_create_resource\n"); + goto fail_rm_create; + } + ECM_IPA_DEBUG("rm client was created"); + + profile.max_supported_bandwidth_mbps = IPA_APPS_MAX_BW_IN_MBPS; + ipa_rm_set_perf_profile(IPA_RM_RESOURCE_STD_ECM_PROD, &profile); + + result = ipa_rm_inactivity_timer_init(IPA_RM_RESOURCE_STD_ECM_PROD, + INACTIVITY_MSEC_DELAY); + if (result) { + ECM_IPA_ERROR("Fail on ipa_rm_inactivity_timer_init\n"); + goto fail_it; + } + ECM_IPA_DEBUG("rm_it client was created"); + + result = ipa_rm_add_dependency_sync(IPA_RM_RESOURCE_STD_ECM_PROD, + ecm_ipa_ctx->ipa_rm_resource_name_cons); + if (result && result != -EINPROGRESS) + ECM_IPA_ERROR("unable to add ECM/USB dependency (%d)\n", + result); + + result = ipa_rm_add_dependency_sync( + ecm_ipa_ctx->ipa_rm_resource_name_prod, + IPA_RM_RESOURCE_APPS_CONS); + if (result && result != -EINPROGRESS) + ECM_IPA_ERROR("unable to add USB/APPS dependency (%d)\n", + result); + + ECM_IPA_DEBUG("rm dependency was set\n"); + + ECM_IPA_LOG_EXIT(); + return 0; + +fail_it: +fail_rm_create: + return result; +} + +static void ecm_ipa_destory_rm_resource(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + int result; + + ECM_IPA_LOG_ENTRY(); + + ipa_rm_delete_dependency(IPA_RM_RESOURCE_STD_ECM_PROD, + ecm_ipa_ctx->ipa_rm_resource_name_cons); + ipa_rm_delete_dependency(ecm_ipa_ctx->ipa_rm_resource_name_prod, + IPA_RM_RESOURCE_APPS_CONS); + ipa_rm_inactivity_timer_destroy(IPA_RM_RESOURCE_STD_ECM_PROD); + result = ipa_rm_delete_resource(IPA_RM_RESOURCE_STD_ECM_PROD); + if (result) + ECM_IPA_ERROR("resource deletion failed\n"); + + ECM_IPA_LOG_EXIT(); +} + +static int resource_request(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + return ipa_rm_inactivity_timer_request_resource( + IPA_RM_RESOURCE_STD_ECM_PROD); +} + +static void resource_release(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + ipa_rm_inactivity_timer_release_resource(IPA_RM_RESOURCE_STD_ECM_PROD); +} + +/** + * ecm_ipa_tx_complete_notify() - Rx notify + * + * @priv: ecm driver context + * @evt: event type + * @data: data provided with event + * + * Check that the packet is the one we sent and release it + * This function will be called in defered context in IPA wq. + */ +static void ecm_ipa_tx_complete_notify(void *priv, + enum ipa_dp_evt_type evt, + unsigned long data) +{ + struct sk_buff *skb = (struct sk_buff *)data; + struct ecm_ipa_dev *ecm_ipa_ctx = priv; + + if (!skb) { + ECM_IPA_ERROR("Bad SKB received from IPA driver\n"); + return; + } + + if (!ecm_ipa_ctx) { + ECM_IPA_ERROR("ecm_ipa_ctx is NULL pointer\n"); + return; + } + + ECM_IPA_DEBUG("Tx-complete, len=%d, skb->prot=%d, outstanding=%d\n", + skb->len, skb->protocol, + atomic_read(&ecm_ipa_ctx->outstanding_pkts)); + + if (evt != IPA_WRITE_DONE) { + ECM_IPA_ERROR("unsupported event on Tx callback\n"); + return; + } + + if (unlikely(ecm_ipa_ctx->state != ECM_IPA_CONNECTED_AND_UP)) { + ECM_IPA_DEBUG("dropping Tx-complete pkt, state=%s", + ecm_ipa_state_string(ecm_ipa_ctx->state)); + goto out; + } + + ecm_ipa_ctx->net->stats.tx_packets++; + ecm_ipa_ctx->net->stats.tx_bytes += skb->len; + + atomic_dec(&ecm_ipa_ctx->outstanding_pkts); + if (netif_queue_stopped(ecm_ipa_ctx->net) && + netif_carrier_ok(ecm_ipa_ctx->net) && + atomic_read(&ecm_ipa_ctx->outstanding_pkts) < + (ecm_ipa_ctx->outstanding_low)) { + ECM_IPA_DEBUG("outstanding low (%d) - waking up queue\n", + ecm_ipa_ctx->outstanding_low); + netif_wake_queue(ecm_ipa_ctx->net); + } + +out: + dev_kfree_skb_any(skb); + return; +} + +static void ecm_ipa_tx_timeout(struct net_device *net) +{ + struct ecm_ipa_dev *ecm_ipa_ctx = netdev_priv(net); + + ECM_IPA_ERROR("possible IPA stall was detected, %d outstanding", + atomic_read(&ecm_ipa_ctx->outstanding_pkts)); + + net->stats.tx_errors++; +} + +static int ecm_ipa_debugfs_atomic_open(struct inode *inode, struct file *file) +{ + struct ecm_ipa_dev *ecm_ipa_ctx = inode->i_private; + ECM_IPA_LOG_ENTRY(); + file->private_data = &(ecm_ipa_ctx->outstanding_pkts); + ECM_IPA_LOG_EXIT(); + return 0; +} + +static ssize_t ecm_ipa_debugfs_atomic_read(struct file *file, + char __user *ubuf, size_t count, loff_t *ppos) +{ + int nbytes; + u8 atomic_str[DEBUGFS_TEMP_BUF_SIZE] = {0}; + atomic_t *atomic_var = file->private_data; + nbytes = scnprintf(atomic_str, sizeof(atomic_str), "%d\n", + atomic_read(atomic_var)); + return simple_read_from_buffer(ubuf, count, ppos, atomic_str, nbytes); +} + +#ifdef CONFIG_DEBUG_FS + +static void ecm_ipa_debugfs_init(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + const mode_t flags_read_write = S_IRUGO | S_IWUGO; + const mode_t flags_read_only = S_IRUGO; + struct dentry *file; + + ECM_IPA_LOG_ENTRY(); + + if (!ecm_ipa_ctx) + return; + + ecm_ipa_ctx->directory = debugfs_create_dir("ecm_ipa", NULL); + if (!ecm_ipa_ctx->directory) { + ECM_IPA_ERROR("could not create debugfs directory entry\n"); + goto fail_directory; + } + file = debugfs_create_u8("outstanding_high", flags_read_write, + ecm_ipa_ctx->directory, &ecm_ipa_ctx->outstanding_high); + if (!file) { + ECM_IPA_ERROR("could not create outstanding_high file\n"); + goto fail_file; + } + file = debugfs_create_u8("outstanding_low", flags_read_write, + ecm_ipa_ctx->directory, &ecm_ipa_ctx->outstanding_low); + if (!file) { + ECM_IPA_ERROR("could not create outstanding_low file\n"); + goto fail_file; + } + file = debugfs_create_file("outstanding", flags_read_only, + ecm_ipa_ctx->directory, + ecm_ipa_ctx, &ecm_ipa_debugfs_atomic_ops); + if (!file) { + ECM_IPA_ERROR("could not create outstanding file\n"); + goto fail_file; + } + + ECM_IPA_DEBUG("debugfs entries were created\n"); + ECM_IPA_LOG_EXIT(); + + return; +fail_file: + debugfs_remove_recursive(ecm_ipa_ctx->directory); +fail_directory: + return; +} + +static void ecm_ipa_debugfs_destroy(struct ecm_ipa_dev *ecm_ipa_ctx) +{ + debugfs_remove_recursive(ecm_ipa_ctx->directory); +} + +#else /* !CONFIG_DEBUG_FS*/ + +static void ecm_ipa_debugfs_init(struct ecm_ipa_dev *ecm_ipa_ctx) {} + +static void ecm_ipa_debugfs_destroy(struct ecm_ipa_dev *ecm_ipa_ctx) {} + +#endif /* CONFIG_DEBUG_FS */ + +/** + * ecm_ipa_ep_cfg() - configure the USB endpoints for ECM + * + *usb_to_ipa_hdl: handle received from ipa_connect + *ipa_to_usb_hdl: handle received from ipa_connect + * + * USB to IPA pipe: + * - No de-aggregation + * - Remove Ethernet header + * - SRC NAT + * - Default routing(0) + * IPA to USB Pipe: + * - No aggregation + * - Add Ethernet header + */ +static int ecm_ipa_ep_registers_cfg(u32 usb_to_ipa_hdl, u32 ipa_to_usb_hdl) +{ + int result = 0; + struct ipa_ep_cfg usb_to_ipa_ep_cfg; + struct ipa_ep_cfg ipa_to_usb_ep_cfg; + ECM_IPA_LOG_ENTRY(); + memset(&usb_to_ipa_ep_cfg, 0 , sizeof(struct ipa_ep_cfg)); + usb_to_ipa_ep_cfg.aggr.aggr_en = IPA_BYPASS_AGGR; + usb_to_ipa_ep_cfg.hdr.hdr_len = ETH_HLEN; + usb_to_ipa_ep_cfg.nat.nat_en = IPA_SRC_NAT; + usb_to_ipa_ep_cfg.route.rt_tbl_hdl = 0; + usb_to_ipa_ep_cfg.mode.dst = IPA_CLIENT_A5_LAN_WAN_CONS; + usb_to_ipa_ep_cfg.mode.mode = IPA_BASIC; + result = ipa_cfg_ep(usb_to_ipa_hdl, &usb_to_ipa_ep_cfg); + if (result) { + ECM_IPA_ERROR("failed to configure USB to IPA point\n"); + goto out; + } + memset(&ipa_to_usb_ep_cfg, 0 , sizeof(struct ipa_ep_cfg)); + ipa_to_usb_ep_cfg.aggr.aggr_en = IPA_BYPASS_AGGR; + ipa_to_usb_ep_cfg.hdr.hdr_len = ETH_HLEN; + ipa_to_usb_ep_cfg.nat.nat_en = IPA_BYPASS_NAT; + result = ipa_cfg_ep(ipa_to_usb_hdl, &ipa_to_usb_ep_cfg); + if (result) { + ECM_IPA_ERROR("failed to configure IPA to USB end-point\n"); + goto out; + } + ECM_IPA_DEBUG("end-point registers successfully configured\n"); +out: + ECM_IPA_LOG_EXIT(); + return result; +} + +/** + * ecm_ipa_set_device_ethernet_addr() - set device etherenet address + * @dev_ethaddr: device etherenet address + * + * Returns 0 for success, negative otherwise + */ +static int ecm_ipa_set_device_ethernet_addr(u8 *dev_ethaddr, + u8 device_ethaddr[]) +{ + if (!is_valid_ether_addr(device_ethaddr)) + return -EINVAL; + memcpy(dev_ethaddr, device_ethaddr, ETH_ALEN); + ECM_IPA_DEBUG("device ethernet address: %pM\n", dev_ethaddr); + return 0; +} + +/** ecm_ipa_next_state - return the next state of the driver + * @current_state: the current state of the driver + * @operation: an enum which represent the operation being made on the driver + * by its API. + * + * This function implements the driver internal state machine. + * Its decisions are based on the driver current state and the operation + * being made. + * In case the operation is invalid this state machine will return + * the value ECM_IPA_INVALID to inform the caller for a forbidden sequence. + */ +static enum ecm_ipa_state ecm_ipa_next_state(enum ecm_ipa_state current_state, + enum ecm_ipa_operation operation) +{ + int next_state = ECM_IPA_INVALID; + + switch (current_state) { + case ECM_IPA_UNLOADED: + if (operation == ECM_IPA_INITIALIZE) + next_state = ECM_IPA_INITIALIZED; + break; + case ECM_IPA_INITIALIZED: + if (operation == ECM_IPA_CONNECT) + next_state = ECM_IPA_CONNECTED; + else if (operation == ECM_IPA_OPEN) + next_state = ECM_IPA_UP; + else if (operation == ECM_IPA_CLEANUP) + next_state = ECM_IPA_UNLOADED; + break; + case ECM_IPA_CONNECTED: + if (operation == ECM_IPA_DISCONNECT) + next_state = ECM_IPA_INITIALIZED; + else if (operation == ECM_IPA_OPEN) + next_state = ECM_IPA_CONNECTED_AND_UP; + break; + case ECM_IPA_UP: + if (operation == ECM_IPA_STOP) + next_state = ECM_IPA_INITIALIZED; + else if (operation == ECM_IPA_CONNECT) + next_state = ECM_IPA_CONNECTED_AND_UP; + else if (operation == ECM_IPA_CLEANUP) + next_state = ECM_IPA_UNLOADED; + break; + case ECM_IPA_CONNECTED_AND_UP: + if (operation == ECM_IPA_STOP) + next_state = ECM_IPA_CONNECTED; + else if (operation == ECM_IPA_DISCONNECT) + next_state = ECM_IPA_UP; + break; + default: + ECM_IPA_ERROR("State is not supported\n"); + break; + } + + ECM_IPA_DEBUG("state transition ( %s -> %s )- %s\n", + ecm_ipa_state_string(current_state), + ecm_ipa_state_string(next_state) , + next_state == ECM_IPA_INVALID ? + "Forbidden" : "Allowed"); + + return next_state; +} + +/** + * ecm_ipa_state_string - return the state string representation + * @state: enum which describe the state + */ +static const char *ecm_ipa_state_string(enum ecm_ipa_state state) +{ + switch (state) { + case ECM_IPA_UNLOADED: + return "ECM_IPA_UNLOADED"; + case ECM_IPA_INITIALIZED: + return "ECM_IPA_INITIALIZED"; + case ECM_IPA_CONNECTED: + return "ECM_IPA_CONNECTED"; + case ECM_IPA_UP: + return "ECM_IPA_UP"; + case ECM_IPA_CONNECTED_AND_UP: + return "ECM_IPA_CONNECTED_AND_UP"; + default: + return "Not supported"; + } +} + +/** + * ecm_ipa_init_module() - module initialization + * + */ +static int ecm_ipa_init_module(void) +{ + ECM_IPA_LOG_ENTRY(); + ECM_IPA_LOG_EXIT(); + return 0; +} + +/** + * ecm_ipa_cleanup_module() - module cleanup + * + */ +static void ecm_ipa_cleanup_module(void) +{ + ECM_IPA_LOG_ENTRY(); + ECM_IPA_LOG_EXIT(); + return; +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ECM IPA network interface"); + +late_initcall(ecm_ipa_init_module); +module_exit(ecm_ipa_cleanup_module); diff --git a/drivers/net/ethernet/msm/msm_rmnet_mhi.c b/drivers/net/ethernet/msm/msm_rmnet_mhi.c new file mode 100644 index 000000000000..a342e39b9f43 --- /dev/null +++ b/drivers/net/ethernet/msm/msm_rmnet_mhi.c @@ -0,0 +1,1397 @@ +/* Copyright (c) 2014-2017, 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. + */ +/* + * MHI RMNET Network interface + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/msm_rmnet.h> +#include <linux/if_arp.h> +#include <linux/dma-mapping.h> +#include <linux/msm_mhi.h> +#include <linux/debugfs.h> +#include <linux/ipc_logging.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/of_device.h> +#include <linux/rtnetlink.h> + +#define RMNET_MHI_DRIVER_NAME "rmnet_mhi" +#define MHI_DEFAULT_MTU 8000 +#define MHI_MAX_MRU 0xFFFF +#define MHI_NAPI_WEIGHT_VALUE 12 +#define WATCHDOG_TIMEOUT (30 * HZ) +#define RMNET_IPC_LOG_PAGES (100) +#define IRQ_MASKED_BIT (0) + +enum DBG_LVL { + MSG_VERBOSE = 0x1, + MSG_INFO = 0x2, + MSG_DBG = 0x4, + MSG_WARNING = 0x8, + MSG_ERROR = 0x10, + MSG_CRITICAL = 0x20, + MSG_reserved = 0x80000000 +}; + +struct debug_params { + enum DBG_LVL rmnet_msg_lvl; + enum DBG_LVL rmnet_ipc_log_lvl; + u64 tx_interrupts_count; + u64 rx_interrupts_count; + u64 tx_ring_full_count; + u64 tx_queued_packets_count; + u64 rx_interrupts_in_masked_irq; + u64 rx_napi_skb_burst_min; + u64 rx_napi_skb_burst_max; + u64 tx_cb_skb_free_burst_min; + u64 tx_cb_skb_free_burst_max; + u64 rx_napi_budget_overflow; + u64 rx_fragmentation; +}; + +struct __packed mhi_skb_priv { + dma_addr_t dma_addr; + size_t dma_size; +}; + +#define rmnet_log(rmnet_mhi_ptr, _msg_lvl, _msg, ...) do { \ + if ((_msg_lvl) >= rmnet_mhi_ptr->debug.rmnet_msg_lvl) \ + pr_alert("[%s] " _msg, __func__, ##__VA_ARGS__);\ + if (rmnet_mhi_ptr->rmnet_ipc_log && \ + ((_msg_lvl) >= rmnet_mhi_ptr->debug.rmnet_ipc_log_lvl)) \ + ipc_log_string(rmnet_mhi_ptr->rmnet_ipc_log, \ + "[%s] " _msg, __func__, ##__VA_ARGS__); \ +} while (0) + +struct rmnet_mhi_private { + struct list_head node; + u32 dev_id; + const char *interface_name; + struct mhi_client_handle *tx_client_handle; + struct mhi_client_handle *rx_client_handle; + enum MHI_CLIENT_CHANNEL tx_channel; + enum MHI_CLIENT_CHANNEL rx_channel; + struct sk_buff_head tx_buffers; + struct sk_buff_head rx_buffers; + atomic_t rx_pool_len; + u32 mru; + u32 max_mru; + u32 max_mtu; + struct napi_struct napi; + gfp_t allocation_flags; + uint32_t tx_buffers_max; + uint32_t rx_buffers_max; + u32 alloc_fail; + u32 tx_enabled; + u32 rx_enabled; + u32 mhi_enabled; + struct platform_device *pdev; + struct net_device *dev; + unsigned long flags; + int wake_count; + spinlock_t out_chan_full_lock; /* tx queue lock */ + struct sk_buff *frag_skb; + struct work_struct alloc_work; + /* lock to queue hardware and internal queue */ + spinlock_t alloc_lock; + void *rmnet_ipc_log; + rwlock_t pm_lock; /* state change lock */ + struct debug_params debug; + struct dentry *dentry; +}; + +static LIST_HEAD(rmnet_mhi_ctxt_list); +static struct platform_driver rmnet_mhi_driver; + +static int rmnet_mhi_process_fragment(struct rmnet_mhi_private *rmnet_mhi_ptr, + struct sk_buff *skb, int frag) +{ + struct sk_buff *temp_skb; + if (rmnet_mhi_ptr->frag_skb) { + /* Merge the new skb into the old fragment */ + temp_skb = skb_copy_expand(rmnet_mhi_ptr->frag_skb, + 0, + skb->len, + GFP_ATOMIC); + if (!temp_skb) { + kfree(rmnet_mhi_ptr->frag_skb); + rmnet_mhi_ptr->frag_skb = NULL; + return -ENOMEM; + } + dev_kfree_skb_any(rmnet_mhi_ptr->frag_skb); + rmnet_mhi_ptr->frag_skb = temp_skb; + memcpy(skb_put(rmnet_mhi_ptr->frag_skb, skb->len), + skb->data, + skb->len); + dev_kfree_skb_any(skb); + if (!frag) { + /* Last fragmented piece was received, ship it */ + netif_receive_skb(rmnet_mhi_ptr->frag_skb); + rmnet_mhi_ptr->frag_skb = NULL; + } + } else { + if (frag) { + /* This is the first fragment */ + rmnet_mhi_ptr->frag_skb = skb; + rmnet_mhi_ptr->debug.rx_fragmentation++; + } else { + netif_receive_skb(skb); + } + } + return 0; +} +static void rmnet_mhi_internal_clean_unmap_buffers(struct net_device *dev, + struct sk_buff_head *queue, + enum dma_data_direction dir) +{ + struct mhi_skb_priv *skb_priv; + struct rmnet_mhi_private *rmnet_mhi_ptr = + *(struct rmnet_mhi_private **)netdev_priv(dev); + + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Entered\n"); + while (!skb_queue_empty(queue)) { + struct sk_buff *skb = skb_dequeue(queue); + skb_priv = (struct mhi_skb_priv *)(skb->cb); + if (skb != 0) { + kfree_skb(skb); + } + } + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Exited\n"); +} + +static __be16 rmnet_mhi_ip_type_trans(struct sk_buff *skb) +{ + __be16 protocol = 0; + + /* Determine L3 protocol */ + switch (skb->data[0] & 0xf0) { + case 0x40: + protocol = htons(ETH_P_IP); + break; + case 0x60: + protocol = htons(ETH_P_IPV6); + break; + default: + /* Default is QMAP */ + protocol = htons(ETH_P_MAP); + break; + } + return protocol; +} + +static int rmnet_alloc_rx(struct rmnet_mhi_private *rmnet_mhi_ptr, + gfp_t alloc_flags) +{ + u32 cur_mru = rmnet_mhi_ptr->mru; + struct mhi_skb_priv *skb_priv; + int ret; + struct sk_buff *skb; + + while (atomic_read(&rmnet_mhi_ptr->rx_pool_len) < + rmnet_mhi_ptr->rx_buffers_max) { + skb = alloc_skb(cur_mru, alloc_flags); + if (!skb) { + rmnet_log(rmnet_mhi_ptr, + MSG_INFO, + "SKB Alloc failed with flags:0x%x\n", + alloc_flags); + return -ENOMEM; + } + skb_priv = (struct mhi_skb_priv *)(skb->cb); + skb_priv->dma_size = cur_mru; + skb_priv->dma_addr = 0; + + /* These steps must be in atomic context */ + spin_lock_bh(&rmnet_mhi_ptr->alloc_lock); + + /* It's possible by the time alloc_skb (GFP_KERNEL) + * returns we already called rmnet_alloc_rx + * in atomic context and allocated memory using + * GFP_ATOMIC and returned. + */ + if (unlikely(atomic_read(&rmnet_mhi_ptr->rx_pool_len) >= + rmnet_mhi_ptr->rx_buffers_max)) { + spin_unlock_bh(&rmnet_mhi_ptr->alloc_lock); + dev_kfree_skb_any(skb); + return 0; + } + + read_lock_bh(&rmnet_mhi_ptr->pm_lock); + if (unlikely(!rmnet_mhi_ptr->mhi_enabled)) { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "!interface is disabled\n"); + dev_kfree_skb_any(skb); + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + spin_unlock_bh(&rmnet_mhi_ptr->alloc_lock); + return -EIO; + } + + ret = mhi_queue_xfer(rmnet_mhi_ptr->rx_client_handle, + skb->data, + skb_priv->dma_size, + MHI_EOT); + if (unlikely(ret != 0)) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "mhi_queue_xfer failed, error %d", ret); + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + spin_unlock_bh(&rmnet_mhi_ptr->alloc_lock); + dev_kfree_skb_any(skb); + return ret; + } + skb_queue_tail(&rmnet_mhi_ptr->rx_buffers, skb); + atomic_inc(&rmnet_mhi_ptr->rx_pool_len); + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + spin_unlock_bh(&rmnet_mhi_ptr->alloc_lock); + } + + return 0; +} + +static void rmnet_mhi_alloc_work(struct work_struct *work) +{ + struct rmnet_mhi_private *rmnet_mhi_ptr = container_of(work, + struct rmnet_mhi_private, + alloc_work); + int ret; + /* sleep about 1 sec and retry, that should be enough time + * for system to reclaim freed memory back. + */ + const int sleep_ms = 1000; + int retry = 60; + + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Entered\n"); + do { + ret = rmnet_alloc_rx(rmnet_mhi_ptr, + rmnet_mhi_ptr->allocation_flags); + /* sleep and try again */ + if (ret == -ENOMEM) { + msleep(sleep_ms); + retry--; + } + } while (ret == -ENOMEM && retry); + + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Exit with status:%d retry:%d\n", + ret, retry); +} + +static int rmnet_mhi_poll(struct napi_struct *napi, int budget) +{ + int received_packets = 0; + struct net_device *dev = napi->dev; + struct rmnet_mhi_private *rmnet_mhi_ptr = + *(struct rmnet_mhi_private **)netdev_priv(dev); + int res = 0; + bool should_reschedule = true; + struct sk_buff *skb; + struct mhi_skb_priv *skb_priv; + int r; + + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Entered\n"); + + read_lock_bh(&rmnet_mhi_ptr->pm_lock); + if (unlikely(!rmnet_mhi_ptr->mhi_enabled)) { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "interface is disabled!\n"); + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + return 0; + } + while (received_packets < budget) { + struct mhi_result *result = + mhi_poll(rmnet_mhi_ptr->rx_client_handle); + if (result->transaction_status == -ENOTCONN) { + rmnet_log(rmnet_mhi_ptr, + MSG_INFO, + "Transaction status not ready, continuing\n"); + break; + } else if (result->transaction_status != 0 && + result->transaction_status != -EOVERFLOW) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "mhi_poll failed, error %d\n", + result->transaction_status); + break; + } + + /* Nothing more to read, or out of buffers in MHI layer */ + if (unlikely(!result->buf_addr || !result->bytes_xferd)) { + should_reschedule = false; + break; + } + + atomic_dec(&rmnet_mhi_ptr->rx_pool_len); + skb = skb_dequeue(&(rmnet_mhi_ptr->rx_buffers)); + if (unlikely(!skb)) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "No RX buffers to match"); + break; + } + + skb_priv = (struct mhi_skb_priv *)(skb->cb); + + /* Setup the tail to the end of data */ + skb_put(skb, result->bytes_xferd); + + skb->dev = dev; + skb->protocol = rmnet_mhi_ip_type_trans(skb); + + if (result->transaction_status == -EOVERFLOW) + r = rmnet_mhi_process_fragment(rmnet_mhi_ptr, skb, 1); + else + r = rmnet_mhi_process_fragment(rmnet_mhi_ptr, skb, 0); + if (r) { + rmnet_log(rmnet_mhi_ptr, MSG_CRITICAL, + "Failed to process fragmented packet ret %d", + r); + BUG(); + } + + /* Statistics */ + received_packets++; + dev->stats.rx_packets++; + dev->stats.rx_bytes += result->bytes_xferd; + + } /* while (received_packets < budget) or any other error */ + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + + /* Queue new buffers */ + res = rmnet_alloc_rx(rmnet_mhi_ptr, GFP_ATOMIC); + + read_lock_bh(&rmnet_mhi_ptr->pm_lock); + if (likely(rmnet_mhi_ptr->mhi_enabled)) { + if (res == -ENOMEM) { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "out of mem, queuing bg worker\n"); + rmnet_mhi_ptr->alloc_fail++; + schedule_work(&rmnet_mhi_ptr->alloc_work); + } + + napi_complete(napi); + + /* We got a NULL descriptor back */ + if (!should_reschedule) { + if (test_and_clear_bit(IRQ_MASKED_BIT, + &rmnet_mhi_ptr->flags)) + mhi_unmask_irq(rmnet_mhi_ptr->rx_client_handle); + mhi_set_lpm(rmnet_mhi_ptr->rx_client_handle, true); + rmnet_mhi_ptr->wake_count--; + } else { + if (received_packets == budget) + rmnet_mhi_ptr->debug.rx_napi_budget_overflow++; + napi_reschedule(napi); + } + + rmnet_mhi_ptr->debug.rx_napi_skb_burst_min = + min((u64)received_packets, + rmnet_mhi_ptr->debug.rx_napi_skb_burst_min); + + rmnet_mhi_ptr->debug.rx_napi_skb_burst_max = + max((u64)received_packets, + rmnet_mhi_ptr->debug.rx_napi_skb_burst_max); + } + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, + "Exited, polled %d pkts\n", received_packets); + return received_packets; +} + +static int rmnet_mhi_init_inbound(struct rmnet_mhi_private *rmnet_mhi_ptr) +{ + int res; + + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Entered\n"); + rmnet_mhi_ptr->tx_buffers_max = mhi_get_max_desc( + rmnet_mhi_ptr->tx_client_handle); + rmnet_mhi_ptr->rx_buffers_max = mhi_get_max_desc( + rmnet_mhi_ptr->rx_client_handle); + atomic_set(&rmnet_mhi_ptr->rx_pool_len, 0); + res = rmnet_alloc_rx(rmnet_mhi_ptr, + rmnet_mhi_ptr->allocation_flags); + + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Exited with %d\n", res); + return res; +} + +static void rmnet_mhi_tx_cb(struct mhi_result *result) +{ + struct net_device *dev; + struct rmnet_mhi_private *rmnet_mhi_ptr; + unsigned long burst_counter = 0; + unsigned long flags, pm_flags; + + rmnet_mhi_ptr = result->user_data; + dev = rmnet_mhi_ptr->dev; + rmnet_mhi_ptr->debug.tx_interrupts_count++; + + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Entered\n"); + if (!result->buf_addr || !result->bytes_xferd) + return; + /* Free the buffers which are TX'd up to the provided address */ + while (!skb_queue_empty(&(rmnet_mhi_ptr->tx_buffers))) { + struct sk_buff *skb = + skb_dequeue(&(rmnet_mhi_ptr->tx_buffers)); + if (!skb) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "NULL buffer returned, error"); + break; + } else { + if (skb->data == result->buf_addr) { + dev_kfree_skb_any(skb); + break; + } + dev_kfree_skb_any(skb); + burst_counter++; + + /* Update statistics */ + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + + /* The payload is expected to be the phy addr. + Comparing to see if it's the last skb to + replenish + */ + } + } /* While TX queue is not empty */ + + rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_min = + min((u64)burst_counter, + rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_min); + + rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_max = + max((u64)burst_counter, + rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_max); + + /* In case we couldn't write again, now we can! */ + read_lock_irqsave(&rmnet_mhi_ptr->pm_lock, pm_flags); + if (likely(rmnet_mhi_ptr->mhi_enabled)) { + spin_lock_irqsave(&rmnet_mhi_ptr->out_chan_full_lock, flags); + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Waking up queue\n"); + netif_wake_queue(dev); + spin_unlock_irqrestore(&rmnet_mhi_ptr->out_chan_full_lock, + flags); + } + read_unlock_irqrestore(&rmnet_mhi_ptr->pm_lock, pm_flags); + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Exited\n"); +} + +static void rmnet_mhi_rx_cb(struct mhi_result *result) +{ + struct net_device *dev; + struct rmnet_mhi_private *rmnet_mhi_ptr; + unsigned long flags; + + rmnet_mhi_ptr = result->user_data; + dev = rmnet_mhi_ptr->dev; + + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Entered\n"); + rmnet_mhi_ptr->debug.rx_interrupts_count++; + read_lock_irqsave(&rmnet_mhi_ptr->pm_lock, flags); + if (likely(rmnet_mhi_ptr->mhi_enabled)) { + if (napi_schedule_prep(&rmnet_mhi_ptr->napi)) { + if (!test_and_set_bit(IRQ_MASKED_BIT, + &rmnet_mhi_ptr->flags)) + mhi_mask_irq(rmnet_mhi_ptr->rx_client_handle); + mhi_set_lpm(rmnet_mhi_ptr->rx_client_handle, false); + rmnet_mhi_ptr->wake_count++; + __napi_schedule(&rmnet_mhi_ptr->napi); + } else { + rmnet_mhi_ptr->debug.rx_interrupts_in_masked_irq++; + } + } + read_unlock_irqrestore(&rmnet_mhi_ptr->pm_lock, flags); + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Exited\n"); +} + +static int rmnet_mhi_open(struct net_device *dev) +{ + struct rmnet_mhi_private *rmnet_mhi_ptr = + *(struct rmnet_mhi_private **)netdev_priv(dev); + + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "Opened net dev interface for MHI chans %d and %d\n", + rmnet_mhi_ptr->tx_channel, + rmnet_mhi_ptr->rx_channel); + + /* tx queue may not necessarily be stopped already + * so stop the queue if tx path is not enabled + */ + if (!rmnet_mhi_ptr->tx_client_handle) + netif_stop_queue(dev); + else + netif_start_queue(dev); + + /* Poll to check if any buffers are accumulated in the + * transport buffers + */ + read_lock_bh(&rmnet_mhi_ptr->pm_lock); + if (likely(rmnet_mhi_ptr->mhi_enabled)) { + if (napi_schedule_prep(&rmnet_mhi_ptr->napi)) { + if (!test_and_set_bit(IRQ_MASKED_BIT, + &rmnet_mhi_ptr->flags)) { + mhi_mask_irq(rmnet_mhi_ptr->rx_client_handle); + } + mhi_set_lpm(rmnet_mhi_ptr->rx_client_handle, false); + rmnet_mhi_ptr->wake_count++; + __napi_schedule(&rmnet_mhi_ptr->napi); + } else { + rmnet_mhi_ptr->debug.rx_interrupts_in_masked_irq++; + } + } + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + return 0; + +} + +static int rmnet_mhi_disable(struct rmnet_mhi_private *rmnet_mhi_ptr) +{ + napi_disable(&(rmnet_mhi_ptr->napi)); + rmnet_mhi_ptr->rx_enabled = 0; + rmnet_mhi_internal_clean_unmap_buffers(rmnet_mhi_ptr->dev, + &rmnet_mhi_ptr->rx_buffers, + DMA_FROM_DEVICE); + if (test_and_clear_bit(IRQ_MASKED_BIT, &rmnet_mhi_ptr->flags)) + mhi_unmask_irq(rmnet_mhi_ptr->rx_client_handle); + + return 0; +} + +static int rmnet_mhi_stop(struct net_device *dev) +{ + struct rmnet_mhi_private *rmnet_mhi_ptr = + *(struct rmnet_mhi_private **)netdev_priv(dev); + + netif_stop_queue(dev); + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Entered\n"); + if (test_and_clear_bit(IRQ_MASKED_BIT, &rmnet_mhi_ptr->flags)) { + mhi_unmask_irq(rmnet_mhi_ptr->rx_client_handle); + rmnet_log(rmnet_mhi_ptr, MSG_ERROR, + "IRQ was masked, unmasking...\n"); + } + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Exited\n"); + return 0; +} + +static int rmnet_mhi_change_mtu(struct net_device *dev, int new_mtu) +{ + struct rmnet_mhi_private *rmnet_mhi_ptr = + *(struct rmnet_mhi_private **)netdev_priv(dev); + + if (new_mtu < 0 || rmnet_mhi_ptr->max_mtu < new_mtu) + return -EINVAL; + + dev->mtu = new_mtu; + return 0; +} + +static int rmnet_mhi_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_mhi_private *rmnet_mhi_ptr = + *(struct rmnet_mhi_private **)netdev_priv(dev); + int res = 0; + unsigned long flags; + struct mhi_skb_priv *tx_priv; + + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, + "Entered chan %d\n", rmnet_mhi_ptr->tx_channel); + + tx_priv = (struct mhi_skb_priv *)(skb->cb); + tx_priv->dma_size = skb->len; + tx_priv->dma_addr = 0; + read_lock_bh(&rmnet_mhi_ptr->pm_lock); + if (unlikely(!rmnet_mhi_ptr->mhi_enabled)) { + /* Only reason interface could be disabled and we get data + * is due to an SSR. We do not want to stop the queue and + * return error. instead we will flush all the uplink packets + * and return successful + */ + res = NETDEV_TX_OK; + dev_kfree_skb_any(skb); + goto mhi_xmit_exit; + } + + if (mhi_get_free_desc(rmnet_mhi_ptr->tx_client_handle) <= 0) { + rmnet_log(rmnet_mhi_ptr, + MSG_VERBOSE, + "Stopping Queue\n"); + spin_lock_irqsave(&rmnet_mhi_ptr->out_chan_full_lock, + flags); + rmnet_mhi_ptr->debug.tx_ring_full_count++; + netif_stop_queue(dev); + spin_unlock_irqrestore(&rmnet_mhi_ptr->out_chan_full_lock, + flags); + res = NETDEV_TX_BUSY; + goto mhi_xmit_exit; + } + res = mhi_queue_xfer(rmnet_mhi_ptr->tx_client_handle, + skb->data, + skb->len, + MHI_EOT); + + if (res != 0) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "Failed to queue with reason:%d\n", + res); + spin_lock_irqsave(&rmnet_mhi_ptr->out_chan_full_lock, + flags); + netif_stop_queue(dev); + spin_unlock_irqrestore(&rmnet_mhi_ptr->out_chan_full_lock, + flags); + res = NETDEV_TX_BUSY; + goto mhi_xmit_exit; + } + res = NETDEV_TX_OK; + skb_queue_tail(&(rmnet_mhi_ptr->tx_buffers), skb); + dev->trans_start = jiffies; + rmnet_mhi_ptr->debug.tx_queued_packets_count++; +mhi_xmit_exit: + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + rmnet_log(rmnet_mhi_ptr, MSG_VERBOSE, "Exited\n"); + return res; +} + +static int rmnet_mhi_ioctl_extended(struct net_device *dev, struct ifreq *ifr) +{ + struct rmnet_ioctl_extended_s ext_cmd; + int rc = 0; + struct rmnet_mhi_private *rmnet_mhi_ptr = + *(struct rmnet_mhi_private **)netdev_priv(dev); + + + rc = copy_from_user(&ext_cmd, ifr->ifr_ifru.ifru_data, + sizeof(struct rmnet_ioctl_extended_s)); + + if (rc) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "copy_from_user failed ,error %d", + rc); + return rc; + } + + switch (ext_cmd.extended_ioctl) { + case RMNET_IOCTL_SET_MRU: + if (!ext_cmd.u.data || + ext_cmd.u.data > rmnet_mhi_ptr->max_mru) { + rmnet_log(rmnet_mhi_ptr, MSG_CRITICAL, + "Can't set MRU, value:%u is invalid max:%u\n", + ext_cmd.u.data, rmnet_mhi_ptr->max_mru); + return -EINVAL; + } + rmnet_log(rmnet_mhi_ptr, + MSG_INFO, + "MRU change request to 0x%x\n", + ext_cmd.u.data); + rmnet_mhi_ptr->mru = ext_cmd.u.data; + break; + case RMNET_IOCTL_GET_EPID: + ext_cmd.u.data = + mhi_get_epid(rmnet_mhi_ptr->tx_client_handle); + break; + case RMNET_IOCTL_GET_SUPPORTED_FEATURES: + ext_cmd.u.data = 0; + break; + case RMNET_IOCTL_GET_DRIVER_NAME: + strlcpy(ext_cmd.u.if_name, rmnet_mhi_ptr->interface_name, + sizeof(ext_cmd.u.if_name)); + break; + case RMNET_IOCTL_SET_SLEEP_STATE: + read_lock_bh(&rmnet_mhi_ptr->pm_lock); + if (rmnet_mhi_ptr->mhi_enabled && + rmnet_mhi_ptr->tx_client_handle != NULL) { + rmnet_mhi_ptr->wake_count += (ext_cmd.u.data) ? -1 : 1; + mhi_set_lpm(rmnet_mhi_ptr->tx_client_handle, + ext_cmd.u.data); + } else { + rmnet_log(rmnet_mhi_ptr, MSG_ERROR, + "Cannot set LPM value, MHI is not up.\n"); + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + return -ENODEV; + } + read_unlock_bh(&rmnet_mhi_ptr->pm_lock); + break; + default: + rc = -EINVAL; + break; + } + + rc = copy_to_user(ifr->ifr_ifru.ifru_data, &ext_cmd, + sizeof(struct rmnet_ioctl_extended_s)); + + if (rc) + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "copy_to_user failed, error %d\n", + rc); + + return rc; +} + +static int rmnet_mhi_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + int rc = 0; + struct rmnet_ioctl_data_s ioctl_data; + + switch (cmd) { + case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol */ + break; + case RMNET_IOCTL_GET_LLP: /* Get link protocol state */ + ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP; + if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, + sizeof(struct rmnet_ioctl_data_s))) + rc = -EFAULT; + break; + case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */ + ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP; + if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, + sizeof(struct rmnet_ioctl_data_s))) + rc = -EFAULT; + break; + case RMNET_IOCTL_SET_QOS_ENABLE: + rc = -EINVAL; + break; + case RMNET_IOCTL_SET_QOS_DISABLE: + rc = 0; + break; + case RMNET_IOCTL_OPEN: + case RMNET_IOCTL_CLOSE: + /* We just ignore them and return success */ + rc = 0; + break; + case RMNET_IOCTL_EXTENDED: + rc = rmnet_mhi_ioctl_extended(dev, ifr); + break; + default: + /* Don't fail any IOCTL right now */ + rc = 0; + break; + } + + return rc; +} + +static const struct net_device_ops rmnet_mhi_ops_ip = { + .ndo_open = rmnet_mhi_open, + .ndo_stop = rmnet_mhi_stop, + .ndo_start_xmit = rmnet_mhi_xmit, + .ndo_do_ioctl = rmnet_mhi_ioctl, + .ndo_change_mtu = rmnet_mhi_change_mtu, + .ndo_set_mac_address = 0, + .ndo_validate_addr = 0, +}; + +static void rmnet_mhi_setup(struct net_device *dev) +{ + dev->netdev_ops = &rmnet_mhi_ops_ip; + ether_setup(dev); + + /* set this after calling ether_setup */ + dev->header_ops = 0; /* No header */ + dev->type = ARPHRD_RAWIP; + dev->hard_header_len = 0; + dev->mtu = MHI_DEFAULT_MTU; + dev->addr_len = 0; + dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + dev->watchdog_timeo = WATCHDOG_TIMEOUT; +} + +static int rmnet_mhi_enable_iface(struct rmnet_mhi_private *rmnet_mhi_ptr) +{ + int ret = 0; + struct rmnet_mhi_private **rmnet_mhi_ctxt = NULL; + int r = 0; + char ifalias[IFALIASZ]; + char ifname[IFNAMSIZ]; + struct mhi_client_handle *client_handle = NULL; + + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Entered.\n"); + + rmnet_mhi_ptr->debug.tx_interrupts_count = 0; + rmnet_mhi_ptr->debug.rx_interrupts_count = 0; + rmnet_mhi_ptr->debug.rx_interrupts_in_masked_irq = 0; + rmnet_mhi_ptr->debug.rx_napi_skb_burst_min = 0; + rmnet_mhi_ptr->debug.rx_napi_skb_burst_max = 0; + rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_min = 0; + rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_max = 0; + rmnet_mhi_ptr->debug.tx_ring_full_count = 0; + rmnet_mhi_ptr->debug.tx_queued_packets_count = 0; + rmnet_mhi_ptr->debug.rx_napi_budget_overflow = 0; + rmnet_mhi_ptr->debug.rx_napi_skb_burst_min = UINT_MAX; + rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_min = UINT_MAX; + + skb_queue_head_init(&(rmnet_mhi_ptr->tx_buffers)); + skb_queue_head_init(&(rmnet_mhi_ptr->rx_buffers)); + + if (rmnet_mhi_ptr->tx_client_handle != NULL) { + rmnet_log(rmnet_mhi_ptr, + MSG_INFO, + "Opening TX channel\n"); + r = mhi_open_channel(rmnet_mhi_ptr->tx_client_handle); + if (r != 0) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "Failed to start TX chan ret %d\n", + r); + goto mhi_tx_chan_start_fail; + } + + client_handle = rmnet_mhi_ptr->tx_client_handle; + } + if (rmnet_mhi_ptr->rx_client_handle != NULL) { + rmnet_log(rmnet_mhi_ptr, + MSG_INFO, + "Opening RX channel\n"); + r = mhi_open_channel(rmnet_mhi_ptr->rx_client_handle); + if (r != 0) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "Failed to start RX chan ret %d\n", + r); + goto mhi_rx_chan_start_fail; + } + /* Both tx & rx client handle contain same device info */ + client_handle = rmnet_mhi_ptr->rx_client_handle; + } + + if (!client_handle) { + ret = -EINVAL; + goto net_dev_alloc_fail; + } + + + if (!rmnet_mhi_ptr->dev) { + snprintf(ifalias, sizeof(ifalias), + "%s_%04x_%02u.%02u.%02u_%u", + rmnet_mhi_ptr->interface_name, + client_handle->dev_id, + client_handle->domain, + client_handle->bus, + client_handle->slot, + rmnet_mhi_ptr->dev_id); + + snprintf(ifname, sizeof(ifname), "%s%%d", + rmnet_mhi_ptr->interface_name); + + rtnl_lock(); + rmnet_mhi_ptr->dev = alloc_netdev( + sizeof(struct rmnet_mhi_private *), + ifname, NET_NAME_PREDICTABLE, rmnet_mhi_setup); + + if (!rmnet_mhi_ptr->dev) { + rmnet_log(rmnet_mhi_ptr, MSG_CRITICAL, + "Network device allocation failed\n"); + ret = -ENOMEM; + goto net_dev_alloc_fail; + } + SET_NETDEV_DEV(rmnet_mhi_ptr->dev, &rmnet_mhi_ptr->pdev->dev); + dev_set_alias(rmnet_mhi_ptr->dev, ifalias, strlen(ifalias)); + rmnet_mhi_ctxt = netdev_priv(rmnet_mhi_ptr->dev); + rtnl_unlock(); + *rmnet_mhi_ctxt = rmnet_mhi_ptr; + + ret = dma_set_mask(&rmnet_mhi_ptr->dev->dev, MHI_DMA_MASK); + if (ret) + rmnet_mhi_ptr->allocation_flags = GFP_KERNEL; + else + rmnet_mhi_ptr->allocation_flags = GFP_DMA; + + netif_napi_add(rmnet_mhi_ptr->dev, &rmnet_mhi_ptr->napi, + rmnet_mhi_poll, MHI_NAPI_WEIGHT_VALUE); + + ret = register_netdev(rmnet_mhi_ptr->dev); + if (ret) { + rmnet_log(rmnet_mhi_ptr, MSG_CRITICAL, + "Network device registration failed\n"); + goto net_dev_reg_fail; + } + } + + write_lock_irq(&rmnet_mhi_ptr->pm_lock); + rmnet_mhi_ptr->mhi_enabled = 1; + write_unlock_irq(&rmnet_mhi_ptr->pm_lock); + + r = rmnet_mhi_init_inbound(rmnet_mhi_ptr); + if (r) { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "Failed to init inbound ret %d\n", r); + } + + napi_enable(&(rmnet_mhi_ptr->napi)); + + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Exited.\n"); + + return 0; + +net_dev_reg_fail: + netif_napi_del(&(rmnet_mhi_ptr->napi)); + free_netdev(rmnet_mhi_ptr->dev); +net_dev_alloc_fail: + if (rmnet_mhi_ptr->rx_client_handle) { + mhi_close_channel(rmnet_mhi_ptr->rx_client_handle); + rmnet_mhi_ptr->dev = NULL; + } +mhi_rx_chan_start_fail: + if (rmnet_mhi_ptr->tx_client_handle) + mhi_close_channel(rmnet_mhi_ptr->tx_client_handle); +mhi_tx_chan_start_fail: + rmnet_log(rmnet_mhi_ptr, MSG_INFO, "Exited ret %d.\n", ret); + return ret; +} + +static void rmnet_mhi_cb(struct mhi_cb_info *cb_info) +{ + struct rmnet_mhi_private *rmnet_mhi_ptr; + struct mhi_result *result; + char ifalias[IFALIASZ]; + int r = 0; + + if (!cb_info || !cb_info->result) { + pr_err("%s: Invalid data in MHI callback\n", __func__); + return; + } + + result = cb_info->result; + rmnet_mhi_ptr = result->user_data; + + switch (cb_info->cb_reason) { + case MHI_CB_MHI_DISABLED: + case MHI_CB_MHI_SHUTDOWN: + case MHI_CB_SYS_ERROR: + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "Got MHI_SYS_ERROR notification. Stopping stack\n"); + + /* Disable interface on first notification. Long + * as we set mhi_enabled = 0, we gurantee rest of + * driver will not touch any critical data. + */ + snprintf(ifalias, sizeof(ifalias), "%s", "unidentified_netdev"); + write_lock_irq(&rmnet_mhi_ptr->pm_lock); + rmnet_mhi_ptr->mhi_enabled = 0; + write_unlock_irq(&rmnet_mhi_ptr->pm_lock); + /* Set unidentified_net_dev string to ifalias + * on error notification + */ + rtnl_lock(); + dev_set_alias(rmnet_mhi_ptr->dev, ifalias, strlen(ifalias)); + rtnl_unlock(); + + if (cb_info->chan == rmnet_mhi_ptr->rx_channel) { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "Receive MHI_DISABLE notification for rx path\n"); + if (rmnet_mhi_ptr->dev) + rmnet_mhi_disable(rmnet_mhi_ptr); + } else { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "Receive MHI_DISABLE notification for tx path\n"); + rmnet_mhi_ptr->tx_enabled = 0; + if (rmnet_mhi_ptr->dev) + rmnet_mhi_internal_clean_unmap_buffers( + rmnet_mhi_ptr->dev, + &rmnet_mhi_ptr->tx_buffers, + DMA_TO_DEVICE); + } + + /* Remove all votes disabling low power mode */ + if (!rmnet_mhi_ptr->tx_enabled && !rmnet_mhi_ptr->rx_enabled) { + struct mhi_client_handle *handle = + rmnet_mhi_ptr->rx_client_handle; + + if (!handle) + handle = rmnet_mhi_ptr->tx_client_handle; + while (rmnet_mhi_ptr->wake_count) { + mhi_set_lpm(handle, true); + rmnet_mhi_ptr->wake_count--; + } + } + break; + case MHI_CB_MHI_ENABLED: + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "Got MHI_ENABLED notification. Starting stack\n"); + if (cb_info->chan == rmnet_mhi_ptr->rx_channel) + rmnet_mhi_ptr->rx_enabled = 1; + else + rmnet_mhi_ptr->tx_enabled = 1; + + if ((rmnet_mhi_ptr->tx_enabled && rmnet_mhi_ptr->rx_enabled) || + (rmnet_mhi_ptr->tx_enabled && + !rmnet_mhi_ptr->rx_client_handle) || + (rmnet_mhi_ptr->rx_enabled && + !rmnet_mhi_ptr->tx_client_handle)) { + rmnet_log(rmnet_mhi_ptr, + MSG_INFO, + "enabling iface.\n"); + r = rmnet_mhi_enable_iface(rmnet_mhi_ptr); + if (r) + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "Failed to enable iface for chan %d\n", + cb_info->chan); + else + rmnet_log(rmnet_mhi_ptr, + MSG_INFO, + "Enabled iface for chan %d\n", + cb_info->chan); + } + break; + case MHI_CB_XFER: + if (cb_info->chan == rmnet_mhi_ptr->rx_channel) + rmnet_mhi_rx_cb(cb_info->result); + else + rmnet_mhi_tx_cb(cb_info->result); + break; + default: + break; + } +} + +#ifdef CONFIG_DEBUG_FS +struct dentry *dentry; + +static void rmnet_mhi_create_debugfs(struct rmnet_mhi_private *rmnet_mhi_ptr) +{ + char node_name[32]; + int i; + const umode_t mode = (S_IRUSR | S_IWUSR); + struct dentry *file; + struct mhi_client_handle *client_handle; + + const struct { + char *name; + u64 *ptr; + } debugfs_table[] = { + { + "tx_interrupts_count", + &rmnet_mhi_ptr->debug.tx_interrupts_count + }, + { + "rx_interrupts_count", + &rmnet_mhi_ptr->debug.rx_interrupts_count + }, + { + "tx_ring_full_count", + &rmnet_mhi_ptr->debug.tx_ring_full_count + }, + { + "tx_queued_packets_count", + &rmnet_mhi_ptr->debug.tx_queued_packets_count + }, + { + "rx_interrupts_in_masked_irq", + &rmnet_mhi_ptr-> + debug.rx_interrupts_in_masked_irq + }, + { + "rx_napi_skb_burst_min", + &rmnet_mhi_ptr->debug.rx_napi_skb_burst_min + }, + { + "rx_napi_skb_burst_max", + &rmnet_mhi_ptr->debug.rx_napi_skb_burst_max + }, + { + "tx_cb_skb_free_burst_min", + &rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_min + }, + { + "tx_cb_skb_free_burst_max", + &rmnet_mhi_ptr->debug.tx_cb_skb_free_burst_max + }, + { + "rx_napi_budget_overflow", + &rmnet_mhi_ptr->debug.rx_napi_budget_overflow + }, + { + "rx_fragmentation", + &rmnet_mhi_ptr->debug.rx_fragmentation + }, + { + NULL, NULL + }, + }; + + /* Both tx & rx client handle contain same device info */ + client_handle = rmnet_mhi_ptr->rx_client_handle; + if (!client_handle) + client_handle = rmnet_mhi_ptr->tx_client_handle; + + snprintf(node_name, + sizeof(node_name), + "%s_%04x_%02u.%02u.%02u_%u", + rmnet_mhi_ptr->interface_name, + client_handle->dev_id, + client_handle->domain, + client_handle->bus, + client_handle->slot, + rmnet_mhi_ptr->dev_id); + + if (IS_ERR_OR_NULL(dentry)) + return; + + rmnet_mhi_ptr->dentry = debugfs_create_dir(node_name, dentry); + if (IS_ERR_OR_NULL(rmnet_mhi_ptr->dentry)) + return; + + file = debugfs_create_u32("msg_lvl", + mode, + rmnet_mhi_ptr->dentry, + (u32 *)&rmnet_mhi_ptr->debug.rmnet_msg_lvl); + if (IS_ERR_OR_NULL(file)) + return; + + file = debugfs_create_u32("ipc_log_lvl", + mode, + rmnet_mhi_ptr->dentry, + (u32 *)&rmnet_mhi_ptr-> + debug.rmnet_ipc_log_lvl); + if (IS_ERR_OR_NULL(file)) + return; + + file = debugfs_create_u32("mru", + mode, + rmnet_mhi_ptr->dentry, + &rmnet_mhi_ptr->mru); + if (IS_ERR_OR_NULL(file)) + return; + + /* Add debug stats table */ + for (i = 0; debugfs_table[i].name; i++) { + file = debugfs_create_u64(debugfs_table[i].name, + mode, + rmnet_mhi_ptr->dentry, + debugfs_table[i].ptr); + if (IS_ERR_OR_NULL(file)) + return; + } +} + +static void rmnet_mhi_create_debugfs_dir(void) +{ + dentry = debugfs_create_dir(RMNET_MHI_DRIVER_NAME, 0); +} +#else +static void rmnet_mhi_create_debugfs(struct rmnet_mhi_private *rmnet_mhi_ptr) +{ +} + +static void rmnet_mhi_create_debugfs_dir(void) +{ +} +#endif + +static int rmnet_mhi_probe(struct platform_device *pdev) +{ + int rc; + u32 channel; + struct rmnet_mhi_private *rmnet_mhi_ptr; + struct mhi_client_handle *client_handle = NULL; + char node_name[32]; + struct mhi_client_info_t client_info; + + if (unlikely(!pdev->dev.of_node)) + return -ENODEV; + + if (!mhi_is_device_ready(&pdev->dev, "qcom,mhi")) + return -EPROBE_DEFER; + + pdev->id = of_alias_get_id(pdev->dev.of_node, "mhi_rmnet"); + if (unlikely(pdev->id < 0)) + return -ENODEV; + + rmnet_mhi_ptr = kzalloc(sizeof(*rmnet_mhi_ptr), GFP_KERNEL); + if (unlikely(!rmnet_mhi_ptr)) + return -ENOMEM; + rmnet_mhi_ptr->pdev = pdev; + spin_lock_init(&rmnet_mhi_ptr->out_chan_full_lock); + rwlock_init(&rmnet_mhi_ptr->pm_lock); + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,mhi-mru", + &rmnet_mhi_ptr->mru); + if (unlikely(rc)) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "failed to get valid mru\n"); + goto probe_fail; + } + + rc = of_property_read_u32(pdev->dev.of_node, + "cell-index", + &rmnet_mhi_ptr->dev_id); + if (unlikely(rc)) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "failed to get valid 'cell-index'\n"); + goto probe_fail; + } + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,mhi-max-mru", + &rmnet_mhi_ptr->max_mru); + if (likely(rc)) { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "max-mru not defined, setting to max %d\n", + MHI_MAX_MRU); + rmnet_mhi_ptr->max_mru = MHI_MAX_MRU; + } + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,mhi-max-mtu", + &rmnet_mhi_ptr->max_mtu); + if (likely(rc)) { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "max-mtu not defined, setting to max %d\n", + MHI_MAX_MTU); + rmnet_mhi_ptr->max_mtu = MHI_MAX_MTU; + } + + rc = of_property_read_string(pdev->dev.of_node, "qcom,interface-name", + &rmnet_mhi_ptr->interface_name); + if (likely(rc)) { + rmnet_log(rmnet_mhi_ptr, MSG_INFO, + "interface-name not defined, setting to default name %s\n", + RMNET_MHI_DRIVER_NAME); + rmnet_mhi_ptr->interface_name = rmnet_mhi_driver.driver.name; + } + + client_info.dev = &pdev->dev; + client_info.node_name = "qcom,mhi"; + client_info.mhi_client_cb = rmnet_mhi_cb; + client_info.user_data = rmnet_mhi_ptr; + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,mhi-tx-channel", + &channel); + if (rc == 0) { + rmnet_mhi_ptr->tx_channel = channel; + client_info.chan = channel; + client_info.max_payload = rmnet_mhi_ptr->max_mtu; + + rc = mhi_register_channel(&rmnet_mhi_ptr->tx_client_handle, + &client_info); + if (unlikely(rc)) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "mhi_register_channel failed chan %d ret %d\n", + rmnet_mhi_ptr->tx_channel, + rc); + goto probe_fail; + } + client_handle = rmnet_mhi_ptr->tx_client_handle; + } + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,mhi-rx-channel", + &channel); + if (rc == 0) { + rmnet_mhi_ptr->rx_channel = channel; + client_info.max_payload = rmnet_mhi_ptr->max_mru; + client_info.chan = channel; + rc = mhi_register_channel(&rmnet_mhi_ptr->rx_client_handle, + &client_info); + if (unlikely(rc)) { + rmnet_log(rmnet_mhi_ptr, + MSG_CRITICAL, + "mhi_register_channel failed chan %d ret %d\n", + rmnet_mhi_ptr->rx_channel, + rc); + goto probe_fail; + } + /* overwriting tx_client_handle is ok because dev_id and + * bdf are same for both channels + */ + client_handle = rmnet_mhi_ptr->rx_client_handle; + INIT_WORK(&rmnet_mhi_ptr->alloc_work, rmnet_mhi_alloc_work); + spin_lock_init(&rmnet_mhi_ptr->alloc_lock); + } + + /* We must've have @ least one valid channel */ + if (!client_handle) { + rmnet_log(rmnet_mhi_ptr, MSG_CRITICAL, + "No registered channels\n"); + rc = -ENODEV; + goto probe_fail; + } + + snprintf(node_name, + sizeof(node_name), + "%s_%04x_%02u.%02u.%02u_%u", + rmnet_mhi_ptr->interface_name, + client_handle->dev_id, + client_handle->domain, + client_handle->bus, + client_handle->slot, + rmnet_mhi_ptr->dev_id); + rmnet_mhi_ptr->rmnet_ipc_log = + ipc_log_context_create(RMNET_IPC_LOG_PAGES, + node_name, 0); + rmnet_mhi_ptr->debug.rmnet_msg_lvl = MSG_CRITICAL; + +#ifdef CONFIG_MSM_MHI_DEBUG + rmnet_mhi_ptr->debug.rmnet_ipc_log_lvl = MSG_VERBOSE; +#else + rmnet_mhi_ptr->debug.rmnet_ipc_log_lvl = MSG_ERROR; +#endif + + rmnet_mhi_create_debugfs(rmnet_mhi_ptr); + list_add_tail(&rmnet_mhi_ptr->node, &rmnet_mhi_ctxt_list); + return 0; + +probe_fail: + kfree(rmnet_mhi_ptr); + return rc; +} + +static const struct of_device_id msm_mhi_match_table[] = { + {.compatible = "qcom,mhi-rmnet"}, + {}, +}; + +static struct platform_driver rmnet_mhi_driver = { + .probe = rmnet_mhi_probe, + .driver = { + .name = RMNET_MHI_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = msm_mhi_match_table, + }, +}; + +static int __init rmnet_mhi_init(void) +{ + rmnet_mhi_create_debugfs_dir(); + + return platform_driver_register(&rmnet_mhi_driver); +} + +static void __exit rmnet_mhi_exit(void) +{ + struct rmnet_mhi_private *rmnet_mhi_ptr = 0; + + list_for_each_entry(rmnet_mhi_ptr, &rmnet_mhi_ctxt_list, node) { + if (rmnet_mhi_ptr->tx_client_handle) + mhi_deregister_channel(rmnet_mhi_ptr->tx_client_handle); + if (rmnet_mhi_ptr->rx_client_handle) + mhi_deregister_channel(rmnet_mhi_ptr->rx_client_handle); + } +} + +module_exit(rmnet_mhi_exit); +module_init(rmnet_mhi_init); + +MODULE_DESCRIPTION("MHI RMNET Network Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/msm/rndis_ipa.c b/drivers/net/ethernet/msm/rndis_ipa.c new file mode 100644 index 000000000000..683f364ed27b --- /dev/null +++ b/drivers/net/ethernet/msm/rndis_ipa.c @@ -0,0 +1,2409 @@ +/* Copyright (c) 2013-2017, 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/atomic.h> +#include <linux/errno.h> +#include <linux/etherdevice.h> +#include <linux/debugfs.h> +#include <linux/in.h> +#include <linux/stddef.h> +#include <linux/ip.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/msm_ipa.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/sched.h> +#include <linux/ipa.h> +#include <linux/random.h> +#include <linux/rndis_ipa.h> +#include <linux/workqueue.h> + +#define CREATE_TRACE_POINTS +#include "rndis_ipa_trace.h" + +#define DRV_NAME "RNDIS_IPA" +#define DEBUGFS_DIR_NAME "rndis_ipa" +#define DEBUGFS_AGGR_DIR_NAME "rndis_ipa_aggregation" +#define NETDEV_NAME "rndis" +#define DRV_RESOURCE_ID IPA_RM_RESOURCE_RNDIS_PROD +#define IPV4_HDR_NAME "rndis_eth_ipv4" +#define IPV6_HDR_NAME "rndis_eth_ipv6" +#define IPA_TO_USB_CLIENT IPA_CLIENT_USB_CONS +#define INACTIVITY_MSEC_DELAY 100 +#define DEFAULT_OUTSTANDING_HIGH 64 +#define DEFAULT_OUTSTANDING_LOW 32 +#define DEBUGFS_TEMP_BUF_SIZE 4 +#define RNDIS_IPA_PKT_TYPE 0x00000001 +#define RNDIS_IPA_DFLT_RT_HDL 0 +#define FROM_IPA_TO_USB_BAMDMA 4 +#define FROM_USB_TO_IPA_BAMDMA 5 +#define BAM_DMA_MAX_PKT_NUMBER 10 +#define BAM_DMA_DATA_FIFO_SIZE \ + (BAM_DMA_MAX_PKT_NUMBER* \ + (ETH_FRAME_LEN + sizeof(struct rndis_pkt_hdr))) +#define BAM_DMA_DESC_FIFO_SIZE \ + (BAM_DMA_MAX_PKT_NUMBER*(sizeof(struct sps_iovec))) +#define TX_TIMEOUT (5 * HZ) +#define MIN_TX_ERROR_SLEEP_PERIOD 500 +#define DEFAULT_AGGR_TIME_LIMIT 1 +#define DEFAULT_AGGR_PKT_LIMIT 0 + + +#define RNDIS_IPA_ERROR(fmt, args...) \ + pr_err(DRV_NAME "@%s@%d@ctx:%s: "\ + fmt, __func__, __LINE__, current->comm, ## args) +#define RNDIS_IPA_DEBUG(fmt, args...) \ + pr_debug("ctx: %s, "fmt, current->comm, ## args) + +#define NULL_CHECK_RETVAL(ptr) \ + do { \ + if (!(ptr)) { \ + RNDIS_IPA_ERROR("null pointer #ptr\n"); \ + return -EINVAL; \ + } \ + } \ + while (0) + +#define NULL_CHECK_NO_RETVAL(ptr) \ + do { \ + if (!(ptr)) {\ + RNDIS_IPA_ERROR("null pointer #ptr\n"); \ + return; \ + } \ + } \ + while (0) + +#define RNDIS_HDR_OFST(field) offsetof(struct rndis_pkt_hdr, field) +#define RNDIS_IPA_LOG_ENTRY() RNDIS_IPA_DEBUG("begin\n") +#define RNDIS_IPA_LOG_EXIT() RNDIS_IPA_DEBUG("end\n") + + +/** + * enum rndis_ipa_state - specify the current driver internal state + * which is guarded by a state machine. + * + * The driver internal state changes due to its external API usage. + * The driver saves its internal state to guard from caller illegal + * call sequence. + * states: + * UNLOADED is the first state which is the default one and is also the state + * after the driver gets unloaded(cleanup). + * INITIALIZED is the driver state once it finished registering + * the network device and all internal data struct were initialized + * CONNECTED is the driver state once the USB pipes were connected to IPA + * UP is the driver state after the interface mode was set to UP but the + * pipes are not connected yet - this state is meta-stable state. + * CONNECTED_AND_UP is the driver state when the pipe were connected and + * the interface got UP request from the network stack. this is the driver + * idle operation state which allows it to transmit/receive data. + * INVALID is a state which is not allowed. + */ +enum rndis_ipa_state { + RNDIS_IPA_UNLOADED = 0, + RNDIS_IPA_INITIALIZED = 1, + RNDIS_IPA_CONNECTED = 2, + RNDIS_IPA_UP = 3, + RNDIS_IPA_CONNECTED_AND_UP = 4, + RNDIS_IPA_INVALID = 5, +}; + +/** + * enum rndis_ipa_operation - enumerations used to describe the API operation + * + * Those enums are used as input for the driver state machine. + */ +enum rndis_ipa_operation { + RNDIS_IPA_INITIALIZE, + RNDIS_IPA_CONNECT, + RNDIS_IPA_OPEN, + RNDIS_IPA_STOP, + RNDIS_IPA_DISCONNECT, + RNDIS_IPA_CLEANUP, +}; + +#define RNDIS_IPA_STATE_DEBUG(ctx) \ + RNDIS_IPA_DEBUG("Driver state: %s\n",\ + rndis_ipa_state_string(ctx->state)); + + +/** + * struct rndis_ipa_dev - main driver context parameters + * + * @net: network interface struct implemented by this driver + * @directory: debugfs directory for various debugging switches + * @tx_filter: flag that enable/disable Tx path to continue to IPA + * @tx_dropped: number of filtered out Tx packets + * @tx_dump_enable: dump all Tx packets + * @rx_filter: flag that enable/disable Rx path to continue to IPA + * @rx_dropped: number of filtered out Rx packets + * @rx_dump_enable: dump all Rx packets + * @icmp_filter: allow all ICMP packet to pass through the filters + * @rm_enable: flag that enable/disable Resource manager request prior to Tx + * @deaggregation_enable: enable/disable IPA HW deaggregation logic + * @during_xmit_error: flags that indicate that the driver is in a middle + * of error handling in Tx path + * @directory: holds all debug flags used by the driver to allow cleanup + * for driver unload + * @eth_ipv4_hdr_hdl: saved handle for ipv4 header-insertion table + * @eth_ipv6_hdr_hdl: saved handle for ipv6 header-insertion table + * @usb_to_ipa_hdl: save handle for IPA pipe operations + * @ipa_to_usb_hdl: save handle for IPA pipe operations + * @outstanding_pkts: number of packets sent to IPA without TX complete ACKed + * @outstanding_high: number of outstanding packets allowed + * @outstanding_low: number of outstanding packets which shall cause + * to netdev queue start (after stopped due to outstanding_high reached) + * @error_msec_sleep_time: number of msec for sleeping in case of Tx error + * @state: current state of the driver + * @host_ethaddr: holds the tethered PC ethernet address + * @device_ethaddr: holds the device ethernet address + * @device_ready_notify: callback supplied by USB core driver + * This callback shall be called by the Netdev once the Netdev internal + * state is changed to RNDIS_IPA_CONNECTED_AND_UP + * @xmit_error_delayed_work: work item for cases where IPA driver Tx fails + * @state_lock: used to protect the state variable. + */ +struct rndis_ipa_dev { + struct net_device *net; + bool tx_filter; + u32 tx_dropped; + bool tx_dump_enable; + bool rx_filter; + u32 rx_dropped; + bool rx_dump_enable; + bool icmp_filter; + bool rm_enable; + bool deaggregation_enable; + bool during_xmit_error; + struct dentry *directory; + uint32_t eth_ipv4_hdr_hdl; + uint32_t eth_ipv6_hdr_hdl; + u32 usb_to_ipa_hdl; + u32 ipa_to_usb_hdl; + atomic_t outstanding_pkts; + u32 outstanding_high; + u32 outstanding_low; + u32 error_msec_sleep_time; + enum rndis_ipa_state state; + u8 host_ethaddr[ETH_ALEN]; + u8 device_ethaddr[ETH_ALEN]; + void (*device_ready_notify)(void); + struct delayed_work xmit_error_delayed_work; + spinlock_t state_lock; /* Spinlock for the state variable.*/ +}; + +/** + * rndis_pkt_hdr - RNDIS_IPA representation of REMOTE_NDIS_PACKET_MSG + * @msg_type: for REMOTE_NDIS_PACKET_MSG this value should be 1 + * @msg_len: total message length in bytes, including RNDIS header an payload + * @data_ofst: offset in bytes from start of the data_ofst to payload + * @data_len: payload size in bytes + * @zeroes: OOB place holder - not used for RNDIS_IPA. + */ +struct rndis_pkt_hdr { + __le32 msg_type; + __le32 msg_len; + __le32 data_ofst; + __le32 data_len; + __le32 zeroes[7]; +} __packed__; + +static int rndis_ipa_open(struct net_device *net); +static void rndis_ipa_packet_receive_notify(void *private, + enum ipa_dp_evt_type evt, unsigned long data); +static void rndis_ipa_tx_complete_notify(void *private, + enum ipa_dp_evt_type evt, unsigned long data); +static void rndis_ipa_tx_timeout(struct net_device *net); +static int rndis_ipa_stop(struct net_device *net); +static void rndis_ipa_enable_data_path(struct rndis_ipa_dev *rndis_ipa_ctx); +static struct sk_buff *rndis_encapsulate_skb(struct sk_buff *skb); +static void rndis_ipa_xmit_error(struct sk_buff *skb); +static void rndis_ipa_xmit_error_aftercare_wq(struct work_struct *work); +static void rndis_ipa_prepare_header_insertion(int eth_type, + const char *hdr_name, struct ipa_hdr_add *add_hdr, + const void *dst_mac, const void *src_mac); +static int rndis_ipa_hdrs_cfg(struct rndis_ipa_dev *rndis_ipa_ctx, + const void *dst_mac, const void *src_mac); +static int rndis_ipa_hdrs_destroy(struct rndis_ipa_dev *rndis_ipa_ctx); +static struct net_device_stats *rndis_ipa_get_stats(struct net_device *net); +static int rndis_ipa_register_properties(char *netdev_name); +static int rndis_ipa_deregister_properties(char *netdev_name); +static void rndis_ipa_rm_notify(void *user_data, enum ipa_rm_event event, + unsigned long data); +static int rndis_ipa_create_rm_resource(struct rndis_ipa_dev *rndis_ipa_ctx); +static int rndis_ipa_destory_rm_resource(struct rndis_ipa_dev *rndis_ipa_ctx); +static bool rx_filter(struct sk_buff *skb); +static bool tx_filter(struct sk_buff *skb); +static bool rm_enabled(struct rndis_ipa_dev *rndis_ipa_ctx); +static int resource_request(struct rndis_ipa_dev *rndis_ipa_ctx); +static void resource_release(struct rndis_ipa_dev *rndis_ipa_ctx); +static netdev_tx_t rndis_ipa_start_xmit(struct sk_buff *skb, + struct net_device *net); +static int rndis_ipa_debugfs_atomic_open(struct inode *inode, + struct file *file); +static int rndis_ipa_debugfs_aggr_open(struct inode *inode, + struct file *file); +static ssize_t rndis_ipa_debugfs_aggr_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos); +static ssize_t rndis_ipa_debugfs_atomic_read(struct file *file, + char __user *ubuf, size_t count, loff_t *ppos); +static void rndis_ipa_dump_skb(struct sk_buff *skb); +static void rndis_ipa_debugfs_init(struct rndis_ipa_dev *rndis_ipa_ctx); +static void rndis_ipa_debugfs_destroy(struct rndis_ipa_dev *rndis_ipa_ctx); +static int rndis_ipa_ep_registers_cfg(u32 usb_to_ipa_hdl, + u32 ipa_to_usb_hdl, u32 max_xfer_size_bytes_to_dev, + u32 max_xfer_size_bytes_to_host, u32 mtu, + bool deaggr_enable); +static int rndis_ipa_set_device_ethernet_addr(u8 *dev_ethaddr, + u8 device_ethaddr[]); +static enum rndis_ipa_state rndis_ipa_next_state( + enum rndis_ipa_state current_state, + enum rndis_ipa_operation operation); +static const char *rndis_ipa_state_string(enum rndis_ipa_state state); +static int rndis_ipa_init_module(void); +static void rndis_ipa_cleanup_module(void); + +struct rndis_ipa_dev *rndis_ipa; + +static const struct net_device_ops rndis_ipa_netdev_ops = { + .ndo_open = rndis_ipa_open, + .ndo_stop = rndis_ipa_stop, + .ndo_start_xmit = rndis_ipa_start_xmit, + .ndo_tx_timeout = rndis_ipa_tx_timeout, + .ndo_get_stats = rndis_ipa_get_stats, + .ndo_set_mac_address = eth_mac_addr, +}; + +const struct file_operations rndis_ipa_debugfs_atomic_ops = { + .open = rndis_ipa_debugfs_atomic_open, + .read = rndis_ipa_debugfs_atomic_read, +}; + +const struct file_operations rndis_ipa_aggr_ops = { + .open = rndis_ipa_debugfs_aggr_open, + .write = rndis_ipa_debugfs_aggr_write, +}; + +static struct ipa_ep_cfg ipa_to_usb_ep_cfg = { + .mode = { + .mode = IPA_BASIC, + .dst = IPA_CLIENT_APPS_LAN_CONS, + }, + .hdr = { + .hdr_len = ETH_HLEN + sizeof(struct rndis_pkt_hdr), + .hdr_ofst_metadata_valid = false, + .hdr_ofst_metadata = 0, + .hdr_additional_const_len = ETH_HLEN, + .hdr_ofst_pkt_size_valid = true, + .hdr_ofst_pkt_size = 3*sizeof(u32), + .hdr_a5_mux = false, + .hdr_remove_additional = false, + .hdr_metadata_reg_valid = false, + }, + .hdr_ext = { + .hdr_pad_to_alignment = 0, + .hdr_total_len_or_pad_offset = 1*sizeof(u32), + .hdr_payload_len_inc_padding = false, + .hdr_total_len_or_pad = IPA_HDR_TOTAL_LEN, + .hdr_total_len_or_pad_valid = true, + .hdr_little_endian = true, + }, + .aggr = { + .aggr_en = IPA_ENABLE_AGGR, + .aggr = IPA_GENERIC, + .aggr_byte_limit = 4, + .aggr_time_limit = DEFAULT_AGGR_TIME_LIMIT, + .aggr_pkt_limit = DEFAULT_AGGR_PKT_LIMIT + }, + .deaggr = { + .deaggr_hdr_len = 0, + .packet_offset_valid = 0, + .packet_offset_location = 0, + .max_packet_len = 0, + }, + .route = { + .rt_tbl_hdl = RNDIS_IPA_DFLT_RT_HDL, + }, + .nat = { + .nat_en = IPA_SRC_NAT, + }, +}; + +static struct ipa_ep_cfg usb_to_ipa_ep_cfg_deaggr_dis = { + .mode = { + .mode = IPA_BASIC, + .dst = IPA_CLIENT_APPS_LAN_CONS, + }, + .hdr = { + .hdr_len = ETH_HLEN + sizeof(struct rndis_pkt_hdr), + .hdr_ofst_metadata_valid = false, + .hdr_ofst_metadata = 0, + .hdr_additional_const_len = 0, + .hdr_ofst_pkt_size_valid = true, + .hdr_ofst_pkt_size = 3*sizeof(u32) + + sizeof(struct rndis_pkt_hdr), + .hdr_a5_mux = false, + .hdr_remove_additional = false, + .hdr_metadata_reg_valid = false, + }, + .hdr_ext = { + .hdr_pad_to_alignment = 0, + .hdr_total_len_or_pad_offset = 1*sizeof(u32), + .hdr_payload_len_inc_padding = false, + .hdr_total_len_or_pad = IPA_HDR_TOTAL_LEN, + .hdr_total_len_or_pad_valid = true, + .hdr_little_endian = true, + }, + + .aggr = { + .aggr_en = IPA_BYPASS_AGGR, + .aggr = 0, + .aggr_byte_limit = 0, + .aggr_time_limit = 0, + .aggr_pkt_limit = 0, + }, + .deaggr = { + .deaggr_hdr_len = 0, + .packet_offset_valid = false, + .packet_offset_location = 0, + .max_packet_len = 0, + }, + + .route = { + .rt_tbl_hdl = RNDIS_IPA_DFLT_RT_HDL, + }, + .nat = { + .nat_en = IPA_BYPASS_NAT, + }, +}; + +static struct ipa_ep_cfg usb_to_ipa_ep_cfg_deaggr_en = { + .mode = { + .mode = IPA_BASIC, + .dst = IPA_CLIENT_APPS_LAN_CONS, + }, + .hdr = { + .hdr_len = ETH_HLEN, + .hdr_ofst_metadata_valid = false, + .hdr_ofst_metadata = 0, + .hdr_additional_const_len = 0, + .hdr_ofst_pkt_size_valid = true, + .hdr_ofst_pkt_size = 3*sizeof(u32), + .hdr_a5_mux = false, + .hdr_remove_additional = false, + .hdr_metadata_reg_valid = false, + }, + .hdr_ext = { + .hdr_pad_to_alignment = 0, + .hdr_total_len_or_pad_offset = 1*sizeof(u32), + .hdr_payload_len_inc_padding = false, + .hdr_total_len_or_pad = IPA_HDR_TOTAL_LEN, + .hdr_total_len_or_pad_valid = true, + .hdr_little_endian = true, + }, + .aggr = { + .aggr_en = IPA_ENABLE_DEAGGR, + .aggr = IPA_GENERIC, + .aggr_byte_limit = 0, + .aggr_time_limit = 0, + .aggr_pkt_limit = 0, + }, + .deaggr = { + .deaggr_hdr_len = sizeof(struct rndis_pkt_hdr), + .packet_offset_valid = true, + .packet_offset_location = 8, + .max_packet_len = 8192, /* Will be overridden*/ + }, + .route = { + .rt_tbl_hdl = RNDIS_IPA_DFLT_RT_HDL, + }, + .nat = { + .nat_en = IPA_BYPASS_NAT, + }, +}; + + +/** + * rndis_template_hdr - RNDIS template structure for RNDIS_IPA SW insertion + * @msg_type: set for REMOTE_NDIS_PACKET_MSG (0x00000001) + * this value will be used for all data packets + * @msg_len: will add the skb length to get final size + * @data_ofst: this field value will not be changed + * @data_len: set as skb length to get final size + * @zeroes: make sure all OOB data is not used + */ +struct rndis_pkt_hdr rndis_template_hdr = { + .msg_type = RNDIS_IPA_PKT_TYPE, + .msg_len = sizeof(struct rndis_pkt_hdr), + .data_ofst = sizeof(struct rndis_pkt_hdr) - RNDIS_HDR_OFST(data_ofst), + .data_len = 0, + .zeroes = {0}, +}; + +/** + * rndis_ipa_init() - create network device and initialize internal + * data structures + * @params: in/out parameters required for initialization, + * see "struct ipa_usb_init_params" for more details + * + * Shall be called prior to pipe connection. + * Detailed description: + * - allocate the network device + * - set default values for driver internal switches and stash them inside + * the netdev private field + * - set needed headroom for RNDIS header + * - create debugfs folder and files + * - create IPA resource manager client + * - set the ethernet address for the netdev to be added on SW Tx path + * - add header insertion rules for IPA driver (based on host/device Ethernet + * addresses given in input params and on RNDIS data template struct) + * - register tx/rx properties to IPA driver (will be later used + * by IPA configuration manager to configure rest of the IPA rules) + * - set the carrier state to "off" (until connect is called) + * - register the network device + * - set the out parameters + * - change driver internal state to INITIALIZED + * + * Returns negative errno, or zero on success + */ +int rndis_ipa_init(struct ipa_usb_init_params *params) +{ + int result = 0; + struct net_device *net; + struct rndis_ipa_dev *rndis_ipa_ctx; + + RNDIS_IPA_LOG_ENTRY(); + RNDIS_IPA_DEBUG("%s initializing\n", DRV_NAME); + NULL_CHECK_RETVAL(params); + + RNDIS_IPA_DEBUG("host_ethaddr=%pM, device_ethaddr=%pM\n", + params->host_ethaddr, + params->device_ethaddr); + + net = alloc_etherdev(sizeof(struct rndis_ipa_dev)); + if (!net) { + result = -ENOMEM; + RNDIS_IPA_ERROR("fail to allocate Ethernet device\n"); + goto fail_alloc_etherdev; + } + RNDIS_IPA_DEBUG("network device was successfully allocated\n"); + + rndis_ipa_ctx = netdev_priv(net); + if (!rndis_ipa_ctx) { + result = -ENOMEM; + RNDIS_IPA_ERROR("fail to extract netdev priv\n"); + goto fail_netdev_priv; + } + memset(rndis_ipa_ctx, 0, sizeof(*rndis_ipa_ctx)); + RNDIS_IPA_DEBUG("rndis_ipa_ctx (private)=%p\n", rndis_ipa_ctx); + + spin_lock_init(&rndis_ipa_ctx->state_lock); + + rndis_ipa_ctx->net = net; + rndis_ipa_ctx->tx_filter = false; + rndis_ipa_ctx->rx_filter = false; + rndis_ipa_ctx->icmp_filter = true; + rndis_ipa_ctx->rm_enable = true; + rndis_ipa_ctx->tx_dropped = 0; + rndis_ipa_ctx->rx_dropped = 0; + rndis_ipa_ctx->tx_dump_enable = false; + rndis_ipa_ctx->rx_dump_enable = false; + rndis_ipa_ctx->deaggregation_enable = false; + rndis_ipa_ctx->outstanding_high = DEFAULT_OUTSTANDING_HIGH; + rndis_ipa_ctx->outstanding_low = DEFAULT_OUTSTANDING_LOW; + atomic_set(&rndis_ipa_ctx->outstanding_pkts, 0); + memcpy(rndis_ipa_ctx->device_ethaddr, params->device_ethaddr, + sizeof(rndis_ipa_ctx->device_ethaddr)); + memcpy(rndis_ipa_ctx->host_ethaddr, params->host_ethaddr, + sizeof(rndis_ipa_ctx->host_ethaddr)); + INIT_DELAYED_WORK(&rndis_ipa_ctx->xmit_error_delayed_work, + rndis_ipa_xmit_error_aftercare_wq); + rndis_ipa_ctx->error_msec_sleep_time = + MIN_TX_ERROR_SLEEP_PERIOD; + RNDIS_IPA_DEBUG("internal data structures were set\n"); + + if (!params->device_ready_notify) + RNDIS_IPA_DEBUG("device_ready_notify() was not supplied\n"); + rndis_ipa_ctx->device_ready_notify = params->device_ready_notify; + + snprintf(net->name, sizeof(net->name), "%s%%d", NETDEV_NAME); + RNDIS_IPA_DEBUG("Setting network interface driver name to: %s\n", + net->name); + + net->netdev_ops = &rndis_ipa_netdev_ops; + net->watchdog_timeo = TX_TIMEOUT; + + net->needed_headroom = sizeof(rndis_template_hdr); + RNDIS_IPA_DEBUG("Needed headroom for RNDIS header set to %d\n", + net->needed_headroom); + + rndis_ipa_debugfs_init(rndis_ipa_ctx); + + result = rndis_ipa_set_device_ethernet_addr(net->dev_addr, + rndis_ipa_ctx->device_ethaddr); + if (result) { + RNDIS_IPA_ERROR("set device MAC failed\n"); + goto fail_set_device_ethernet; + } + RNDIS_IPA_DEBUG("Device Ethernet address set %pM\n", net->dev_addr); + + result = rndis_ipa_hdrs_cfg(rndis_ipa_ctx, + params->host_ethaddr, + params->device_ethaddr); + if (result) { + RNDIS_IPA_ERROR("fail on ipa hdrs set\n"); + goto fail_hdrs_cfg; + } + RNDIS_IPA_DEBUG("IPA header-insertion configed for Ethernet+RNDIS\n"); + + result = rndis_ipa_register_properties(net->name); + if (result) { + RNDIS_IPA_ERROR("fail on properties set\n"); + goto fail_register_tx; + } + RNDIS_IPA_DEBUG("2 TX and 2 RX properties were registered\n"); + + netif_carrier_off(net); + RNDIS_IPA_DEBUG("set carrier off until pipes are connected\n"); + + result = register_netdev(net); + if (result) { + RNDIS_IPA_ERROR("register_netdev failed: %d\n", result); + goto fail_register_netdev; + } + RNDIS_IPA_DEBUG("netdev:%s registration succeeded, index=%d\n", + net->name, net->ifindex); + + rndis_ipa = rndis_ipa_ctx; + params->ipa_rx_notify = rndis_ipa_packet_receive_notify; + params->ipa_tx_notify = rndis_ipa_tx_complete_notify; + params->private = rndis_ipa_ctx; + params->skip_ep_cfg = false; + rndis_ipa_ctx->state = RNDIS_IPA_INITIALIZED; + RNDIS_IPA_STATE_DEBUG(rndis_ipa_ctx); + pr_info("RNDIS_IPA NetDev was initialized"); + + RNDIS_IPA_LOG_EXIT(); + + return 0; + +fail_register_netdev: + rndis_ipa_deregister_properties(net->name); +fail_register_tx: + rndis_ipa_hdrs_destroy(rndis_ipa_ctx); +fail_set_device_ethernet: +fail_hdrs_cfg: + rndis_ipa_debugfs_destroy(rndis_ipa_ctx); +fail_netdev_priv: + free_netdev(net); +fail_alloc_etherdev: + return result; +} +EXPORT_SYMBOL(rndis_ipa_init); + +/** + * rndis_ipa_pipe_connect_notify() - notify rndis_ipa Netdev that the USB pipes + * were connected + * @usb_to_ipa_hdl: handle from IPA driver client for USB->IPA + * @ipa_to_usb_hdl: handle from IPA driver client for IPA->USB + * @private: same value that was set by init(), this parameter holds the + * network device pointer. + * @max_transfer_byte_size: RNDIS protocol specific, the maximum size that + * the host expect + * @max_packet_number: RNDIS protocol specific, the maximum packet number + * that the host expects + * + * Once USB driver finishes the pipe connection between IPA core + * and USB core this method shall be called in order to + * allow the driver to complete the data path configurations. + * Detailed description: + * - configure the IPA end-points register + * - notify the Linux kernel for "carrier_on" + * - change the driver internal state + * + * After this function is done the driver state changes to "Connected" or + * Connected and Up. + * This API is expected to be called after initialization() or + * after a call to disconnect(). + * + * Returns negative errno, or zero on success + */ +int rndis_ipa_pipe_connect_notify(u32 usb_to_ipa_hdl, + u32 ipa_to_usb_hdl, + u32 max_xfer_size_bytes_to_dev, + u32 max_packet_number_to_dev, + u32 max_xfer_size_bytes_to_host, + void *private) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = private; + int next_state; + int result; + unsigned long flags; + + RNDIS_IPA_LOG_ENTRY(); + + NULL_CHECK_RETVAL(private); + + RNDIS_IPA_DEBUG("usb_to_ipa_hdl=%d, ipa_to_usb_hdl=%d, private=0x%p\n", + usb_to_ipa_hdl, ipa_to_usb_hdl, private); + RNDIS_IPA_DEBUG("max_xfer_sz_to_dev=%d, max_pkt_num_to_dev=%d\n", + max_xfer_size_bytes_to_dev, + max_packet_number_to_dev); + RNDIS_IPA_DEBUG("max_xfer_sz_to_host=%d\n", + max_xfer_size_bytes_to_host); + + spin_lock_irqsave(&rndis_ipa_ctx->state_lock, flags); + next_state = rndis_ipa_next_state(rndis_ipa_ctx->state, + RNDIS_IPA_CONNECT); + if (next_state == RNDIS_IPA_INVALID) { + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + RNDIS_IPA_ERROR("use init()/disconnect() before connect()\n"); + return -EPERM; + } + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + + if (usb_to_ipa_hdl >= IPA_CLIENT_MAX) { + RNDIS_IPA_ERROR("usb_to_ipa_hdl(%d) - not valid ipa handle\n", + usb_to_ipa_hdl); + return -EINVAL; + } + if (ipa_to_usb_hdl >= IPA_CLIENT_MAX) { + RNDIS_IPA_ERROR("ipa_to_usb_hdl(%d) - not valid ipa handle\n", + ipa_to_usb_hdl); + return -EINVAL; + } + + result = rndis_ipa_create_rm_resource(rndis_ipa_ctx); + if (result) { + RNDIS_IPA_ERROR("fail on RM create\n"); + goto fail_create_rm; + } + RNDIS_IPA_DEBUG("RM resource was created\n"); + + rndis_ipa_ctx->ipa_to_usb_hdl = ipa_to_usb_hdl; + rndis_ipa_ctx->usb_to_ipa_hdl = usb_to_ipa_hdl; + if (max_packet_number_to_dev > 1) + rndis_ipa_ctx->deaggregation_enable = true; + else + rndis_ipa_ctx->deaggregation_enable = false; + result = rndis_ipa_ep_registers_cfg(usb_to_ipa_hdl, + ipa_to_usb_hdl, + max_xfer_size_bytes_to_dev, + max_xfer_size_bytes_to_host, + rndis_ipa_ctx->net->mtu, + rndis_ipa_ctx->deaggregation_enable); + if (result) { + RNDIS_IPA_ERROR("fail on ep cfg\n"); + goto fail; + } + RNDIS_IPA_DEBUG("end-points configured\n"); + + netif_stop_queue(rndis_ipa_ctx->net); + RNDIS_IPA_DEBUG("netif_stop_queue() was called\n"); + + netif_carrier_on(rndis_ipa_ctx->net); + if (!netif_carrier_ok(rndis_ipa_ctx->net)) { + RNDIS_IPA_ERROR("netif_carrier_ok error\n"); + result = -EBUSY; + goto fail; + } + RNDIS_IPA_DEBUG("netif_carrier_on() was called\n"); + + spin_lock_irqsave(&rndis_ipa_ctx->state_lock, flags); + next_state = rndis_ipa_next_state(rndis_ipa_ctx->state, + RNDIS_IPA_CONNECT); + if (next_state == RNDIS_IPA_INVALID) { + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + RNDIS_IPA_ERROR("use init()/disconnect() before connect()\n"); + return -EPERM; + } + rndis_ipa_ctx->state = next_state; + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + + RNDIS_IPA_STATE_DEBUG(rndis_ipa_ctx); + + if (next_state == RNDIS_IPA_CONNECTED_AND_UP) + rndis_ipa_enable_data_path(rndis_ipa_ctx); + else + RNDIS_IPA_DEBUG("queue shall be started after open()\n"); + + pr_info("RNDIS_IPA NetDev pipes were connected\n"); + + RNDIS_IPA_LOG_EXIT(); + + return 0; + +fail: + rndis_ipa_destory_rm_resource(rndis_ipa_ctx); +fail_create_rm: + return result; +} +EXPORT_SYMBOL(rndis_ipa_pipe_connect_notify); + +/** + * rndis_ipa_open() - notify Linux network stack to start sending packets + * @net: the network interface supplied by the network stack + * + * Linux uses this API to notify the driver that the network interface + * transitions to the up state. + * The driver will instruct the Linux network stack to start + * delivering data packets. + * The driver internal state shall be changed to Up or Connected and Up + * + * Returns negative errno, or zero on success + */ +static int rndis_ipa_open(struct net_device *net) +{ + struct rndis_ipa_dev *rndis_ipa_ctx; + int next_state; + unsigned long flags; + + RNDIS_IPA_LOG_ENTRY(); + + rndis_ipa_ctx = netdev_priv(net); + + spin_lock_irqsave(&rndis_ipa_ctx->state_lock, flags); + + next_state = rndis_ipa_next_state(rndis_ipa_ctx->state, RNDIS_IPA_OPEN); + if (next_state == RNDIS_IPA_INVALID) { + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + RNDIS_IPA_ERROR("can't bring driver up before initialize\n"); + return -EPERM; + } + + rndis_ipa_ctx->state = next_state; + + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + + RNDIS_IPA_STATE_DEBUG(rndis_ipa_ctx); + + + if (next_state == RNDIS_IPA_CONNECTED_AND_UP) + rndis_ipa_enable_data_path(rndis_ipa_ctx); + else + RNDIS_IPA_DEBUG("queue shall be started after connect()\n"); + + pr_info("RNDIS_IPA NetDev was opened\n"); + + RNDIS_IPA_LOG_EXIT(); + + return 0; +} + +/** + * rndis_ipa_start_xmit() - send data from APPs to USB core via IPA core + * using SW path (Tx data path) + * Tx path for this Netdev is Apps-processor->IPA->USB + * @skb: packet received from Linux network stack destined for tethered PC + * @net: the network device being used to send this packet (rndis0) + * + * Several conditions needed in order to send the packet to IPA: + * - Transmit queue for the network driver is currently + * in "started" state + * - The driver internal state is in Connected and Up state. + * - Filters Tx switch are turned off + * - The IPA resource manager state for the driver producer client + * is "Granted" which implies that all the resources in the dependency + * graph are valid for data flow. + * - outstanding high boundary was not reached. + * + * In case the outstanding packets high boundary is reached, the driver will + * stop the send queue until enough packets are processed by + * the IPA core (based on calls to rndis_ipa_tx_complete_notify). + * + * In case all of the conditions are met, the network driver shall: + * - encapsulate the Ethernet packet with RNDIS header (REMOTE_NDIS_PACKET_MSG) + * - send the packet by using IPA Driver SW path (IP_PACKET_INIT) + * - Netdev status fields shall be updated based on the current Tx packet + * + * Returns NETDEV_TX_BUSY if retry should be made later, + * or NETDEV_TX_OK on success. + */ +static netdev_tx_t rndis_ipa_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + int ret; + netdev_tx_t status = NETDEV_TX_BUSY; + struct rndis_ipa_dev *rndis_ipa_ctx = netdev_priv(net); + + net->trans_start = jiffies; + + RNDIS_IPA_DEBUG("Tx, len=%d, skb->protocol=%d, outstanding=%d\n", + skb->len, skb->protocol, + atomic_read(&rndis_ipa_ctx->outstanding_pkts)); + + if (unlikely(netif_queue_stopped(net))) { + RNDIS_IPA_ERROR("interface queue is stopped\n"); + goto out; + } + + if (unlikely(rndis_ipa_ctx->tx_dump_enable)) + rndis_ipa_dump_skb(skb); + + if (unlikely(rndis_ipa_ctx->state != RNDIS_IPA_CONNECTED_AND_UP)) { + RNDIS_IPA_ERROR("Missing pipe connected and/or iface up\n"); + return NETDEV_TX_BUSY; + } + + if (unlikely(tx_filter(skb))) { + dev_kfree_skb_any(skb); + RNDIS_IPA_DEBUG("packet got filtered out on Tx path\n"); + rndis_ipa_ctx->tx_dropped++; + status = NETDEV_TX_OK; + goto out; + } + + ret = resource_request(rndis_ipa_ctx); + if (ret) { + RNDIS_IPA_DEBUG("Waiting to resource\n"); + netif_stop_queue(net); + goto resource_busy; + } + + if (atomic_read(&rndis_ipa_ctx->outstanding_pkts) >= + rndis_ipa_ctx->outstanding_high) { + RNDIS_IPA_DEBUG("Outstanding high boundary reached (%d)\n", + rndis_ipa_ctx->outstanding_high); + netif_stop_queue(net); + RNDIS_IPA_DEBUG("send queue was stopped\n"); + status = NETDEV_TX_BUSY; + goto out; + } + + skb = rndis_encapsulate_skb(skb); + trace_rndis_tx_dp(skb->protocol); + ret = ipa_tx_dp(IPA_TO_USB_CLIENT, skb, NULL); + if (ret) { + RNDIS_IPA_ERROR("ipa transmit failed (%d)\n", ret); + goto fail_tx_packet; + } + + atomic_inc(&rndis_ipa_ctx->outstanding_pkts); + + status = NETDEV_TX_OK; + goto out; + +fail_tx_packet: + rndis_ipa_xmit_error(skb); +out: + resource_release(rndis_ipa_ctx); +resource_busy: + RNDIS_IPA_DEBUG("packet Tx done - %s\n", + (status == NETDEV_TX_OK) ? "OK" : "FAIL"); + + return status; +} + +/** + * rndis_ipa_tx_complete_notify() - notification for Netdev that the + * last packet was successfully sent + * @private: driver context stashed by IPA driver upon pipe connect + * @evt: event type (expected to be write-done event) + * @data: data provided with event (this is actually the skb that + * holds the sent packet) + * + * This function will be called on interrupt bottom halve deferred context. + * outstanding packets counter shall be decremented. + * Network stack send queue will be re-started in case low outstanding + * boundary is reached and queue was stopped before. + * At the end the skb shall be freed. + */ +static void rndis_ipa_tx_complete_notify(void *private, + enum ipa_dp_evt_type evt, + unsigned long data) +{ + struct sk_buff *skb = (struct sk_buff *)data; + struct rndis_ipa_dev *rndis_ipa_ctx = private; + + NULL_CHECK_NO_RETVAL(private); + + trace_rndis_status_rcvd(skb->protocol); + + RNDIS_IPA_DEBUG("Tx-complete, len=%d, skb->prot=%d, outstanding=%d\n", + skb->len, skb->protocol, + atomic_read(&rndis_ipa_ctx->outstanding_pkts)); + + if (unlikely((evt != IPA_WRITE_DONE))) { + RNDIS_IPA_ERROR("unsupported event on TX call-back\n"); + return; + } + + if (unlikely(rndis_ipa_ctx->state != RNDIS_IPA_CONNECTED_AND_UP)) { + RNDIS_IPA_DEBUG("dropping Tx-complete pkt, state=%s\n", + rndis_ipa_state_string(rndis_ipa_ctx->state)); + goto out; + } + + rndis_ipa_ctx->net->stats.tx_packets++; + rndis_ipa_ctx->net->stats.tx_bytes += skb->len; + + atomic_dec(&rndis_ipa_ctx->outstanding_pkts); + if (netif_queue_stopped(rndis_ipa_ctx->net) && + netif_carrier_ok(rndis_ipa_ctx->net) && + atomic_read(&rndis_ipa_ctx->outstanding_pkts) < + (rndis_ipa_ctx->outstanding_low)) { + RNDIS_IPA_DEBUG("outstanding low boundary reached (%d)n", + rndis_ipa_ctx->outstanding_low); + netif_wake_queue(rndis_ipa_ctx->net); + RNDIS_IPA_DEBUG("send queue was awaken\n"); + } + +out: + dev_kfree_skb_any(skb); + + return; +} + +static void rndis_ipa_tx_timeout(struct net_device *net) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = netdev_priv(net); + int outstanding = atomic_read(&rndis_ipa_ctx->outstanding_pkts); + + RNDIS_IPA_ERROR("possible IPA stall was detected, %d outstanding\n", + outstanding); + + net->stats.tx_errors++; +} + +/** + * rndis_ipa_rm_notify() - callback supplied to IPA resource manager + * for grant/release events + * user_data: the driver context supplied to IPA resource manager during call + * to ipa_rm_create_resource(). + * event: the event notified to us by IPA resource manager (Release/Grant) + * data: reserved field supplied by IPA resource manager + * + * This callback shall be called based on resource request/release sent + * to the IPA resource manager. + * In case the queue was stopped during EINPROGRESS for Tx path and the + * event received is Grant then the queue shall be restarted. + * In case the event notified is a release notification the netdev discard it. + */ +static void rndis_ipa_rm_notify(void *user_data, enum ipa_rm_event event, + unsigned long data) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = user_data; + + RNDIS_IPA_LOG_ENTRY(); + + if (event == IPA_RM_RESOURCE_RELEASED) { + RNDIS_IPA_DEBUG("Resource Released\n"); + return; + } + + if (event != IPA_RM_RESOURCE_GRANTED) { + RNDIS_IPA_ERROR("Unexceoted event receieved from RM (%d\n)", + event); + return; + } + RNDIS_IPA_DEBUG("Resource Granted\n"); + + if (netif_queue_stopped(rndis_ipa_ctx->net)) { + RNDIS_IPA_DEBUG("starting queue\n"); + netif_start_queue(rndis_ipa_ctx->net); + } else { + RNDIS_IPA_DEBUG("queue already awake\n"); + } + + RNDIS_IPA_LOG_EXIT(); +} + +/** + * rndis_ipa_packet_receive_notify() - Rx notify for packet sent from + * tethered PC (USB->IPA). + * is USB->IPA->Apps-processor + * @private: driver context + * @evt: event type + * @data: data provided with event + * + * Once IPA driver receives a packet from USB client this callback will be + * called from bottom-half interrupt handling context (ipa Rx workqueue). + * + * Packets that shall be sent to Apps processor may be of two types: + * 1) Packets that are destined for Apps (e.g: WEBSERVER running on Apps) + * 2) Exception packets that need special handling (based on IPA core + * configuration, e.g: new TCP session or any other packets that IPA core + * can't handle) + * If the next conditions are met, the packet shall be sent up to the + * Linux network stack: + * - Driver internal state is Connected and Up + * - Notification received from IPA driver meets the expected type + * for Rx packet + * -Filters Rx switch are turned off + * + * Prior to the sending to the network stack: + * - Netdev struct shall be stashed to the skb as required by the network stack + * - Ethernet header shall be removed (skb->data shall point to the Ethernet + * payload, Ethernet still stashed under MAC header). + * - The skb->pkt_protocol shall be set based on the ethernet destination + * address, Can be Broadcast, Multicast or Other-Host, The later + * pkt-types packets shall be dropped in case the Netdev is not + * in promisc mode. + * - Set the skb protocol field based on the EtherType field + * + * Netdev status fields shall be updated based on the current Rx packet + */ +static void rndis_ipa_packet_receive_notify(void *private, + enum ipa_dp_evt_type evt, + unsigned long data) +{ + struct sk_buff *skb = (struct sk_buff *)data; + struct rndis_ipa_dev *rndis_ipa_ctx = private; + int result; + unsigned int packet_len = skb->len; + + RNDIS_IPA_DEBUG("packet Rx, len=%d\n", + skb->len); + + if (unlikely(rndis_ipa_ctx->rx_dump_enable)) + rndis_ipa_dump_skb(skb); + + if (unlikely(rndis_ipa_ctx->state != RNDIS_IPA_CONNECTED_AND_UP)) { + RNDIS_IPA_DEBUG("use connect()/up() before receive()\n"); + RNDIS_IPA_DEBUG("packet dropped (length=%d)\n", + skb->len); + return; + } + + if (evt != IPA_RECEIVE) { + RNDIS_IPA_ERROR("a none IPA_RECEIVE event in driver RX\n"); + return; + } + + if (!rndis_ipa_ctx->deaggregation_enable) + skb_pull(skb, sizeof(struct rndis_pkt_hdr)); + + skb->dev = rndis_ipa_ctx->net; + skb->protocol = eth_type_trans(skb, rndis_ipa_ctx->net); + + if (rx_filter(skb)) { + RNDIS_IPA_DEBUG("packet got filtered out on RX path\n"); + rndis_ipa_ctx->rx_dropped++; + dev_kfree_skb_any(skb); + return; + } + + trace_rndis_netif_ni(skb->protocol); + result = netif_rx_ni(skb); + if (result) + RNDIS_IPA_ERROR("fail on netif_rx_ni\n"); + rndis_ipa_ctx->net->stats.rx_packets++; + rndis_ipa_ctx->net->stats.rx_bytes += packet_len; + + return; +} + +/** rndis_ipa_stop() - notify the network interface to stop + * sending/receiving data + * @net: the network device being stopped. + * + * This API is used by Linux network stack to notify the network driver that + * its state was changed to "down" + * The driver will stop the "send" queue and change its internal + * state to "Connected". + * The Netdev shall be returned to be "Up" after rndis_ipa_open(). + */ +static int rndis_ipa_stop(struct net_device *net) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = netdev_priv(net); + int next_state; + unsigned long flags; + + RNDIS_IPA_LOG_ENTRY(); + + spin_lock_irqsave(&rndis_ipa_ctx->state_lock, flags); + + next_state = rndis_ipa_next_state(rndis_ipa_ctx->state, RNDIS_IPA_STOP); + if (next_state == RNDIS_IPA_INVALID) { + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + RNDIS_IPA_DEBUG("can't do network interface down without up\n"); + return -EPERM; + } + + rndis_ipa_ctx->state = next_state; + + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + + netif_stop_queue(net); + pr_info("RNDIS_IPA NetDev queue is stopped\n"); + + RNDIS_IPA_STATE_DEBUG(rndis_ipa_ctx); + + RNDIS_IPA_LOG_EXIT(); + + return 0; +} + +/** rndis_ipa_disconnect() - notify rndis_ipa Netdev that the USB pipes + * were disconnected + * @private: same value that was set by init(), this parameter holds the + * network device pointer. + * + * USB shall notify the Netdev after disconnecting the pipe. + * - The internal driver state shall returned to its previous + * state (Up or Initialized). + * - Linux network stack shall be informed for carrier off to notify + * user space for pipe disconnect + * - send queue shall be stopped + * During the transition between the pipe disconnection to + * the Netdev notification packets + * are expected to be dropped by IPA driver or IPA core. + */ +int rndis_ipa_pipe_disconnect_notify(void *private) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = private; + int next_state; + int outstanding_dropped_pkts; + int retval; + unsigned long flags; + + RNDIS_IPA_LOG_ENTRY(); + + NULL_CHECK_RETVAL(rndis_ipa_ctx); + RNDIS_IPA_DEBUG("private=0x%p\n", private); + + spin_lock_irqsave(&rndis_ipa_ctx->state_lock, flags); + + next_state = rndis_ipa_next_state(rndis_ipa_ctx->state, + RNDIS_IPA_DISCONNECT); + if (next_state == RNDIS_IPA_INVALID) { + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + RNDIS_IPA_ERROR("can't disconnect before connect\n"); + return -EPERM; + } + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + + if (rndis_ipa_ctx->during_xmit_error) { + RNDIS_IPA_DEBUG("canceling xmit-error delayed work\n"); + cancel_delayed_work_sync( + &rndis_ipa_ctx->xmit_error_delayed_work); + rndis_ipa_ctx->during_xmit_error = false; + } + + netif_carrier_off(rndis_ipa_ctx->net); + RNDIS_IPA_DEBUG("carrier_off notification was sent\n"); + + netif_stop_queue(rndis_ipa_ctx->net); + RNDIS_IPA_DEBUG("queue stopped\n"); + + outstanding_dropped_pkts = + atomic_read(&rndis_ipa_ctx->outstanding_pkts); + + rndis_ipa_ctx->net->stats.tx_dropped += outstanding_dropped_pkts; + atomic_set(&rndis_ipa_ctx->outstanding_pkts, 0); + + retval = rndis_ipa_destory_rm_resource(rndis_ipa_ctx); + if (retval) { + RNDIS_IPA_ERROR("Fail to clean RM\n"); + return retval; + } + RNDIS_IPA_DEBUG("RM was successfully destroyed\n"); + + spin_lock_irqsave(&rndis_ipa_ctx->state_lock, flags); + next_state = rndis_ipa_next_state(rndis_ipa_ctx->state, + RNDIS_IPA_DISCONNECT); + if (next_state == RNDIS_IPA_INVALID) { + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + RNDIS_IPA_ERROR("can't disconnect before connect\n"); + return -EPERM; + } + rndis_ipa_ctx->state = next_state; + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + + RNDIS_IPA_STATE_DEBUG(rndis_ipa_ctx); + + pr_info("RNDIS_IPA NetDev pipes disconnected (%d outstanding clr)\n", + outstanding_dropped_pkts); + + RNDIS_IPA_LOG_EXIT(); + + return 0; +} +EXPORT_SYMBOL(rndis_ipa_pipe_disconnect_notify); + +/** + * rndis_ipa_cleanup() - unregister the network interface driver and free + * internal data structs. + * @private: same value that was set by init(), this + * parameter holds the network device pointer. + * + * This function shall be called once the network interface is not + * needed anymore, e.g: when the USB composition does not support it. + * This function shall be called after the pipes were disconnected. + * Detailed description: + * - remove header-insertion headers from IPA core + * - delete the driver dependency defined for IPA resource manager and + * destroy the producer resource. + * - remove the debugfs entries + * - deregister the network interface from Linux network stack + * - free all internal data structs + * + * It is assumed that no packets shall be sent through HW bridging + * during cleanup to avoid packets trying to add an header that is + * removed during cleanup (IPA configuration manager should have + * removed them at this point) + */ +void rndis_ipa_cleanup(void *private) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = private; + int next_state; + int retval; + unsigned long flags; + + RNDIS_IPA_LOG_ENTRY(); + + RNDIS_IPA_DEBUG("private=0x%p\n", private); + + if (!rndis_ipa_ctx) { + RNDIS_IPA_ERROR("rndis_ipa_ctx NULL pointer\n"); + return; + } + + spin_lock_irqsave(&rndis_ipa_ctx->state_lock, flags); + next_state = rndis_ipa_next_state(rndis_ipa_ctx->state, + RNDIS_IPA_CLEANUP); + if (next_state == RNDIS_IPA_INVALID) { + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + RNDIS_IPA_ERROR("use disconnect()before clean()\n"); + return; + } + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + + RNDIS_IPA_STATE_DEBUG(rndis_ipa_ctx); + + retval = rndis_ipa_deregister_properties(rndis_ipa_ctx->net->name); + if (retval) { + RNDIS_IPA_ERROR("Fail to deregister Tx/Rx properties\n"); + return; + } + RNDIS_IPA_DEBUG("deregister Tx/Rx properties was successful\n"); + + retval = rndis_ipa_hdrs_destroy(rndis_ipa_ctx); + if (retval) + RNDIS_IPA_ERROR( + "Failed removing RNDIS headers from IPA core. Continue anyway\n"); + else + RNDIS_IPA_DEBUG("RNDIS headers were removed from IPA core\n"); + + rndis_ipa_debugfs_destroy(rndis_ipa_ctx); + RNDIS_IPA_DEBUG("debugfs remove was done\n"); + + unregister_netdev(rndis_ipa_ctx->net); + RNDIS_IPA_DEBUG("netdev unregistered\n"); + + spin_lock_irqsave(&rndis_ipa_ctx->state_lock, flags); + next_state = rndis_ipa_next_state(rndis_ipa_ctx->state, + RNDIS_IPA_CLEANUP); + if (next_state == RNDIS_IPA_INVALID) { + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + RNDIS_IPA_ERROR("use disconnect()before clean()\n"); + return; + } + rndis_ipa_ctx->state = next_state; + spin_unlock_irqrestore(&rndis_ipa_ctx->state_lock, flags); + free_netdev(rndis_ipa_ctx->net); + pr_info("RNDIS_IPA NetDev was cleaned\n"); + + RNDIS_IPA_LOG_EXIT(); + + return; +} +EXPORT_SYMBOL(rndis_ipa_cleanup); + + +static void rndis_ipa_enable_data_path(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + if (rndis_ipa_ctx->device_ready_notify) { + rndis_ipa_ctx->device_ready_notify(); + RNDIS_IPA_DEBUG("USB device_ready_notify() was called\n"); + } else { + RNDIS_IPA_DEBUG("device_ready_notify() not supplied\n"); + } + + netif_start_queue(rndis_ipa_ctx->net); + RNDIS_IPA_DEBUG("netif_start_queue() was called\n"); +} + +static void rndis_ipa_xmit_error(struct sk_buff *skb) +{ + bool retval; + struct rndis_ipa_dev *rndis_ipa_ctx = netdev_priv(skb->dev); + unsigned long delay_jiffies; + u8 rand_dealy_msec; + + RNDIS_IPA_LOG_ENTRY(); + + RNDIS_IPA_DEBUG("starting Tx-queue backoff\n"); + + netif_stop_queue(rndis_ipa_ctx->net); + RNDIS_IPA_DEBUG("netif_stop_queue was called\n"); + + skb_pull(skb, sizeof(rndis_template_hdr)); + rndis_ipa_ctx->net->stats.tx_errors++; + + get_random_bytes(&rand_dealy_msec, sizeof(rand_dealy_msec)); + delay_jiffies = msecs_to_jiffies( + rndis_ipa_ctx->error_msec_sleep_time + rand_dealy_msec); + + retval = schedule_delayed_work( + &rndis_ipa_ctx->xmit_error_delayed_work, delay_jiffies); + if (!retval) { + RNDIS_IPA_ERROR("fail to schedule delayed work\n"); + netif_start_queue(rndis_ipa_ctx->net); + } else { + RNDIS_IPA_DEBUG("work scheduled to start Tx-queue in %d msec\n", + rndis_ipa_ctx->error_msec_sleep_time + rand_dealy_msec); + rndis_ipa_ctx->during_xmit_error = true; + } + + RNDIS_IPA_LOG_EXIT(); +} + +static void rndis_ipa_xmit_error_aftercare_wq(struct work_struct *work) +{ + struct rndis_ipa_dev *rndis_ipa_ctx; + struct delayed_work *delayed_work; + + RNDIS_IPA_LOG_ENTRY(); + + RNDIS_IPA_DEBUG("Starting queue after xmit error\n"); + + delayed_work = to_delayed_work(work); + rndis_ipa_ctx = container_of(delayed_work, struct rndis_ipa_dev, + xmit_error_delayed_work); + + if (unlikely(rndis_ipa_ctx->state != RNDIS_IPA_CONNECTED_AND_UP)) { + RNDIS_IPA_ERROR("error aftercare handling in bad state (%d)", + rndis_ipa_ctx->state); + return; + } + + rndis_ipa_ctx->during_xmit_error = false; + + netif_start_queue(rndis_ipa_ctx->net); + RNDIS_IPA_DEBUG("netif_start_queue() was called\n"); + + RNDIS_IPA_LOG_EXIT(); +} + +/** + * rndis_ipa_prepare_header_insertion() - prepare the header insertion request + * for IPA driver + * eth_type: the Ethernet type for this header-insertion header + * hdr_name: string that shall represent this header in IPA data base + * add_hdr: output for caller to be used with ipa_add_hdr() to configure + * the IPA core + * dst_mac: tethered PC MAC (Ethernet) address to be added to packets + * for IPA->USB pipe + * src_mac: device MAC (Ethernet) address to be added to packets + * for IPA->USB pipe + * + * This function shall build the header-insertion block request for a + * single Ethernet+RNDIS header) + * this header shall be inserted for packets processed by IPA + * and destined for USB client. + * This header shall be used for HW bridging for packets destined for + * tethered PC. + * For SW data-path, this header won't be used. + */ +static void rndis_ipa_prepare_header_insertion(int eth_type, + const char *hdr_name, struct ipa_hdr_add *add_hdr, + const void *dst_mac, const void *src_mac) +{ + struct ethhdr *eth_hdr; + + add_hdr->hdr_len = sizeof(rndis_template_hdr); + add_hdr->is_partial = false; + strlcpy(add_hdr->name, hdr_name, IPA_RESOURCE_NAME_MAX); + + memcpy(add_hdr->hdr, &rndis_template_hdr, sizeof(rndis_template_hdr)); + eth_hdr = (struct ethhdr *)(add_hdr->hdr + sizeof(rndis_template_hdr)); + memcpy(eth_hdr->h_dest, dst_mac, ETH_ALEN); + memcpy(eth_hdr->h_source, src_mac, ETH_ALEN); + eth_hdr->h_proto = htons(eth_type); + add_hdr->hdr_len += ETH_HLEN; + add_hdr->is_eth2_ofst_valid = true; + add_hdr->eth2_ofst = sizeof(rndis_template_hdr); + add_hdr->type = IPA_HDR_L2_ETHERNET_II; +} + +/** + * rndis_ipa_hdrs_cfg() - configure header insertion block in IPA core + * to allow HW bridging + * @rndis_ipa_ctx: main driver context + * @dst_mac: destination MAC address (tethered PC) + * @src_mac: source MAC address (MDM device) + * + * This function shall add 2 headers. + * One header for Ipv4 and one header for Ipv6. + * Both headers shall contain Ethernet header and RNDIS header, the only + * difference shall be in the EtherTye field. + * Headers will be committed to HW + * + * Returns negative errno, or zero on success + */ +static int rndis_ipa_hdrs_cfg(struct rndis_ipa_dev *rndis_ipa_ctx, + const void *dst_mac, const void *src_mac) +{ + struct ipa_ioc_add_hdr *hdrs; + struct ipa_hdr_add *ipv4_hdr; + struct ipa_hdr_add *ipv6_hdr; + int result = 0; + + RNDIS_IPA_LOG_ENTRY(); + + hdrs = kzalloc(sizeof(*hdrs) + sizeof(*ipv4_hdr) + sizeof(*ipv6_hdr), + GFP_KERNEL); + if (!hdrs) { + RNDIS_IPA_ERROR("mem allocation fail for header-insertion\n"); + result = -ENOMEM; + goto fail_mem; + } + + ipv4_hdr = &hdrs->hdr[0]; + ipv6_hdr = &hdrs->hdr[1]; + rndis_ipa_prepare_header_insertion(ETH_P_IP, IPV4_HDR_NAME, + ipv4_hdr, dst_mac, src_mac); + rndis_ipa_prepare_header_insertion(ETH_P_IPV6, IPV6_HDR_NAME, + ipv6_hdr, dst_mac, src_mac); + + hdrs->commit = 1; + hdrs->num_hdrs = 2; + result = ipa_add_hdr(hdrs); + if (result) { + RNDIS_IPA_ERROR("Fail on Header-Insertion(%d)\n", result); + goto fail_add_hdr; + } + if (ipv4_hdr->status) { + RNDIS_IPA_ERROR("Fail on Header-Insertion ipv4(%d)\n", + ipv4_hdr->status); + result = ipv4_hdr->status; + goto fail_add_hdr; + } + if (ipv6_hdr->status) { + RNDIS_IPA_ERROR("Fail on Header-Insertion ipv6(%d)\n", + ipv6_hdr->status); + result = ipv6_hdr->status; + goto fail_add_hdr; + } + rndis_ipa_ctx->eth_ipv4_hdr_hdl = ipv4_hdr->hdr_hdl; + rndis_ipa_ctx->eth_ipv6_hdr_hdl = ipv6_hdr->hdr_hdl; + + RNDIS_IPA_LOG_EXIT(); + +fail_add_hdr: + kfree(hdrs); +fail_mem: + return result; +} + +/** + * rndis_ipa_hdrs_destroy() - remove the IPA core configuration done for + * the driver data path bridging. + * @rndis_ipa_ctx: the driver context + * + * Revert the work done on rndis_ipa_hdrs_cfg(), which is, + * remove 2 headers for Ethernet+RNDIS. + */ +static int rndis_ipa_hdrs_destroy(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + struct ipa_ioc_del_hdr *del_hdr; + struct ipa_hdr_del *ipv4; + struct ipa_hdr_del *ipv6; + int result; + + del_hdr = kzalloc(sizeof(*del_hdr) + sizeof(*ipv4) + + sizeof(*ipv6), GFP_KERNEL); + if (!del_hdr) { + RNDIS_IPA_ERROR("memory allocation for del_hdr failed\n"); + return -ENOMEM; + } + + del_hdr->commit = 1; + del_hdr->num_hdls = 2; + + ipv4 = &del_hdr->hdl[0]; + ipv4->hdl = rndis_ipa_ctx->eth_ipv4_hdr_hdl; + ipv6 = &del_hdr->hdl[1]; + ipv6->hdl = rndis_ipa_ctx->eth_ipv6_hdr_hdl; + + result = ipa_del_hdr(del_hdr); + if (result || ipv4->status || ipv6->status) + RNDIS_IPA_ERROR("ipa_del_hdr failed\n"); + else + RNDIS_IPA_DEBUG("hdrs deletion done\n"); + + kfree(del_hdr); + return result; +} + +static struct net_device_stats *rndis_ipa_get_stats(struct net_device *net) +{ + return &net->stats; +} + + +/** + * rndis_ipa_register_properties() - set Tx/Rx properties needed + * by IPA configuration manager + * @netdev_name: a string with the name of the network interface device + * + * Register Tx/Rx properties to allow user space configuration (IPA + * Configuration Manager): + * + * - Two Tx properties (IPA->USB): specify the header names and pipe number + * that shall be used by user space for header-addition configuration + * for ipv4/ipv6 packets flowing from IPA to USB for HW bridging data. + * That header-addition header is added by the Netdev and used by user + * space to close the the HW bridge by adding filtering and routing rules + * that point to this header. + * + * - Two Rx properties (USB->IPA): these properties shall be used by user space + * to configure the IPA core to identify the packets destined + * for Apps-processor by configuring the unicast rules destined for + * the Netdev IP address. + * This rules shall be added based on the attribute mask supplied at + * this function, that is, always hit rule. + */ +static int rndis_ipa_register_properties(char *netdev_name) +{ + struct ipa_tx_intf tx_properties = {0}; + struct ipa_ioc_tx_intf_prop properties[2] = { {0}, {0} }; + struct ipa_ioc_tx_intf_prop *ipv4_property; + struct ipa_ioc_tx_intf_prop *ipv6_property; + struct ipa_ioc_rx_intf_prop rx_ioc_properties[2] = { {0}, {0} }; + struct ipa_rx_intf rx_properties = {0}; + struct ipa_ioc_rx_intf_prop *rx_ipv4_property; + struct ipa_ioc_rx_intf_prop *rx_ipv6_property; + int result = 0; + + RNDIS_IPA_LOG_ENTRY(); + + tx_properties.prop = properties; + ipv4_property = &tx_properties.prop[0]; + ipv4_property->ip = IPA_IP_v4; + ipv4_property->dst_pipe = IPA_TO_USB_CLIENT; + strlcpy(ipv4_property->hdr_name, IPV4_HDR_NAME, + IPA_RESOURCE_NAME_MAX); + ipv4_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; + ipv6_property = &tx_properties.prop[1]; + ipv6_property->ip = IPA_IP_v6; + ipv6_property->dst_pipe = IPA_TO_USB_CLIENT; + strlcpy(ipv6_property->hdr_name, IPV6_HDR_NAME, + IPA_RESOURCE_NAME_MAX); + ipv6_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; + tx_properties.num_props = 2; + + rx_properties.prop = rx_ioc_properties; + rx_ipv4_property = &rx_properties.prop[0]; + rx_ipv4_property->ip = IPA_IP_v4; + rx_ipv4_property->attrib.attrib_mask = 0; + rx_ipv4_property->src_pipe = IPA_CLIENT_USB_PROD; + rx_ipv4_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; + rx_ipv6_property = &rx_properties.prop[1]; + rx_ipv6_property->ip = IPA_IP_v6; + rx_ipv6_property->attrib.attrib_mask = 0; + rx_ipv6_property->src_pipe = IPA_CLIENT_USB_PROD; + rx_ipv6_property->hdr_l2_type = IPA_HDR_L2_ETHERNET_II; + rx_properties.num_props = 2; + + result = ipa_register_intf("rndis0", &tx_properties, &rx_properties); + if (result) + RNDIS_IPA_ERROR("fail on Tx/Rx properties registration\n"); + else + RNDIS_IPA_DEBUG("Tx/Rx properties registration done\n"); + + RNDIS_IPA_LOG_EXIT(); + + return result; +} + +/** + * rndis_ipa_deregister_properties() - remove the 2 Tx and 2 Rx properties + * @netdev_name: a string with the name of the network interface device + * + * This function revert the work done on rndis_ipa_register_properties(). + */ +static int rndis_ipa_deregister_properties(char *netdev_name) +{ + int result; + + RNDIS_IPA_LOG_ENTRY(); + + result = ipa_deregister_intf(netdev_name); + if (result) { + RNDIS_IPA_DEBUG("Fail on Tx prop deregister\n"); + return result; + } + RNDIS_IPA_LOG_EXIT(); + + return 0; +} + +/** + * rndis_ipa_create_rm_resource() -creates the resource representing + * this Netdev and supply notification callback for resource event + * such as Grant/Release + * @rndis_ipa_ctx: this driver context + * + * In order make sure all needed resources are available during packet + * transmit this Netdev shall use Request/Release mechanism of + * the IPA resource manager. + * This mechanism shall iterate over a dependency graph and make sure + * all dependent entities are ready to for packet Tx + * transfer (Apps->IPA->USB). + * In this function the resource representing the Netdev is created + * in addition to the basic dependency between the Netdev and the USB client. + * Hence, USB client, is a dependency for the Netdev and may be notified in + * case of packet transmit from this Netdev to tethered Host. + * As implied from the "may" in the above sentence there is a scenario where + * the USB is not notified. This is done thanks to the IPA resource manager + * inactivity timer. + * The inactivity timer allow the Release requests to be delayed in order + * prevent ping-pong with the USB and other dependencies. + */ +static int rndis_ipa_create_rm_resource(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + struct ipa_rm_create_params create_params = {0}; + struct ipa_rm_perf_profile profile; + int result; + + RNDIS_IPA_LOG_ENTRY(); + + create_params.name = DRV_RESOURCE_ID; + create_params.reg_params.user_data = rndis_ipa_ctx; + create_params.reg_params.notify_cb = rndis_ipa_rm_notify; + result = ipa_rm_create_resource(&create_params); + if (result) { + RNDIS_IPA_ERROR("Fail on ipa_rm_create_resource\n"); + goto fail_rm_create; + } + RNDIS_IPA_DEBUG("RM client was created\n"); + + profile.max_supported_bandwidth_mbps = IPA_APPS_MAX_BW_IN_MBPS; + ipa_rm_set_perf_profile(DRV_RESOURCE_ID, &profile); + + result = ipa_rm_inactivity_timer_init(DRV_RESOURCE_ID, + INACTIVITY_MSEC_DELAY); + if (result) { + RNDIS_IPA_ERROR("Fail on ipa_rm_inactivity_timer_init\n"); + goto fail_inactivity_timer; + } + + RNDIS_IPA_DEBUG("rm_it client was created\n"); + + result = ipa_rm_add_dependency_sync(DRV_RESOURCE_ID, + IPA_RM_RESOURCE_USB_CONS); + + if (result && result != -EINPROGRESS) + RNDIS_IPA_ERROR("unable to add RNDIS/USB dependency (%d)\n", + result); + else + RNDIS_IPA_DEBUG("RNDIS/USB dependency was set\n"); + + result = ipa_rm_add_dependency_sync(IPA_RM_RESOURCE_USB_PROD, + IPA_RM_RESOURCE_APPS_CONS); + if (result && result != -EINPROGRESS) + RNDIS_IPA_ERROR("unable to add USB/APPS dependency (%d)\n", + result); + else + RNDIS_IPA_DEBUG("USB/APPS dependency was set\n"); + + RNDIS_IPA_LOG_EXIT(); + + return 0; + +fail_inactivity_timer: +fail_rm_create: + return result; +} + +/** + * rndis_ipa_destroy_rm_resource() - delete the dependency and destroy + * the resource done on rndis_ipa_create_rm_resource() + * @rndis_ipa_ctx: this driver context + * + * This function shall delete the dependency create between + * the Netdev to the USB. + * In addition the inactivity time shall be destroy and the resource shall + * be deleted. + */ +static int rndis_ipa_destory_rm_resource(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + int result; + + RNDIS_IPA_LOG_ENTRY(); + + result = ipa_rm_delete_dependency(DRV_RESOURCE_ID, + IPA_RM_RESOURCE_USB_CONS); + if (result && result != -EINPROGRESS) { + RNDIS_IPA_ERROR("Fail to delete RNDIS/USB dependency\n"); + goto bail; + } + RNDIS_IPA_DEBUG("RNDIS/USB dependency was successfully deleted\n"); + + result = ipa_rm_delete_dependency(IPA_RM_RESOURCE_USB_PROD, + IPA_RM_RESOURCE_APPS_CONS); + if (result == -EINPROGRESS) { + RNDIS_IPA_DEBUG("RM dependency deletion is in progress"); + } else if (result) { + RNDIS_IPA_ERROR("Fail to delete USB/APPS dependency\n"); + goto bail; + } else { + RNDIS_IPA_DEBUG("USB/APPS dependency was deleted\n"); + } + + result = ipa_rm_inactivity_timer_destroy(DRV_RESOURCE_ID); + if (result) { + RNDIS_IPA_ERROR("Fail to destroy inactivity timern"); + goto bail; + } + RNDIS_IPA_DEBUG("RM inactivity timer was successfully destroy\n"); + + result = ipa_rm_delete_resource(DRV_RESOURCE_ID); + if (result) { + RNDIS_IPA_ERROR("resource deletion failed\n"); + goto bail; + } + RNDIS_IPA_DEBUG("Netdev RM resource was deleted (resid:%d)\n", + DRV_RESOURCE_ID); + + + RNDIS_IPA_LOG_EXIT(); + +bail: + return result; +} + +/** + * resource_request() - request for the Netdev resource + * @rndis_ipa_ctx: main driver context + * + * This function shall send the IPA resource manager inactivity time a request + * to Grant the Netdev producer. + * In case the resource is already Granted the function shall return immediately + * and "pet" the inactivity timer. + * In case the resource was not already Granted this function shall + * return EINPROGRESS and the Netdev shall stop the send queue until + * the IPA resource manager notify it that the resource is + * granted (done in a differ context) + */ +static int resource_request(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + int result = 0; + + if (!rm_enabled(rndis_ipa_ctx)) + goto out; + result = ipa_rm_inactivity_timer_request_resource( + DRV_RESOURCE_ID); +out: + return result; +} + +/** + * resource_release() - release the Netdev resource + * @rndis_ipa_ctx: main driver context + * + * start the inactivity timer count down.by using the IPA resource + * manager inactivity time. + * The actual resource release shall occur only if no request shall be done + * during the INACTIVITY_MSEC_DELAY. + */ +static void resource_release(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + if (!rm_enabled(rndis_ipa_ctx)) + goto out; + ipa_rm_inactivity_timer_release_resource(DRV_RESOURCE_ID); +out: + return; +} + +/** + * rndis_encapsulate_skb() - encapsulate the given Ethernet skb with + * an RNDIS header + * @skb: packet to be encapsulated with the RNDIS header + * + * Shall use a template header for RNDIS and update it with the given + * skb values. + * Ethernet is expected to be already encapsulate the packet. + */ +static struct sk_buff *rndis_encapsulate_skb(struct sk_buff *skb) +{ + struct rndis_pkt_hdr *rndis_hdr; + int payload_byte_len = skb->len; + + /* if there is no room in this skb, allocate a new one */ + if (unlikely(skb_headroom(skb) < sizeof(rndis_template_hdr))) { + struct sk_buff *new_skb = skb_copy_expand(skb, + sizeof(rndis_template_hdr), 0, GFP_ATOMIC); + if (!new_skb) { + RNDIS_IPA_ERROR("no memory for skb expand\n"); + return skb; + } + RNDIS_IPA_DEBUG("skb expanded. old %p new %p\n", skb, new_skb); + dev_kfree_skb_any(skb); + skb = new_skb; + } + + /* make room at the head of the SKB to put the RNDIS header */ + rndis_hdr = (struct rndis_pkt_hdr *)skb_push(skb, + sizeof(rndis_template_hdr)); + + memcpy(rndis_hdr, &rndis_template_hdr, sizeof(*rndis_hdr)); + rndis_hdr->msg_len += payload_byte_len; + rndis_hdr->data_len += payload_byte_len; + + return skb; +} + +/** + * rx_filter() - logic that decide if the current skb is to be filtered out + * @skb: skb that may be sent up to the network stack + * + * This function shall do Rx packet filtering on the Netdev level. + */ +static bool rx_filter(struct sk_buff *skb) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = netdev_priv(skb->dev); + + return rndis_ipa_ctx->rx_filter; +} + +/** + * tx_filter() - logic that decide if the current skb is to be filtered out + * @skb: skb that may be sent to the USB core + * + * This function shall do Tx packet filtering on the Netdev level. + * ICMP filter bypass is possible to allow only ICMP packet to be + * sent (pings and etc) + */ + +static bool tx_filter(struct sk_buff *skb) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = netdev_priv(skb->dev); + bool is_icmp; + + if (likely(rndis_ipa_ctx->tx_filter == false)) + return false; + + is_icmp = (skb->protocol == htons(ETH_P_IP) && + ip_hdr(skb)->protocol == IPPROTO_ICMP); + + if ((rndis_ipa_ctx->icmp_filter == false) && is_icmp) + return false; + + return true; +} + +/** + * rm_enabled() - allow the use of resource manager Request/Release to + * be bypassed + * @rndis_ipa_ctx: main driver context + * + * By disabling the resource manager flag the Request for the Netdev resource + * shall be bypassed and the packet shall be sent. + * accordingly, Release request shall be bypass as well. + */ +static bool rm_enabled(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + return rndis_ipa_ctx->rm_enable; +} + +/** + * rndis_ipa_ep_registers_cfg() - configure the USB endpoints + * @usb_to_ipa_hdl: handle received from ipa_connect which represents + * the USB to IPA end-point + * @ipa_to_usb_hdl: handle received from ipa_connect which represents + * the IPA to USB end-point + * @max_xfer_size_bytes_to_dev: the maximum size, in bytes, that the device + * expects to receive from the host. supplied on REMOTE_NDIS_INITIALIZE_CMPLT. + * @max_xfer_size_bytes_to_host: the maximum size, in bytes, that the host + * expects to receive from the device. supplied on REMOTE_NDIS_INITIALIZE_MSG. + * @mtu: the netdev MTU size, in bytes + * + * USB to IPA pipe: + * - de-aggregation + * - Remove Ethernet header + * - Remove RNDIS header + * - SRC NAT + * - Default routing(0) + * IPA to USB Pipe: + * - aggregation + * - Add Ethernet header + * - Add RNDIS header + */ +static int rndis_ipa_ep_registers_cfg(u32 usb_to_ipa_hdl, + u32 ipa_to_usb_hdl, + u32 max_xfer_size_bytes_to_dev, + u32 max_xfer_size_bytes_to_host, + u32 mtu, + bool deaggr_enable) +{ + int result; + struct ipa_ep_cfg *usb_to_ipa_ep_cfg; + + if (deaggr_enable) { + usb_to_ipa_ep_cfg = &usb_to_ipa_ep_cfg_deaggr_en; + RNDIS_IPA_DEBUG("deaggregation enabled\n"); + } else { + usb_to_ipa_ep_cfg = &usb_to_ipa_ep_cfg_deaggr_dis; + RNDIS_IPA_DEBUG("deaggregation disabled\n"); + } + + usb_to_ipa_ep_cfg->deaggr.max_packet_len = max_xfer_size_bytes_to_dev; + result = ipa_cfg_ep(usb_to_ipa_hdl, usb_to_ipa_ep_cfg); + if (result) { + pr_err("failed to configure USB to IPA point\n"); + return result; + } + RNDIS_IPA_DEBUG("IPA<-USB end-point configured\n"); + + ipa_to_usb_ep_cfg.aggr.aggr_byte_limit = + (max_xfer_size_bytes_to_host - mtu)/1024; + + if (ipa_to_usb_ep_cfg.aggr.aggr_byte_limit == 0) { + ipa_to_usb_ep_cfg.aggr.aggr_time_limit = 0; + ipa_to_usb_ep_cfg.aggr.aggr_pkt_limit = 1; + } else { + ipa_to_usb_ep_cfg.aggr.aggr_time_limit = + DEFAULT_AGGR_TIME_LIMIT; + ipa_to_usb_ep_cfg.aggr.aggr_pkt_limit = + DEFAULT_AGGR_PKT_LIMIT; + } + + RNDIS_IPA_DEBUG("RNDIS aggregation param:" + " en=%d" + " byte_limit=%d" + " time_limit=%d" + " pkt_limit=%d\n", + ipa_to_usb_ep_cfg.aggr.aggr_en, + ipa_to_usb_ep_cfg.aggr.aggr_byte_limit, + ipa_to_usb_ep_cfg.aggr.aggr_time_limit, + ipa_to_usb_ep_cfg.aggr.aggr_pkt_limit); + + result = ipa_cfg_ep(ipa_to_usb_hdl, &ipa_to_usb_ep_cfg); + if (result) { + pr_err("failed to configure IPA to USB end-point\n"); + return result; + } + RNDIS_IPA_DEBUG("IPA->USB end-point configured\n"); + + return 0; +} + +/** + * rndis_ipa_set_device_ethernet_addr() - set device Ethernet address + * @dev_ethaddr: device Ethernet address + * + * Returns 0 for success, negative otherwise + */ +static int rndis_ipa_set_device_ethernet_addr(u8 *dev_ethaddr, + u8 device_ethaddr[]) +{ + if (!is_valid_ether_addr(device_ethaddr)) + return -EINVAL; + memcpy(dev_ethaddr, device_ethaddr, ETH_ALEN); + + return 0; +} + +/** rndis_ipa_next_state - return the next state of the driver + * @current_state: the current state of the driver + * @operation: an enum which represent the operation being made on the driver + * by its API. + * + * This function implements the driver internal state machine. + * Its decisions are based on the driver current state and the operation + * being made. + * In case the operation is invalid this state machine will return + * the value RNDIS_IPA_INVALID to inform the caller for a forbidden sequence. + */ +static enum rndis_ipa_state rndis_ipa_next_state( + enum rndis_ipa_state current_state, + enum rndis_ipa_operation operation) +{ + int next_state = RNDIS_IPA_INVALID; + + switch (current_state) { + case RNDIS_IPA_UNLOADED: + if (operation == RNDIS_IPA_INITIALIZE) + next_state = RNDIS_IPA_INITIALIZED; + break; + case RNDIS_IPA_INITIALIZED: + if (operation == RNDIS_IPA_CONNECT) + next_state = RNDIS_IPA_CONNECTED; + else if (operation == RNDIS_IPA_OPEN) + next_state = RNDIS_IPA_UP; + else if (operation == RNDIS_IPA_CLEANUP) + next_state = RNDIS_IPA_UNLOADED; + break; + case RNDIS_IPA_CONNECTED: + if (operation == RNDIS_IPA_DISCONNECT) + next_state = RNDIS_IPA_INITIALIZED; + else if (operation == RNDIS_IPA_OPEN) + next_state = RNDIS_IPA_CONNECTED_AND_UP; + break; + case RNDIS_IPA_UP: + if (operation == RNDIS_IPA_STOP) + next_state = RNDIS_IPA_INITIALIZED; + else if (operation == RNDIS_IPA_CONNECT) + next_state = RNDIS_IPA_CONNECTED_AND_UP; + else if (operation == RNDIS_IPA_CLEANUP) + next_state = RNDIS_IPA_UNLOADED; + break; + case RNDIS_IPA_CONNECTED_AND_UP: + if (operation == RNDIS_IPA_STOP) + next_state = RNDIS_IPA_CONNECTED; + else if (operation == RNDIS_IPA_DISCONNECT) + next_state = RNDIS_IPA_UP; + break; + default: + RNDIS_IPA_ERROR("State is not supported\n"); + WARN_ON(true); + break; + } + + RNDIS_IPA_DEBUG("state transition ( %s -> %s )- %s\n", + rndis_ipa_state_string(current_state), + rndis_ipa_state_string(next_state) , + next_state == RNDIS_IPA_INVALID ? + "Forbidden" : "Allowed"); + + return next_state; +} + +/** + * rndis_ipa_state_string - return the state string representation + * @state: enum which describe the state + */ +static const char *rndis_ipa_state_string(enum rndis_ipa_state state) +{ + switch (state) { + case RNDIS_IPA_UNLOADED: + return "RNDIS_IPA_UNLOADED"; + case RNDIS_IPA_INITIALIZED: + return "RNDIS_IPA_INITIALIZED"; + case RNDIS_IPA_CONNECTED: + return "RNDIS_IPA_CONNECTED"; + case RNDIS_IPA_UP: + return "RNDIS_IPA_UP"; + case RNDIS_IPA_CONNECTED_AND_UP: + return "RNDIS_IPA_CONNECTED_AND_UP"; + default: + return "Not supported"; + } +} + +static void rndis_ipa_dump_skb(struct sk_buff *skb) +{ + int i; + u32 *cur = (u32 *)skb->data; + u8 *byte; + + RNDIS_IPA_DEBUG("packet dump start for skb->len=%d\n", + skb->len); + + for (i = 0; i < (skb->len/4); i++) { + byte = (u8 *)(cur + i); + pr_info("%2d %08x %02x %02x %02x %02x\n", + i, *(cur + i), + byte[0], byte[1], byte[2], byte[3]); + } + RNDIS_IPA_DEBUG("packet dump ended for skb->len=%d\n", + skb->len); +} + +#ifdef CONFIG_DEBUG_FS +/** + * Creates the root folder for the driver + */ +static void rndis_ipa_debugfs_init(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + const mode_t flags_read_write = S_IRUGO | S_IWUGO; + const mode_t flags_read_only = S_IRUGO; + const mode_t flags_write_only = S_IWUGO; + struct dentry *file; + struct dentry *aggr_directory; + + RNDIS_IPA_LOG_ENTRY(); + + if (!rndis_ipa_ctx) + return; + + rndis_ipa_ctx->directory = debugfs_create_dir(DEBUGFS_DIR_NAME, NULL); + if (!rndis_ipa_ctx->directory) { + RNDIS_IPA_ERROR("could not create debugfs directory entry\n"); + goto fail_directory; + } + + file = debugfs_create_bool("tx_filter", flags_read_write, + rndis_ipa_ctx->directory, &rndis_ipa_ctx->tx_filter); + if (!file) { + RNDIS_IPA_ERROR("could not create debugfs tx_filter file\n"); + goto fail_file; + } + + file = debugfs_create_bool("rx_filter", flags_read_write, + rndis_ipa_ctx->directory, &rndis_ipa_ctx->rx_filter); + if (!file) { + RNDIS_IPA_ERROR("could not create debugfs rx_filter file\n"); + goto fail_file; + } + + file = debugfs_create_bool("icmp_filter", flags_read_write, + rndis_ipa_ctx->directory, &rndis_ipa_ctx->icmp_filter); + if (!file) { + RNDIS_IPA_ERROR("could not create debugfs icmp_filter file\n"); + goto fail_file; + } + + file = debugfs_create_bool("rm_enable", flags_read_write, + rndis_ipa_ctx->directory, &rndis_ipa_ctx->rm_enable); + if (!file) { + RNDIS_IPA_ERROR("could not create debugfs rm file\n"); + goto fail_file; + } + + file = debugfs_create_u32("outstanding_high", flags_read_write, + rndis_ipa_ctx->directory, + &rndis_ipa_ctx->outstanding_high); + if (!file) { + RNDIS_IPA_ERROR("could not create outstanding_high file\n"); + goto fail_file; + } + + file = debugfs_create_u32("outstanding_low", flags_read_write, + rndis_ipa_ctx->directory, + &rndis_ipa_ctx->outstanding_low); + if (!file) { + RNDIS_IPA_ERROR("could not create outstanding_low file\n"); + goto fail_file; + } + + file = debugfs_create_file("outstanding", flags_read_only, + rndis_ipa_ctx->directory, + rndis_ipa_ctx, &rndis_ipa_debugfs_atomic_ops); + if (!file) { + RNDIS_IPA_ERROR("could not create outstanding file\n"); + goto fail_file; + } + + file = debugfs_create_u8("state", flags_read_only, + rndis_ipa_ctx->directory, (u8 *)&rndis_ipa_ctx->state); + if (!file) { + RNDIS_IPA_ERROR("could not create state file\n"); + goto fail_file; + } + + file = debugfs_create_u32("tx_dropped", flags_read_only, + rndis_ipa_ctx->directory, &rndis_ipa_ctx->tx_dropped); + if (!file) { + RNDIS_IPA_ERROR("could not create tx_dropped file\n"); + goto fail_file; + } + + file = debugfs_create_u32("rx_dropped", flags_read_only, + rndis_ipa_ctx->directory, &rndis_ipa_ctx->rx_dropped); + if (!file) { + RNDIS_IPA_ERROR("could not create rx_dropped file\n"); + goto fail_file; + } + + aggr_directory = debugfs_create_dir(DEBUGFS_AGGR_DIR_NAME, + rndis_ipa_ctx->directory); + if (!aggr_directory) { + RNDIS_IPA_ERROR("could not create debugfs aggr entry\n"); + goto fail_directory; + } + + file = debugfs_create_file("aggr_value_set", flags_write_only, + aggr_directory, + rndis_ipa_ctx, &rndis_ipa_aggr_ops); + if (!file) { + RNDIS_IPA_ERROR("could not create aggr_value_set file\n"); + goto fail_file; + } + + file = debugfs_create_u8("aggr_enable", flags_read_write, + aggr_directory, (u8 *)&ipa_to_usb_ep_cfg.aggr.aggr_en); + if (!file) { + RNDIS_IPA_ERROR("could not create aggr_enable file\n"); + goto fail_file; + } + + file = debugfs_create_u8("aggr_type", flags_read_write, + aggr_directory, (u8 *)&ipa_to_usb_ep_cfg.aggr.aggr); + if (!file) { + RNDIS_IPA_ERROR("could not create aggr_type file\n"); + goto fail_file; + } + + file = debugfs_create_u32("aggr_byte_limit", flags_read_write, + aggr_directory, + &ipa_to_usb_ep_cfg.aggr.aggr_byte_limit); + if (!file) { + RNDIS_IPA_ERROR("could not create aggr_byte_limit file\n"); + goto fail_file; + } + + file = debugfs_create_u32("aggr_time_limit", flags_read_write, + aggr_directory, + &ipa_to_usb_ep_cfg.aggr.aggr_time_limit); + if (!file) { + RNDIS_IPA_ERROR("could not create aggr_time_limit file\n"); + goto fail_file; + } + + file = debugfs_create_u32("aggr_pkt_limit", flags_read_write, + aggr_directory, + &ipa_to_usb_ep_cfg.aggr.aggr_pkt_limit); + if (!file) { + RNDIS_IPA_ERROR("could not create aggr_pkt_limit file\n"); + goto fail_file; + } + + file = debugfs_create_bool("tx_dump_enable", flags_read_write, + rndis_ipa_ctx->directory, + &rndis_ipa_ctx->tx_dump_enable); + if (!file) { + RNDIS_IPA_ERROR("fail to create tx_dump_enable file\n"); + goto fail_file; + } + + file = debugfs_create_bool("rx_dump_enable", flags_read_write, + rndis_ipa_ctx->directory, + &rndis_ipa_ctx->rx_dump_enable); + if (!file) { + RNDIS_IPA_ERROR("fail to create rx_dump_enable file\n"); + goto fail_file; + } + + file = debugfs_create_bool("deaggregation_enable", flags_read_write, + rndis_ipa_ctx->directory, + &rndis_ipa_ctx->deaggregation_enable); + if (!file) { + RNDIS_IPA_ERROR("fail to create deaggregation_enable file\n"); + goto fail_file; + } + + file = debugfs_create_u32("error_msec_sleep_time", flags_read_write, + rndis_ipa_ctx->directory, + &rndis_ipa_ctx->error_msec_sleep_time); + if (!file) { + RNDIS_IPA_ERROR("fail to create error_msec_sleep_time file\n"); + goto fail_file; + } + + file = debugfs_create_bool("during_xmit_error", flags_read_only, + rndis_ipa_ctx->directory, + &rndis_ipa_ctx->during_xmit_error); + if (!file) { + RNDIS_IPA_ERROR("fail to create during_xmit_error file\n"); + goto fail_file; + } + + RNDIS_IPA_DEBUG("debugfs entries were created\n"); + RNDIS_IPA_LOG_EXIT(); + + return; +fail_file: + debugfs_remove_recursive(rndis_ipa_ctx->directory); +fail_directory: + return; +} + +static void rndis_ipa_debugfs_destroy(struct rndis_ipa_dev *rndis_ipa_ctx) +{ + debugfs_remove_recursive(rndis_ipa_ctx->directory); +} + +#else /* !CONFIG_DEBUG_FS */ + +static void rndis_ipa_debugfs_init(struct rndis_ipa_dev *rndis_ipa_ctx) {} + +static void rndis_ipa_debugfs_destroy(struct rndis_ipa_dev *rndis_ipa_ctx) {} + +#endif /* CONFIG_DEBUG_FS*/ + +static int rndis_ipa_debugfs_aggr_open(struct inode *inode, + struct file *file) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = inode->i_private; + file->private_data = rndis_ipa_ctx; + + return 0; +} + + +static ssize_t rndis_ipa_debugfs_aggr_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = file->private_data; + int result; + + result = ipa_cfg_ep(rndis_ipa_ctx->usb_to_ipa_hdl, &ipa_to_usb_ep_cfg); + if (result) { + pr_err("failed to re-configure USB to IPA point\n"); + return result; + } + pr_info("IPA<-USB end-point re-configured\n"); + + return count; +} + +static int rndis_ipa_debugfs_atomic_open(struct inode *inode, struct file *file) +{ + struct rndis_ipa_dev *rndis_ipa_ctx = inode->i_private; + + RNDIS_IPA_LOG_ENTRY(); + + file->private_data = &(rndis_ipa_ctx->outstanding_pkts); + + RNDIS_IPA_LOG_EXIT(); + + return 0; +} + +static ssize_t rndis_ipa_debugfs_atomic_read(struct file *file, + char __user *ubuf, size_t count, loff_t *ppos) +{ + int nbytes; + u8 atomic_str[DEBUGFS_TEMP_BUF_SIZE] = {0}; + atomic_t *atomic_var = file->private_data; + + RNDIS_IPA_LOG_ENTRY(); + + nbytes = scnprintf(atomic_str, sizeof(atomic_str), "%d\n", + atomic_read(atomic_var)); + + RNDIS_IPA_LOG_EXIT(); + + return simple_read_from_buffer(ubuf, count, ppos, atomic_str, nbytes); +} + +static int rndis_ipa_init_module(void) +{ + pr_info("RNDIS_IPA module is loaded."); + return 0; +} + +static void rndis_ipa_cleanup_module(void) +{ + pr_info("RNDIS_IPA module is unloaded."); + return; +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("RNDIS_IPA network interface"); + +late_initcall(rndis_ipa_init_module); +module_exit(rndis_ipa_cleanup_module); diff --git a/drivers/net/ethernet/msm/rndis_ipa_trace.h b/drivers/net/ethernet/msm/rndis_ipa_trace.h new file mode 100644 index 000000000000..c0fc573799f2 --- /dev/null +++ b/drivers/net/ethernet/msm/rndis_ipa_trace.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2015, 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. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM rndis_ipa +#define TRACE_INCLUDE_FILE rndis_ipa_trace + +#if !defined(_RNDIS_IPA_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _RNDIS_IPA_TRACE_H + +#include <linux/tracepoint.h> + +TRACE_EVENT( + rndis_netif_ni, + + TP_PROTO(unsigned long proto), + + TP_ARGS(proto), + + TP_STRUCT__entry( + __field(unsigned long, proto) + ), + + TP_fast_assign( + __entry->proto = proto; + ), + + TP_printk("proto =%lu\n", __entry->proto) +); + +TRACE_EVENT( + rndis_tx_dp, + + TP_PROTO(unsigned long proto), + + TP_ARGS(proto), + + TP_STRUCT__entry( + __field(unsigned long, proto) + ), + + TP_fast_assign( + __entry->proto = proto; + ), + + TP_printk("proto =%lu\n", __entry->proto) +); + +TRACE_EVENT( + rndis_status_rcvd, + + TP_PROTO(unsigned long proto), + + TP_ARGS(proto), + + TP_STRUCT__entry( + __field(unsigned long, proto) + ), + + TP_fast_assign( + __entry->proto = proto; + ), + + TP_printk("proto =%lu\n", __entry->proto) +); + +#endif /* _RNDIS_IPA_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#include <trace/define_trace.h> diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c new file mode 100644 index 000000000000..9c88e6749b9a --- /dev/null +++ b/drivers/net/phy/mdio_device.c @@ -0,0 +1,171 @@ +/* Framework for MDIO devices, other than PHYs. + * + * Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mdio.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/unistd.h> + +void mdio_device_free(struct mdio_device *mdiodev) +{ + put_device(&mdiodev->dev); +} +EXPORT_SYMBOL(mdio_device_free); + +static void mdio_device_release(struct device *dev) +{ + kfree(to_mdio_device(dev)); +} + +struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr) +{ + struct mdio_device *mdiodev; + + /* We allocate the device, and initialize the default values */ + mdiodev = kzalloc(sizeof(*mdiodev), GFP_KERNEL); + if (!mdiodev) + return ERR_PTR(-ENOMEM); + + mdiodev->dev.release = mdio_device_release; + mdiodev->dev.parent = &bus->dev; + mdiodev->dev.bus = &mdio_bus_type; + mdiodev->device_free = mdio_device_free; + mdiodev->device_remove = mdio_device_remove; + mdiodev->bus = bus; + mdiodev->addr = addr; + + dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr); + + device_initialize(&mdiodev->dev); + + return mdiodev; +} +EXPORT_SYMBOL(mdio_device_create); + +/** + * mdio_device_register - Register the mdio device on the MDIO bus + * @mdiodev: mdio_device structure to be added to the MDIO bus + */ +int mdio_device_register(struct mdio_device *mdiodev) +{ + int err; + + dev_info(&mdiodev->dev, "mdio_device_register\n"); + + err = mdiobus_register_device(mdiodev); + if (err) + return err; + + err = device_add(&mdiodev->dev); + if (err) { + pr_err("MDIO %d failed to add\n", mdiodev->addr); + goto out; + } + + return 0; + + out: + mdiobus_unregister_device(mdiodev); + return err; +} +EXPORT_SYMBOL(mdio_device_register); + +/** + * mdio_device_remove - Remove a previously registered mdio device from the + * MDIO bus + * @mdiodev: mdio_device structure to remove + * + * This doesn't free the mdio_device itself, it merely reverses the effects + * of mdio_device_register(). Use mdio_device_free() to free the device + * after calling this function. + */ +void mdio_device_remove(struct mdio_device *mdiodev) +{ + device_del(&mdiodev->dev); + mdiobus_unregister_device(mdiodev); +} +EXPORT_SYMBOL(mdio_device_remove); + +/** + * mdio_probe - probe an MDIO device + * @dev: device to probe + * + * Description: Take care of setting up the mdio_device structure + * and calling the driver to probe the device. + */ +static int mdio_probe(struct device *dev) +{ + struct mdio_device *mdiodev = to_mdio_device(dev); + struct device_driver *drv = mdiodev->dev.driver; + struct mdio_driver *mdiodrv = to_mdio_driver(drv); + int err = 0; + + if (mdiodrv->probe) + err = mdiodrv->probe(mdiodev); + + return err; +} + +static int mdio_remove(struct device *dev) +{ + struct mdio_device *mdiodev = to_mdio_device(dev); + struct device_driver *drv = mdiodev->dev.driver; + struct mdio_driver *mdiodrv = to_mdio_driver(drv); + + if (mdiodrv->remove) + mdiodrv->remove(mdiodev); + + return 0; +} + +/** + * mdio_driver_register - register an mdio_driver with the MDIO layer + * @new_driver: new mdio_driver to register + */ +int mdio_driver_register(struct mdio_driver *drv) +{ + struct mdio_driver_common *mdiodrv = &drv->mdiodrv; + int retval; + + pr_info("mdio_driver_register: %s\n", mdiodrv->driver.name); + + mdiodrv->driver.bus = &mdio_bus_type; + mdiodrv->driver.probe = mdio_probe; + mdiodrv->driver.remove = mdio_remove; + + retval = driver_register(&mdiodrv->driver); + if (retval) { + pr_err("%s: Error %d in registering driver\n", + mdiodrv->driver.name, retval); + + return retval; + } + + return 0; +} +EXPORT_SYMBOL(mdio_driver_register); + +void mdio_driver_unregister(struct mdio_driver *drv) +{ + struct mdio_driver_common *mdiodrv = &drv->mdiodrv; + + driver_unregister(&mdiodrv->driver); +} +EXPORT_SYMBOL(mdio_driver_unregister); diff --git a/drivers/net/rmnet/Kconfig b/drivers/net/rmnet/Kconfig new file mode 100644 index 000000000000..751893959b57 --- /dev/null +++ b/drivers/net/rmnet/Kconfig @@ -0,0 +1,21 @@ +# +# RMNET MAP driver +# + +menuconfig RMNET + depends on NETDEVICES + bool "RmNet MAP driver" + ---help--- + If you say Y here, then the rmnet module will be statically + compiled into the kernel. The rmnet module provides MAP + functionality for embedded and bridged traffic. +if RMNET + +config RMNET_DEBUG + bool "RmNet Debug Logging" + ---help--- + Say Y here if you want RmNet to be able to log packets in main + system log. This should not be enabled on production builds as it can + impact system performance. Note that simply enabling it here will not + enable the logging; it must be enabled at run-time as well. +endif # RMNET diff --git a/drivers/net/rmnet/Makefile b/drivers/net/rmnet/Makefile new file mode 100644 index 000000000000..2b6c9cf3756b --- /dev/null +++ b/drivers/net/rmnet/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for the RMNET module +# + +rmnet-y := rmnet_main.o +rmnet-y += rmnet_config.o +rmnet-y += rmnet_vnd.o +rmnet-y += rmnet_handlers.o +rmnet-y += rmnet_map_data.o +rmnet-y += rmnet_map_command.o +rmnet-y += rmnet_stats.o +obj-$(CONFIG_RMNET) += rmnet.o + +CFLAGS_rmnet_main.o := -I$(src) diff --git a/drivers/net/rmnet/rmnet_config.c b/drivers/net/rmnet/rmnet_config.c new file mode 100644 index 000000000000..a20f54adc0b3 --- /dev/null +++ b/drivers/net/rmnet/rmnet_config.c @@ -0,0 +1,1157 @@ +/* Copyright (c) 2013-2017, 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. + * + * RMNET configuration engine + * + */ + +#include <net/sock.h> +#include <linux/module.h> +#include <linux/netlink.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/rmnet.h> +#include "rmnet_config.h" +#include "rmnet_handlers.h" +#include "rmnet_vnd.h" +#include "rmnet_private.h" + +RMNET_LOG_MODULE(RMNET_LOGMASK_CONFIG); + +/* Local Definitions and Declarations */ +#define RMNET_LOCAL_LOGICAL_ENDPOINT -1 +#define RMNET_MAX_AGG_COUNT (128) + +static struct sock *nl_socket_handle; + +static struct netlink_kernel_cfg rmnet_netlink_cfg = { + .input = rmnet_config_netlink_msg_handler +}; + +static struct notifier_block rmnet_dev_notifier = { + .notifier_call = rmnet_config_notify_cb, + .next = 0, + .priority = 0 +}; + +struct rmnet_free_vnd_work { + struct work_struct work; + int vnd_id[RMNET_MAX_VND]; + int count; +}; + +/* Init and Cleanup */ + +static struct sock *_rmnet_config_start_netlink(void) +{ + return netlink_kernel_create(&init_net, + RMNET_NETLINK_PROTO, + &rmnet_netlink_cfg); +} + +/* rmnet_config_init() - Startup init + * + * Registers netlink protocol with kernel and opens socket. Netlink handler is + * registered with kernel. + */ +int rmnet_config_init(void) +{ + int rc; + + nl_socket_handle = _rmnet_config_start_netlink(); + if (!nl_socket_handle) { + LOGE("%s", "Failed to init netlink socket"); + return RMNET_INIT_ERROR; + } + + rc = register_netdevice_notifier(&rmnet_dev_notifier); + if (rc != 0) { + LOGE("Failed to register device notifier; rc=%d", rc); + netlink_kernel_release(nl_socket_handle); + return RMNET_INIT_ERROR; + } + + return 0; +} + +/* rmnet_config_exit() - Cleans up all netlink related resources + */ +void rmnet_config_exit(void) +{ + int rc; + + netlink_kernel_release(nl_socket_handle); + rc = unregister_netdevice_notifier(&rmnet_dev_notifier); + if (rc != 0) + LOGE("Failed to unregister device notifier; rc=%d", rc); +} + +/* Helper Functions */ + +/* _rmnet_is_physical_endpoint_associated() - Determines if device is associated + * @dev: Device to get check + * + * Compares device rx_handler callback pointer against known function + * + * Return: + * - 1 if associated + * - 0 if NOT associated + */ +static inline int _rmnet_is_physical_endpoint_associated(struct net_device *dev) +{ + rx_handler_func_t *rx_handler; + + rx_handler = rcu_dereference(dev->rx_handler); + + if (rx_handler == rmnet_rx_handler) + return 1; + else + return 0; +} + +/* _rmnet_get_phys_ep_config() - Get physical ep config for an associated device + * @dev: Device to get endpoint configuration from + * + * Return: + * - pointer to configuration if successful + * - 0 (null) if device is not associated + */ +static inline struct rmnet_phys_ep_conf_s *_rmnet_get_phys_ep_config + (struct net_device *dev) +{ + if (_rmnet_is_physical_endpoint_associated(dev)) + return (struct rmnet_phys_ep_conf_s *) + rcu_dereference(dev->rx_handler_data); + else + return NULL; +} + +/* _rmnet_get_logical_ep() - Gets the logical end point configuration + * structure for a network device + * @dev: Device to get endpoint configuration from + * @config_id: Logical endpoint id on device + * Retrieves the logical_endpoint_config structure. + * + * Return: + * - End point configuration structure + * - NULL in case of an error + */ +struct rmnet_logical_ep_conf_s *_rmnet_get_logical_ep(struct net_device *dev, + int config_id) +{ + struct rmnet_phys_ep_conf_s *config; + struct rmnet_logical_ep_conf_s *epconfig_l; + + if (rmnet_vnd_is_vnd(dev)) { + epconfig_l = rmnet_vnd_get_le_config(dev); + } else { + config = _rmnet_get_phys_ep_config(dev); + + if (!config) + return NULL; + + if (config_id == RMNET_LOCAL_LOGICAL_ENDPOINT) + epconfig_l = &config->local_ep; + else + epconfig_l = &config->muxed_ep[config_id]; + } + + return epconfig_l; +} + +static void _rmnet_netlink_set_link_egress_data_format + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + dev = dev_get_by_name(&init_net, rmnet_header->data_format.dev); + + if (!dev) { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + return; + } + + resp_rmnet->return_code = + rmnet_set_egress_data_format(dev, + rmnet_header->data_format.flags, + rmnet_header->data_format.agg_size, + rmnet_header->data_format.agg_count + ); + dev_put(dev); +} + +static void _rmnet_netlink_set_link_ingress_data_format + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + + dev = dev_get_by_name(&init_net, rmnet_header->data_format.dev); + if (!dev) { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + return; + } + + resp_rmnet->return_code = rmnet_set_ingress_data_format( + dev, + rmnet_header->data_format.flags); + dev_put(dev); +} + +static void _rmnet_netlink_set_logical_ep_config + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev, *dev2; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + if (rmnet_header->local_ep_config.ep_id < -1 || + rmnet_header->local_ep_config.ep_id > 254) { + resp_rmnet->return_code = RMNET_CONFIG_BAD_ARGUMENTS; + return; + } + + dev = dev_get_by_name(&init_net, + rmnet_header->local_ep_config.dev); + + dev2 = dev_get_by_name(&init_net, + rmnet_header->local_ep_config.next_dev); + + if (dev && dev2) + resp_rmnet->return_code = + rmnet_set_logical_endpoint_config( + dev, + rmnet_header->local_ep_config.ep_id, + rmnet_header->local_ep_config.operating_mode, + dev2); + else + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + + if (dev) + dev_put(dev); + if (dev2) + dev_put(dev2); +} + +static void _rmnet_netlink_unset_logical_ep_config + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + if (rmnet_header->local_ep_config.ep_id < -1 || + rmnet_header->local_ep_config.ep_id > 254) { + resp_rmnet->return_code = RMNET_CONFIG_BAD_ARGUMENTS; + return; + } + + dev = dev_get_by_name(&init_net, rmnet_header->local_ep_config.dev); + + if (dev) { + resp_rmnet->return_code = + rmnet_unset_logical_endpoint_config( + dev, + rmnet_header->local_ep_config.ep_id); + dev_put(dev); + } else { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + } +} + +static void _rmnet_netlink_get_logical_ep_config + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + if (rmnet_header->local_ep_config.ep_id < -1 || + rmnet_header->local_ep_config.ep_id > 254) { + resp_rmnet->return_code = RMNET_CONFIG_BAD_ARGUMENTS; + return; + } + + dev = dev_get_by_name(&init_net, rmnet_header->local_ep_config.dev); + + if (dev) { + resp_rmnet->return_code = + rmnet_get_logical_endpoint_config( + dev, + rmnet_header->local_ep_config.ep_id, + &resp_rmnet->local_ep_config.operating_mode, + resp_rmnet->local_ep_config.next_dev, + sizeof(resp_rmnet->local_ep_config.next_dev)); + } else { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + return; + } + + if (resp_rmnet->return_code == RMNET_CONFIG_OK) { + /* Begin Data */ + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA; + resp_rmnet->arg_length = sizeof(((struct rmnet_nl_msg_s *)0) + ->local_ep_config); + } + dev_put(dev); +} + +static void _rmnet_netlink_associate_network_device + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + + dev = dev_get_by_name(&init_net, rmnet_header->data); + if (!dev) { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + return; + } + + resp_rmnet->return_code = rmnet_associate_network_device(dev); + dev_put(dev); +} + +static void _rmnet_netlink_unassociate_network_device + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + + dev = dev_get_by_name(&init_net, rmnet_header->data); + if (!dev) { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + return; + } + + resp_rmnet->return_code = rmnet_unassociate_network_device(dev); + dev_put(dev); +} + +static void _rmnet_netlink_get_network_device_associated + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + + dev = dev_get_by_name(&init_net, rmnet_header->data); + if (!dev) { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + return; + } + + resp_rmnet->return_code = _rmnet_is_physical_endpoint_associated(dev); + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA; + dev_put(dev); +} + +static void _rmnet_netlink_get_link_egress_data_format + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + struct rmnet_phys_ep_conf_s *config; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + + dev = dev_get_by_name(&init_net, rmnet_header->data_format.dev); + if (!dev) { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + return; + } + + config = _rmnet_get_phys_ep_config(dev); + if (!config) { + resp_rmnet->return_code = RMNET_CONFIG_INVALID_REQUEST; + dev_put(dev); + return; + } + + /* Begin Data */ + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA; + resp_rmnet->arg_length = sizeof(((struct rmnet_nl_msg_s *)0) + ->data_format); + resp_rmnet->data_format.flags = config->egress_data_format; + resp_rmnet->data_format.agg_count = 0; + resp_rmnet->data_format.agg_size = 0; + dev_put(dev); +} + +static void _rmnet_netlink_get_link_ingress_data_format + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + struct net_device *dev; + struct rmnet_phys_ep_conf_s *config; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + + dev = dev_get_by_name(&init_net, rmnet_header->data_format.dev); + if (!dev) { + resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE; + return; + } + + config = _rmnet_get_phys_ep_config(dev); + if (!config) { + resp_rmnet->return_code = RMNET_CONFIG_INVALID_REQUEST; + dev_put(dev); + return; + } + + /* Begin Data */ + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA; + resp_rmnet->arg_length = sizeof(((struct rmnet_nl_msg_s *)0) + ->data_format); + resp_rmnet->data_format.flags = config->ingress_data_format; + dev_put(dev); +} + +static void _rmnet_netlink_get_vnd_name + (struct rmnet_nl_msg_s *rmnet_header, + struct rmnet_nl_msg_s *resp_rmnet) +{ + int r; + + if (!rmnet_header || !resp_rmnet) + return; + + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + + r = rmnet_vnd_get_name(rmnet_header->vnd.id, resp_rmnet->vnd.vnd_name, + RMNET_MAX_STR_LEN); + + if (r != 0) { + resp_rmnet->return_code = RMNET_CONFIG_INVALID_REQUEST; + return; + } + + /* Begin Data */ + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA; + resp_rmnet->arg_length = sizeof(((struct rmnet_nl_msg_s *)0)->vnd); +} + +/* rmnet_config_netlink_msg_handler() - Netlink message handler callback + * @skb: Packet containing netlink messages + * + * Standard kernel-expected format for a netlink message handler. Processes SKBs + * which contain RmNet data specific netlink messages. + */ +void rmnet_config_netlink_msg_handler(struct sk_buff *skb) +{ + struct nlmsghdr *nlmsg_header, *resp_nlmsg; + struct rmnet_nl_msg_s *rmnet_header, *resp_rmnet; + int return_pid, response_data_length; + struct sk_buff *skb_response; + + response_data_length = 0; + nlmsg_header = (struct nlmsghdr *)skb->data; + rmnet_header = (struct rmnet_nl_msg_s *)nlmsg_data(nlmsg_header); + + if (!nlmsg_header->nlmsg_pid || + (nlmsg_header->nlmsg_len < sizeof(struct nlmsghdr) + + sizeof(struct rmnet_nl_msg_s))) + return; + + LOGL("Netlink message pid=%d, seq=%d, length=%d, rmnet_type=%d", + nlmsg_header->nlmsg_pid, + nlmsg_header->nlmsg_seq, + nlmsg_header->nlmsg_len, + rmnet_header->message_type); + + return_pid = nlmsg_header->nlmsg_pid; + + skb_response = nlmsg_new(sizeof(struct nlmsghdr) + + sizeof(struct rmnet_nl_msg_s), + GFP_KERNEL); + + if (!skb_response) + return; + + resp_nlmsg = nlmsg_put(skb_response, + 0, + nlmsg_header->nlmsg_seq, + NLMSG_DONE, + sizeof(struct rmnet_nl_msg_s), + 0); + + resp_rmnet = nlmsg_data(resp_nlmsg); + + if (!resp_rmnet) + return; + + resp_rmnet->message_type = rmnet_header->message_type; + rtnl_lock(); + switch (rmnet_header->message_type) { + case RMNET_NETLINK_ASSOCIATE_NETWORK_DEVICE: + _rmnet_netlink_associate_network_device + (rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_UNASSOCIATE_NETWORK_DEVICE: + _rmnet_netlink_unassociate_network_device + (rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_GET_NETWORK_DEVICE_ASSOCIATED: + _rmnet_netlink_get_network_device_associated + (rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_SET_LINK_EGRESS_DATA_FORMAT: + _rmnet_netlink_set_link_egress_data_format + (rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_GET_LINK_EGRESS_DATA_FORMAT: + _rmnet_netlink_get_link_egress_data_format + (rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_SET_LINK_INGRESS_DATA_FORMAT: + _rmnet_netlink_set_link_ingress_data_format + (rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_GET_LINK_INGRESS_DATA_FORMAT: + _rmnet_netlink_get_link_ingress_data_format + (rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_SET_LOGICAL_EP_CONFIG: + _rmnet_netlink_set_logical_ep_config(rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_UNSET_LOGICAL_EP_CONFIG: + _rmnet_netlink_unset_logical_ep_config(rmnet_header, + resp_rmnet); + break; + + case RMNET_NETLINK_GET_LOGICAL_EP_CONFIG: + _rmnet_netlink_get_logical_ep_config(rmnet_header, resp_rmnet); + break; + + case RMNET_NETLINK_NEW_VND: + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + resp_rmnet->return_code = + rmnet_create_vnd(rmnet_header->vnd.id); + break; + + case RMNET_NETLINK_NEW_VND_WITH_PREFIX: + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + resp_rmnet->return_code = rmnet_create_vnd_prefix( + rmnet_header->vnd.id, + rmnet_header->vnd.vnd_name); + break; + + case RMNET_NETLINK_FREE_VND: + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + /* Please check rmnet_vnd_free_dev documentation regarding + * the below locking sequence + */ + rtnl_unlock(); + resp_rmnet->return_code = rmnet_free_vnd(rmnet_header->vnd.id); + rtnl_lock(); + break; + + case RMNET_NETLINK_GET_VND_NAME: + _rmnet_netlink_get_vnd_name(rmnet_header, resp_rmnet); + break; + + default: + resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE; + resp_rmnet->return_code = RMNET_CONFIG_UNKNOWN_MESSAGE; + break; + } + rtnl_unlock(); + nlmsg_unicast(nl_socket_handle, skb_response, return_pid); + LOGD("%s", "Done processing command"); +} + +/* Configuration API */ + +/* rmnet_unassociate_network_device() - Unassociate network device + * @dev: Device to unassociate + * + * Frees all structures generate for device. Unregisters rx_handler + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_NO_SUCH_DEVICE dev is null + * - RMNET_CONFIG_INVALID_REQUEST if device is not already associated + * - RMNET_CONFIG_DEVICE_IN_USE if device has logical ep that wasn't unset + * - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null + */ +int rmnet_unassociate_network_device(struct net_device *dev) +{ + struct rmnet_phys_ep_conf_s *config; + int config_id = RMNET_LOCAL_LOGICAL_ENDPOINT; + struct rmnet_logical_ep_conf_s *epconfig_l; + + ASSERT_RTNL(); + + LOGL("(%s);", dev->name); + + if (!dev) + return RMNET_CONFIG_NO_SUCH_DEVICE; + + if (!_rmnet_is_physical_endpoint_associated(dev)) + return RMNET_CONFIG_INVALID_REQUEST; + + for (; config_id < RMNET_MAX_LOGICAL_EP; config_id++) { + epconfig_l = _rmnet_get_logical_ep(dev, config_id); + if (epconfig_l && epconfig_l->refcount) + return RMNET_CONFIG_DEVICE_IN_USE; + } + + config = (struct rmnet_phys_ep_conf_s *) + rcu_dereference(dev->rx_handler_data); + + if (!config) + return RMNET_CONFIG_UNKNOWN_ERROR; + + kfree(config); + + netdev_rx_handler_unregister(dev); + + /* Explicitly release the reference from the device */ + dev_put(dev); + return RMNET_CONFIG_OK; +} + +/* rmnet_set_ingress_data_format() - Set ingress data format on network device + * @dev: Device to ingress data format on + * @egress_data_format: 32-bit unsigned bitmask of ingress format + * + * Network device must already have association with RmNet Data driver + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_NO_SUCH_DEVICE dev is null + * - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null + */ +int rmnet_set_ingress_data_format(struct net_device *dev, + u32 ingress_data_format) +{ + struct rmnet_phys_ep_conf_s *config; + + ASSERT_RTNL(); + + LOGL("(%s,0x%08X);", dev->name, ingress_data_format); + + if (!dev) + return RMNET_CONFIG_NO_SUCH_DEVICE; + + config = _rmnet_get_phys_ep_config(dev); + + if (!config) + return RMNET_CONFIG_INVALID_REQUEST; + + config->ingress_data_format = ingress_data_format; + + return RMNET_CONFIG_OK; +} + +/* rmnet_set_egress_data_format() - Set egress data format on network device + * @dev: Device to egress data format on + * @egress_data_format: 32-bit unsigned bitmask of egress format + * + * Network device must already have association with RmNet Data driver + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_NO_SUCH_DEVICE dev is null + * - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null + */ +int rmnet_set_egress_data_format(struct net_device *dev, + u32 egress_data_format, + u16 agg_size, + u16 agg_count) +{ + struct rmnet_phys_ep_conf_s *config; + + ASSERT_RTNL(); + + LOGL("(%s,0x%08X, %d, %d);", + dev->name, egress_data_format, agg_size, agg_count); + + if (!dev) + return RMNET_CONFIG_NO_SUCH_DEVICE; + + config = _rmnet_get_phys_ep_config(dev); + + if (!config || (agg_count > RMNET_MAX_AGG_COUNT)) + return RMNET_CONFIG_UNKNOWN_ERROR; + + config->egress_data_format = egress_data_format; + + return RMNET_CONFIG_OK; +} + +/* rmnet_associate_network_device() - Associate network device + * @dev: Device to register with RmNet data + * + * Typically used on physical network devices. Registers RX handler and private + * metadata structures. + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_NO_SUCH_DEVICE dev is null + * - RMNET_CONFIG_INVALID_REQUEST if the device to be associated is a vnd + * - RMNET_CONFIG_DEVICE_IN_USE if dev rx_handler is already filled + * - RMNET_CONFIG_DEVICE_IN_USE if netdev_rx_handler_register() fails + */ +int rmnet_associate_network_device(struct net_device *dev) +{ + struct rmnet_phys_ep_conf_s *config; + int rc; + + ASSERT_RTNL(); + + LOGL("(%s);\n", dev->name); + + if (!dev) + return RMNET_CONFIG_NO_SUCH_DEVICE; + + if (_rmnet_is_physical_endpoint_associated(dev)) { + LOGM("%s is already regestered", dev->name); + return RMNET_CONFIG_DEVICE_IN_USE; + } + + if (rmnet_vnd_is_vnd(dev)) { + LOGM("%s is a vnd", dev->name); + return RMNET_CONFIG_INVALID_REQUEST; + } + + config = kmalloc(sizeof(*config), GFP_ATOMIC); + + if (!config) + return RMNET_CONFIG_NOMEM; + + memset(config, 0, sizeof(struct rmnet_phys_ep_conf_s)); + config->dev = dev; + + rc = netdev_rx_handler_register(dev, rmnet_rx_handler, config); + + if (rc) { + LOGM("netdev_rx_handler_register returns %d", rc); + kfree(config); + return RMNET_CONFIG_DEVICE_IN_USE; + } + + /* Explicitly hold a reference to the device */ + dev_hold(dev); + return RMNET_CONFIG_OK; +} + +/* _rmnet_set_logical_endpoint_config() - Set logical endpoing config on device + * @dev: Device to set endpoint configuration on + * @config_id: logical endpoint id on device + * @epconfig: endpoing configuration structure to set + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null + * - RMNET_CONFIG_NO_SUCH_DEVICE if device to set config on is null + * - RMNET_CONFIG_DEVICE_IN_USE if device already has a logical ep + * - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range + */ +int _rmnet_set_logical_endpoint_config(struct net_device *dev, + int config_id, + struct rmnet_logical_ep_conf_s *epconfig) +{ + struct rmnet_logical_ep_conf_s *epconfig_l; + + ASSERT_RTNL(); + + if (!dev) + return RMNET_CONFIG_NO_SUCH_DEVICE; + + if (config_id < RMNET_LOCAL_LOGICAL_ENDPOINT || + config_id >= RMNET_MAX_LOGICAL_EP) + return RMNET_CONFIG_BAD_ARGUMENTS; + + epconfig_l = _rmnet_get_logical_ep(dev, config_id); + + if (!epconfig_l) + return RMNET_CONFIG_UNKNOWN_ERROR; + + if (epconfig_l->refcount) + return RMNET_CONFIG_DEVICE_IN_USE; + + memcpy(epconfig_l, epconfig, sizeof(struct rmnet_logical_ep_conf_s)); + if (config_id == RMNET_LOCAL_LOGICAL_ENDPOINT) + epconfig_l->mux_id = 0; + else + epconfig_l->mux_id = config_id; + + /* Explicitly hold a reference to the egress device */ + dev_hold(epconfig_l->egress_dev); + return RMNET_CONFIG_OK; +} + +/* _rmnet_unset_logical_endpoint_config() - Un-set the logical endpoing config + * on device + * @dev: Device to set endpoint configuration on + * @config_id: logical endpoint id on device + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null + * - RMNET_CONFIG_NO_SUCH_DEVICE if device to set config on is null + * - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range + */ +int _rmnet_unset_logical_endpoint_config(struct net_device *dev, + int config_id) +{ + struct rmnet_logical_ep_conf_s *epconfig_l = 0; + + ASSERT_RTNL(); + + if (!dev) + return RMNET_CONFIG_NO_SUCH_DEVICE; + + if (config_id < RMNET_LOCAL_LOGICAL_ENDPOINT || + config_id >= RMNET_MAX_LOGICAL_EP) + return RMNET_CONFIG_BAD_ARGUMENTS; + + epconfig_l = _rmnet_get_logical_ep(dev, config_id); + + if (!epconfig_l || !epconfig_l->refcount) + return RMNET_CONFIG_NO_SUCH_DEVICE; + + /* Explicitly release the reference from the egress device */ + dev_put(epconfig_l->egress_dev); + memset(epconfig_l, 0, sizeof(struct rmnet_logical_ep_conf_s)); + + return RMNET_CONFIG_OK; +} + +/* rmnet_set_logical_endpoint_config() - Set logical endpoint config on a device + * @dev: Device to set endpoint configuration on + * @config_id: logical endpoint id on device + * @rmnet_mode: endpoint mode. Values from: rmnet_config_endpoint_modes_e + * @egress_device: device node to forward packet to once done processing in + * ingress/egress handlers + * + * Creates a logical_endpoint_config structure and fills in the information from + * function arguments. Calls _rmnet_set_logical_endpoint_config() to finish + * configuration. Network device must already have association with RmNet Data + * driver + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_BAD_EGRESS_DEVICE if egress device is null + * - RMNET_CONFIG_BAD_EGRESS_DEVICE if egress device is not handled by + * RmNet data module + * - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null + * - RMNET_CONFIG_NO_SUCH_DEVICE if device to set config on is null + * - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range + */ +int rmnet_set_logical_endpoint_config(struct net_device *dev, + int config_id, + u8 rmnet_mode, + struct net_device *egress_dev) +{ + struct rmnet_logical_ep_conf_s epconfig; + + LOGL("(%s, %d, %d, %s);", + dev->name, config_id, rmnet_mode, egress_dev->name); + + if (!egress_dev || + ((!_rmnet_is_physical_endpoint_associated(egress_dev)) && + (!rmnet_vnd_is_vnd(egress_dev)))) { + return RMNET_CONFIG_BAD_EGRESS_DEVICE; + } + + memset(&epconfig, 0, sizeof(struct rmnet_logical_ep_conf_s)); + epconfig.refcount = 1; + epconfig.rmnet_mode = rmnet_mode; + epconfig.egress_dev = egress_dev; + + return _rmnet_set_logical_endpoint_config(dev, config_id, &epconfig); +} + +/* rmnet_unset_logical_endpoint_config() - Un-set logical endpoing configuration + * on a device + * @dev: Device to set endpoint configuration on + * @config_id: logical endpoint id on device + * + * Retrieves the logical_endpoint_config structure and frees the egress device. + * Network device must already have association with RmNet Data driver + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null + * - RMNET_CONFIG_NO_SUCH_DEVICE device is not associated + * - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range + */ +int rmnet_unset_logical_endpoint_config(struct net_device *dev, + int config_id) +{ + LOGL("(%s, %d);", dev->name, config_id); + + if (!dev || + ((!_rmnet_is_physical_endpoint_associated(dev)) && + (!rmnet_vnd_is_vnd(dev)))) { + return RMNET_CONFIG_NO_SUCH_DEVICE; + } + + return _rmnet_unset_logical_endpoint_config(dev, config_id); +} + +/* rmnet_get_logical_endpoint_config() - Gets logical endpoing configuration + * for a device + * @dev: Device to get endpoint configuration on + * @config_id: logical endpoint id on device + * @rmnet_mode: (I/O) logical endpoint mode + * @egress_dev_name: (I/O) logical endpoint egress device name + * @egress_dev_name_size: The maximal size of the I/O egress_dev_name + * + * Retrieves the logical_endpoint_config structure. + * Network device must already have association with RmNet Data driver + * + * Return: + * - RMNET_CONFIG_OK if successful + * - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null + * - RMNET_CONFIG_NO_SUCH_DEVICE device is not associated + * - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range or + * if the provided buffer size for egress dev name is too short + */ +int rmnet_get_logical_endpoint_config(struct net_device *dev, + int config_id, + u8 *rmnet_mode, + u8 *egress_dev_name, + size_t egress_dev_name_size) +{ + struct rmnet_logical_ep_conf_s *epconfig_l = 0; + size_t strlcpy_res = 0; + + LOGL("(%s, %d);", dev->name, config_id); + + if (!egress_dev_name || !rmnet_mode) + return RMNET_CONFIG_BAD_ARGUMENTS; + if (config_id < RMNET_LOCAL_LOGICAL_ENDPOINT || + config_id >= RMNET_MAX_LOGICAL_EP) + return RMNET_CONFIG_BAD_ARGUMENTS; + + epconfig_l = _rmnet_get_logical_ep(dev, config_id); + + if (!epconfig_l || !epconfig_l->refcount) + return RMNET_CONFIG_NO_SUCH_DEVICE; + + *rmnet_mode = epconfig_l->rmnet_mode; + + strlcpy_res = strlcpy(egress_dev_name, epconfig_l->egress_dev->name, + egress_dev_name_size); + + if (strlcpy_res >= egress_dev_name_size) + return RMNET_CONFIG_BAD_ARGUMENTS; + + return RMNET_CONFIG_OK; +} + +/* rmnet_create_vnd() - Create virtual network device node + * @id: RmNet virtual device node id + * + * Return: + * - result of rmnet_vnd_create_dev() + */ +int rmnet_create_vnd(int id) +{ + struct net_device *dev; + + ASSERT_RTNL(); + LOGL("(%d);", id); + return rmnet_vnd_create_dev(id, &dev, NULL); +} + +/* rmnet_create_vnd() - Create virtual network device node + * @id: RmNet virtual device node id + * @prefix: String prefix for device name + * + * Return: + * - result of rmnet_vnd_create_dev() + */ +int rmnet_create_vnd_prefix(int id, const char *prefix) +{ + struct net_device *dev; + + ASSERT_RTNL(); + LOGL("(%d, \"%s\");", id, prefix); + return rmnet_vnd_create_dev(id, &dev, prefix); +} + +/* rmnet_free_vnd() - Free virtual network device node + * @id: RmNet virtual device node id + * + * Return: + * - result of rmnet_vnd_free_dev() + */ +int rmnet_free_vnd(int id) +{ + LOGL("(%d);", id); + return rmnet_vnd_free_dev(id); +} + +static void _rmnet_free_vnd_later(struct work_struct *work) +{ + int i; + struct rmnet_free_vnd_work *fwork; + + fwork = container_of(work, struct rmnet_free_vnd_work, work); + + for (i = 0; i < fwork->count; i++) + rmnet_free_vnd(fwork->vnd_id[i]); + kfree(fwork); +} + +/* rmnet_force_unassociate_device() - Force a device to unassociate + * @dev: Device to unassociate + * + * Return: + * - void + */ +static void rmnet_force_unassociate_device(struct net_device *dev) +{ + int i, j; + struct net_device *vndev; + struct rmnet_logical_ep_conf_s *cfg; + struct rmnet_free_vnd_work *vnd_work; + + ASSERT_RTNL(); + if (!dev) + return; + + if (!_rmnet_is_physical_endpoint_associated(dev)) { + LOGM("%s", "Called on unassociated device, skipping"); + return; + } + + vnd_work = kmalloc(sizeof(*vnd_work), GFP_KERNEL); + if (!vnd_work) { + LOGH("%s", "Out of Memory"); + return; + } + INIT_WORK(&vnd_work->work, _rmnet_free_vnd_later); + vnd_work->count = 0; + + /* Check the VNDs for offending mappings */ + for (i = 0, j = 0; i < RMNET_MAX_VND && + j < RMNET_MAX_VND; i++) { + vndev = rmnet_vnd_get_by_id(i); + if (!vndev) { + LOGL("VND %d not in use; skipping", i); + continue; + } + cfg = rmnet_vnd_get_le_config(vndev); + if (!cfg) { + LOGH("Got NULL config from VND %d", i); + continue; + } + if (cfg->refcount && (cfg->egress_dev == dev)) { + /* Make sure the device is down before clearing any of + * the mappings. Otherwise we could see a potential + * race condition if packets are actively being + * transmitted. + */ + dev_close(vndev); + rmnet_unset_logical_endpoint_config + (vndev, RMNET_LOCAL_LOGICAL_ENDPOINT); + vnd_work->vnd_id[j] = i; + j++; + } + } + if (j > 0) { + vnd_work->count = j; + schedule_work(&vnd_work->work); + } else { + kfree(vnd_work); + } + + /* Clear the mappings on the phys ep */ + rmnet_unset_logical_endpoint_config(dev, RMNET_LOCAL_LOGICAL_ENDPOINT); + for (i = 0; i < RMNET_MAX_LOGICAL_EP; i++) + rmnet_unset_logical_endpoint_config(dev, i); + rmnet_unassociate_network_device(dev); +} + +/* rmnet_config_notify_cb() - Callback for netdevice notifier chain + * @nb: Notifier block data + * @event: Netdevice notifier event ID + * @data: Contains a net device for which we are getting notified + * + * Return: + * - result of NOTIFY_DONE() + */ +int rmnet_config_notify_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct net_device *dev = netdev_notifier_info_to_dev(data); + + if (!dev) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_UNREGISTER_FINAL: + case NETDEV_UNREGISTER: + LOGH("Kernel is trying to unregister %s", dev->name); + rmnet_force_unassociate_device(dev); + break; + + default: + LOGD("Unhandled event [%lu]", event); + break; + } + + return NOTIFY_DONE; +} diff --git a/drivers/net/rmnet/rmnet_config.h b/drivers/net/rmnet/rmnet_config.h new file mode 100644 index 000000000000..be2fc8964dad --- /dev/null +++ b/drivers/net/rmnet/rmnet_config.h @@ -0,0 +1,107 @@ +/* Copyright (c) 2013-2014, 2016-2017 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. + * + * RMNET Data configuration engine + * + */ + +#include <linux/types.h> +#include <linux/time.h> +#include <linux/skbuff.h> + +#ifndef _RMNET_CONFIG_H_ +#define _RMNET_CONFIG_H_ + +#define RMNET_MAX_LOGICAL_EP 256 + +/* struct rmnet_logical_ep_conf_s - Logical end-point configuration + * + * @refcount: Reference count for this endpoint. 0 signifies the endpoint is not + * configured for use + * @rmnet_mode: Specifies how the traffic should be finally delivered. Possible + * options are available in enum rmnet_config_endpoint_modes_e + * @mux_id: Virtual channel ID used by MAP protocol + * @egress_dev: Next device to deliver the packet to. Exact usage of this + * parmeter depends on the rmnet_mode + */ +struct rmnet_logical_ep_conf_s { + u8 refcount; + u8 rmnet_mode; + u8 mux_id; + struct timespec flush_time; + struct net_device *egress_dev; +}; + +/* struct rmnet_phys_ep_conf_s - Physical endpoint configuration + * One instance of this structure is instantiated for each net_device associated + * with rmnet. + * + * @dev: The device which is associated with rmnet. Corresponds to this + * specific instance of rmnet_phys_ep_conf_s + * @local_ep: Default non-muxed endpoint. Used for non-MAP protocols/formats + * @muxed_ep: All multiplexed logical endpoints associated with this device + * @ingress_data_format: RMNET_INGRESS_FORMAT_* flags from rmnet.h + * @egress_data_format: RMNET_EGRESS_FORMAT_* flags from rmnet.h + * + * @egress_agg_size: Maximum size (bytes) of data which should be aggregated + * @egress_agg_count: Maximum count (packets) of data which should be aggregated + * Smaller of the two parameters above are chosen for + * aggregation + * @tail_spacing: Guaranteed padding (bytes) when de-aggregating ingress frames + * @agg_time: Wall clock time when aggregated frame was created + * @agg_last: Last time the aggregation routing was invoked + */ +struct rmnet_phys_ep_conf_s { + struct net_device *dev; + struct rmnet_logical_ep_conf_s local_ep; + struct rmnet_logical_ep_conf_s muxed_ep[RMNET_MAX_LOGICAL_EP]; + u32 ingress_data_format; + u32 egress_data_format; +}; + +int rmnet_config_init(void); +void rmnet_config_exit(void); + +int rmnet_unassociate_network_device(struct net_device *dev); +int rmnet_set_ingress_data_format(struct net_device *dev, + u32 ingress_data_format); +int rmnet_set_egress_data_format(struct net_device *dev, + u32 egress_data_format, + u16 agg_size, + u16 agg_count); +int rmnet_associate_network_device(struct net_device *dev); +int _rmnet_set_logical_endpoint_config(struct net_device *dev, + int config_id, + struct rmnet_logical_ep_conf_s *epconfig); +int rmnet_set_logical_endpoint_config(struct net_device *dev, + int config_id, + u8 rmnet_mode, + struct net_device *egress_dev); +int _rmnet_unset_logical_endpoint_config(struct net_device *dev, + int config_id); +int rmnet_unset_logical_endpoint_config(struct net_device *dev, + int config_id); +int _rmnet_get_logical_endpoint_config(struct net_device *dev, + int config_id, + struct rmnet_logical_ep_conf_s *epconfig); +int rmnet_get_logical_endpoint_config(struct net_device *dev, + int config_id, + u8 *rmnet_mode, + u8 *egress_dev_name, + size_t egress_dev_name_size); +void rmnet_config_netlink_msg_handler (struct sk_buff *skb); +int rmnet_config_notify_cb(struct notifier_block *nb, + unsigned long event, void *data); +int rmnet_create_vnd(int id); +int rmnet_create_vnd_prefix(int id, const char *name); +int rmnet_free_vnd(int id); + +#endif /* _RMNET_CONFIG_H_ */ diff --git a/drivers/net/rmnet/rmnet_handlers.c b/drivers/net/rmnet/rmnet_handlers.c new file mode 100644 index 000000000000..c2ade2d61e2f --- /dev/null +++ b/drivers/net/rmnet/rmnet_handlers.c @@ -0,0 +1,550 @@ +/* Copyright (c) 2013-2017, 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. + * + * RMNET Data ingress/egress handler + * + */ + +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/module.h> +#include <linux/rmnet.h> +#include <linux/netdev_features.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include "rmnet_private.h" +#include "rmnet_config.h" +#include "rmnet_vnd.h" +#include "rmnet_map.h" +#include "rmnet_stats.h" +#include "rmnet_handlers.h" + +RMNET_LOG_MODULE(RMNET_LOGMASK_HANDLER); + +#ifdef CONFIG_RMNET_DEBUG +unsigned int dump_pkt_rx; +module_param(dump_pkt_rx, uint, 0644); +MODULE_PARM_DESC(dump_pkt_rx, "Dump packets entering ingress handler"); + +unsigned int dump_pkt_tx; +module_param(dump_pkt_tx, uint, 0644); +MODULE_PARM_DESC(dump_pkt_tx, "Dump packets exiting egress handler"); +#endif /* CONFIG_RMNET_DEBUG */ + +#define RMNET_IP_VERSION_4 0x40 +#define RMNET_IP_VERSION_6 0x60 + +/* Helper Functions */ + +/* __rmnet_set_skb_proto() - Set skb->protocol field + * @skb: packet being modified + * + * Peek at the first byte of the packet and set the protocol. There is not + * good way to determine if a packet has a MAP header. As of writing this, + * the reserved bit in the MAP frame will prevent it from overlapping with + * IPv4/IPv6 frames. This could change in the future! + */ +static inline void __rmnet_set_skb_proto(struct sk_buff *skb) +{ + switch (skb->data[0] & 0xF0) { + case RMNET_IP_VERSION_4: + skb->protocol = htons(ETH_P_IP); + break; + case RMNET_IP_VERSION_6: + skb->protocol = htons(ETH_P_IPV6); + break; + default: + skb->protocol = htons(ETH_P_MAP); + break; + } +} + +#ifdef CONFIG_RMNET_DEBUG +/* rmnet_print_packet() - Print packet / diagnostics + * @skb: Packet to print + * @printlen: Number of bytes to print + * @dev: Name of interface + * @dir: Character representing direction (e.g.. 'r' for receive) + * + * This function prints out raw bytes in an SKB. Use of this will have major + * performance impacts and may even trigger watchdog resets if too much is being + * printed. Hence, this should always be compiled out unless absolutely needed. + */ +void rmnet_print_packet(const struct sk_buff *skb, const char *dev, char dir) +{ + char buffer[200]; + unsigned int len, printlen; + int i, buffloc = 0; + + switch (dir) { + case 'r': + printlen = dump_pkt_rx; + break; + + case 't': + printlen = dump_pkt_tx; + break; + + default: + printlen = 0; + break; + } + + if (!printlen) + return; + + pr_err("[%s][%c] - PKT skb->len=%d skb->head=%pK skb->data=%pK\n", + dev, dir, skb->len, (void *)skb->head, (void *)skb->data); + pr_err("[%s][%c] - PKT skb->tail=%pK skb->end=%pK\n", + dev, dir, skb_tail_pointer(skb), skb_end_pointer(skb)); + + if (skb->len > 0) + len = skb->len; + else + len = ((unsigned int)(uintptr_t)skb->end) - + ((unsigned int)(uintptr_t)skb->data); + + pr_err("[%s][%c] - PKT len: %d, printing first %d bytes\n", + dev, dir, len, printlen); + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; (i < printlen) && (i < len); i++) { + if ((i % 16) == 0) { + pr_err("[%s][%c] - PKT%s\n", dev, dir, buffer); + memset(buffer, 0, sizeof(buffer)); + buffloc = 0; + buffloc += snprintf(&buffer[buffloc], + sizeof(buffer) - buffloc, "%04X:", + i); + } + + buffloc += snprintf(&buffer[buffloc], sizeof(buffer) - buffloc, + " %02x", skb->data[i]); + } + pr_err("[%s][%c] - PKT%s\n", dev, dir, buffer); +} +#else +void rmnet_print_packet(const struct sk_buff *skb, const char *dev, char dir) +{ +} +#endif /* CONFIG_RMNET_DEBUG */ + +/* Generic handler */ + +/* rmnet_bridge_handler() - Bridge related functionality + * + * Return: + * - RX_HANDLER_CONSUMED in all cases + */ +static rx_handler_result_t rmnet_bridge_handler + (struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep) +{ + if (!ep->egress_dev) { + LOGD("Missing egress device for packet arriving on %s", + skb->dev->name); + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_BRDG_NO_EGRESS); + } else { + rmnet_egress_handler(skb, ep); + } + + return RX_HANDLER_CONSUMED; +} + +#ifdef NET_SKBUFF_DATA_USES_OFFSET +static void rmnet_reset_mac_header(struct sk_buff *skb) +{ + skb->mac_header = 0; + skb->mac_len = 0; +} +#else +static void rmnet_reset_mac_header(struct sk_buff *skb) +{ + skb->mac_header = skb->network_header; + skb->mac_len = 0; +} +#endif /*NET_SKBUFF_DATA_USES_OFFSET*/ + +/* __rmnet_deliver_skb() - Deliver skb + * + * Determines where to deliver skb. Options are: consume by network stack, + * pass to bridge handler, or pass to virtual network device + * + * Return: + * - RX_HANDLER_CONSUMED if packet forwarded or dropped + * - RX_HANDLER_PASS if packet is to be consumed by network stack as-is + */ +static rx_handler_result_t __rmnet_deliver_skb + (struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep) +{ + switch (ep->rmnet_mode) { + case RMNET_EPMODE_NONE: + return RX_HANDLER_PASS; + + case RMNET_EPMODE_BRIDGE: + return rmnet_bridge_handler(skb, ep); + + case RMNET_EPMODE_VND: + skb_reset_transport_header(skb); + skb_reset_network_header(skb); + switch (rmnet_vnd_rx_fixup(skb, skb->dev)) { + case RX_HANDLER_CONSUMED: + return RX_HANDLER_CONSUMED; + + case RX_HANDLER_PASS: + skb->pkt_type = PACKET_HOST; + rmnet_reset_mac_header(skb); + netif_receive_skb(skb); + return RX_HANDLER_CONSUMED; + } + return RX_HANDLER_PASS; + + default: + LOGD("Unknown ep mode %d", ep->rmnet_mode); + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_DELIVER_NO_EP); + return RX_HANDLER_CONSUMED; + } +} + +/* rmnet_ingress_deliver_packet() - Ingress handler for raw IP and bridged + * MAP packets. + * @skb: Packet needing a destination. + * @config: Physical end point configuration that the packet arrived on. + * + * Return: + * - RX_HANDLER_CONSUMED if packet forwarded/dropped + * - RX_HANDLER_PASS if packet should be passed up the stack by caller + */ +static rx_handler_result_t rmnet_ingress_deliver_packet + (struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config) +{ + if (!config) { + LOGD("%s", "NULL physical EP provided"); + kfree_skb(skb); + return RX_HANDLER_CONSUMED; + } + + if (!(config->local_ep.refcount)) { + LOGD("Packet on %s has no local endpoint configuration", + skb->dev->name); + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_IPINGRESS_NO_EP); + return RX_HANDLER_CONSUMED; + } + + skb->dev = config->local_ep.egress_dev; + + return __rmnet_deliver_skb(skb, &config->local_ep); +} + +/* MAP handler */ + +/* _rmnet_map_ingress_handler() - Actual MAP ingress handler + * @skb: Packet being received + * @config: Physical endpoint configuration for the ingress device + * + * Most MAP ingress functions are processed here. Packets are processed + * individually; aggregated packets should use rmnet_map_ingress_handler() + * + * Return: + * - RX_HANDLER_CONSUMED if packet is dropped + * - result of __rmnet_deliver_skb() for all other cases + */ +static rx_handler_result_t _rmnet_map_ingress_handler + (struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config) +{ + struct rmnet_logical_ep_conf_s *ep; + u8 mux_id; + u16 len; + + if (RMNET_MAP_GET_CD_BIT(skb)) { + if (config->ingress_data_format + & RMNET_INGRESS_FORMAT_MAP_COMMANDS) + return rmnet_map_command(skb, config); + + LOGM("MAP command packet on %s; %s", skb->dev->name, + "Not configured for MAP commands"); + rmnet_kfree_skb(skb, + RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPC); + return RX_HANDLER_CONSUMED; + } + + mux_id = RMNET_MAP_GET_MUX_ID(skb); + len = RMNET_MAP_GET_LENGTH(skb) - RMNET_MAP_GET_PAD(skb); + + if (mux_id >= RMNET_MAX_LOGICAL_EP) { + LOGD("Got packet on %s with bad mux id %d", + skb->dev->name, mux_id); + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_BAD_MUX); + return RX_HANDLER_CONSUMED; + } + + ep = &config->muxed_ep[mux_id]; + + if (!ep->refcount) { + LOGD("Packet on %s:%d; has no logical endpoint config", + skb->dev->name, mux_id); + + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_MUX_NO_EP); + return RX_HANDLER_CONSUMED; + } + + if (config->ingress_data_format & RMNET_INGRESS_FORMAT_DEMUXING) + skb->dev = ep->egress_dev; + + /* Subtract MAP header */ + skb_pull(skb, sizeof(struct rmnet_map_header_s)); + skb_trim(skb, len); + __rmnet_set_skb_proto(skb); + return __rmnet_deliver_skb(skb, ep); +} + +/* rmnet_map_ingress_handler() - MAP ingress handler + * @skb: Packet being received + * @config: Physical endpoint configuration for the ingress device + * + * Called if and only if MAP is configured in the ingress device's ingress data + * format. Deaggregation is done here, actual MAP processing is done in + * _rmnet_map_ingress_handler(). + * + * Return: + * - RX_HANDLER_CONSUMED for aggregated packets + * - RX_HANDLER_CONSUMED for dropped packets + * - result of _rmnet_map_ingress_handler() for all other cases + */ +static rx_handler_result_t rmnet_map_ingress_handler + (struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config) +{ + struct sk_buff *skbn; + int rc, co = 0; + + if (config->ingress_data_format & RMNET_INGRESS_FORMAT_DEAGGREGATION) { + while ((skbn = rmnet_map_deaggregate(skb, config)) != NULL) { + _rmnet_map_ingress_handler(skbn, config); + co++; + } + LOGD("De-aggregated %d packets", co); + rmnet_stats_deagg_pkts(co); + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_AGGBUF); + rc = RX_HANDLER_CONSUMED; + } else { + rc = _rmnet_map_ingress_handler(skb, config); + } + + return rc; +} + +/* rmnet_map_egress_handler() - MAP egress handler + * @skb: Packet being sent + * @config: Physical endpoint configuration for the egress device + * @ep: logical endpoint configuration of the packet originator + * (e.g.. RmNet virtual network device) + * @orig_dev: The originator vnd device + * + * Called if and only if MAP is configured in the egress device's egress data + * format. Will expand skb if there is insufficient headroom for MAP protocol. + * Note: headroomexpansion will incur a performance penalty. + * + * Return: + * - 0 on success + * - 1 on failure + */ +static int rmnet_map_egress_handler(struct sk_buff *skb, + struct rmnet_phys_ep_conf_s *config, + struct rmnet_logical_ep_conf_s *ep, + struct net_device *orig_dev) +{ + int required_headroom, additional_header_length; + struct rmnet_map_header_s *map_header; + + additional_header_length = 0; + required_headroom = sizeof(struct rmnet_map_header_s); + + LOGD("headroom of %d bytes", required_headroom); + + if (skb_headroom(skb) < required_headroom) { + if (pskb_expand_head(skb, required_headroom, 0, GFP_KERNEL)) { + LOGD("Failed to add headroom of %d bytes", + required_headroom); + return RMNET_MAP_CONSUMED; + } + } + + map_header = rmnet_map_add_map_header + (skb, additional_header_length, RMNET_MAP_NO_PAD_BYTES); + + if (!map_header) { + LOGD("%s", "Failed to add MAP header to egress packet"); + return RMNET_MAP_CONSUMED; + } + + if (config->egress_data_format & RMNET_EGRESS_FORMAT_MUXING) { + if (ep->mux_id == 0xff) + map_header->mux_id = 0; + else + map_header->mux_id = ep->mux_id; + } + + skb->protocol = htons(ETH_P_MAP); + + return RMNET_MAP_SUCCESS; +} + +/* Ingress / Egress Entry Points */ + +/* rmnet_ingress_handler() - Ingress handler entry point + * @skb: Packet being received + * + * Processes packet as per ingress data format for receiving device. Logical + * endpoint is determined from packet inspection. Packet is then sent to the + * egress device listed in the logical endpoint configuration. + * + * Return: + * - RX_HANDLER_PASS if packet is not processed by handler (caller must + * deal with the packet) + * - RX_HANDLER_CONSUMED if packet is forwarded or processed by MAP + */ +rx_handler_result_t rmnet_ingress_handler(struct sk_buff *skb) +{ + struct rmnet_phys_ep_conf_s *config; + struct net_device *dev; + int rc; + + if (!skb) + return RX_HANDLER_CONSUMED; + + dev = skb->dev; + rmnet_print_packet(skb, dev->name, 'r'); + + config = (struct rmnet_phys_ep_conf_s *) + rcu_dereference(skb->dev->rx_handler_data); + + if (!config) { + LOGD("%s is not associated with rmnet", skb->dev->name); + kfree_skb(skb); + return RX_HANDLER_CONSUMED; + } + + /* Sometimes devices operate in ethernet mode even thouth there is no + * ethernet header. This causes the skb->protocol to contain a bogus + * value and the skb->data pointer to be off by 14 bytes. Fix it if + * configured to do so + */ + if (config->ingress_data_format & RMNET_INGRESS_FIX_ETHERNET) { + skb_push(skb, RMNET_ETHERNET_HEADER_LENGTH); + __rmnet_set_skb_proto(skb); + } + + if (config->ingress_data_format & RMNET_INGRESS_FORMAT_MAP) { + rc = rmnet_map_ingress_handler(skb, config); + } else { + switch (ntohs(skb->protocol)) { + case ETH_P_MAP: + if (config->local_ep.rmnet_mode == + RMNET_EPMODE_BRIDGE) { + rc = rmnet_ingress_deliver_packet(skb, config); + } else { + LOGD("MAP packet on %s; MAP not set", + dev->name); + rmnet_kfree_skb + (skb, + RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPD); + rc = RX_HANDLER_CONSUMED; + } + break; + + case ETH_P_ARP: + case ETH_P_IP: + case ETH_P_IPV6: + rc = rmnet_ingress_deliver_packet(skb, config); + break; + + default: + LOGD("Unknown skb->proto 0x%04X", + ntohs(skb->protocol) & 0xFFFF); + rc = RX_HANDLER_PASS; + } + } + + return rc; +} + +/* rmnet_rx_handler() - Rx handler callback registered with kernel + * @pskb: Packet to be processed by rx handler + * + * Standard kernel-expected footprint for rx handlers. Calls + * rmnet_ingress_handler with correctly formatted arguments + * + * Return: + * - Whatever rmnet_ingress_handler() returns + */ +rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb) +{ + return rmnet_ingress_handler(*pskb); +} + +/* rmnet_egress_handler() - Egress handler entry point + * @skb: packet to transmit + * @ep: logical endpoint configuration of the packet originator + * (e.g.. RmNet virtual network device) + * + * Modifies packet as per logical endpoint configuration and egress data format + * for egress device configured in logical endpoint. Packet is then transmitted + * on the egress device. + */ +void rmnet_egress_handler(struct sk_buff *skb, + struct rmnet_logical_ep_conf_s *ep) +{ + struct rmnet_phys_ep_conf_s *config; + struct net_device *orig_dev; + int rc; + + orig_dev = skb->dev; + skb->dev = ep->egress_dev; + + config = (struct rmnet_phys_ep_conf_s *) + rcu_dereference(skb->dev->rx_handler_data); + + if (!config) { + LOGD("%s is not associated with rmnet", skb->dev->name); + kfree_skb(skb); + return; + } + + LOGD("Packet going out on %s with egress format 0x%08X", + skb->dev->name, config->egress_data_format); + + if (config->egress_data_format & RMNET_EGRESS_FORMAT_MAP) { + switch (rmnet_map_egress_handler(skb, config, ep, orig_dev)) { + case RMNET_MAP_CONSUMED: + LOGD("%s", "MAP process consumed packet"); + return; + + case RMNET_MAP_SUCCESS: + break; + + default: + LOGD("MAP egress failed on packet on %s", + skb->dev->name); + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_EGR_MAPFAIL); + return; + } + } + + if (ep->rmnet_mode == RMNET_EPMODE_VND) + rmnet_vnd_tx_fixup(skb, orig_dev); + + rmnet_print_packet(skb, skb->dev->name, 't'); + rc = dev_queue_xmit(skb); + if (rc != 0) { + LOGD("Failed to queue packet for transmission on [%s]", + skb->dev->name); + } + rmnet_stats_queue_xmit(rc, RMNET_STATS_QUEUE_XMIT_EGRESS); +} diff --git a/drivers/net/rmnet/rmnet_handlers.h b/drivers/net/rmnet/rmnet_handlers.h new file mode 100644 index 000000000000..43c42c2130cd --- /dev/null +++ b/drivers/net/rmnet/rmnet_handlers.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2013, 2016-2017 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. + * + * RMNET Data ingress/egress handler + * + */ + +#ifndef _RMNET_HANDLERS_H_ +#define _RMNET_HANDLERS_H_ + +void rmnet_egress_handler(struct sk_buff *skb, + struct rmnet_logical_ep_conf_s *ep); + +rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb); + +#endif /* _RMNET_HANDLERS_H_ */ diff --git a/drivers/net/rmnet/rmnet_main.c b/drivers/net/rmnet/rmnet_main.c new file mode 100644 index 000000000000..677791893ad4 --- /dev/null +++ b/drivers/net/rmnet/rmnet_main.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2013-2014, 2016-2017 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. + * + * + * RMNET Data generic framework + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/export.h> +#include "rmnet_private.h" +#include "rmnet_config.h" +#include "rmnet_vnd.h" + +/* Trace Points */ +#define CREATE_TRACE_POINTS +#include "rmnet_trace.h" + +/* Module Parameters */ +unsigned int rmnet_log_level = RMNET_LOG_LVL_ERR | RMNET_LOG_LVL_HI; +module_param(rmnet_log_level, uint, 0644); +MODULE_PARM_DESC(log_level, "Logging level"); + +unsigned int rmnet_log_module_mask; +module_param(rmnet_log_module_mask, uint, 0644); +MODULE_PARM_DESC(rmnet_log_module_mask, "Logging module mask"); + +/* Startup/Shutdown */ + +/* rmnet_init() - Module initialization + * + * todo: check for (and init) startup errors + */ +static int __init rmnet_init(void) +{ + rmnet_config_init(); + rmnet_vnd_init(); + + LOGL("%s", "RMNET Data driver loaded successfully"); + return 0; +} + +static void __exit rmnet_exit(void) +{ + rmnet_config_exit(); + rmnet_vnd_exit(); +} + +module_init(rmnet_init) +module_exit(rmnet_exit) +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/rmnet/rmnet_map.h b/drivers/net/rmnet/rmnet_map.h new file mode 100644 index 000000000000..7d533aa5fbca --- /dev/null +++ b/drivers/net/rmnet/rmnet_map.h @@ -0,0 +1,100 @@ +/* Copyright (c) 2013-2017, 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/types.h> +#include <linux/spinlock.h> + +#ifndef _RMNET_MAP_H_ +#define _RMNET_MAP_H_ + +struct rmnet_map_control_command_s { + u8 command_name; + u8 cmd_type:2; + u8 reserved:6; + u16 reserved2; + u32 transaction_id; + union { + u8 data[65528]; + struct { + u16 ip_family:2; + u16 reserved:14; + u16 flow_control_seq_num; + u32 qos_id; + } flow_control; + }; +} __aligned(1); + +enum rmnet_map_results_e { + RMNET_MAP_SUCCESS, + RMNET_MAP_CONSUMED, + RMNET_MAP_GENERAL_FAILURE, + RMNET_MAP_NOT_ENABLED, + RMNET_MAP_FAILED_AGGREGATION, + RMNET_MAP_FAILED_MUX +}; + +enum rmnet_map_mux_errors_e { + RMNET_MAP_MUX_SUCCESS, + RMNET_MAP_MUX_INVALID_MUX_ID, + RMNET_MAP_MUX_INVALID_PAD_LENGTH, + RMNET_MAP_MUX_INVALID_PKT_LENGTH, + /* This should always be the last element */ + RMNET_MAP_MUX_ENUM_LENGTH +}; + +enum rmnet_map_commands_e { + RMNET_MAP_COMMAND_NONE, + RMNET_MAP_COMMAND_FLOW_DISABLE, + RMNET_MAP_COMMAND_FLOW_ENABLE, + /* These should always be the last 2 elements */ + RMNET_MAP_COMMAND_UNKNOWN, + RMNET_MAP_COMMAND_ENUM_LENGTH +}; + +struct rmnet_map_header_s { + u8 pad_len:6; + u8 reserved_bit:1; + u8 cd_bit:1; + u8 mux_id; + u16 pkt_len; +} __aligned(1); + +#define RMNET_MAP_GET_MUX_ID(Y) (((struct rmnet_map_header_s *) \ + (Y)->data)->mux_id) +#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *) \ + (Y)->data)->cd_bit) +#define RMNET_MAP_GET_PAD(Y) (((struct rmnet_map_header_s *) \ + (Y)->data)->pad_len) +#define RMNET_MAP_GET_CMD_START(Y) ((struct rmnet_map_control_command_s *) \ + ((Y)->data + \ + sizeof(struct rmnet_map_header_s))) +#define RMNET_MAP_GET_LENGTH(Y) (ntohs(((struct rmnet_map_header_s *) \ + (Y)->data)->pkt_len)) + +#define RMNET_MAP_COMMAND_REQUEST 0 +#define RMNET_MAP_COMMAND_ACK 1 +#define RMNET_MAP_COMMAND_UNSUPPORTED 2 +#define RMNET_MAP_COMMAND_INVALID 3 + +#define RMNET_MAP_NO_PAD_BYTES 0 +#define RMNET_MAP_ADD_PAD_BYTES 1 + +u8 rmnet_map_demultiplex(struct sk_buff *skb); +struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb, + struct rmnet_phys_ep_conf_s *config); + +struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff *skb, + int hdrlen, int pad); +rx_handler_result_t rmnet_map_command(struct sk_buff *skb, + struct rmnet_phys_ep_conf_s *config); + +#endif /* _RMNET_MAP_H_ */ diff --git a/drivers/net/rmnet/rmnet_map_command.c b/drivers/net/rmnet/rmnet_map_command.c new file mode 100644 index 000000000000..13bcee3cfdac --- /dev/null +++ b/drivers/net/rmnet/rmnet_map_command.c @@ -0,0 +1,180 @@ +/* Copyright (c) 2013-2017, 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/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/rmnet.h> +#include <net/pkt_sched.h> +#include "rmnet_config.h" +#include "rmnet_map.h" +#include "rmnet_private.h" +#include "rmnet_vnd.h" +#include "rmnet_stats.h" + +RMNET_LOG_MODULE(RMNET_LOGMASK_MAPC); + +unsigned long int rmnet_map_command_stats[RMNET_MAP_COMMAND_ENUM_LENGTH]; +module_param_array(rmnet_map_command_stats, ulong, 0, 0444); +MODULE_PARM_DESC(rmnet_map_command_stats, "MAP command statistics"); + +/* rmnet_map_do_flow_control() - Process MAP flow control command + * @skb: Socket buffer containing the MAP flow control message + * @config: Physical end-point configuration of ingress device + * @enable: boolean for enable/disable + * + * Process in-band MAP flow control messages. Assumes mux ID is mapped to a + * RmNet Data vitrual network device. + * + * Return: + * - RMNET_MAP_COMMAND_UNSUPPORTED on any error + * - RMNET_MAP_COMMAND_ACK on success + */ +static u8 rmnet_map_do_flow_control(struct sk_buff *skb, + struct rmnet_phys_ep_conf_s *config, + int enable) +{ + struct rmnet_map_control_command_s *cmd; + struct net_device *vnd; + struct rmnet_logical_ep_conf_s *ep; + u8 mux_id; + u16 ip_family; + u16 fc_seq; + u32 qos_id; + int r; + + if (unlikely(!skb || !config)) + return RX_HANDLER_CONSUMED; + + mux_id = RMNET_MAP_GET_MUX_ID(skb); + cmd = RMNET_MAP_GET_CMD_START(skb); + + if (mux_id >= RMNET_MAX_LOGICAL_EP) { + LOGD("Got packet on %s with bad mux id %d", + skb->dev->name, mux_id); + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_BAD_MUX); + return RX_HANDLER_CONSUMED; + } + + ep = &config->muxed_ep[mux_id]; + + if (!ep->refcount) { + LOGD("Packet on %s:%d; has no logical endpoint config", + skb->dev->name, mux_id); + + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_MUX_NO_EP); + return RX_HANDLER_CONSUMED; + } + + vnd = ep->egress_dev; + + ip_family = cmd->flow_control.ip_family; + fc_seq = ntohs(cmd->flow_control.flow_control_seq_num); + qos_id = ntohl(cmd->flow_control.qos_id); + + /* Ignore the ip family and pass the sequence number for both v4 and v6 + * sequence. User space does not support creating dedicated flows for + * the 2 protocols + */ + r = rmnet_vnd_do_flow_control(vnd, enable); + LOGD("dev:%s, qos_id:0x%08X, ip_family:%hd, fc_seq %hd, en:%d", + skb->dev->name, qos_id, ip_family & 3, fc_seq, enable); + + if (r) { + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED); + return RMNET_MAP_COMMAND_UNSUPPORTED; + } else { + return RMNET_MAP_COMMAND_ACK; + } +} + +/* rmnet_map_send_ack() - Send N/ACK message for MAP commands + * @skb: Socket buffer containing the MAP command message + * @type: N/ACK message selector + * @config: Physical end-point configuration of ingress device + * + * skb is modified to contain the message type selector. The message is then + * transmitted on skb->dev. Note that this function grabs global Tx lock on + * skb->dev for latency reasons. + * + * Return: + * - void + */ +static void rmnet_map_send_ack(struct sk_buff *skb, + unsigned char type, + struct rmnet_phys_ep_conf_s *config) +{ + struct rmnet_map_control_command_s *cmd; + int xmit_status; + + if (unlikely(!skb)) + return; + + skb->protocol = htons(ETH_P_MAP); + + cmd = RMNET_MAP_GET_CMD_START(skb); + cmd->cmd_type = type & 0x03; + + netif_tx_lock(skb->dev); + xmit_status = skb->dev->netdev_ops->ndo_start_xmit(skb, skb->dev); + netif_tx_unlock(skb->dev); + + LOGD("MAP command ACK=%hhu sent with rc: %d", type & 0x03, xmit_status); +} + +/* rmnet_map_command() - Entry point for handling MAP commands + * @skb: Socket buffer containing the MAP command message + * @config: Physical end-point configuration of ingress device + * + * Process MAP command frame and send N/ACK message as appropriate. Message cmd + * name is decoded here and appropriate handler is called. + * + * Return: + * - RX_HANDLER_CONSUMED. Command frames are always consumed. + */ +rx_handler_result_t rmnet_map_command(struct sk_buff *skb, + struct rmnet_phys_ep_conf_s *config) +{ + struct rmnet_map_control_command_s *cmd; + unsigned char command_name; + unsigned char rc = 0; + + if (unlikely(!skb)) + return RX_HANDLER_CONSUMED; + + cmd = RMNET_MAP_GET_CMD_START(skb); + command_name = cmd->command_name; + + if (command_name < RMNET_MAP_COMMAND_ENUM_LENGTH) + rmnet_map_command_stats[command_name]++; + + switch (command_name) { + case RMNET_MAP_COMMAND_FLOW_ENABLE: + rc = rmnet_map_do_flow_control(skb, config, 1); + break; + + case RMNET_MAP_COMMAND_FLOW_DISABLE: + rc = rmnet_map_do_flow_control(skb, config, 0); + break; + + default: + rmnet_map_command_stats[RMNET_MAP_COMMAND_UNKNOWN]++; + LOGM("Uknown MAP command: %d", command_name); + rc = RMNET_MAP_COMMAND_UNSUPPORTED; + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED); + break; + } + if (rc == RMNET_MAP_COMMAND_ACK) + rmnet_map_send_ack(skb, rc, config); + return RX_HANDLER_CONSUMED; +} diff --git a/drivers/net/rmnet/rmnet_map_data.c b/drivers/net/rmnet/rmnet_map_data.c new file mode 100644 index 000000000000..1b4eda9f46a5 --- /dev/null +++ b/drivers/net/rmnet/rmnet_map_data.c @@ -0,0 +1,147 @@ +/* Copyright (c) 2013-2017, 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. + * + * RMNET Data MAP protocol + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/rmnet.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/time.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/udp.h> +#include <linux/tcp.h> +#include <linux/in.h> +#include <net/ip.h> +#include <net/checksum.h> +#include <net/ip6_checksum.h> +#include "rmnet_config.h" +#include "rmnet_map.h" +#include "rmnet_private.h" +#include "rmnet_stats.h" + +RMNET_LOG_MODULE(RMNET_LOGMASK_MAPD); + +#define RMNET_MAP_DEAGGR_SPACING 64 +#define RMNET_MAP_DEAGGR_HEADROOM (RMNET_MAP_DEAGGR_SPACING / 2) + +/* rmnet_map_add_map_header() - Adds MAP header to front of skb->data + * @skb: Socket buffer ("packet") to modify + * @hdrlen: Number of bytes of header data which should not be included in + * MAP length field + * @pad: Specify if padding the MAP packet to make it 4 byte aligned is + * necessary + * + * Padding is calculated and set appropriately in MAP header. Mux ID is + * initialized to 0. + * + * Return: + * - Pointer to MAP structure + * - 0 (null) if insufficient headroom + * - 0 (null) if insufficient tailroom for padding bytes + * + * todo: Parameterize skb alignment + */ +struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff *skb, + int hdrlen, int pad) +{ + u32 padding, map_datalen; + u8 *padbytes; + struct rmnet_map_header_s *map_header; + + if (skb_headroom(skb) < sizeof(struct rmnet_map_header_s)) + return 0; + + map_datalen = skb->len - hdrlen; + map_header = (struct rmnet_map_header_s *) + skb_push(skb, sizeof(struct rmnet_map_header_s)); + memset(map_header, 0, sizeof(struct rmnet_map_header_s)); + + if (pad == RMNET_MAP_NO_PAD_BYTES) { + map_header->pkt_len = htons(map_datalen); + return map_header; + } + + padding = ALIGN(map_datalen, 4) - map_datalen; + + if (padding == 0) + goto done; + + if (skb_tailroom(skb) < padding) + return 0; + + padbytes = (u8 *)skb_put(skb, padding); + LOGD("pad: %d", padding); + memset(padbytes, 0, padding); + +done: + map_header->pkt_len = htons(map_datalen + padding); + map_header->pad_len = padding & 0x3F; + + return map_header; +} + +/* rmnet_map_deaggregate() - Deaggregates a single packet + * @skb: Source socket buffer containing multiple MAP frames + * @config: Physical endpoint configuration of the ingress device + * + * A whole new buffer is allocated for each portion of an aggregated frame. + * Caller should keep calling deaggregate() on the source skb until 0 is + * returned, indicating that there are no more packets to deaggregate. Caller + * is responsible for freeing the original skb. + * + * Return: + * - Pointer to new skb + * - 0 (null) if no more aggregated packets + */ +struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb, + struct rmnet_phys_ep_conf_s *config) +{ + struct sk_buff *skbn; + struct rmnet_map_header_s *maph; + u32 packet_len; + + if (skb->len == 0) + return 0; + + maph = (struct rmnet_map_header_s *)skb->data; + packet_len = ntohs(maph->pkt_len) + sizeof(struct rmnet_map_header_s); + + if ((((int)skb->len) - ((int)packet_len)) < 0) { + LOGM("%s", "Got malformed packet. Dropping"); + return 0; + } + + skbn = alloc_skb(packet_len + RMNET_MAP_DEAGGR_SPACING, GFP_ATOMIC); + if (!skbn) + return 0; + + skbn->dev = skb->dev; + skb_reserve(skbn, RMNET_MAP_DEAGGR_HEADROOM); + skb_put(skbn, packet_len); + memcpy(skbn->data, skb->data, packet_len); + skb_pull(skb, packet_len); + + /* Some hardware can send us empty frames. Catch them */ + if (ntohs(maph->pkt_len) == 0) { + LOGD("Dropping empty MAP frame"); + rmnet_kfree_skb(skbn, RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0); + return 0; + } + + return skbn; +} diff --git a/drivers/net/rmnet/rmnet_private.h b/drivers/net/rmnet/rmnet_private.h new file mode 100644 index 000000000000..f27e0b3679cb --- /dev/null +++ b/drivers/net/rmnet/rmnet_private.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2013-2014, 2016-2017 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. + */ + +#ifndef _RMNET_PRIVATE_H_ +#define _RMNET_PRIVATE_H_ + +#define RMNET_MAX_VND 32 +#define RMNET_MAX_PACKET_SIZE 16384 +#define RMNET_DFLT_PACKET_SIZE 1500 +#define RMNET_DEV_NAME_STR "rmnet" +#define RMNET_NEEDED_HEADROOM 16 +#define RMNET_TX_QUEUE_LEN 1000 +#define RMNET_ETHERNET_HEADER_LENGTH 14 + +extern unsigned int rmnet_log_level; +extern unsigned int rmnet_log_module_mask; + +#define RMNET_INIT_OK 0 +#define RMNET_INIT_ERROR 1 + +#define RMNET_LOG_LVL_DBG BIT(4) +#define RMNET_LOG_LVL_LOW BIT(3) +#define RMNET_LOG_LVL_MED BIT(2) +#define RMNET_LOG_LVL_HI BIT(1) +#define RMNET_LOG_LVL_ERR BIT(0) + +#define RMNET_LOG_MODULE(X) \ + static u32 rmnet_mod_mask = X + +#define RMNET_LOGMASK_CONFIG BIT(0) +#define RMNET_LOGMASK_HANDLER BIT(1) +#define RMNET_LOGMASK_VND BIT(2) +#define RMNET_LOGMASK_MAPD BIT(3) +#define RMNET_LOGMASK_MAPC BIT(4) + +#define LOGE(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_ERR) \ + pr_err("[RMNET:ERR] %s(): " fmt "\n", __func__, \ + ##__VA_ARGS__); \ + } while (0) + +#define LOGH(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_HI) \ + pr_err("[RMNET:HI] %s(): " fmt "\n", __func__, \ + ##__VA_ARGS__); \ + } while (0) + +#define LOGM(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_MED) \ + pr_warn("[RMNET:MED] %s(): " fmt "\n", __func__, \ + ##__VA_ARGS__); \ + } while (0) + +#define LOGL(fmt, ...) do { if (unlikely \ + (rmnet_log_level & RMNET_LOG_LVL_LOW)) \ + pr_notice("[RMNET:LOW] %s(): " fmt "\n", __func__, \ + ##__VA_ARGS__); \ + } while (0) + +/* Don't use pr_debug as it is compiled out of the kernel. We can be sure of + * minimal impact as LOGD is not enabled by default. + */ +#define LOGD(fmt, ...) do { if (unlikely( \ + (rmnet_log_level & RMNET_LOG_LVL_DBG) &&\ + (rmnet_log_module_mask & rmnet_mod_mask))) \ + pr_notice("[RMNET:DBG] %s(): " fmt "\n", __func__, \ + ##__VA_ARGS__); \ + } while (0) + +#endif /* _RMNET_PRIVATE_H_ */ diff --git a/drivers/net/rmnet/rmnet_stats.c b/drivers/net/rmnet/rmnet_stats.c new file mode 100644 index 000000000000..d53ce38e96fe --- /dev/null +++ b/drivers/net/rmnet/rmnet_stats.c @@ -0,0 +1,86 @@ +/* Copyright (c) 2014, 2016-2017 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. + * + * + * RMNET Data statistics + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/netdevice.h> +#include "rmnet_private.h" +#include "rmnet_stats.h" +#include "rmnet_config.h" +#include "rmnet_map.h" + +enum rmnet_deagg_e { + RMNET_STATS_AGG_BUFF, + RMNET_STATS_AGG_PKT, + RMNET_STATS_AGG_MAX +}; + +static DEFINE_SPINLOCK(rmnet_skb_free_lock); +unsigned long int skb_free[RMNET_STATS_SKBFREE_MAX]; +module_param_array(skb_free, ulong, 0, 0444); +MODULE_PARM_DESC(skb_free, "SKBs dropped or freed"); + +static DEFINE_SPINLOCK(rmnet_queue_xmit_lock); +unsigned long int queue_xmit[RMNET_STATS_QUEUE_XMIT_MAX * 2]; +module_param_array(queue_xmit, ulong, 0, 0444); +MODULE_PARM_DESC(queue_xmit, "SKBs queued for transmit"); + +static DEFINE_SPINLOCK(rmnet_deagg_count); +unsigned long int deagg_count[RMNET_STATS_AGG_MAX]; +module_param_array(deagg_count, ulong, 0, 0444); +MODULE_PARM_DESC(deagg_count, "SKBs De-aggregated"); + +void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason) +{ + unsigned long flags; + + if (reason >= RMNET_STATS_SKBFREE_MAX) + reason = RMNET_STATS_SKBFREE_UNKNOWN; + + spin_lock_irqsave(&rmnet_skb_free_lock, flags); + skb_free[reason]++; + spin_unlock_irqrestore(&rmnet_skb_free_lock, flags); + + if (skb) + kfree_skb(skb); +} + +void rmnet_stats_queue_xmit(int rc, unsigned int reason) +{ + unsigned long flags; + + if (rc != 0) + reason += RMNET_STATS_QUEUE_XMIT_MAX; + if (reason >= RMNET_STATS_QUEUE_XMIT_MAX * 2) + reason = RMNET_STATS_SKBFREE_UNKNOWN; + + spin_lock_irqsave(&rmnet_queue_xmit_lock, flags); + queue_xmit[reason]++; + spin_unlock_irqrestore(&rmnet_queue_xmit_lock, flags); +} + +void rmnet_stats_deagg_pkts(int aggcount) +{ + unsigned long flags; + + spin_lock_irqsave(&rmnet_deagg_count, flags); + deagg_count[RMNET_STATS_AGG_BUFF]++; + deagg_count[RMNET_STATS_AGG_PKT] += aggcount; + spin_unlock_irqrestore(&rmnet_deagg_count, flags); +} diff --git a/drivers/net/rmnet/rmnet_stats.h b/drivers/net/rmnet/rmnet_stats.h new file mode 100644 index 000000000000..c8d0469bfe6a --- /dev/null +++ b/drivers/net/rmnet/rmnet_stats.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2014, 2016-2017 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. + * + * + * RMNET Data statistics + * + */ + +#ifndef _RMNET_STATS_H_ +#define _RMNET_STATS_H_ + +enum rmnet_skb_free_e { + RMNET_STATS_SKBFREE_UNKNOWN, + RMNET_STATS_SKBFREE_BRDG_NO_EGRESS, + RMNET_STATS_SKBFREE_DELIVER_NO_EP, + RMNET_STATS_SKBFREE_IPINGRESS_NO_EP, + RMNET_STATS_SKBFREE_MAPINGRESS_BAD_MUX, + RMNET_STATS_SKBFREE_MAPINGRESS_MUX_NO_EP, + RMNET_STATS_SKBFREE_MAPINGRESS_AGGBUF, + RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPD, + RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPC, + RMNET_STATS_SKBFREE_EGR_MAPFAIL, + RMNET_STATS_SKBFREE_VND_NO_EGRESS, + RMNET_STATS_SKBFREE_MAPC_BAD_MUX, + RMNET_STATS_SKBFREE_MAPC_MUX_NO_EP, + RMNET_STATS_SKBFREE_AGG_CPY_EXPAND, + RMNET_STATS_SKBFREE_AGG_INTO_BUFF, + RMNET_STATS_SKBFREE_DEAGG_MALFORMED, + RMNET_STATS_SKBFREE_DEAGG_CLONE_FAIL, + RMNET_STATS_SKBFREE_DEAGG_UNKNOWN_IP_TYPE, + RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0, + RMNET_STATS_SKBFREE_INGRESS_BAD_MAP_CKSUM, + RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED, + RMNET_STATS_SKBFREE_MAX +}; + +enum rmnet_queue_xmit_e { + RMNET_STATS_QUEUE_XMIT_UNKNOWN, + RMNET_STATS_QUEUE_XMIT_EGRESS, + RMNET_STATS_QUEUE_XMIT_AGG_FILL_BUFFER, + RMNET_STATS_QUEUE_XMIT_AGG_TIMEOUT, + RMNET_STATS_QUEUE_XMIT_AGG_CPY_EXP_FAIL, + RMNET_STATS_QUEUE_XMIT_AGG_SKIP, + RMNET_STATS_QUEUE_XMIT_MAX +}; + +void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason); +void rmnet_stats_queue_xmit(int rc, unsigned int reason); +void rmnet_stats_deagg_pkts(int aggcount); +void rmnet_stats_agg_pkts(int aggcount); +void rmnet_stats_dl_checksum(unsigned int rc); +void rmnet_stats_ul_checksum(unsigned int rc); +#endif /* _RMNET_STATS_H_ */ diff --git a/drivers/net/rmnet/rmnet_vnd.c b/drivers/net/rmnet/rmnet_vnd.c new file mode 100644 index 000000000000..a5b1cb891798 --- /dev/null +++ b/drivers/net/rmnet/rmnet_vnd.c @@ -0,0 +1,457 @@ +/* Copyright (c) 2013-2017, 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. + * + * + * RMNET Data virtual network driver + * + */ + +#include <linux/types.h> +#include <linux/rmnet.h> +#include <linux/etherdevice.h> +#include <linux/if_arp.h> +#include <linux/spinlock.h> +#include <net/pkt_sched.h> +#include <linux/atomic.h> +#include "rmnet_config.h" +#include "rmnet_handlers.h" +#include "rmnet_private.h" +#include "rmnet_map.h" +#include "rmnet_vnd.h" +#include "rmnet_stats.h" + +RMNET_LOG_MODULE(RMNET_LOGMASK_VND); + +struct net_device *rmnet_devices[RMNET_MAX_VND]; + +struct rmnet_vnd_private_s { + struct rmnet_logical_ep_conf_s local_ep; +}; + +/* RX/TX Fixup */ + +/* rmnet_vnd_rx_fixup() - Virtual Network Device receive fixup hook + * @skb: Socket buffer ("packet") to modify + * @dev: Virtual network device + * + * Additional VND specific packet processing for ingress packets + * + * Return: + * - RX_HANDLER_PASS if packet should continue to process in stack + * - RX_HANDLER_CONSUMED if packet should not be processed in stack + * + */ +int rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev) +{ + if (unlikely(!dev || !skb)) + return RX_HANDLER_CONSUMED; + + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + + return RX_HANDLER_PASS; +} + +/* rmnet_vnd_tx_fixup() - Virtual Network Device transmic fixup hook + * @skb: Socket buffer ("packet") to modify + * @dev: Virtual network device + * + * Additional VND specific packet processing for egress packets + * + * Return: + * - RX_HANDLER_PASS if packet should continue to be transmitted + * - RX_HANDLER_CONSUMED if packet should not be transmitted by stack + */ +int rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_vnd_private_s *dev_conf; + + dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev); + + if (unlikely(!dev || !skb)) + return RX_HANDLER_CONSUMED; + + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + + return RX_HANDLER_PASS; +} + +/* Network Device Operations */ + +/* rmnet_vnd_start_xmit() - Transmit NDO callback + * @skb: Socket buffer ("packet") being sent from network stack + * @dev: Virtual Network Device + * + * Standard network driver operations hook to transmit packets on virtual + * network device. Called by network stack. Packet is not transmitted directly + * from here; instead it is given to the rmnet egress handler. + * + * Return: + * - NETDEV_TX_OK under all cirumstances (cannot block/fail) + */ +static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct rmnet_vnd_private_s *dev_conf; + + dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev); + if (dev_conf->local_ep.egress_dev) { + rmnet_egress_handler(skb, &dev_conf->local_ep); + } else { + dev->stats.tx_dropped++; + rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_VND_NO_EGRESS); + } + return NETDEV_TX_OK; +} + +/* rmnet_vnd_change_mtu() - Change MTU NDO callback + * @dev: Virtual network device + * @new_mtu: New MTU value to set (in bytes) + * + * Standard network driver operations hook to set the MTU. Called by kernel to + * set the device MTU. Checks if desired MTU is less than zero or greater than + * RMNET_MAX_PACKET_SIZE; + * + * Return: + * - 0 if successful + * - -EINVAL if new_mtu is out of range + */ +static int rmnet_vnd_change_mtu(struct net_device *dev, int new_mtu) +{ + if (new_mtu < 0 || new_mtu > RMNET_MAX_PACKET_SIZE) + return -EINVAL; + + dev->mtu = new_mtu; + return 0; +} + +static const struct net_device_ops rmnet_vnd_ops = { + .ndo_init = 0, + .ndo_start_xmit = rmnet_vnd_start_xmit, + .ndo_change_mtu = rmnet_vnd_change_mtu, + .ndo_set_mac_address = 0, + .ndo_validate_addr = 0, +}; + +/* rmnet_vnd_setup() - net_device initialization callback + * @dev: Virtual network device + * + * Called by kernel whenever a new rmnet<n> device is created. Sets MTU, + * flags, ARP type, needed headroom, etc... + */ +static void rmnet_vnd_setup(struct net_device *dev) +{ + struct rmnet_vnd_private_s *dev_conf; + + LOGM("Setting up device %s", dev->name); + + /* Clear out private data */ + dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev); + memset(dev_conf, 0, sizeof(struct rmnet_vnd_private_s)); + + dev->netdev_ops = &rmnet_vnd_ops; + dev->mtu = RMNET_DFLT_PACKET_SIZE; + dev->needed_headroom = RMNET_NEEDED_HEADROOM; + random_ether_addr(dev->dev_addr); + dev->tx_queue_len = RMNET_TX_QUEUE_LEN; + + /* Raw IP mode */ + dev->header_ops = 0; /* No header */ + dev->type = ARPHRD_RAWIP; + dev->hard_header_len = 0; + dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); +} + +/* Exposed API */ + +/* rmnet_vnd_exit() - Shutdown cleanup hook + * + * Called by RmNet main on module unload. Cleans up data structures and + * unregisters/frees net_devices. + */ +void rmnet_vnd_exit(void) +{ + int i; + + for (i = 0; i < RMNET_MAX_VND; i++) + if (rmnet_devices[i]) { + unregister_netdev(rmnet_devices[i]); + free_netdev(rmnet_devices[i]); + } +} + +/* rmnet_vnd_init() - Init hook + * + * Called by RmNet main on module load. Initializes data structures + */ +int rmnet_vnd_init(void) +{ + memset(rmnet_devices, 0, + sizeof(struct net_device *) * RMNET_MAX_VND); + return 0; +} + +/* rmnet_vnd_create_dev() - Create a new virtual network device node. + * @id: Virtual device node id + * @new_device: Pointer to newly created device node + * @prefix: Device name prefix + * + * Allocates structures for new virtual network devices. Sets the name of the + * new device and registers it with the network stack. Device will appear in + * ifconfig list after this is called. If the prefix is null, then + * RMNET_DEV_NAME_STR will be assumed. + * + * Return: + * - 0 if successful + * - RMNET_CONFIG_BAD_ARGUMENTS if id is out of range or prefix is too long + * - RMNET_CONFIG_DEVICE_IN_USE if id already in use + * - RMNET_CONFIG_NOMEM if net_device allocation failed + * - RMNET_CONFIG_UNKNOWN_ERROR if register_netdevice() fails + */ +int rmnet_vnd_create_dev(int id, struct net_device **new_device, + const char *prefix) +{ + struct net_device *dev; + char dev_prefix[IFNAMSIZ]; + int p, rc = 0; + + if (id < 0 || id >= RMNET_MAX_VND) { + *new_device = 0; + return RMNET_CONFIG_BAD_ARGUMENTS; + } + + if (rmnet_devices[id]) { + *new_device = 0; + return RMNET_CONFIG_DEVICE_IN_USE; + } + + if (!prefix) + p = scnprintf(dev_prefix, IFNAMSIZ, "%s%%d", + RMNET_DEV_NAME_STR); + else + p = scnprintf(dev_prefix, IFNAMSIZ, "%s%%d", prefix); + if (p >= (IFNAMSIZ - 1)) { + LOGE("Specified prefix longer than IFNAMSIZ"); + return RMNET_CONFIG_BAD_ARGUMENTS; + } + + dev = alloc_netdev(sizeof(struct rmnet_vnd_private_s), + dev_prefix, + NET_NAME_ENUM, + rmnet_vnd_setup); + if (!dev) { + LOGE("Failed to to allocate netdev for id %d", id); + *new_device = 0; + return RMNET_CONFIG_NOMEM; + } + + rc = register_netdevice(dev); + if (rc != 0) { + LOGE("Failed to to register netdev [%s]", dev->name); + free_netdev(dev); + *new_device = 0; + rc = RMNET_CONFIG_UNKNOWN_ERROR; + } else { + rmnet_devices[id] = dev; + *new_device = dev; + LOGM("Registered device %s", dev->name); + } + + return rc; +} + +/* rmnet_vnd_free_dev() - free a virtual network device node. + * @id: Virtual device node id + * + * Unregisters the virtual network device node and frees it. + * unregister_netdev locks the rtnl mutex, so the mutex must not be locked + * by the caller of the function. unregister_netdev enqueues the request to + * unregister the device into a TODO queue. The requests in the TODO queue + * are only done after rtnl mutex is unlocked, therefore free_netdev has to + * called after unlocking rtnl mutex. + * + * Return: + * - 0 if successful + * - RMNET_CONFIG_NO_SUCH_DEVICE if id is invalid or not in range + * - RMNET_CONFIG_DEVICE_IN_USE if device has logical ep that wasn't unset + */ +int rmnet_vnd_free_dev(int id) +{ + struct rmnet_logical_ep_conf_s *epconfig_l; + struct net_device *dev; + + rtnl_lock(); + if ((id < 0) || (id >= RMNET_MAX_VND) || !rmnet_devices[id]) { + rtnl_unlock(); + LOGM("Invalid id [%d]", id); + return RMNET_CONFIG_NO_SUCH_DEVICE; + } + + epconfig_l = rmnet_vnd_get_le_config(rmnet_devices[id]); + if (epconfig_l && epconfig_l->refcount) { + rtnl_unlock(); + return RMNET_CONFIG_DEVICE_IN_USE; + } + + dev = rmnet_devices[id]; + rmnet_devices[id] = 0; + rtnl_unlock(); + + if (dev) { + unregister_netdev(dev); + free_netdev(dev); + return 0; + } else { + return RMNET_CONFIG_NO_SUCH_DEVICE; + } +} + +/* rmnet_vnd_get_name() - Gets the string name of a VND based on ID + * @id: Virtual device node id + * @name: Buffer to store name of virtual device node + * @name_len: Length of name buffer + * + * Copies the name of the virtual device node into the users buffer. Will throw + * an error if the buffer is null, or too small to hold the device name. + * + * Return: + * - 0 if successful + * - -EINVAL if name is null + * - -EINVAL if id is invalid or not in range + * - -EINVAL if name is too small to hold things + */ +int rmnet_vnd_get_name(int id, char *name, int name_len) +{ + int p; + + if (!name) { + LOGM("%s", "Bad arguments; name buffer null"); + return -EINVAL; + } + + if ((id < 0) || (id >= RMNET_MAX_VND) || !rmnet_devices[id]) { + LOGM("Invalid id [%d]", id); + return -EINVAL; + } + + p = strlcpy(name, rmnet_devices[id]->name, name_len); + if (p >= name_len) { + LOGM("Buffer to small (%d) to fit device name", name_len); + return -EINVAL; + } + LOGL("Found mapping [%d]->\"%s\"", id, name); + + return 0; +} + +/* rmnet_vnd_is_vnd() - Determine if net_device is RmNet owned virtual devices + * @dev: Network device to test + * + * Searches through list of known RmNet virtual devices. This function is O(n) + * and should not be used in the data path. + * + * Return: + * - 0 if device is not RmNet virtual device + * - 1 if device is RmNet virtual device + */ +int rmnet_vnd_is_vnd(struct net_device *dev) +{ + /* This is not an efficient search, but, this will only be called in + * a configuration context, and the list is small. + */ + int i; + + if (!dev) + return 0; + + for (i = 0; i < RMNET_MAX_VND; i++) + if (dev == rmnet_devices[i]) + return i + 1; + + return 0; +} + +/* rmnet_vnd_get_le_config() - Get the logical endpoint configuration + * @dev: Virtual device node + * + * Gets the logical endpoint configuration for a RmNet virtual network device + * node. Caller should confirm that devices is a RmNet VND before calling. + * + * Return: + * - Pointer to logical endpoint configuration structure + * - 0 (null) if dev is null + */ +struct rmnet_logical_ep_conf_s *rmnet_vnd_get_le_config(struct net_device *dev) +{ + struct rmnet_vnd_private_s *dev_conf; + + if (!dev) + return 0; + + dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev); + if (!dev_conf) + return 0; + + return &dev_conf->local_ep; +} + +/* rmnet_vnd_do_flow_control() - Process flow control request + * @dev: Virtual network device node to do lookup on + * @enable: boolean to enable/disable flow. + * + * Return: + * - 0 if successful + * - -EINVAL if dev is not RmNet virtual network device node + */ +int rmnet_vnd_do_flow_control(struct net_device *dev, int enable) +{ + struct rmnet_vnd_private_s *dev_conf; + + if (unlikely(!dev)) + return -EINVAL; + + if (!rmnet_vnd_is_vnd(dev)) + return -EINVAL; + + dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev); + + if (unlikely(!dev_conf)) + return -EINVAL; + + LOGD("Setting VND TX queue state to %d", enable); + /* Although we expect similar number of enable/disable + * commands, optimize for the disable. That is more + * latency sensitive than enable + */ + if (unlikely(enable)) + netif_wake_queue(dev); + else + netif_stop_queue(dev); + + return 0; +} + +/* rmnet_vnd_get_by_id() - Get VND by array index ID + * @id: Virtual network deice id [0:RMNET_MAX_VND] + * + * Return: + * - 0 if no device or ID out of range + * - otherwise return pointer to VND net_device struct + */ +struct net_device *rmnet_vnd_get_by_id(int id) +{ + if (id < 0 || id >= RMNET_MAX_VND) { + pr_err("Bug; VND ID out of bounds"); + return 0; + } + return rmnet_devices[id]; +} diff --git a/drivers/net/rmnet/rmnet_vnd.h b/drivers/net/rmnet/rmnet_vnd.h new file mode 100644 index 000000000000..428240898ff0 --- /dev/null +++ b/drivers/net/rmnet/rmnet_vnd.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2013-2017, 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. + * + * RMNET Data Virtual Network Device APIs + * + */ + +#include <linux/types.h> + +#ifndef _RMNET_VND_H_ +#define _RMNET_VND_H_ + +int rmnet_vnd_do_flow_control(struct net_device *dev, int enable); +struct rmnet_logical_ep_conf_s *rmnet_vnd_get_le_config(struct net_device *dev); +int rmnet_vnd_get_name(int id, char *name, int name_len); +int rmnet_vnd_create_dev(int id, struct net_device **new_device, + const char *prefix); +int rmnet_vnd_free_dev(int id); +int rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev); +int rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev); +int rmnet_vnd_is_vnd(struct net_device *dev); +int rmnet_vnd_init(void); +void rmnet_vnd_exit(void); +struct net_device *rmnet_vnd_get_by_id(int id); + +#endif /* _RMNET_VND_H_ */ diff --git a/drivers/net/tun.c b/drivers/net/tun.c index e33ec3bee2e5..1b0184b3818a 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1189,6 +1189,10 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } + if (!(tun->flags & IFF_NO_PI)) + if (pi.flags & htons(CHECKSUM_UNNECESSARY)) + skb->ip_summed = CHECKSUM_UNNECESSARY; + switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 9710cf71054a..c0bcfa979253 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -42,7 +42,6 @@ #include <linux/mii.h> #include <linux/usb.h> #include <linux/usb/usbnet.h> -#include <linux/usb/cdc.h> #include <linux/slab.h> #include <linux/kernel.h> #include <linux/pm_runtime.h> @@ -1964,147 +1963,6 @@ out: return err; } -int cdc_parse_cdc_header(struct usb_cdc_parsed_header *hdr, - struct usb_interface *intf, - u8 *buffer, - int buflen) -{ - /* duplicates are ignored */ - struct usb_cdc_union_desc *union_header = NULL; - - /* duplicates are not tolerated */ - struct usb_cdc_header_desc *header = NULL; - struct usb_cdc_ether_desc *ether = NULL; - struct usb_cdc_mdlm_detail_desc *detail = NULL; - struct usb_cdc_mdlm_desc *desc = NULL; - - unsigned int elength; - int cnt = 0; - - memset(hdr, 0x00, sizeof(struct usb_cdc_parsed_header)); - hdr->phonet_magic_present = false; - while (buflen > 0) { - elength = buffer[0]; - if (!elength) { - dev_err(&intf->dev, "skipping garbage byte\n"); - elength = 1; - goto next_desc; - } - if ((buflen < elength) || (elength < 3)) { - dev_err(&intf->dev, "invalid descriptor buffer length\n"); - break; - } - if (buffer[1] != USB_DT_CS_INTERFACE) { - dev_err(&intf->dev, "skipping garbage\n"); - goto next_desc; - } - - switch (buffer[2]) { - case USB_CDC_UNION_TYPE: /* we've found it */ - if (elength < sizeof(struct usb_cdc_union_desc)) - goto next_desc; - if (union_header) { - dev_err(&intf->dev, "More than one union descriptor, skipping ...\n"); - goto next_desc; - } - union_header = (struct usb_cdc_union_desc *)buffer; - break; - case USB_CDC_COUNTRY_TYPE: - if (elength < sizeof(struct usb_cdc_country_functional_desc)) - goto next_desc; - hdr->usb_cdc_country_functional_desc = - (struct usb_cdc_country_functional_desc *)buffer; - break; - case USB_CDC_HEADER_TYPE: - if (elength != sizeof(struct usb_cdc_header_desc)) - goto next_desc; - if (header) - return -EINVAL; - header = (struct usb_cdc_header_desc *)buffer; - break; - case USB_CDC_ACM_TYPE: - if (elength < sizeof(struct usb_cdc_acm_descriptor)) - goto next_desc; - hdr->usb_cdc_acm_descriptor = - (struct usb_cdc_acm_descriptor *)buffer; - break; - case USB_CDC_ETHERNET_TYPE: - if (elength != sizeof(struct usb_cdc_ether_desc)) - goto next_desc; - if (ether) - return -EINVAL; - ether = (struct usb_cdc_ether_desc *)buffer; - break; - case USB_CDC_CALL_MANAGEMENT_TYPE: - if (elength < sizeof(struct usb_cdc_call_mgmt_descriptor)) - goto next_desc; - hdr->usb_cdc_call_mgmt_descriptor = - (struct usb_cdc_call_mgmt_descriptor *)buffer; - break; - case USB_CDC_DMM_TYPE: - if (elength < sizeof(struct usb_cdc_dmm_desc)) - goto next_desc; - hdr->usb_cdc_dmm_desc = - (struct usb_cdc_dmm_desc *)buffer; - break; - case USB_CDC_MDLM_TYPE: - if (elength < sizeof(struct usb_cdc_mdlm_desc *)) - goto next_desc; - if (desc) - return -EINVAL; - desc = (struct usb_cdc_mdlm_desc *)buffer; - break; - case USB_CDC_MDLM_DETAIL_TYPE: - if (elength < sizeof(struct usb_cdc_mdlm_detail_desc *)) - goto next_desc; - if (detail) - return -EINVAL; - detail = (struct usb_cdc_mdlm_detail_desc *)buffer; - break; - case USB_CDC_NCM_TYPE: - if (elength < sizeof(struct usb_cdc_ncm_desc)) - goto next_desc; - hdr->usb_cdc_ncm_desc = (struct usb_cdc_ncm_desc *)buffer; - break; - case USB_CDC_MBIM_TYPE: - if (elength < sizeof(struct usb_cdc_mbim_desc)) - goto next_desc; - - hdr->usb_cdc_mbim_desc = (struct usb_cdc_mbim_desc *)buffer; - break; - case USB_CDC_MBIM_EXTENDED_TYPE: - if (elength < sizeof(struct usb_cdc_mbim_extended_desc)) - break; - hdr->usb_cdc_mbim_extended_desc = - (struct usb_cdc_mbim_extended_desc *)buffer; - break; - case CDC_PHONET_MAGIC_NUMBER: - hdr->phonet_magic_present = true; - break; - default: - /* - * there are LOTS more CDC descriptors that - * could legitimately be found here. - */ - dev_dbg(&intf->dev, "Ignoring descriptor: type %02x, length %ud\n", - buffer[2], elength); - goto next_desc; - } - cnt++; -next_desc: - buflen -= elength; - buffer += elength; - } - hdr->usb_cdc_union_desc = union_header; - hdr->usb_cdc_header_desc = header; - hdr->usb_cdc_mdlm_detail_desc = detail; - hdr->usb_cdc_mdlm_desc = desc; - hdr->usb_cdc_ether_desc = ether; - return cnt; -} - -EXPORT_SYMBOL(cdc_parse_cdc_header); - /* * The function can't be called inside suspend/resume callback, * otherwise deadlock will be caused. diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index f9f94229bf1b..7945b1cd6244 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -265,6 +265,60 @@ config MWL8K To compile this driver as a module, choose M here: the module will be called mwl8k. If unsure, say N. +config WIFI_CONTROL_FUNC + bool "Enable WiFi control function abstraction" + help + Enables Power/Reset/Carddetect function abstraction + +config WCNSS_CORE + tristate "Qualcomm WCNSS CORE driver" + select WIRELESS_EXT + select WEXT_PRIV + select WEXT_CORE + select WEXT_SPY + ---help--- + Core driver for the Qualcomm WCNSS triple play connectivity subsystem + +config WCNSS_CORE_PRONTO + tristate "Qualcomm WCNSS Pronto Support" + depends on WCNSS_CORE + ---help--- + Pronto Support for the Qualcomm WCNSS triple play connectivity subsystem + +config WCNSS_REGISTER_DUMP_ON_BITE + bool "Enable/disable WCNSS register dump when there is a WCNSS bite" + depends on WCNSS_CORE_PRONTO + ---help--- + When Apps recieves a WDOG bite from WCNSS, collecting a register dump + of WCNSS is helpful to root cause the failure. WCNSS may not be + properly clocked in some WCNSS bite cases, and that may cause unclocked + register access failures. So this feature is to enable/disable the + register dump on WCNSS WDOG bite. + +config WCNSS_MEM_PRE_ALLOC + tristate "WCNSS pre-alloc memory support" + ---help--- + Pre-allocate memory for the WLAN driver module. + This feature enable cld wlan driver to use pre allocated memory + for it's internal usage and release it to back to pre allocated pool. + This memory is allocated at the cold boot time. + +config CNSS_CRYPTO + tristate "Enable CNSS crypto support" + ---help--- + Add crypto support for the WLAN driver module. + This feature enable wlan driver to use the crypto APIs exported + from cnss platform driver. This crypto APIs used to generate cipher + key and add support for the WLAN driver module security protocol. + +config CNSS_QCA6290 + bool "Enable CNSS QCA6290 chipset specific changes" + ---help--- + This enables the changes from WLAN host driver that are specific to + CNSS QCA6290 chipset. + These changes are needed to support the new hardware architecture + for CNSS QCA6290 chipset. + source "drivers/net/wireless/ath/Kconfig" source "drivers/net/wireless/b43/Kconfig" source "drivers/net/wireless/b43legacy/Kconfig" @@ -285,5 +339,9 @@ source "drivers/net/wireless/zd1211rw/Kconfig" source "drivers/net/wireless/mwifiex/Kconfig" source "drivers/net/wireless/cw1200/Kconfig" source "drivers/net/wireless/rsi/Kconfig" +source "drivers/net/wireless/cnss/Kconfig" +source "drivers/net/wireless/cnss2/Kconfig" +source "drivers/net/wireless/cnss_genl/Kconfig" +source "drivers/net/wireless/cnss_utils/Kconfig" endif # WLAN diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 740fdd353c5d..f23a2fbc3afa 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -60,3 +60,12 @@ obj-$(CONFIG_BRCMSMAC) += brcm80211/ obj-$(CONFIG_CW1200) += cw1200/ obj-$(CONFIG_RSI_91X) += rsi/ + +obj-$(CONFIG_WCNSS_CORE) += wcnss/ + +obj-$(CONFIG_CNSS) += cnss/ +obj-$(CONFIG_CNSS2) += cnss2/ +obj-$(CONFIG_WCNSS_MEM_PRE_ALLOC) += cnss_prealloc/ +obj-$(CONFIG_CNSS_CRYPTO) += cnss_crypto/ +obj-$(CONFIG_CNSS_GENL) += cnss_genl/ +obj-$(CONFIG_CNSS_UTILS) += cnss_utils/ diff --git a/drivers/net/wireless/ath/ath10k/Kconfig b/drivers/net/wireless/ath/ath10k/Kconfig index 72acb822bb11..f0c831b4b3d9 100644 --- a/drivers/net/wireless/ath/ath10k/Kconfig +++ b/drivers/net/wireless/ath/ath10k/Kconfig @@ -2,6 +2,7 @@ config ATH10K tristate "Atheros 802.11ac wireless cards support" depends on MAC80211 && HAS_DMA select ATH_COMMON + select CRC32 ---help--- This module adds support for wireless adapters based on Atheros IEEE 802.11ac family of chipsets. @@ -14,6 +15,32 @@ config ATH10K_PCI ---help--- This module adds support for PCIE bus +config ATH10K_AHB + bool "Atheros ath10k AHB support" + depends on ATH10K_PCI && OF && RESET_CONTROLLER + ---help--- + This module adds support for AHB bus + +config ATH10K_TARGET_SNOC + tristate "Atheros ath10k SNOC support" + depends on ATH10K + ---help--- + This module adds support for the Integrated WCN3990 WLAN module, + WCN3990 has integrated 802.11ac chipset with SNOC bus interface. + This module also adds support to register the WCN3990 wlan module + with MAC80211 network subsystem. + +config ATH10K_SNOC + bool "Enable/disable Atheros ath10k SNOC bus interface support" + depends on ATH10K + depends on ATH10K_TARGET_SNOC + ---help--- + This module add support for WLAN SNOC bus registration, WLAN + copy engine configuration for the WCN3990 chipset, WLAN hardware + shadow register configuration, create host to target communication + interface to interact with WLAN firmware, WLAN module interface + control and data receive(RX)/transmit(TX) control. + config ATH10K_DEBUG bool "Atheros ath10k debugging" depends on ATH10K diff --git a/drivers/net/wireless/ath/ath10k/Makefile b/drivers/net/wireless/ath/ath10k/Makefile index c04fb00e7930..27a6c75682c4 100644 --- a/drivers/net/wireless/ath/ath10k/Makefile +++ b/drivers/net/wireless/ath/ath10k/Makefile @@ -24,6 +24,13 @@ ath10k_core-$(CONFIG_PM) += wow.o obj-$(CONFIG_ATH10K_PCI) += ath10k_pci.o ath10k_pci-y += pci.o \ ce.o +obj-$(CONFIG_ATH10K_TARGET_SNOC) += ath10k_snoc.o +ath10k_snoc-y += snoc.o \ + qmi.o \ + wcn3990_qmi_service_v01.o \ + ce.o + +ath10k_pci-$(CONFIG_ATH10K_AHB) += ahb.o # for tracing framework to find trace.h CFLAGS_trace.o := -I$(src) diff --git a/drivers/net/wireless/ath/ath10k/ahb.c b/drivers/net/wireless/ath/ath10k/ahb.c new file mode 100644 index 000000000000..766c63bf05c4 --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/ahb.c @@ -0,0 +1,875 @@ +/* + * Copyright (c) 2016 Qualcomm Atheros, Inc. All rights reserved. + * Copyright (c) 2015 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk.h> +#include <linux/reset.h> +#include "core.h" +#include "debug.h" +#include "pci.h" +#include "ahb.h" + +static const struct of_device_id ath10k_ahb_of_match[] = { + { .compatible = "qcom,ipq4019-wifi", + .data = (void *)ATH10K_HW_QCA4019 + }, + { } +}; + +MODULE_DEVICE_TABLE(of, ath10k_ahb_of_match); + +static inline struct ath10k_ahb *ath10k_ahb_priv(struct ath10k *ar) +{ + return &((struct ath10k_pci *)ar->drv_priv)->ahb[0]; +} + +static void ath10k_ahb_write32(struct ath10k *ar, u32 offset, u32 value) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + iowrite32(value, ar_ahb->mem + offset); +} + +static u32 ath10k_ahb_read32(struct ath10k *ar, u32 offset) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + return ioread32(ar_ahb->mem + offset); +} + +static u32 ath10k_ahb_gcc_read32(struct ath10k *ar, u32 offset) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + return ioread32(ar_ahb->gcc_mem + offset); +} + +static void ath10k_ahb_tcsr_write32(struct ath10k *ar, u32 offset, u32 value) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + iowrite32(value, ar_ahb->tcsr_mem + offset); +} + +static u32 ath10k_ahb_tcsr_read32(struct ath10k *ar, u32 offset) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + return ioread32(ar_ahb->tcsr_mem + offset); +} + +static u32 ath10k_ahb_soc_read32(struct ath10k *ar, u32 addr) +{ + return ath10k_ahb_read32(ar, RTC_SOC_BASE_ADDRESS + addr); +} + +static int ath10k_ahb_get_num_banks(struct ath10k *ar) +{ + if (ar->hw_rev == ATH10K_HW_QCA4019) + return 1; + + ath10k_warn(ar, "unknown number of banks, assuming 1\n"); + return 1; +} + +static int ath10k_ahb_clock_init(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + struct device *dev; + + dev = &ar_ahb->pdev->dev; + + ar_ahb->cmd_clk = devm_clk_get(dev, "wifi_wcss_cmd"); + if (IS_ERR_OR_NULL(ar_ahb->cmd_clk)) { + ath10k_err(ar, "failed to get cmd clk: %ld\n", + PTR_ERR(ar_ahb->cmd_clk)); + return ar_ahb->cmd_clk ? PTR_ERR(ar_ahb->cmd_clk) : -ENODEV; + } + + ar_ahb->ref_clk = devm_clk_get(dev, "wifi_wcss_ref"); + if (IS_ERR_OR_NULL(ar_ahb->ref_clk)) { + ath10k_err(ar, "failed to get ref clk: %ld\n", + PTR_ERR(ar_ahb->ref_clk)); + return ar_ahb->ref_clk ? PTR_ERR(ar_ahb->ref_clk) : -ENODEV; + } + + ar_ahb->rtc_clk = devm_clk_get(dev, "wifi_wcss_rtc"); + if (IS_ERR_OR_NULL(ar_ahb->rtc_clk)) { + ath10k_err(ar, "failed to get rtc clk: %ld\n", + PTR_ERR(ar_ahb->rtc_clk)); + return ar_ahb->rtc_clk ? PTR_ERR(ar_ahb->rtc_clk) : -ENODEV; + } + + return 0; +} + +static void ath10k_ahb_clock_deinit(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + ar_ahb->cmd_clk = NULL; + ar_ahb->ref_clk = NULL; + ar_ahb->rtc_clk = NULL; +} + +static int ath10k_ahb_clock_enable(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + struct device *dev; + int ret; + + dev = &ar_ahb->pdev->dev; + + if (IS_ERR_OR_NULL(ar_ahb->cmd_clk) || + IS_ERR_OR_NULL(ar_ahb->ref_clk) || + IS_ERR_OR_NULL(ar_ahb->rtc_clk)) { + ath10k_err(ar, "clock(s) is/are not initialized\n"); + ret = -EIO; + goto out; + } + + ret = clk_prepare_enable(ar_ahb->cmd_clk); + if (ret) { + ath10k_err(ar, "failed to enable cmd clk: %d\n", ret); + goto out; + } + + ret = clk_prepare_enable(ar_ahb->ref_clk); + if (ret) { + ath10k_err(ar, "failed to enable ref clk: %d\n", ret); + goto err_cmd_clk_disable; + } + + ret = clk_prepare_enable(ar_ahb->rtc_clk); + if (ret) { + ath10k_err(ar, "failed to enable rtc clk: %d\n", ret); + goto err_ref_clk_disable; + } + + return 0; + +err_ref_clk_disable: + clk_disable_unprepare(ar_ahb->ref_clk); + +err_cmd_clk_disable: + clk_disable_unprepare(ar_ahb->cmd_clk); + +out: + return ret; +} + +static void ath10k_ahb_clock_disable(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + if (!IS_ERR_OR_NULL(ar_ahb->cmd_clk)) + clk_disable_unprepare(ar_ahb->cmd_clk); + + if (!IS_ERR_OR_NULL(ar_ahb->ref_clk)) + clk_disable_unprepare(ar_ahb->ref_clk); + + if (!IS_ERR_OR_NULL(ar_ahb->rtc_clk)) + clk_disable_unprepare(ar_ahb->rtc_clk); +} + +static int ath10k_ahb_rst_ctrl_init(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + struct device *dev; + + dev = &ar_ahb->pdev->dev; + + ar_ahb->core_cold_rst = devm_reset_control_get(dev, "wifi_core_cold"); + if (IS_ERR(ar_ahb->core_cold_rst)) { + ath10k_err(ar, "failed to get core cold rst ctrl: %ld\n", + PTR_ERR(ar_ahb->core_cold_rst)); + return PTR_ERR(ar_ahb->core_cold_rst); + } + + ar_ahb->radio_cold_rst = devm_reset_control_get(dev, "wifi_radio_cold"); + if (IS_ERR(ar_ahb->radio_cold_rst)) { + ath10k_err(ar, "failed to get radio cold rst ctrl: %ld\n", + PTR_ERR(ar_ahb->radio_cold_rst)); + return PTR_ERR(ar_ahb->radio_cold_rst); + } + + ar_ahb->radio_warm_rst = devm_reset_control_get(dev, "wifi_radio_warm"); + if (IS_ERR(ar_ahb->radio_warm_rst)) { + ath10k_err(ar, "failed to get radio warm rst ctrl: %ld\n", + PTR_ERR(ar_ahb->radio_warm_rst)); + return PTR_ERR(ar_ahb->radio_warm_rst); + } + + ar_ahb->radio_srif_rst = devm_reset_control_get(dev, "wifi_radio_srif"); + if (IS_ERR(ar_ahb->radio_srif_rst)) { + ath10k_err(ar, "failed to get radio srif rst ctrl: %ld\n", + PTR_ERR(ar_ahb->radio_srif_rst)); + return PTR_ERR(ar_ahb->radio_srif_rst); + } + + ar_ahb->cpu_init_rst = devm_reset_control_get(dev, "wifi_cpu_init"); + if (IS_ERR(ar_ahb->cpu_init_rst)) { + ath10k_err(ar, "failed to get cpu init rst ctrl: %ld\n", + PTR_ERR(ar_ahb->cpu_init_rst)); + return PTR_ERR(ar_ahb->cpu_init_rst); + } + + return 0; +} + +static void ath10k_ahb_rst_ctrl_deinit(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + ar_ahb->core_cold_rst = NULL; + ar_ahb->radio_cold_rst = NULL; + ar_ahb->radio_warm_rst = NULL; + ar_ahb->radio_srif_rst = NULL; + ar_ahb->cpu_init_rst = NULL; +} + +static int ath10k_ahb_release_reset(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + int ret; + + if (IS_ERR_OR_NULL(ar_ahb->radio_cold_rst) || + IS_ERR_OR_NULL(ar_ahb->radio_warm_rst) || + IS_ERR_OR_NULL(ar_ahb->radio_srif_rst) || + IS_ERR_OR_NULL(ar_ahb->cpu_init_rst)) { + ath10k_err(ar, "rst ctrl(s) is/are not initialized\n"); + return -EINVAL; + } + + ret = reset_control_deassert(ar_ahb->radio_cold_rst); + if (ret) { + ath10k_err(ar, "failed to deassert radio cold rst: %d\n", ret); + return ret; + } + + ret = reset_control_deassert(ar_ahb->radio_warm_rst); + if (ret) { + ath10k_err(ar, "failed to deassert radio warm rst: %d\n", ret); + return ret; + } + + ret = reset_control_deassert(ar_ahb->radio_srif_rst); + if (ret) { + ath10k_err(ar, "failed to deassert radio srif rst: %d\n", ret); + return ret; + } + + ret = reset_control_deassert(ar_ahb->cpu_init_rst); + if (ret) { + ath10k_err(ar, "failed to deassert cpu init rst: %d\n", ret); + return ret; + } + + return 0; +} + +static void ath10k_ahb_halt_axi_bus(struct ath10k *ar, u32 haltreq_reg, + u32 haltack_reg) +{ + unsigned long timeout; + u32 val; + + /* Issue halt axi bus request */ + val = ath10k_ahb_tcsr_read32(ar, haltreq_reg); + val |= AHB_AXI_BUS_HALT_REQ; + ath10k_ahb_tcsr_write32(ar, haltreq_reg, val); + + /* Wait for axi bus halted ack */ + timeout = jiffies + msecs_to_jiffies(ATH10K_AHB_AXI_BUS_HALT_TIMEOUT); + do { + val = ath10k_ahb_tcsr_read32(ar, haltack_reg); + if (val & AHB_AXI_BUS_HALT_ACK) + break; + + mdelay(1); + } while (time_before(jiffies, timeout)); + + if (!(val & AHB_AXI_BUS_HALT_ACK)) { + ath10k_err(ar, "failed to halt axi bus: %d\n", val); + return; + } + + ath10k_dbg(ar, ATH10K_DBG_AHB, "axi bus halted\n"); +} + +static void ath10k_ahb_halt_chip(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + u32 core_id, glb_cfg_reg, haltreq_reg, haltack_reg; + u32 val; + int ret; + + if (IS_ERR_OR_NULL(ar_ahb->core_cold_rst) || + IS_ERR_OR_NULL(ar_ahb->radio_cold_rst) || + IS_ERR_OR_NULL(ar_ahb->radio_warm_rst) || + IS_ERR_OR_NULL(ar_ahb->radio_srif_rst) || + IS_ERR_OR_NULL(ar_ahb->cpu_init_rst)) { + ath10k_err(ar, "rst ctrl(s) is/are not initialized\n"); + return; + } + + core_id = ath10k_ahb_read32(ar, ATH10K_AHB_WLAN_CORE_ID_REG); + + switch (core_id) { + case 0: + glb_cfg_reg = ATH10K_AHB_TCSR_WIFI0_GLB_CFG; + haltreq_reg = ATH10K_AHB_TCSR_WCSS0_HALTREQ; + haltack_reg = ATH10K_AHB_TCSR_WCSS0_HALTACK; + break; + case 1: + glb_cfg_reg = ATH10K_AHB_TCSR_WIFI1_GLB_CFG; + haltreq_reg = ATH10K_AHB_TCSR_WCSS1_HALTREQ; + haltack_reg = ATH10K_AHB_TCSR_WCSS1_HALTACK; + break; + default: + ath10k_err(ar, "invalid core id %d found, skipping reset sequence\n", + core_id); + return; + } + + ath10k_ahb_halt_axi_bus(ar, haltreq_reg, haltack_reg); + + val = ath10k_ahb_tcsr_read32(ar, glb_cfg_reg); + val |= TCSR_WIFIX_GLB_CFG_DISABLE_CORE_CLK; + ath10k_ahb_tcsr_write32(ar, glb_cfg_reg, val); + + ret = reset_control_assert(ar_ahb->core_cold_rst); + if (ret) + ath10k_err(ar, "failed to assert core cold rst: %d\n", ret); + msleep(1); + + ret = reset_control_assert(ar_ahb->radio_cold_rst); + if (ret) + ath10k_err(ar, "failed to assert radio cold rst: %d\n", ret); + msleep(1); + + ret = reset_control_assert(ar_ahb->radio_warm_rst); + if (ret) + ath10k_err(ar, "failed to assert radio warm rst: %d\n", ret); + msleep(1); + + ret = reset_control_assert(ar_ahb->radio_srif_rst); + if (ret) + ath10k_err(ar, "failed to assert radio srif rst: %d\n", ret); + msleep(1); + + ret = reset_control_assert(ar_ahb->cpu_init_rst); + if (ret) + ath10k_err(ar, "failed to assert cpu init rst: %d\n", ret); + msleep(10); + + /* Clear halt req and core clock disable req before + * deasserting wifi core reset. + */ + val = ath10k_ahb_tcsr_read32(ar, haltreq_reg); + val &= ~AHB_AXI_BUS_HALT_REQ; + ath10k_ahb_tcsr_write32(ar, haltreq_reg, val); + + val = ath10k_ahb_tcsr_read32(ar, glb_cfg_reg); + val &= ~TCSR_WIFIX_GLB_CFG_DISABLE_CORE_CLK; + ath10k_ahb_tcsr_write32(ar, glb_cfg_reg, val); + + ret = reset_control_deassert(ar_ahb->core_cold_rst); + if (ret) + ath10k_err(ar, "failed to deassert core cold rst: %d\n", ret); + + ath10k_dbg(ar, ATH10K_DBG_AHB, "core %d reset done\n", core_id); +} + +static irqreturn_t ath10k_ahb_interrupt_handler(int irq, void *arg) +{ + struct ath10k *ar = arg; + + if (!ath10k_pci_irq_pending(ar)) + return IRQ_NONE; + + ath10k_pci_disable_and_clear_legacy_irq(ar); + ath10k_pci_irq_msi_fw_mask(ar); + napi_schedule(&ar->napi); + + return IRQ_HANDLED; +} + +static int ath10k_ahb_request_irq_legacy(struct ath10k *ar) +{ + struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + int ret; + + ret = request_irq(ar_ahb->irq, + ath10k_ahb_interrupt_handler, + IRQF_SHARED, "ath10k_ahb", ar); + if (ret) { + ath10k_warn(ar, "failed to request legacy irq %d: %d\n", + ar_ahb->irq, ret); + return ret; + } + ar_pci->oper_irq_mode = ATH10K_PCI_IRQ_LEGACY; + + return 0; +} + +static void ath10k_ahb_release_irq_legacy(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + free_irq(ar_ahb->irq, ar); +} + +static void ath10k_ahb_irq_disable(struct ath10k *ar) +{ + ath10k_ce_disable_interrupts(ar); + ath10k_pci_disable_and_clear_legacy_irq(ar); +} + +static int ath10k_ahb_resource_init(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + struct platform_device *pdev; + struct device *dev; + struct resource *res; + int ret; + + pdev = ar_ahb->pdev; + dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ath10k_err(ar, "failed to get memory resource\n"); + ret = -ENXIO; + goto out; + } + + ar_ahb->mem = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ar_ahb->mem)) { + ath10k_err(ar, "mem ioremap error\n"); + ret = PTR_ERR(ar_ahb->mem); + goto out; + } + + ar_ahb->mem_len = resource_size(res); + + ar_ahb->gcc_mem = ioremap_nocache(ATH10K_GCC_REG_BASE, + ATH10K_GCC_REG_SIZE); + if (!ar_ahb->gcc_mem) { + ath10k_err(ar, "gcc mem ioremap error\n"); + ret = -ENOMEM; + goto err_mem_unmap; + } + + ar_ahb->tcsr_mem = ioremap_nocache(ATH10K_TCSR_REG_BASE, + ATH10K_TCSR_REG_SIZE); + if (!ar_ahb->tcsr_mem) { + ath10k_err(ar, "tcsr mem ioremap error\n"); + ret = -ENOMEM; + goto err_gcc_mem_unmap; + } + + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + ath10k_err(ar, "failed to set 32-bit dma mask: %d\n", ret); + goto err_tcsr_mem_unmap; + } + + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + ath10k_err(ar, "failed to set 32-bit consistent dma: %d\n", + ret); + goto err_tcsr_mem_unmap; + } + + ret = ath10k_ahb_clock_init(ar); + if (ret) + goto err_tcsr_mem_unmap; + + ret = ath10k_ahb_rst_ctrl_init(ar); + if (ret) + goto err_clock_deinit; + + ar_ahb->irq = platform_get_irq_byname(pdev, "legacy"); + if (ar_ahb->irq < 0) { + ath10k_err(ar, "failed to get irq number: %d\n", ar_ahb->irq); + ret = ar_ahb->irq; + goto err_clock_deinit; + } + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "irq: %d\n", ar_ahb->irq); + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "mem: 0x%pK mem_len: %lu gcc mem: 0x%pK tcsr_mem: 0x%pK\n", + ar_ahb->mem, ar_ahb->mem_len, + ar_ahb->gcc_mem, ar_ahb->tcsr_mem); + return 0; + +err_clock_deinit: + ath10k_ahb_clock_deinit(ar); + +err_tcsr_mem_unmap: + iounmap(ar_ahb->tcsr_mem); + +err_gcc_mem_unmap: + ar_ahb->tcsr_mem = NULL; + iounmap(ar_ahb->gcc_mem); + +err_mem_unmap: + ar_ahb->gcc_mem = NULL; + devm_iounmap(&pdev->dev, ar_ahb->mem); + +out: + ar_ahb->mem = NULL; + return ret; +} + +static void ath10k_ahb_resource_deinit(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + struct device *dev; + + dev = &ar_ahb->pdev->dev; + + if (ar_ahb->mem) + devm_iounmap(dev, ar_ahb->mem); + + if (ar_ahb->gcc_mem) + iounmap(ar_ahb->gcc_mem); + + if (ar_ahb->tcsr_mem) + iounmap(ar_ahb->tcsr_mem); + + ar_ahb->mem = NULL; + ar_ahb->gcc_mem = NULL; + ar_ahb->tcsr_mem = NULL; + + ath10k_ahb_clock_deinit(ar); + ath10k_ahb_rst_ctrl_deinit(ar); +} + +static int ath10k_ahb_prepare_device(struct ath10k *ar) +{ + u32 val; + int ret; + + ret = ath10k_ahb_clock_enable(ar); + if (ret) { + ath10k_err(ar, "failed to enable clocks\n"); + return ret; + } + + /* Clock for the target is supplied from outside of target (ie, + * external clock module controlled by the host). Target needs + * to know what frequency target cpu is configured which is needed + * for target internal use. Read target cpu frequency info from + * gcc register and write into target's scratch register where + * target expects this information. + */ + val = ath10k_ahb_gcc_read32(ar, ATH10K_AHB_GCC_FEPLL_PLL_DIV); + ath10k_ahb_write32(ar, ATH10K_AHB_WIFI_SCRATCH_5_REG, val); + + ret = ath10k_ahb_release_reset(ar); + if (ret) + goto err_clk_disable; + + ath10k_ahb_irq_disable(ar); + + ath10k_ahb_write32(ar, FW_INDICATOR_ADDRESS, FW_IND_HOST_READY); + + ret = ath10k_pci_wait_for_target_init(ar); + if (ret) + goto err_halt_chip; + + return 0; + +err_halt_chip: + ath10k_ahb_halt_chip(ar); + +err_clk_disable: + ath10k_ahb_clock_disable(ar); + + return ret; +} + +static int ath10k_ahb_chip_reset(struct ath10k *ar) +{ + int ret; + + ath10k_ahb_halt_chip(ar); + ath10k_ahb_clock_disable(ar); + + ret = ath10k_ahb_prepare_device(ar); + if (ret) + return ret; + + return 0; +} + +static int ath10k_ahb_wake_target_cpu(struct ath10k *ar) +{ + u32 addr, val; + + addr = SOC_CORE_BASE_ADDRESS | CORE_CTRL_ADDRESS; + val = ath10k_ahb_read32(ar, addr); + val |= ATH10K_AHB_CORE_CTRL_CPU_INTR_MASK; + ath10k_ahb_write32(ar, addr, val); + + return 0; +} + +static int ath10k_ahb_hif_start(struct ath10k *ar) +{ + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot ahb hif start\n"); + + ath10k_ce_enable_interrupts(ar); + ath10k_pci_enable_legacy_irq(ar); + + ath10k_pci_rx_post(ar); + + return 0; +} + +static void ath10k_ahb_hif_stop(struct ath10k *ar) +{ + struct ath10k_ahb *ar_ahb = ath10k_ahb_priv(ar); + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot ahb hif stop\n"); + + ath10k_ahb_irq_disable(ar); + synchronize_irq(ar_ahb->irq); + + ath10k_pci_flush(ar); + + napi_synchronize(&ar->napi); + napi_disable(&ar->napi); +} + +static int ath10k_ahb_hif_power_up(struct ath10k *ar) +{ + int ret; + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot ahb hif power up\n"); + + ret = ath10k_ahb_chip_reset(ar); + if (ret) { + ath10k_err(ar, "failed to reset chip: %d\n", ret); + goto out; + } + + ret = ath10k_pci_init_pipes(ar); + if (ret) { + ath10k_err(ar, "failed to initialize CE: %d\n", ret); + goto out; + } + + ret = ath10k_pci_init_config(ar); + if (ret) { + ath10k_err(ar, "failed to setup init config: %d\n", ret); + goto err_ce_deinit; + } + + ret = ath10k_ahb_wake_target_cpu(ar); + if (ret) { + ath10k_err(ar, "could not wake up target CPU: %d\n", ret); + goto err_ce_deinit; + } + napi_enable(&ar->napi); + + return 0; + +err_ce_deinit: + ath10k_pci_ce_deinit(ar); +out: + return ret; +} + +static const struct ath10k_hif_ops ath10k_ahb_hif_ops = { + .tx_sg = ath10k_pci_hif_tx_sg, + .diag_read = ath10k_pci_hif_diag_read, + .diag_write = ath10k_pci_diag_write_mem, + .exchange_bmi_msg = ath10k_pci_hif_exchange_bmi_msg, + .start = ath10k_ahb_hif_start, + .stop = ath10k_ahb_hif_stop, + .map_service_to_pipe = ath10k_pci_hif_map_service_to_pipe, + .get_default_pipe = ath10k_pci_hif_get_default_pipe, + .send_complete_check = ath10k_pci_hif_send_complete_check, + .get_free_queue_number = ath10k_pci_hif_get_free_queue_number, + .power_up = ath10k_ahb_hif_power_up, + .power_down = ath10k_pci_hif_power_down, + .read32 = ath10k_ahb_read32, + .write32 = ath10k_ahb_write32, +}; + +static const struct ath10k_bus_ops ath10k_ahb_bus_ops = { + .read32 = ath10k_ahb_read32, + .write32 = ath10k_ahb_write32, + .get_num_banks = ath10k_ahb_get_num_banks, +}; + +static int ath10k_ahb_probe(struct platform_device *pdev) +{ + struct ath10k *ar; + struct ath10k_ahb *ar_ahb; + struct ath10k_pci *ar_pci; + const struct of_device_id *of_id; + enum ath10k_hw_rev hw_rev; + size_t size; + int ret; + u32 chip_id; + + of_id = of_match_device(ath10k_ahb_of_match, &pdev->dev); + if (!of_id) { + dev_err(&pdev->dev, "failed to find matching device tree id\n"); + return -EINVAL; + } + + hw_rev = (enum ath10k_hw_rev)of_id->data; + + size = sizeof(*ar_pci) + sizeof(*ar_ahb); + ar = ath10k_core_create(size, &pdev->dev, ATH10K_BUS_AHB, + hw_rev, &ath10k_ahb_hif_ops); + if (!ar) { + dev_err(&pdev->dev, "failed to allocate core\n"); + return -ENOMEM; + } + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "ahb probe\n"); + + ar_pci = ath10k_pci_priv(ar); + ar_ahb = ath10k_ahb_priv(ar); + + ar_ahb->pdev = pdev; + platform_set_drvdata(pdev, ar); + + ret = ath10k_ahb_resource_init(ar); + if (ret) + goto err_core_destroy; + + ar->dev_id = 0; + ar_pci->mem = ar_ahb->mem; + ar_pci->mem_len = ar_ahb->mem_len; + ar_pci->ar = ar; + ar_pci->bus_ops = &ath10k_ahb_bus_ops; + + ret = ath10k_pci_setup_resource(ar); + if (ret) { + ath10k_err(ar, "failed to setup resource: %d\n", ret); + goto err_resource_deinit; + } + + ath10k_pci_init_napi(ar); + + ret = ath10k_ahb_request_irq_legacy(ar); + if (ret) + goto err_free_pipes; + + ret = ath10k_ahb_prepare_device(ar); + if (ret) + goto err_free_irq; + + ath10k_pci_ce_deinit(ar); + + chip_id = ath10k_ahb_soc_read32(ar, SOC_CHIP_ID_ADDRESS); + if (chip_id == 0xffffffff) { + ath10k_err(ar, "failed to get chip id\n"); + ret = -ENODEV; + goto err_halt_device; + } + + ret = ath10k_core_register(ar, chip_id); + if (ret) { + ath10k_err(ar, "failed to register driver core: %d\n", ret); + goto err_halt_device; + } + + return 0; + +err_halt_device: + ath10k_ahb_halt_chip(ar); + ath10k_ahb_clock_disable(ar); + +err_free_irq: + ath10k_ahb_release_irq_legacy(ar); + +err_free_pipes: + ath10k_pci_free_pipes(ar); + +err_resource_deinit: + ath10k_ahb_resource_deinit(ar); + +err_core_destroy: + ath10k_core_destroy(ar); + platform_set_drvdata(pdev, NULL); + + return ret; +} + +static int ath10k_ahb_remove(struct platform_device *pdev) +{ + struct ath10k *ar = platform_get_drvdata(pdev); + struct ath10k_ahb *ar_ahb; + + if (!ar) + return -EINVAL; + + ar_ahb = ath10k_ahb_priv(ar); + + if (!ar_ahb) + return -EINVAL; + + ath10k_dbg(ar, ATH10K_DBG_AHB, "ahb remove\n"); + + ath10k_core_unregister(ar); + ath10k_ahb_irq_disable(ar); + ath10k_ahb_release_irq_legacy(ar); + ath10k_pci_release_resource(ar); + ath10k_ahb_halt_chip(ar); + ath10k_ahb_clock_disable(ar); + ath10k_ahb_resource_deinit(ar); + ath10k_core_destroy(ar); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver ath10k_ahb_driver = { + .driver = { + .name = "ath10k_ahb", + .of_match_table = ath10k_ahb_of_match, + }, + .probe = ath10k_ahb_probe, + .remove = ath10k_ahb_remove, +}; + +int ath10k_ahb_init(void) +{ + int ret; + + ret = platform_driver_register(&ath10k_ahb_driver); + if (ret) + printk(KERN_ERR "failed to register ath10k ahb driver: %d\n", + ret); + return ret; +} + +void ath10k_ahb_exit(void) +{ + platform_driver_unregister(&ath10k_ahb_driver); +} diff --git a/drivers/net/wireless/ath/ath10k/ahb.h b/drivers/net/wireless/ath/ath10k/ahb.h new file mode 100644 index 000000000000..d43e375215c8 --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/ahb.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016 Qualcomm Atheros, Inc. All rights reserved. + * Copyright (c) 2015 The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _AHB_H_ +#define _AHB_H_ + +#include <linux/platform_device.h> + +struct ath10k_ahb { + struct platform_device *pdev; + void __iomem *mem; + unsigned long mem_len; + void __iomem *gcc_mem; + void __iomem *tcsr_mem; + + int irq; + + struct clk *cmd_clk; + struct clk *ref_clk; + struct clk *rtc_clk; + + struct reset_control *core_cold_rst; + struct reset_control *radio_cold_rst; + struct reset_control *radio_warm_rst; + struct reset_control *radio_srif_rst; + struct reset_control *cpu_init_rst; +}; + +#ifdef CONFIG_ATH10K_AHB + +#define ATH10K_GCC_REG_BASE 0x1800000 +#define ATH10K_GCC_REG_SIZE 0x60000 + +#define ATH10K_TCSR_REG_BASE 0x1900000 +#define ATH10K_TCSR_REG_SIZE 0x80000 + +#define ATH10K_AHB_GCC_FEPLL_PLL_DIV 0x2f020 +#define ATH10K_AHB_WIFI_SCRATCH_5_REG 0x4f014 + +#define ATH10K_AHB_WLAN_CORE_ID_REG 0x82030 + +#define ATH10K_AHB_TCSR_WIFI0_GLB_CFG 0x49000 +#define ATH10K_AHB_TCSR_WIFI1_GLB_CFG 0x49004 +#define TCSR_WIFIX_GLB_CFG_DISABLE_CORE_CLK BIT(25) + +#define ATH10K_AHB_TCSR_WCSS0_HALTREQ 0x52000 +#define ATH10K_AHB_TCSR_WCSS1_HALTREQ 0x52010 +#define ATH10K_AHB_TCSR_WCSS0_HALTACK 0x52004 +#define ATH10K_AHB_TCSR_WCSS1_HALTACK 0x52014 + +#define ATH10K_AHB_AXI_BUS_HALT_TIMEOUT 10 /* msec */ +#define AHB_AXI_BUS_HALT_REQ 1 +#define AHB_AXI_BUS_HALT_ACK 1 + +#define ATH10K_AHB_CORE_CTRL_CPU_INTR_MASK 1 + +int ath10k_ahb_init(void); +void ath10k_ahb_exit(void); + +#else /* CONFIG_ATH10K_AHB */ + +static inline int ath10k_ahb_init(void) +{ + return 0; +} + +static inline void ath10k_ahb_exit(void) +{ +} + +#endif /* CONFIG_ATH10K_AHB */ + +#endif /* _AHB_H_ */ diff --git a/drivers/net/wireless/ath/ath10k/bmi.c b/drivers/net/wireless/ath/ath10k/bmi.c index 3d29b0875b3e..2872d347ea78 100644 --- a/drivers/net/wireless/ath/ath10k/bmi.c +++ b/drivers/net/wireless/ath/ath10k/bmi.c @@ -221,7 +221,7 @@ int ath10k_bmi_lz_data(struct ath10k *ar, const void *buffer, u32 length) u32 txlen; int ret; - ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi lz data buffer 0x%p length %d\n", + ath10k_dbg(ar, ATH10K_DBG_BMI, "bmi lz data buffer 0x%pK length %d\n", buffer, length); if (ar->bmi.done_sent) { @@ -287,7 +287,7 @@ int ath10k_bmi_fast_download(struct ath10k *ar, int ret; ath10k_dbg(ar, ATH10K_DBG_BMI, - "bmi fast download address 0x%x buffer 0x%p length %d\n", + "bmi fast download address 0x%x buffer 0x%pK length %d\n", address, buffer, length); ret = ath10k_bmi_lz_stream_start(ar, address); diff --git a/drivers/net/wireless/ath/ath10k/ce.c b/drivers/net/wireless/ath/ath10k/ce.c index edf3629288bc..9ccd6212b54a 100644 --- a/drivers/net/wireless/ath/ath10k/ce.c +++ b/drivers/net/wireless/ath/ath10k/ce.c @@ -16,13 +16,12 @@ */ #include "hif.h" -#include "pci.h" #include "ce.h" #include "debug.h" /* * Support for Copy Engine hardware, which is mainly used for - * communication between Host and Target over a PCIe interconnect. + * communication between Host and Target over a PCIe/SNOC/AHB interconnect. */ /* @@ -33,13 +32,13 @@ * Each ring consists of a number of descriptors which specify * an address, length, and meta-data. * - * Typically, one side of the PCIe interconnect (Host or Target) + * Typically, one side of the PCIe/AHB/SNOC interconnect (Host or Target) * controls one ring and the other side controls the other ring. * The source side chooses when to initiate a transfer and it * chooses what to send (buffer address, length). The destination * side keeps a supply of "anonymous receive buffers" available and * it handles incoming data as it arrives (when the destination - * recieves an interrupt). + * receives an interrupt). * * The sender may send a simple buffer (address/length) or it may * send a small list of buffers. When a small list is sent, hardware @@ -63,201 +62,401 @@ static inline void ath10k_ce_dest_ring_write_index_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - ath10k_pci_write32(ar, ce_ctrl_addr + DST_WR_INDEX_ADDRESS, n); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->dst_wr_index_addr, n); } static inline u32 ath10k_ce_dest_ring_write_index_get(struct ath10k *ar, u32 ce_ctrl_addr) { - return ath10k_pci_read32(ar, ce_ctrl_addr + DST_WR_INDEX_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + return ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->dst_wr_index_addr); } static inline void ath10k_ce_src_ring_write_index_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - ath10k_pci_write32(ar, ce_ctrl_addr + SR_WR_INDEX_ADDRESS, n); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->sr_wr_index_addr, n); } static inline u32 ath10k_ce_src_ring_write_index_get(struct ath10k *ar, u32 ce_ctrl_addr) { - return ath10k_pci_read32(ar, ce_ctrl_addr + SR_WR_INDEX_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + return ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->sr_wr_index_addr); +} + +static inline u32 ath10k_ce_src_ring_read_index_get_from_ddr( + struct ath10k *ar, u32 ce_id) +{ + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + return ar_opaque->vaddr_rri_on_ddr[ce_id] & CE_DDR_RRI_MASK; } static inline u32 ath10k_ce_src_ring_read_index_get(struct ath10k *ar, u32 ce_ctrl_addr) { - return ath10k_pci_read32(ar, ce_ctrl_addr + CURRENT_SRRI_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + u32 ce_id = COPY_ENGINE_ID(ce_ctrl_addr); + struct ath10k_ce_pipe *ce_state = &ar_opaque->ce_states[ce_id]; + u32 index; + + if (ar->rri_on_ddr && (ce_state->attr_flags & CE_ATTR_DIS_INTR)) + index = ath10k_ce_src_ring_read_index_get_from_ddr(ar, ce_id); + else + index = ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->current_srri_addr); + + return index; +} + +static inline void ath10k_ce_shadow_src_ring_write_index_set(struct ath10k *ar, + u32 ce_ctrl_addr, + unsigned int n) +{ + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + ar_opaque->bus_ops->write32(ar, shadow_sr_wr_ind_addr(ar, + ce_ctrl_addr), n); +} + +static inline void ath10k_ce_shadow_dest_ring_write_index_set(struct ath10k *ar, + u32 ce_ctrl_addr, + unsigned int n) +{ + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + ar_opaque->bus_ops->write32(ar, shadow_dst_wr_ind_addr(ar, + ce_ctrl_addr), + n); } static inline void ath10k_ce_src_ring_base_addr_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int addr) { - ath10k_pci_write32(ar, ce_ctrl_addr + SR_BA_ADDRESS, addr); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->sr_base_addr, addr); } static inline void ath10k_ce_src_ring_size_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - ath10k_pci_write32(ar, ce_ctrl_addr + SR_SIZE_ADDRESS, n); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->sr_size_addr, n); } static inline void ath10k_ce_src_ring_dmax_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - u32 ctrl1_addr = ath10k_pci_read32((ar), - (ce_ctrl_addr) + CE_CTRL1_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_ctrl1 *ctrl_regs = ar->hw_ce_regs->ctrl1_regs; - ath10k_pci_write32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS, - (ctrl1_addr & ~CE_CTRL1_DMAX_LENGTH_MASK) | - CE_CTRL1_DMAX_LENGTH_SET(n)); + u32 ctrl1_addr = ar_opaque->bus_ops->read32((ar), + (ce_ctrl_addr) + ctrl_regs->addr); + + ar_opaque->bus_ops->write32(ar, ce_ctrl_addr + ctrl_regs->addr, + (ctrl1_addr & ~(ctrl_regs->dmax->mask)) | + ctrl_regs->dmax->set(n, ctrl_regs->dmax)); } static inline void ath10k_ce_src_ring_byte_swap_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - u32 ctrl1_addr = ath10k_pci_read32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_ctrl1 *ctrl_regs = ar->hw_ce_regs->ctrl1_regs; + + u32 ctrl1_addr = ar_opaque->bus_ops->read32(ar, ce_ctrl_addr + + ctrl_regs->addr); - ath10k_pci_write32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS, - (ctrl1_addr & ~CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MASK) | - CE_CTRL1_SRC_RING_BYTE_SWAP_EN_SET(n)); + ar_opaque->bus_ops->write32(ar, ce_ctrl_addr + ctrl_regs->addr, + (ctrl1_addr & ~(ctrl_regs->src_ring->mask)) | + ctrl_regs->src_ring->set(n, ctrl_regs->src_ring)); } static inline void ath10k_ce_dest_ring_byte_swap_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - u32 ctrl1_addr = ath10k_pci_read32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_ctrl1 *ctrl_regs = ar->hw_ce_regs->ctrl1_regs; - ath10k_pci_write32(ar, ce_ctrl_addr + CE_CTRL1_ADDRESS, - (ctrl1_addr & ~CE_CTRL1_DST_RING_BYTE_SWAP_EN_MASK) | - CE_CTRL1_DST_RING_BYTE_SWAP_EN_SET(n)); + u32 ctrl1_addr = ar_opaque->bus_ops->read32(ar, ce_ctrl_addr + + ctrl_regs->addr); + + ar_opaque->bus_ops->write32(ar, ce_ctrl_addr + ctrl_regs->addr, + (ctrl1_addr & ~(ctrl_regs->dst_ring->mask)) | + ctrl_regs->dst_ring->set(n, ctrl_regs->dst_ring)); } static inline u32 ath10k_ce_dest_ring_read_index_get(struct ath10k *ar, u32 ce_ctrl_addr) { - return ath10k_pci_read32(ar, ce_ctrl_addr + CURRENT_DRRI_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + u32 ce_id = COPY_ENGINE_ID(ce_ctrl_addr); + struct ath10k_ce_pipe *ce_state = &ar_opaque->ce_states[ce_id]; + u32 index; + + if (ar->rri_on_ddr && (ce_state->attr_flags & CE_ATTR_DIS_INTR)) + index = (ar_opaque->vaddr_rri_on_ddr[ce_id] >> + CE_DDR_RRI_SHIFT) & + CE_DDR_RRI_MASK; + else + index = ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->current_drri_addr); + + return index; } static inline void ath10k_ce_dest_ring_base_addr_set(struct ath10k *ar, u32 ce_ctrl_addr, u32 addr) { - ath10k_pci_write32(ar, ce_ctrl_addr + DR_BA_ADDRESS, addr); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->dr_base_addr, addr); } static inline void ath10k_ce_dest_ring_size_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - ath10k_pci_write32(ar, ce_ctrl_addr + DR_SIZE_ADDRESS, n); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->dr_size_addr, n); } static inline void ath10k_ce_src_ring_highmark_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - u32 addr = ath10k_pci_read32(ar, ce_ctrl_addr + SRC_WATERMARK_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_dst_src_wm_regs *srcr_wm = ar->hw_ce_regs->wm_srcr; + u32 addr = ar_opaque->bus_ops->read32(ar, ce_ctrl_addr + srcr_wm->addr); - ath10k_pci_write32(ar, ce_ctrl_addr + SRC_WATERMARK_ADDRESS, - (addr & ~SRC_WATERMARK_HIGH_MASK) | - SRC_WATERMARK_HIGH_SET(n)); + ar_opaque->bus_ops->write32(ar, ce_ctrl_addr + srcr_wm->addr, + (addr & ~(srcr_wm->wm_high->mask)) | + (srcr_wm->wm_high->set(n, srcr_wm->wm_high))); } static inline void ath10k_ce_src_ring_lowmark_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - u32 addr = ath10k_pci_read32(ar, ce_ctrl_addr + SRC_WATERMARK_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_dst_src_wm_regs *srcr_wm = ar->hw_ce_regs->wm_srcr; + u32 addr = ar_opaque->bus_ops->read32(ar, ce_ctrl_addr + srcr_wm->addr); - ath10k_pci_write32(ar, ce_ctrl_addr + SRC_WATERMARK_ADDRESS, - (addr & ~SRC_WATERMARK_LOW_MASK) | - SRC_WATERMARK_LOW_SET(n)); + ar_opaque->bus_ops->write32(ar, ce_ctrl_addr + srcr_wm->addr, + (addr & ~(srcr_wm->wm_low->mask)) | + (srcr_wm->wm_low->set(n, srcr_wm->wm_low))); } static inline void ath10k_ce_dest_ring_highmark_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - u32 addr = ath10k_pci_read32(ar, ce_ctrl_addr + DST_WATERMARK_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_dst_src_wm_regs *dstr_wm = ar->hw_ce_regs->wm_dstr; + u32 addr = ar_opaque->bus_ops->read32(ar, ce_ctrl_addr + dstr_wm->addr); - ath10k_pci_write32(ar, ce_ctrl_addr + DST_WATERMARK_ADDRESS, - (addr & ~DST_WATERMARK_HIGH_MASK) | - DST_WATERMARK_HIGH_SET(n)); + ar_opaque->bus_ops->write32(ar, ce_ctrl_addr + dstr_wm->addr, + (addr & ~(dstr_wm->wm_high->mask)) | + (dstr_wm->wm_high->set(n, dstr_wm->wm_high))); } static inline void ath10k_ce_dest_ring_lowmark_set(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int n) { - u32 addr = ath10k_pci_read32(ar, ce_ctrl_addr + DST_WATERMARK_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_dst_src_wm_regs *dstr_wm = ar->hw_ce_regs->wm_dstr; + u32 addr = ar_opaque->bus_ops->read32(ar, ce_ctrl_addr + dstr_wm->addr); - ath10k_pci_write32(ar, ce_ctrl_addr + DST_WATERMARK_ADDRESS, - (addr & ~DST_WATERMARK_LOW_MASK) | - DST_WATERMARK_LOW_SET(n)); + ar_opaque->bus_ops->write32(ar, ce_ctrl_addr + dstr_wm->addr, + (addr & ~(dstr_wm->wm_low->mask)) | + (dstr_wm->wm_low->set(n, dstr_wm->wm_low))); } static inline void ath10k_ce_copy_complete_inter_enable(struct ath10k *ar, u32 ce_ctrl_addr) { - u32 host_ie_addr = ath10k_pci_read32(ar, - ce_ctrl_addr + HOST_IE_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_host_ie *host_ie = ar->hw_ce_regs->host_ie; + + u32 host_ie_addr = ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->host_ie_addr); - ath10k_pci_write32(ar, ce_ctrl_addr + HOST_IE_ADDRESS, - host_ie_addr | HOST_IE_COPY_COMPLETE_MASK); + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->host_ie_addr, + host_ie_addr | host_ie->copy_complete->mask); } static inline void ath10k_ce_copy_complete_intr_disable(struct ath10k *ar, u32 ce_ctrl_addr) { - u32 host_ie_addr = ath10k_pci_read32(ar, - ce_ctrl_addr + HOST_IE_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_host_ie *host_ie = ar->hw_ce_regs->host_ie; + + u32 host_ie_addr = ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->host_ie_addr); - ath10k_pci_write32(ar, ce_ctrl_addr + HOST_IE_ADDRESS, - host_ie_addr & ~HOST_IE_COPY_COMPLETE_MASK); + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->host_ie_addr, + host_ie_addr & ~(host_ie->copy_complete->mask)); } static inline void ath10k_ce_watermark_intr_disable(struct ath10k *ar, u32 ce_ctrl_addr) { - u32 host_ie_addr = ath10k_pci_read32(ar, - ce_ctrl_addr + HOST_IE_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_host_wm_regs *wm_regs = ar->hw_ce_regs->wm_regs; - ath10k_pci_write32(ar, ce_ctrl_addr + HOST_IE_ADDRESS, - host_ie_addr & ~CE_WATERMARK_MASK); + u32 host_ie_addr = ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->host_ie_addr); + + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->host_ie_addr, + host_ie_addr & ~(wm_regs->wm_mask)); } static inline void ath10k_ce_error_intr_enable(struct ath10k *ar, u32 ce_ctrl_addr) { - u32 misc_ie_addr = ath10k_pci_read32(ar, - ce_ctrl_addr + MISC_IE_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_misc_regs *misc_regs = ar->hw_ce_regs->misc_regs; + + u32 misc_ie_addr = ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->misc_ie_addr); - ath10k_pci_write32(ar, ce_ctrl_addr + MISC_IE_ADDRESS, - misc_ie_addr | CE_ERROR_MASK); + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->misc_ie_addr, + misc_ie_addr | misc_regs->err_mask); } static inline void ath10k_ce_error_intr_disable(struct ath10k *ar, u32 ce_ctrl_addr) { - u32 misc_ie_addr = ath10k_pci_read32(ar, - ce_ctrl_addr + MISC_IE_ADDRESS); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_misc_regs *misc_regs = ar->hw_ce_regs->misc_regs; - ath10k_pci_write32(ar, ce_ctrl_addr + MISC_IE_ADDRESS, - misc_ie_addr & ~CE_ERROR_MASK); + u32 misc_ie_addr = ar_opaque->bus_ops->read32(ar, + ce_ctrl_addr + ar->hw_ce_regs->misc_ie_addr); + + ar_opaque->bus_ops->write32(ar, + ce_ctrl_addr + ar->hw_ce_regs->misc_ie_addr, + misc_ie_addr & ~(misc_regs->err_mask)); } static inline void ath10k_ce_engine_int_status_clear(struct ath10k *ar, u32 ce_ctrl_addr, unsigned int mask) { - ath10k_pci_write32(ar, ce_ctrl_addr + HOST_IS_ADDRESS, mask); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_hw_ce_host_wm_regs *wm_regs = ar->hw_ce_regs->wm_regs; + + ar_opaque->bus_ops->write32(ar, ce_ctrl_addr + wm_regs->addr, mask); +} + +u32 shadow_sr_wr_ind_addr(struct ath10k *ar, u32 ctrl_addr) +{ + u32 addr = 0; + u32 ce = COPY_ENGINE_ID(ctrl_addr); + + switch (ce) { + case 0: + addr = SHADOW_VALUE0; + break; + case 3: + addr = SHADOW_VALUE3; + break; + case 4: + addr = SHADOW_VALUE4; + break; + case 5: + addr = SHADOW_VALUE5; + break; + case 7: + addr = SHADOW_VALUE7; + break; + default: + ath10k_err(ar, "invalid CE ctrl_addr (CE=%d)", ce); + WARN_ON(1); + } + return addr; +} + +u32 shadow_dst_wr_ind_addr(struct ath10k *ar, u32 ctrl_addr) +{ + u32 addr = 0; + u32 ce = COPY_ENGINE_ID(ctrl_addr); + + switch (ce) { + case 1: + addr = SHADOW_VALUE13; + break; + case 2: + addr = SHADOW_VALUE14; + break; + case 5: + addr = SHADOW_VALUE17; + break; + case 7: + addr = SHADOW_VALUE19; + break; + case 8: + addr = SHADOW_VALUE20; + break; + case 9: + addr = SHADOW_VALUE21; + break; + case 10: + addr = SHADOW_VALUE22; + break; + case 11: + addr = SHADOW_VALUE23; + break; + default: + ath10k_err(ar, "invalid CE ctrl_addr (CE=%d)", ce); + WARN_ON(1); + } + + return addr; +} + +static inline void ath10k_ce_snoc_addr_config(struct ce_desc *sdesc, + dma_addr_t buffer, + unsigned int flags) +{ + __le32 *addr = (__le32 *)&sdesc->addr; + + flags |= upper_32_bits(buffer) & CE_DESC_FLAGS_GET_MASK; + addr[0] = __cpu_to_le32(buffer); + addr[1] = flags; + if (flags & CE_SEND_FLAG_GATHER) + addr[1] |= CE_WCN3990_DESC_FLAGS_GATHER; + else + addr[1] &= ~CE_WCN3990_DESC_FLAGS_GATHER; } /* @@ -267,7 +466,7 @@ static inline void ath10k_ce_engine_int_status_clear(struct ath10k *ar, */ int ath10k_ce_send_nolock(struct ath10k_ce_pipe *ce_state, void *per_transfer_context, - u32 buffer, + dma_addr_t buffer, unsigned int nbytes, unsigned int transfer_id, unsigned int flags) @@ -276,16 +475,20 @@ int ath10k_ce_send_nolock(struct ath10k_ce_pipe *ce_state, struct ath10k_ce_ring *src_ring = ce_state->src_ring; struct ce_desc *desc, sdesc; unsigned int nentries_mask = src_ring->nentries_mask; - unsigned int sw_index = src_ring->sw_index; + unsigned int sw_index; unsigned int write_index = src_ring->write_index; u32 ctrl_addr = ce_state->ctrl_addr; u32 desc_flags = 0; int ret = 0; + if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + return -ESHUTDOWN; + if (nbytes > ce_state->src_sz_max) ath10k_warn(ar, "%s: send more we can (nbytes: %d, max: %d)\n", __func__, nbytes, ce_state->src_sz_max); + sw_index = ath10k_ce_src_ring_read_index_get_from_ddr(ar, ce_state->id); if (unlikely(CE_RING_DELTA(nentries_mask, write_index, sw_index - 1) <= 0)) { ret = -ENOSR; @@ -299,10 +502,15 @@ int ath10k_ce_send_nolock(struct ath10k_ce_pipe *ce_state, if (flags & CE_SEND_FLAG_GATHER) desc_flags |= CE_DESC_FLAGS_GATHER; + if (flags & CE_SEND_FLAG_BYTE_SWAP) desc_flags |= CE_DESC_FLAGS_BYTE_SWAP; - sdesc.addr = __cpu_to_le32(buffer); + if (QCA_REV_WCN3990(ar)) + ath10k_ce_snoc_addr_config(&sdesc, buffer, flags); + else + sdesc.addr = __cpu_to_le32(buffer); + sdesc.nbytes = __cpu_to_le16(nbytes); sdesc.flags = __cpu_to_le16(desc_flags); @@ -314,8 +522,14 @@ int ath10k_ce_send_nolock(struct ath10k_ce_pipe *ce_state, write_index = CE_RING_IDX_INCR(nentries_mask, write_index); /* WORKAROUND */ - if (!(flags & CE_SEND_FLAG_GATHER)) - ath10k_ce_src_ring_write_index_set(ar, ctrl_addr, write_index); + if (!(flags & CE_SEND_FLAG_GATHER)) { + if (QCA_REV_WCN3990(ar)) + ath10k_ce_shadow_src_ring_write_index_set(ar, ctrl_addr, + write_index); + else + ath10k_ce_src_ring_write_index_set(ar, ctrl_addr, + write_index); + } src_ring->write_index = write_index; exit: @@ -325,11 +539,11 @@ exit: void __ath10k_ce_send_revert(struct ath10k_ce_pipe *pipe) { struct ath10k *ar = pipe->ar; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); struct ath10k_ce_ring *src_ring = pipe->src_ring; u32 ctrl_addr = pipe->ctrl_addr; - lockdep_assert_held(&ar_pci->ce_lock); + lockdep_assert_held(&ar_opaque->ce_lock); /* * This function must be called only if there is an incomplete @@ -351,19 +565,19 @@ void __ath10k_ce_send_revert(struct ath10k_ce_pipe *pipe) int ath10k_ce_send(struct ath10k_ce_pipe *ce_state, void *per_transfer_context, - u32 buffer, + dma_addr_t buffer, unsigned int nbytes, unsigned int transfer_id, unsigned int flags) { struct ath10k *ar = ce_state->ar; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); int ret; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); ret = ath10k_ce_send_nolock(ce_state, per_transfer_context, buffer, nbytes, transfer_id, flags); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_opaque->ce_lock); return ret; } @@ -371,14 +585,14 @@ int ath10k_ce_send(struct ath10k_ce_pipe *ce_state, int ath10k_ce_num_free_src_entries(struct ath10k_ce_pipe *pipe) { struct ath10k *ar = pipe->ar; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); int delta; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); delta = CE_RING_DELTA(pipe->src_ring->nentries_mask, pipe->src_ring->write_index, pipe->src_ring->sw_index - 1); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_opaque->ce_lock); return delta; } @@ -386,21 +600,22 @@ int ath10k_ce_num_free_src_entries(struct ath10k_ce_pipe *pipe) int __ath10k_ce_rx_num_free_bufs(struct ath10k_ce_pipe *pipe) { struct ath10k *ar = pipe->ar; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); struct ath10k_ce_ring *dest_ring = pipe->dest_ring; unsigned int nentries_mask = dest_ring->nentries_mask; unsigned int write_index = dest_ring->write_index; unsigned int sw_index = dest_ring->sw_index; - lockdep_assert_held(&ar_pci->ce_lock); + lockdep_assert_held(&ar_opaque->ce_lock); return CE_RING_DELTA(nentries_mask, write_index, sw_index - 1); } -int __ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, u32 paddr) +int __ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, + dma_addr_t paddr) { struct ath10k *ar = pipe->ar; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); struct ath10k_ce_ring *dest_ring = pipe->dest_ring; unsigned int nentries_mask = dest_ring->nentries_mask; unsigned int write_index = dest_ring->write_index; @@ -409,12 +624,19 @@ int __ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, u32 paddr) struct ce_desc *desc = CE_DEST_RING_TO_DESC(base, write_index); u32 ctrl_addr = pipe->ctrl_addr; - lockdep_assert_held(&ar_pci->ce_lock); + lockdep_assert_held(&ar_opaque->ce_lock); - if (CE_RING_DELTA(nentries_mask, write_index, sw_index - 1) == 0) + if ((pipe->id != 5) && + CE_RING_DELTA(nentries_mask, write_index, sw_index - 1) == 0) return -ENOSPC; - desc->addr = __cpu_to_le32(paddr); + if (QCA_REV_WCN3990(ar)) { + desc->addr = paddr; + desc->addr &= CE_DESC_37BIT_ADDR_MASK; + } else { + desc->addr = __cpu_to_le32(paddr); + } + desc->nbytes = 0; dest_ring->per_transfer_context[write_index] = ctx; @@ -425,15 +647,29 @@ int __ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, u32 paddr) return 0; } -int ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, u32 paddr) +void ath10k_ce_rx_update_write_idx(struct ath10k_ce_pipe *pipe, u32 nentries) { struct ath10k *ar = pipe->ar; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct ath10k_ce_ring *dest_ring = pipe->dest_ring; + unsigned int nentries_mask = dest_ring->nentries_mask; + unsigned int write_index = dest_ring->write_index; + u32 ctrl_addr = pipe->ctrl_addr; + + write_index = CE_RING_IDX_ADD(nentries_mask, write_index, nentries); + ath10k_ce_dest_ring_write_index_set(ar, ctrl_addr, write_index); + dest_ring->write_index = write_index; +} + +int ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, + dma_addr_t paddr) +{ + struct ath10k *ar = pipe->ar; + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); int ret; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); ret = __ath10k_ce_rx_post_buf(pipe, ctx, paddr); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_opaque->ce_lock); return ret; } @@ -444,14 +680,10 @@ int ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, u32 paddr) */ int ath10k_ce_completed_recv_next_nolock(struct ath10k_ce_pipe *ce_state, void **per_transfer_contextp, - u32 *bufferp, - unsigned int *nbytesp, - unsigned int *transfer_idp, - unsigned int *flagsp) + unsigned int *nbytesp) { struct ath10k_ce_ring *dest_ring = ce_state->dest_ring; unsigned int nentries_mask = dest_ring->nentries_mask; - struct ath10k *ar = ce_state->ar; unsigned int sw_index = dest_ring->sw_index; struct ce_desc *base = dest_ring->base_addr_owner_space; @@ -476,21 +708,17 @@ int ath10k_ce_completed_recv_next_nolock(struct ath10k_ce_pipe *ce_state, desc->nbytes = 0; /* Return data from completed destination descriptor */ - *bufferp = __le32_to_cpu(sdesc.addr); *nbytesp = nbytes; - *transfer_idp = MS(__le16_to_cpu(sdesc.flags), CE_DESC_FLAGS_META_DATA); - - if (__le16_to_cpu(sdesc.flags) & CE_DESC_FLAGS_BYTE_SWAP) - *flagsp = CE_RECV_FLAG_SWAPPED; - else - *flagsp = 0; if (per_transfer_contextp) *per_transfer_contextp = dest_ring->per_transfer_context[sw_index]; - /* sanity */ - dest_ring->per_transfer_context[sw_index] = NULL; + /* Copy engine 5 (HTT Rx) will reuse the same transfer context. + * So update transfer context all CEs except CE5. + */ + if (ce_state->id != 5) + dest_ring->per_transfer_context[sw_index] = NULL; /* Update sw_index */ sw_index = CE_RING_IDX_INCR(nentries_mask, sw_index); @@ -501,21 +729,17 @@ int ath10k_ce_completed_recv_next_nolock(struct ath10k_ce_pipe *ce_state, int ath10k_ce_completed_recv_next(struct ath10k_ce_pipe *ce_state, void **per_transfer_contextp, - u32 *bufferp, - unsigned int *nbytesp, - unsigned int *transfer_idp, - unsigned int *flagsp) + unsigned int *nbytesp) { struct ath10k *ar = ce_state->ar; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); int ret; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); ret = ath10k_ce_completed_recv_next_nolock(ce_state, per_transfer_contextp, - bufferp, nbytesp, - transfer_idp, flagsp); - spin_unlock_bh(&ar_pci->ce_lock); + nbytesp); + spin_unlock_bh(&ar_opaque->ce_lock); return ret; } @@ -530,7 +754,7 @@ int ath10k_ce_revoke_recv_next(struct ath10k_ce_pipe *ce_state, unsigned int write_index; int ret; struct ath10k *ar; - struct ath10k_pci *ar_pci; + struct bus_opaque *ar_opaque; dest_ring = ce_state->dest_ring; @@ -538,9 +762,9 @@ int ath10k_ce_revoke_recv_next(struct ath10k_ce_pipe *ce_state, return -EIO; ar = ce_state->ar; - ar_pci = ath10k_pci_priv(ar); + ar_opaque = ath10k_bus_priv(ar); - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); nentries_mask = dest_ring->nentries_mask; sw_index = dest_ring->sw_index; @@ -568,7 +792,7 @@ int ath10k_ce_revoke_recv_next(struct ath10k_ce_pipe *ce_state, ret = -EIO; } - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_opaque->ce_lock); return ret; } @@ -636,7 +860,7 @@ int ath10k_ce_cancel_send_next(struct ath10k_ce_pipe *ce_state, unsigned int write_index; int ret; struct ath10k *ar; - struct ath10k_pci *ar_pci; + struct bus_opaque *ar_opaque; src_ring = ce_state->src_ring; @@ -644,9 +868,9 @@ int ath10k_ce_cancel_send_next(struct ath10k_ce_pipe *ce_state, return -EIO; ar = ce_state->ar; - ar_pci = ath10k_pci_priv(ar); + ar_opaque = ath10k_bus_priv(ar); - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); nentries_mask = src_ring->nentries_mask; sw_index = src_ring->sw_index; @@ -677,7 +901,7 @@ int ath10k_ce_cancel_send_next(struct ath10k_ce_pipe *ce_state, ret = -EIO; } - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_opaque->ce_lock); return ret; } @@ -686,13 +910,13 @@ int ath10k_ce_completed_send_next(struct ath10k_ce_pipe *ce_state, void **per_transfer_contextp) { struct ath10k *ar = ce_state->ar; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); int ret; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); ret = ath10k_ce_completed_send_next_nolock(ce_state, per_transfer_contextp); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_opaque->ce_lock); return ret; } @@ -705,17 +929,18 @@ int ath10k_ce_completed_send_next(struct ath10k_ce_pipe *ce_state, */ void ath10k_ce_per_engine_service(struct ath10k *ar, unsigned int ce_id) { - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - struct ath10k_ce_pipe *ce_state = &ar_pci->ce_states[ce_id]; + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_ce_pipe *ce_state = &ar_opaque->ce_states[ce_id]; + struct ath10k_hw_ce_host_wm_regs *wm_regs = ar->hw_ce_regs->wm_regs; u32 ctrl_addr = ce_state->ctrl_addr; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); /* Clear the copy-complete interrupts that will be handled here. */ ath10k_ce_engine_int_status_clear(ar, ctrl_addr, - HOST_IS_COPY_COMPLETE_MASK); + wm_regs->cc_mask); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_opaque->ce_lock); if (ce_state->recv_cb) ce_state->recv_cb(ce_state); @@ -723,15 +948,15 @@ void ath10k_ce_per_engine_service(struct ath10k *ar, unsigned int ce_id) if (ce_state->send_cb) ce_state->send_cb(ce_state); - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_opaque->ce_lock); /* * Misc CE interrupts are not being handled, but still need * to be cleared. */ - ath10k_ce_engine_int_status_clear(ar, ctrl_addr, CE_WATERMARK_MASK); + ath10k_ce_engine_int_status_clear(ar, ctrl_addr, wm_regs->wm_mask); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_opaque->ce_lock); } /* @@ -744,8 +969,16 @@ void ath10k_ce_per_engine_service_any(struct ath10k *ar) { int ce_id; u32 intr_summary; + struct ath10k_ce_pipe *ce_state; + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); - intr_summary = CE_INTERRUPT_SUMMARY(ar); + if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + return; + + if (ar->target_version == ATH10K_HW_WCN3990) + intr_summary = 0xFFF; + else + intr_summary = CE_INTERRUPT_SUMMARY(ar, ar_opaque); for (ce_id = 0; intr_summary && (ce_id < CE_COUNT); ce_id++) { if (intr_summary & (1 << ce_id)) @@ -754,8 +987,11 @@ void ath10k_ce_per_engine_service_any(struct ath10k *ar) /* no intr pending on this CE */ continue; - ath10k_ce_per_engine_service(ar, ce_id); + ce_state = &ar_opaque->ce_states[ce_id]; + if (ce_state->send_cb || ce_state->recv_cb) + ath10k_ce_per_engine_service(ar, ce_id); } + } /* @@ -797,22 +1033,53 @@ int ath10k_ce_disable_interrupts(struct ath10k *ar) void ath10k_ce_enable_interrupts(struct ath10k *ar) { - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); int ce_id; + struct ath10k_ce_pipe *ce_state; + u8 ce_count; + if (QCA_REV_WCN3990(ar)) + ce_count = CE_COUNT; + else /* Skip the last copy engine, CE7 the diagnostic window, as that * uses polling and isn't initialized for interrupts. */ - for (ce_id = 0; ce_id < CE_COUNT - 1; ce_id++) - ath10k_ce_per_engine_handler_adjust(&ar_pci->ce_states[ce_id]); + ce_count = CE_COUNT - 1; + + for (ce_id = 0; ce_id < ce_count; ce_id++) { + ce_state = &ar_opaque->ce_states[ce_id]; + ath10k_ce_per_engine_handler_adjust(ce_state); + } +} + +void ath10k_ce_enable_per_ce_interrupts(struct ath10k *ar, unsigned int ce_id) +{ + u32 offset; + u32 ctrl_addr = ath10k_ce_base_address(ar, ce_id); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + offset = ar->hw_ce_regs->host_ie_addr + ctrl_addr; + ar_opaque->bus_ops->write32(ar, offset, 1); + ar_opaque->bus_ops->read32(ar, offset); +} + +void ath10k_ce_disable_per_ce_interrupts(struct ath10k *ar, unsigned int ce_id) +{ + u32 offset; + u32 ctrl_addr = ath10k_ce_base_address(ar, ce_id); + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + offset = ar->hw_ce_regs->host_ie_addr + ctrl_addr; + ar_opaque->bus_ops->write32(ar, offset, 0); + ar_opaque->bus_ops->read32(ar, offset); } static int ath10k_ce_init_src_ring(struct ath10k *ar, unsigned int ce_id, const struct ce_attr *attr) { - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - struct ath10k_ce_pipe *ce_state = &ar_pci->ce_states[ce_id]; + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_ce_pipe *ce_state = &ar_opaque->ce_states[ce_id]; struct ath10k_ce_ring *src_ring = ce_state->src_ring; u32 nentries, ctrl_addr = ath10k_ce_base_address(ar, ce_id); @@ -838,7 +1105,7 @@ static int ath10k_ce_init_src_ring(struct ath10k *ar, ath10k_ce_src_ring_highmark_set(ar, ctrl_addr, nentries); ath10k_dbg(ar, ATH10K_DBG_BOOT, - "boot init ce src ring id %d entries %d base_addr %p\n", + "boot init ce src ring id %d entries %d base_addr %pK\n", ce_id, nentries, src_ring->base_addr_owner_space); return 0; @@ -848,8 +1115,8 @@ static int ath10k_ce_init_dest_ring(struct ath10k *ar, unsigned int ce_id, const struct ce_attr *attr) { - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - struct ath10k_ce_pipe *ce_state = &ar_pci->ce_states[ce_id]; + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_ce_pipe *ce_state = &ar_opaque->ce_states[ce_id]; struct ath10k_ce_ring *dest_ring = ce_state->dest_ring; u32 nentries, ctrl_addr = ath10k_ce_base_address(ar, ce_id); @@ -872,7 +1139,7 @@ static int ath10k_ce_init_dest_ring(struct ath10k *ar, ath10k_ce_dest_ring_highmark_set(ar, ctrl_addr, nentries); ath10k_dbg(ar, ATH10K_DBG_BOOT, - "boot ce dest ring id %d entries %d base_addr %p\n", + "boot ce dest ring id %d entries %d base_addr %pK\n", ce_id, nentries, dest_ring->base_addr_owner_space); return 0; @@ -921,6 +1188,24 @@ ath10k_ce_alloc_src_ring(struct ath10k *ar, unsigned int ce_id, src_ring->base_addr_ce_space_unaligned, CE_DESC_RING_ALIGN); + src_ring->shadow_base_unaligned = kzalloc( + nentries * sizeof(struct ce_desc), + GFP_KERNEL); + + if (!src_ring->shadow_base_unaligned) { + dma_free_coherent(ar->dev, + (nentries * sizeof(struct ce_desc) + + CE_DESC_RING_ALIGN), + src_ring->base_addr_owner_space_unaligned, + base_addr); + kfree(src_ring); + return ERR_PTR(-ENOMEM); + } + + src_ring->shadow_base = (struct ce_desc *)PTR_ALIGN( + src_ring->shadow_base_unaligned, + CE_DESC_RING_ALIGN); + return src_ring; } @@ -977,6 +1262,52 @@ ath10k_ce_alloc_dest_ring(struct ath10k *ar, unsigned int ce_id, return dest_ring; } +void ce_config_rri_on_ddr(struct ath10k *ar) +{ + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + u32 hi_paddr, low_paddr; + u32 ce_base_addr; + u32 ctrl1_regs; + int i; + + ar_opaque->vaddr_rri_on_ddr = + (u32 *)dma_alloc_coherent(ar->dev, + (CE_COUNT * sizeof(u32)), + &ar_opaque->paddr_rri_on_ddr, GFP_KERNEL); + + if (!ar_opaque->vaddr_rri_on_ddr) + return; + + low_paddr = lower_32_bits(ar_opaque->paddr_rri_on_ddr); + hi_paddr = upper_32_bits(ar_opaque->paddr_rri_on_ddr) & + CE_DESC_FLAGS_GET_MASK; + + ar_opaque->bus_ops->write32(ar, ar->hw_ce_regs->ce_rri_low, low_paddr); + ar_opaque->bus_ops->write32(ar, ar->hw_ce_regs->ce_rri_high, hi_paddr); + + for (i = 0; i < CE_COUNT; i++) { + ctrl1_regs = ar->hw_ce_regs->ctrl1_regs->addr; + ce_base_addr = ath10k_ce_base_address(ar, i); + ar_opaque->bus_ops->write32(ar, ce_base_addr + ctrl1_regs, + ar_opaque->bus_ops->read32(ar, ce_base_addr + ctrl1_regs) | + ar->hw_ce_regs->upd->mask); + } + + memset(ar_opaque->vaddr_rri_on_ddr, 0, CE_COUNT * sizeof(u32)); +} + +void ce_remove_rri_on_ddr(struct ath10k *ar) +{ + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + + if (!ar_opaque->vaddr_rri_on_ddr) + return; + + dma_free_coherent(ar->dev, (CE_COUNT * sizeof(u32)), + ar_opaque->vaddr_rri_on_ddr, + ar_opaque->paddr_rri_on_ddr); +} + /* * Initialize a Copy Engine based on caller-supplied attributes. * This may be called once to initialize both source and destination @@ -1038,8 +1369,8 @@ void ath10k_ce_deinit_pipe(struct ath10k *ar, unsigned int ce_id) int ath10k_ce_alloc_pipe(struct ath10k *ar, int ce_id, const struct ce_attr *attr) { - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - struct ath10k_ce_pipe *ce_state = &ar_pci->ce_states[ce_id]; + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_ce_pipe *ce_state = &ar_opaque->ce_states[ce_id]; int ret; /* @@ -1048,11 +1379,11 @@ int ath10k_ce_alloc_pipe(struct ath10k *ar, int ce_id, * * For the lack of a better place do the check here. */ - BUILD_BUG_ON(2*TARGET_NUM_MSDU_DESC > + BUILD_BUG_ON(2 * TARGET_NUM_MSDU_DESC > (CE_HTT_H2T_MSG_SRC_NENTRIES - 1)); - BUILD_BUG_ON(2*TARGET_10X_NUM_MSDU_DESC > + BUILD_BUG_ON(2 * TARGET_10X_NUM_MSDU_DESC > (CE_HTT_H2T_MSG_SRC_NENTRIES - 1)); - BUILD_BUG_ON(2*TARGET_TLV_NUM_MSDU_DESC > + BUILD_BUG_ON(2 * TARGET_TLV_NUM_MSDU_DESC > (CE_HTT_H2T_MSG_SRC_NENTRIES - 1)); ce_state->ar = ar; @@ -1095,10 +1426,11 @@ int ath10k_ce_alloc_pipe(struct ath10k *ar, int ce_id, void ath10k_ce_free_pipe(struct ath10k *ar, int ce_id) { - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - struct ath10k_ce_pipe *ce_state = &ar_pci->ce_states[ce_id]; + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + struct ath10k_ce_pipe *ce_state = &ar_opaque->ce_states[ce_id]; if (ce_state->src_ring) { + kfree(ce_state->src_ring->shadow_base_unaligned); dma_free_coherent(ar->dev, (ce_state->src_ring->nentries * sizeof(struct ce_desc) + diff --git a/drivers/net/wireless/ath/ath10k/ce.h b/drivers/net/wireless/ath/ath10k/ce.h index 47b734ce7ecf..fe5f5680ca3d 100644 --- a/drivers/net/wireless/ath/ath10k/ce.h +++ b/drivers/net/wireless/ath/ath10k/ce.h @@ -22,7 +22,7 @@ /* Maximum number of Copy Engine's supported */ #define CE_COUNT_MAX 12 -#define CE_HTT_H2T_MSG_SRC_NENTRIES 4096 +#define CE_HTT_H2T_MSG_SRC_NENTRIES 8192 /* Descriptor rings must be aligned to this boundary */ #define CE_DESC_RING_ALIGN 8 @@ -38,6 +38,12 @@ struct ath10k_ce_pipe; #define CE_DESC_FLAGS_GATHER (1 << 0) #define CE_DESC_FLAGS_BYTE_SWAP (1 << 1) +#define CE_WCN3990_DESC_FLAGS_GATHER BIT(31) + +#define CE_DESC_FLAGS_GET_MASK 0x1F +#define CE_DESC_37BIT_ADDR_MASK 0x1FFFFFFFFF +#define CE_DDR_RRI_MASK 0xFFFF +#define CE_DDR_RRI_SHIFT 16 /* Following desc flags are used in QCA99X0 */ #define CE_DESC_FLAGS_HOST_INT_DIS (1 << 2) @@ -46,11 +52,20 @@ struct ath10k_ce_pipe; #define CE_DESC_FLAGS_META_DATA_MASK ar->hw_values->ce_desc_meta_data_mask #define CE_DESC_FLAGS_META_DATA_LSB ar->hw_values->ce_desc_meta_data_lsb +#ifndef CONFIG_ATH10K_SNOC struct ce_desc { __le32 addr; __le16 nbytes; __le16 flags; /* %CE_DESC_FLAGS_ */ }; +#else +struct ce_desc { + __le64 addr; + u16 nbytes; /* length in register map */ + u16 flags; /* fw_metadata_high */ + u32 toeplitz_hash_result; +}; +#endif struct ath10k_ce_ring { /* Number of entries in this ring; must be power of 2 */ @@ -101,6 +116,9 @@ struct ath10k_ce_ring { /* CE address space */ u32 base_addr_ce_space; + char *shadow_base_unaligned; + struct ce_desc *shadow_base; + /* keep last */ void *per_transfer_context[0]; }; @@ -124,6 +142,81 @@ struct ath10k_ce_pipe { /* Copy Engine settable attributes */ struct ce_attr; +#define SHADOW_VALUE0 (ar->shadow_reg_value->shadow_reg_value_0) +#define SHADOW_VALUE1 (ar->shadow_reg_value->shadow_reg_value_1) +#define SHADOW_VALUE2 (ar->shadow_reg_value->shadow_reg_value_2) +#define SHADOW_VALUE3 (ar->shadow_reg_value->shadow_reg_value_3) +#define SHADOW_VALUE4 (ar->shadow_reg_value->shadow_reg_value_4) +#define SHADOW_VALUE5 (ar->shadow_reg_value->shadow_reg_value_5) +#define SHADOW_VALUE6 (ar->shadow_reg_value->shadow_reg_value_6) +#define SHADOW_VALUE7 (ar->shadow_reg_value->shadow_reg_value_7) +#define SHADOW_VALUE8 (ar->shadow_reg_value->shadow_reg_value_8) +#define SHADOW_VALUE9 (ar->shadow_reg_value->shadow_reg_value_9) +#define SHADOW_VALUE10 (ar->shadow_reg_value->shadow_reg_value_10) +#define SHADOW_VALUE11 (ar->shadow_reg_value->shadow_reg_value_11) +#define SHADOW_VALUE12 (ar->shadow_reg_value->shadow_reg_value_12) +#define SHADOW_VALUE13 (ar->shadow_reg_value->shadow_reg_value_13) +#define SHADOW_VALUE14 (ar->shadow_reg_value->shadow_reg_value_14) +#define SHADOW_VALUE15 (ar->shadow_reg_value->shadow_reg_value_15) +#define SHADOW_VALUE16 (ar->shadow_reg_value->shadow_reg_value_16) +#define SHADOW_VALUE17 (ar->shadow_reg_value->shadow_reg_value_17) +#define SHADOW_VALUE18 (ar->shadow_reg_value->shadow_reg_value_18) +#define SHADOW_VALUE19 (ar->shadow_reg_value->shadow_reg_value_19) +#define SHADOW_VALUE20 (ar->shadow_reg_value->shadow_reg_value_20) +#define SHADOW_VALUE21 (ar->shadow_reg_value->shadow_reg_value_21) +#define SHADOW_VALUE22 (ar->shadow_reg_value->shadow_reg_value_22) +#define SHADOW_VALUE23 (ar->shadow_reg_value->shadow_reg_value_23) +#define SHADOW_ADDRESS0 (ar->shadow_reg_address->shadow_reg_address_0) +#define SHADOW_ADDRESS1 (ar->shadow_reg_address->shadow_reg_address_1) +#define SHADOW_ADDRESS2 (ar->shadow_reg_address->shadow_reg_address_2) +#define SHADOW_ADDRESS3 (ar->shadow_reg_address->shadow_reg_address_3) +#define SHADOW_ADDRESS4 (ar->shadow_reg_address->shadow_reg_address_4) +#define SHADOW_ADDRESS5 (ar->shadow_reg_address->shadow_reg_address_5) +#define SHADOW_ADDRESS6 (ar->shadow_reg_address->shadow_reg_address_6) +#define SHADOW_ADDRESS7 (ar->shadow_reg_address->shadow_reg_address_7) +#define SHADOW_ADDRESS8 (ar->shadow_reg_address->shadow_reg_address_8) +#define SHADOW_ADDRESS9 (ar->shadow_reg_address->shadow_reg_address_9) +#define SHADOW_ADDRESS10 (ar->shadow_reg_address->shadow_reg_address_10) +#define SHADOW_ADDRESS11 (ar->shadow_reg_address->shadow_reg_address_11) +#define SHADOW_ADDRESS12 (ar->shadow_reg_address->shadow_reg_address_12) +#define SHADOW_ADDRESS13 (ar->shadow_reg_address->shadow_reg_address_13) +#define SHADOW_ADDRESS14 (ar->shadow_reg_address->shadow_reg_address_14) +#define SHADOW_ADDRESS15 (ar->shadow_reg_address->shadow_reg_address_15) +#define SHADOW_ADDRESS16 (ar->shadow_reg_address->shadow_reg_address_16) +#define SHADOW_ADDRESS17 (ar->shadow_reg_address->shadow_reg_address_17) +#define SHADOW_ADDRESS18 (ar->shadow_reg_address->shadow_reg_address_18) +#define SHADOW_ADDRESS19 (ar->shadow_reg_address->shadow_reg_address_19) +#define SHADOW_ADDRESS20 (ar->shadow_reg_address->shadow_reg_address_20) +#define SHADOW_ADDRESS21 (ar->shadow_reg_address->shadow_reg_address_21) +#define SHADOW_ADDRESS22 (ar->shadow_reg_address->shadow_reg_address_22) +#define SHADOW_ADDRESS23 (ar->shadow_reg_address->shadow_reg_address_23) + +#define SHADOW_ADDRESS(i) (SHADOW_ADDRESS0 + \ + i * (SHADOW_ADDRESS1 - SHADOW_ADDRESS0)) + +u32 shadow_sr_wr_ind_addr(struct ath10k *ar, u32 ctrl_addr); +u32 shadow_dst_wr_ind_addr(struct ath10k *ar, u32 ctrl_addr); + +struct ath10k_bus_ops { + u32 (*read32)(struct ath10k *ar, u32 offset); + void (*write32)(struct ath10k *ar, u32 offset, u32 value); + int (*get_num_banks)(struct ath10k *ar); +}; + +static inline struct bus_opaque *ath10k_bus_priv(struct ath10k *ar) +{ + return (struct bus_opaque *)ar->drv_priv; +} + +struct bus_opaque { + /* protects CE info */ + spinlock_t ce_lock; + const struct ath10k_bus_ops *bus_ops; + struct ath10k_ce_pipe ce_states[CE_COUNT_MAX]; + u32 *vaddr_rri_on_ddr; + dma_addr_t paddr_rri_on_ddr; +}; + /*==================Send====================*/ /* ath10k_ce_send flags */ @@ -144,7 +237,7 @@ struct ce_attr; */ int ath10k_ce_send(struct ath10k_ce_pipe *ce_state, void *per_transfer_send_context, - u32 buffer, + dma_addr_t buffer, unsigned int nbytes, /* 14 bits */ unsigned int transfer_id, @@ -152,7 +245,7 @@ int ath10k_ce_send(struct ath10k_ce_pipe *ce_state, int ath10k_ce_send_nolock(struct ath10k_ce_pipe *ce_state, void *per_transfer_context, - u32 buffer, + dma_addr_t buffer, unsigned int nbytes, unsigned int transfer_id, unsigned int flags); @@ -164,8 +257,11 @@ int ath10k_ce_num_free_src_entries(struct ath10k_ce_pipe *pipe); /*==================Recv=======================*/ int __ath10k_ce_rx_num_free_bufs(struct ath10k_ce_pipe *pipe); -int __ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, u32 paddr); -int ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, u32 paddr); +int __ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, + dma_addr_t paddr); +int ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, + dma_addr_t paddr); +void ath10k_ce_rx_update_write_idx(struct ath10k_ce_pipe *pipe, u32 nentries); /* recv flags */ /* Data is byte-swapped */ @@ -177,10 +273,7 @@ int ath10k_ce_rx_post_buf(struct ath10k_ce_pipe *pipe, void *ctx, u32 paddr); */ int ath10k_ce_completed_recv_next(struct ath10k_ce_pipe *ce_state, void **per_transfer_contextp, - u32 *bufferp, - unsigned int *nbytesp, - unsigned int *transfer_idp, - unsigned int *flagsp); + unsigned int *nbytesp); /* * Supply data for the next completed unprocessed send descriptor. * Pops 1 completed send buffer from Source ring. @@ -199,6 +292,8 @@ void ath10k_ce_deinit_pipe(struct ath10k *ar, unsigned int ce_id); int ath10k_ce_alloc_pipe(struct ath10k *ar, int ce_id, const struct ce_attr *attr); void ath10k_ce_free_pipe(struct ath10k *ar, int ce_id); +void ce_config_rri_on_ddr(struct ath10k *ar); +void ce_remove_rri_on_ddr(struct ath10k *ar); /*==================CE Engine Shutdown=======================*/ /* @@ -212,10 +307,7 @@ int ath10k_ce_revoke_recv_next(struct ath10k_ce_pipe *ce_state, int ath10k_ce_completed_recv_next_nolock(struct ath10k_ce_pipe *ce_state, void **per_transfer_contextp, - u32 *bufferp, - unsigned int *nbytesp, - unsigned int *transfer_idp, - unsigned int *flagsp); + unsigned int *nbytesp); /* * Support clean shutdown by allowing the caller to cancel @@ -233,6 +325,8 @@ void ath10k_ce_per_engine_service_any(struct ath10k *ar); void ath10k_ce_per_engine_service(struct ath10k *ar, unsigned int ce_id); int ath10k_ce_disable_interrupts(struct ath10k *ar); void ath10k_ce_enable_interrupts(struct ath10k *ar); +void ath10k_ce_disable_per_ce_interrupts(struct ath10k *ar, unsigned int ce_id); +void ath10k_ce_enable_per_ce_interrupts(struct ath10k *ar, unsigned int ce_id); /* ce_attr.flags values */ /* Use NonSnooping PCIe accesses? */ @@ -268,143 +362,14 @@ struct ce_attr { void (*recv_cb)(struct ath10k_ce_pipe *); }; -#define SR_BA_ADDRESS 0x0000 -#define SR_SIZE_ADDRESS 0x0004 -#define DR_BA_ADDRESS 0x0008 -#define DR_SIZE_ADDRESS 0x000c -#define CE_CMD_ADDRESS 0x0018 - -#define CE_CTRL1_DST_RING_BYTE_SWAP_EN_MSB 17 -#define CE_CTRL1_DST_RING_BYTE_SWAP_EN_LSB 17 -#define CE_CTRL1_DST_RING_BYTE_SWAP_EN_MASK 0x00020000 -#define CE_CTRL1_DST_RING_BYTE_SWAP_EN_SET(x) \ - (((0 | (x)) << CE_CTRL1_DST_RING_BYTE_SWAP_EN_LSB) & \ - CE_CTRL1_DST_RING_BYTE_SWAP_EN_MASK) - -#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MSB 16 -#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_LSB 16 -#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MASK 0x00010000 -#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_GET(x) \ - (((x) & CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MASK) >> \ - CE_CTRL1_SRC_RING_BYTE_SWAP_EN_LSB) -#define CE_CTRL1_SRC_RING_BYTE_SWAP_EN_SET(x) \ - (((0 | (x)) << CE_CTRL1_SRC_RING_BYTE_SWAP_EN_LSB) & \ - CE_CTRL1_SRC_RING_BYTE_SWAP_EN_MASK) - -#define CE_CTRL1_DMAX_LENGTH_MSB 15 -#define CE_CTRL1_DMAX_LENGTH_LSB 0 -#define CE_CTRL1_DMAX_LENGTH_MASK 0x0000ffff -#define CE_CTRL1_DMAX_LENGTH_GET(x) \ - (((x) & CE_CTRL1_DMAX_LENGTH_MASK) >> CE_CTRL1_DMAX_LENGTH_LSB) -#define CE_CTRL1_DMAX_LENGTH_SET(x) \ - (((0 | (x)) << CE_CTRL1_DMAX_LENGTH_LSB) & CE_CTRL1_DMAX_LENGTH_MASK) - -#define CE_CTRL1_ADDRESS 0x0010 -#define CE_CTRL1_HW_MASK 0x0007ffff -#define CE_CTRL1_SW_MASK 0x0007ffff -#define CE_CTRL1_HW_WRITE_MASK 0x00000000 -#define CE_CTRL1_SW_WRITE_MASK 0x0007ffff -#define CE_CTRL1_RSTMASK 0xffffffff -#define CE_CTRL1_RESET 0x00000080 - -#define CE_CMD_HALT_STATUS_MSB 3 -#define CE_CMD_HALT_STATUS_LSB 3 -#define CE_CMD_HALT_STATUS_MASK 0x00000008 -#define CE_CMD_HALT_STATUS_GET(x) \ - (((x) & CE_CMD_HALT_STATUS_MASK) >> CE_CMD_HALT_STATUS_LSB) -#define CE_CMD_HALT_STATUS_SET(x) \ - (((0 | (x)) << CE_CMD_HALT_STATUS_LSB) & CE_CMD_HALT_STATUS_MASK) -#define CE_CMD_HALT_STATUS_RESET 0 -#define CE_CMD_HALT_MSB 0 -#define CE_CMD_HALT_MASK 0x00000001 - -#define HOST_IE_COPY_COMPLETE_MSB 0 -#define HOST_IE_COPY_COMPLETE_LSB 0 -#define HOST_IE_COPY_COMPLETE_MASK 0x00000001 -#define HOST_IE_COPY_COMPLETE_GET(x) \ - (((x) & HOST_IE_COPY_COMPLETE_MASK) >> HOST_IE_COPY_COMPLETE_LSB) -#define HOST_IE_COPY_COMPLETE_SET(x) \ - (((0 | (x)) << HOST_IE_COPY_COMPLETE_LSB) & HOST_IE_COPY_COMPLETE_MASK) -#define HOST_IE_COPY_COMPLETE_RESET 0 -#define HOST_IE_ADDRESS 0x002c - -#define HOST_IS_DST_RING_LOW_WATERMARK_MASK 0x00000010 -#define HOST_IS_DST_RING_HIGH_WATERMARK_MASK 0x00000008 -#define HOST_IS_SRC_RING_LOW_WATERMARK_MASK 0x00000004 -#define HOST_IS_SRC_RING_HIGH_WATERMARK_MASK 0x00000002 -#define HOST_IS_COPY_COMPLETE_MASK 0x00000001 -#define HOST_IS_ADDRESS 0x0030 - -#define MISC_IE_ADDRESS 0x0034 - -#define MISC_IS_AXI_ERR_MASK 0x00000400 - -#define MISC_IS_DST_ADDR_ERR_MASK 0x00000200 -#define MISC_IS_SRC_LEN_ERR_MASK 0x00000100 -#define MISC_IS_DST_MAX_LEN_VIO_MASK 0x00000080 -#define MISC_IS_DST_RING_OVERFLOW_MASK 0x00000040 -#define MISC_IS_SRC_RING_OVERFLOW_MASK 0x00000020 - -#define MISC_IS_ADDRESS 0x0038 - -#define SR_WR_INDEX_ADDRESS 0x003c - -#define DST_WR_INDEX_ADDRESS 0x0040 - -#define CURRENT_SRRI_ADDRESS 0x0044 - -#define CURRENT_DRRI_ADDRESS 0x0048 - -#define SRC_WATERMARK_LOW_MSB 31 -#define SRC_WATERMARK_LOW_LSB 16 -#define SRC_WATERMARK_LOW_MASK 0xffff0000 -#define SRC_WATERMARK_LOW_GET(x) \ - (((x) & SRC_WATERMARK_LOW_MASK) >> SRC_WATERMARK_LOW_LSB) -#define SRC_WATERMARK_LOW_SET(x) \ - (((0 | (x)) << SRC_WATERMARK_LOW_LSB) & SRC_WATERMARK_LOW_MASK) -#define SRC_WATERMARK_LOW_RESET 0 -#define SRC_WATERMARK_HIGH_MSB 15 -#define SRC_WATERMARK_HIGH_LSB 0 -#define SRC_WATERMARK_HIGH_MASK 0x0000ffff -#define SRC_WATERMARK_HIGH_GET(x) \ - (((x) & SRC_WATERMARK_HIGH_MASK) >> SRC_WATERMARK_HIGH_LSB) -#define SRC_WATERMARK_HIGH_SET(x) \ - (((0 | (x)) << SRC_WATERMARK_HIGH_LSB) & SRC_WATERMARK_HIGH_MASK) -#define SRC_WATERMARK_HIGH_RESET 0 -#define SRC_WATERMARK_ADDRESS 0x004c - -#define DST_WATERMARK_LOW_LSB 16 -#define DST_WATERMARK_LOW_MASK 0xffff0000 -#define DST_WATERMARK_LOW_SET(x) \ - (((0 | (x)) << DST_WATERMARK_LOW_LSB) & DST_WATERMARK_LOW_MASK) -#define DST_WATERMARK_LOW_RESET 0 -#define DST_WATERMARK_HIGH_MSB 15 -#define DST_WATERMARK_HIGH_LSB 0 -#define DST_WATERMARK_HIGH_MASK 0x0000ffff -#define DST_WATERMARK_HIGH_GET(x) \ - (((x) & DST_WATERMARK_HIGH_MASK) >> DST_WATERMARK_HIGH_LSB) -#define DST_WATERMARK_HIGH_SET(x) \ - (((0 | (x)) << DST_WATERMARK_HIGH_LSB) & DST_WATERMARK_HIGH_MASK) -#define DST_WATERMARK_HIGH_RESET 0 -#define DST_WATERMARK_ADDRESS 0x0050 +#define COPY_ENGINE_ID(COPY_ENGINE_BASE_ADDRESS) ((COPY_ENGINE_BASE_ADDRESS \ + - CE0_BASE_ADDRESS) / (CE1_BASE_ADDRESS - CE0_BASE_ADDRESS)) static inline u32 ath10k_ce_base_address(struct ath10k *ar, unsigned int ce_id) { return CE0_BASE_ADDRESS + (CE1_BASE_ADDRESS - CE0_BASE_ADDRESS) * ce_id; } -#define CE_WATERMARK_MASK (HOST_IS_SRC_RING_LOW_WATERMARK_MASK | \ - HOST_IS_SRC_RING_HIGH_WATERMARK_MASK | \ - HOST_IS_DST_RING_LOW_WATERMARK_MASK | \ - HOST_IS_DST_RING_HIGH_WATERMARK_MASK) - -#define CE_ERROR_MASK (MISC_IS_AXI_ERR_MASK | \ - MISC_IS_DST_ADDR_ERR_MASK | \ - MISC_IS_SRC_LEN_ERR_MASK | \ - MISC_IS_DST_MAX_LEN_VIO_MASK | \ - MISC_IS_DST_RING_OVERFLOW_MASK | \ - MISC_IS_SRC_RING_OVERFLOW_MASK) - #define CE_SRC_RING_TO_DESC(baddr, idx) \ (&(((struct ce_desc *)baddr)[idx])) @@ -413,9 +378,11 @@ static inline u32 ath10k_ce_base_address(struct ath10k *ar, unsigned int ce_id) /* Ring arithmetic (modulus number of entries in ring, which is a pwr of 2). */ #define CE_RING_DELTA(nentries_mask, fromidx, toidx) \ - (((int)(toidx)-(int)(fromidx)) & (nentries_mask)) + (((int)(toidx) - (int)(fromidx)) & (nentries_mask)) #define CE_RING_IDX_INCR(nentries_mask, idx) (((idx) + 1) & (nentries_mask)) +#define CE_RING_IDX_ADD(nentries_mask, idx, num) \ + (((idx) + (num)) & (nentries_mask)) #define CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_LSB \ ar->regs->ce_wrap_intr_sum_host_msi_lsb @@ -426,9 +393,9 @@ static inline u32 ath10k_ce_base_address(struct ath10k *ar, unsigned int ce_id) CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_LSB) #define CE_WRAPPER_INTERRUPT_SUMMARY_ADDRESS 0x0000 -#define CE_INTERRUPT_SUMMARY(ar) \ +#define CE_INTERRUPT_SUMMARY(ar, ar_opaque) \ CE_WRAPPER_INTERRUPT_SUMMARY_HOST_MSI_GET( \ - ath10k_pci_read32((ar), CE_WRAPPER_BASE_ADDRESS + \ + ar_opaque->bus_ops->read32((ar), CE_WRAPPER_BASE_ADDRESS + \ CE_WRAPPER_INTERRUPT_SUMMARY_ADDRESS)) #endif /* _CE_H_ */ diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index 0c23768aa1ec..784a11a74443 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2005-2011 Atheros Communications Inc. - * Copyright (c) 2011-2013 Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, 2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,6 +18,7 @@ #include <linux/module.h> #include <linux/firmware.h> #include <linux/of.h> +#include <asm/byteorder.h> #include "core.h" #include "mac.h" @@ -55,18 +56,38 @@ static const struct ath10k_hw_params ath10k_hw_params_list[] = { .name = "qca988x hw2.0", .patch_load_addr = QCA988X_HW_2_0_PATCH_LOAD_ADDR, .uart_pin = 7, - .has_shifted_cc_wraparound = true, + .cc_wraparound_type = ATH10K_HW_CC_WRAP_SHIFTED_ALL, .otp_exe_param = 0, .channel_counters_freq_hz = 88000, .max_probe_resp_desc_thres = 0, + .cal_data_len = 2116, .fw = { .dir = QCA988X_HW_2_0_FW_DIR, - .fw = QCA988X_HW_2_0_FW_FILE, - .otp = QCA988X_HW_2_0_OTP_FILE, .board = QCA988X_HW_2_0_BOARD_DATA_FILE, .board_size = QCA988X_BOARD_DATA_SZ, .board_ext_size = QCA988X_BOARD_EXT_DATA_SZ, }, + .hw_ops = &qca988x_ops, + .decap_align_bytes = 4, + }, + { + .id = QCA9887_HW_1_0_VERSION, + .dev_id = QCA9887_1_0_DEVICE_ID, + .name = "qca9887 hw1.0", + .patch_load_addr = QCA9887_HW_1_0_PATCH_LOAD_ADDR, + .uart_pin = 7, + .cc_wraparound_type = ATH10K_HW_CC_WRAP_SHIFTED_ALL, + .otp_exe_param = 0, + .channel_counters_freq_hz = 88000, + .max_probe_resp_desc_thres = 0, + .cal_data_len = 2116, + .fw = { + .dir = QCA9887_HW_1_0_FW_DIR, + .board = QCA9887_HW_1_0_BOARD_DATA_FILE, + .board_size = QCA9887_BOARD_DATA_SZ, + .board_ext_size = QCA9887_BOARD_EXT_DATA_SZ, + }, + .hw_ops = &qca988x_ops, .decap_align_bytes = 4, }, { @@ -78,14 +99,14 @@ static const struct ath10k_hw_params ath10k_hw_params_list[] = { .otp_exe_param = 0, .channel_counters_freq_hz = 88000, .max_probe_resp_desc_thres = 0, + .cal_data_len = 8124, .fw = { .dir = QCA6174_HW_2_1_FW_DIR, - .fw = QCA6174_HW_2_1_FW_FILE, - .otp = QCA6174_HW_2_1_OTP_FILE, .board = QCA6174_HW_2_1_BOARD_DATA_FILE, .board_size = QCA6174_BOARD_DATA_SZ, .board_ext_size = QCA6174_BOARD_EXT_DATA_SZ, }, + .hw_ops = &qca988x_ops, .decap_align_bytes = 4, }, { @@ -97,14 +118,14 @@ static const struct ath10k_hw_params ath10k_hw_params_list[] = { .otp_exe_param = 0, .channel_counters_freq_hz = 88000, .max_probe_resp_desc_thres = 0, + .cal_data_len = 8124, .fw = { .dir = QCA6174_HW_2_1_FW_DIR, - .fw = QCA6174_HW_2_1_FW_FILE, - .otp = QCA6174_HW_2_1_OTP_FILE, .board = QCA6174_HW_2_1_BOARD_DATA_FILE, .board_size = QCA6174_BOARD_DATA_SZ, .board_ext_size = QCA6174_BOARD_EXT_DATA_SZ, }, + .hw_ops = &qca988x_ops, .decap_align_bytes = 4, }, { @@ -116,14 +137,14 @@ static const struct ath10k_hw_params ath10k_hw_params_list[] = { .otp_exe_param = 0, .channel_counters_freq_hz = 88000, .max_probe_resp_desc_thres = 0, + .cal_data_len = 8124, .fw = { .dir = QCA6174_HW_3_0_FW_DIR, - .fw = QCA6174_HW_3_0_FW_FILE, - .otp = QCA6174_HW_3_0_OTP_FILE, .board = QCA6174_HW_3_0_BOARD_DATA_FILE, .board_size = QCA6174_BOARD_DATA_SZ, .board_ext_size = QCA6174_BOARD_EXT_DATA_SZ, }, + .hw_ops = &qca988x_ops, .decap_align_bytes = 4, }, { @@ -135,15 +156,15 @@ static const struct ath10k_hw_params ath10k_hw_params_list[] = { .otp_exe_param = 0, .channel_counters_freq_hz = 88000, .max_probe_resp_desc_thres = 0, + .cal_data_len = 8124, .fw = { /* uses same binaries as hw3.0 */ .dir = QCA6174_HW_3_0_FW_DIR, - .fw = QCA6174_HW_3_0_FW_FILE, - .otp = QCA6174_HW_3_0_OTP_FILE, .board = QCA6174_HW_3_0_BOARD_DATA_FILE, .board_size = QCA6174_BOARD_DATA_SZ, .board_ext_size = QCA6174_BOARD_EXT_DATA_SZ, }, + .hw_ops = &qca988x_ops, .decap_align_bytes = 4, }, { @@ -154,16 +175,70 @@ static const struct ath10k_hw_params ath10k_hw_params_list[] = { .uart_pin = 7, .otp_exe_param = 0x00000700, .continuous_frag_desc = true, + .cck_rate_map_rev2 = true, .channel_counters_freq_hz = 150000, .max_probe_resp_desc_thres = 24, + .tx_chain_mask = 0xf, + .rx_chain_mask = 0xf, + .max_spatial_stream = 4, + .cal_data_len = 12064, .fw = { .dir = QCA99X0_HW_2_0_FW_DIR, - .fw = QCA99X0_HW_2_0_FW_FILE, - .otp = QCA99X0_HW_2_0_OTP_FILE, .board = QCA99X0_HW_2_0_BOARD_DATA_FILE, .board_size = QCA99X0_BOARD_DATA_SZ, .board_ext_size = QCA99X0_BOARD_EXT_DATA_SZ, }, + .sw_decrypt_mcast_mgmt = true, + .hw_ops = &qca99x0_ops, + .decap_align_bytes = 1, + }, + { + .id = QCA9984_HW_1_0_DEV_VERSION, + .dev_id = QCA9984_1_0_DEVICE_ID, + .name = "qca9984/qca9994 hw1.0", + .patch_load_addr = QCA9984_HW_1_0_PATCH_LOAD_ADDR, + .uart_pin = 7, + .otp_exe_param = 0x00000700, + .continuous_frag_desc = true, + .cck_rate_map_rev2 = true, + .channel_counters_freq_hz = 150000, + .max_probe_resp_desc_thres = 24, + .tx_chain_mask = 0xf, + .rx_chain_mask = 0xf, + .max_spatial_stream = 4, + .cal_data_len = 12064, + .fw = { + .dir = QCA9984_HW_1_0_FW_DIR, + .board = QCA9984_HW_1_0_BOARD_DATA_FILE, + .board_size = QCA99X0_BOARD_DATA_SZ, + .board_ext_size = QCA99X0_BOARD_EXT_DATA_SZ, + }, + .sw_decrypt_mcast_mgmt = true, + .hw_ops = &qca99x0_ops, + .decap_align_bytes = 1, + }, + { + .id = QCA9888_HW_2_0_DEV_VERSION, + .dev_id = QCA9888_2_0_DEVICE_ID, + .name = "qca9888 hw2.0", + .patch_load_addr = QCA9888_HW_2_0_PATCH_LOAD_ADDR, + .uart_pin = 7, + .otp_exe_param = 0x00000700, + .continuous_frag_desc = true, + .channel_counters_freq_hz = 150000, + .max_probe_resp_desc_thres = 24, + .tx_chain_mask = 3, + .rx_chain_mask = 3, + .max_spatial_stream = 2, + .cal_data_len = 12064, + .fw = { + .dir = QCA9888_HW_2_0_FW_DIR, + .board = QCA9888_HW_2_0_BOARD_DATA_FILE, + .board_size = QCA99X0_BOARD_DATA_SZ, + .board_ext_size = QCA99X0_BOARD_EXT_DATA_SZ, + }, + .sw_decrypt_mcast_mgmt = true, + .hw_ops = &qca99x0_ops, .decap_align_bytes = 1, }, { @@ -175,14 +250,14 @@ static const struct ath10k_hw_params ath10k_hw_params_list[] = { .otp_exe_param = 0, .channel_counters_freq_hz = 88000, .max_probe_resp_desc_thres = 0, + .cal_data_len = 8124, .fw = { .dir = QCA9377_HW_1_0_FW_DIR, - .fw = QCA9377_HW_1_0_FW_FILE, - .otp = QCA9377_HW_1_0_OTP_FILE, .board = QCA9377_HW_1_0_BOARD_DATA_FILE, .board_size = QCA9377_BOARD_DATA_SZ, .board_ext_size = QCA9377_BOARD_EXT_DATA_SZ, }, + .hw_ops = &qca988x_ops, .decap_align_bytes = 4, }, { @@ -194,16 +269,57 @@ static const struct ath10k_hw_params ath10k_hw_params_list[] = { .otp_exe_param = 0, .channel_counters_freq_hz = 88000, .max_probe_resp_desc_thres = 0, + .cal_data_len = 8124, .fw = { .dir = QCA9377_HW_1_0_FW_DIR, - .fw = QCA9377_HW_1_0_FW_FILE, - .otp = QCA9377_HW_1_0_OTP_FILE, .board = QCA9377_HW_1_0_BOARD_DATA_FILE, .board_size = QCA9377_BOARD_DATA_SZ, .board_ext_size = QCA9377_BOARD_EXT_DATA_SZ, }, + .hw_ops = &qca988x_ops, .decap_align_bytes = 4, }, + { + .id = QCA4019_HW_1_0_DEV_VERSION, + .dev_id = 0, + .name = "qca4019 hw1.0", + .patch_load_addr = QCA4019_HW_1_0_PATCH_LOAD_ADDR, + .uart_pin = 7, + .cc_wraparound_type = ATH10K_HW_CC_WRAP_SHIFTED_EACH, + .otp_exe_param = 0x0010000, + .continuous_frag_desc = true, + .cck_rate_map_rev2 = true, + .channel_counters_freq_hz = 125000, + .max_probe_resp_desc_thres = 24, + .tx_chain_mask = 0x3, + .rx_chain_mask = 0x3, + .max_spatial_stream = 2, + .cal_data_len = 12064, + .fw = { + .dir = QCA4019_HW_1_0_FW_DIR, + .board = QCA4019_HW_1_0_BOARD_DATA_FILE, + .board_size = QCA4019_BOARD_DATA_SZ, + .board_ext_size = QCA4019_BOARD_EXT_DATA_SZ, + }, + .sw_decrypt_mcast_mgmt = true, + .hw_ops = &qca99x0_ops, + .decap_align_bytes = 1, + }, + { + .id = ATH10K_HW_WCN3990, + .dev_id = 0, + .name = "wcn3990 hw1.0", + .continuous_frag_desc = true, + .tx_chain_mask = 0x7, + .rx_chain_mask = 0x7, + .max_spatial_stream = 4, + .fw = { + .dir = WCN3990_HW_1_0_FW_DIR, + }, + .sw_decrypt_mcast_mgmt = true, + .hw_ops = &wcn3990_ops, + .decap_align_bytes = 1, + }, }; static const char *const ath10k_core_fw_feature_str[] = { @@ -219,6 +335,10 @@ static const char *const ath10k_core_fw_feature_str[] = { [ATH10K_FW_FEATURE_SUPPORTS_SKIP_CLOCK_INIT] = "skip-clock-init", [ATH10K_FW_FEATURE_RAW_MODE_SUPPORT] = "raw-mode", [ATH10K_FW_FEATURE_SUPPORTS_ADAPTIVE_CCA] = "adaptive-cca", + [ATH10K_FW_FEATURE_MFP_SUPPORT] = "mfp", + [ATH10K_FW_FEATURE_PEER_FLOW_CONTROL] = "peer-flow-ctrl", + [ATH10K_FW_FEATURE_BTCOEX_PARAM] = "btcoex-param", + [ATH10K_FW_FEATURE_SKIP_NULL_FUNC_WAR] = "skip-null-func-war", }; static unsigned int ath10k_core_get_fw_feature_str(char *buf, @@ -245,7 +365,7 @@ void ath10k_core_get_fw_features_str(struct ath10k *ar, int i; for (i = 0; i < ATH10K_FW_FEATURE_COUNT; i++) { - if (test_bit(i, ar->fw_features)) { + if (test_bit(i, ar->normal_mode_fw.fw_file.fw_features)) { if (len > 0) len += scnprintf(buf + len, buf_len - len, ","); @@ -437,18 +557,18 @@ exit: return ret; } -static int ath10k_download_cal_file(struct ath10k *ar) +static int ath10k_download_cal_file(struct ath10k *ar, + const struct firmware *file) { int ret; - if (!ar->cal_file) + if (!file) return -ENOENT; - if (IS_ERR(ar->cal_file)) - return PTR_ERR(ar->cal_file); + if (IS_ERR(file)) + return PTR_ERR(file); - ret = ath10k_download_board_data(ar, ar->cal_file->data, - ar->cal_file->size); + ret = ath10k_download_board_data(ar, file->data, file->size); if (ret) { ath10k_err(ar, "failed to download cal_file data: %d\n", ret); return ret; @@ -459,7 +579,7 @@ static int ath10k_download_cal_file(struct ath10k *ar) return 0; } -static int ath10k_download_cal_dt(struct ath10k *ar) +static int ath10k_download_cal_dt(struct ath10k *ar, const char *dt_name) { struct device_node *node; int data_len; @@ -473,13 +593,12 @@ static int ath10k_download_cal_dt(struct ath10k *ar) */ return -ENOENT; - if (!of_get_property(node, "qcom,ath10k-calibration-data", - &data_len)) { + if (!of_get_property(node, dt_name, &data_len)) { /* The calibration data node is optional */ return -ENOENT; } - if (data_len != QCA988X_CAL_DATA_LEN) { + if (data_len != ar->hw_params.cal_data_len) { ath10k_warn(ar, "invalid calibration data length in DT: %d\n", data_len); ret = -EMSGSIZE; @@ -492,8 +611,7 @@ static int ath10k_download_cal_dt(struct ath10k *ar) goto out; } - ret = of_property_read_u8_array(node, "qcom,ath10k-calibration-data", - data, data_len); + ret = of_property_read_u8_array(node, dt_name, data, data_len); if (ret) { ath10k_warn(ar, "failed to read calibration data from DT: %d\n", ret); @@ -516,6 +634,35 @@ out: return ret; } +static int ath10k_download_cal_eeprom(struct ath10k *ar) +{ + size_t data_len; + void *data = NULL; + int ret; + + ret = ath10k_hif_fetch_cal_eeprom(ar, &data, &data_len); + if (ret) { + if (ret != -EOPNOTSUPP) + ath10k_warn(ar, "failed to read calibration data from EEPROM: %d\n", + ret); + goto out_free; + } + + ret = ath10k_download_board_data(ar, data, data_len); + if (ret) { + ath10k_warn(ar, "failed to download calibration data from EEPROM: %d\n", + ret); + goto out_free; + } + + ret = 0; + +out_free: + kfree(data); + + return ret; +} + static int ath10k_core_get_board_id_from_otp(struct ath10k *ar) { u32 result, address; @@ -524,7 +671,8 @@ static int ath10k_core_get_board_id_from_otp(struct ath10k *ar) address = ar->hw_params.patch_load_addr; - if (!ar->otp_data || !ar->otp_len) { + if (!ar->normal_mode_fw.fw_file.otp_data || + !ar->normal_mode_fw.fw_file.otp_len) { ath10k_warn(ar, "failed to retrieve board id because of invalid otp\n"); return -ENODATA; @@ -532,9 +680,11 @@ static int ath10k_core_get_board_id_from_otp(struct ath10k *ar) ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot upload otp to 0x%x len %zd for board id\n", - address, ar->otp_len); + address, ar->normal_mode_fw.fw_file.otp_len); - ret = ath10k_bmi_fast_download(ar, address, ar->otp_data, ar->otp_len); + ret = ath10k_bmi_fast_download(ar, address, + ar->normal_mode_fw.fw_file.otp_data, + ar->normal_mode_fw.fw_file.otp_len); if (ret) { ath10k_err(ar, "could not write otp for board id check: %d\n", ret); @@ -575,7 +725,9 @@ static int ath10k_download_and_run_otp(struct ath10k *ar) u32 bmi_otp_exe_param = ar->hw_params.otp_exe_param; int ret; - ret = ath10k_download_board_data(ar, ar->board_data, ar->board_len); + ret = ath10k_download_board_data(ar, + ar->running_fw->board_data, + ar->running_fw->board_len); if (ret) { ath10k_err(ar, "failed to download board data: %d\n", ret); return ret; @@ -583,16 +735,20 @@ static int ath10k_download_and_run_otp(struct ath10k *ar) /* OTP is optional */ - if (!ar->otp_data || !ar->otp_len) { - ath10k_warn(ar, "Not running otp, calibration will be incorrect (otp-data %p otp_len %zd)!\n", - ar->otp_data, ar->otp_len); + if (!ar->running_fw->fw_file.otp_data || + !ar->running_fw->fw_file.otp_len) { + ath10k_warn(ar, "Not running otp, calibration will be incorrect (otp-data %pK otp_len %zd)!\n", + ar->running_fw->fw_file.otp_data, + ar->running_fw->fw_file.otp_len); return 0; } ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot upload otp to 0x%x len %zd\n", - address, ar->otp_len); + address, ar->running_fw->fw_file.otp_len); - ret = ath10k_bmi_fast_download(ar, address, ar->otp_data, ar->otp_len); + ret = ath10k_bmi_fast_download(ar, address, + ar->running_fw->fw_file.otp_data, + ar->running_fw->fw_file.otp_len); if (ret) { ath10k_err(ar, "could not write otp (%d)\n", ret); return ret; @@ -607,7 +763,7 @@ static int ath10k_download_and_run_otp(struct ath10k *ar) ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot otp execute result %d\n", result); if (!(skip_otp || test_bit(ATH10K_FW_FEATURE_IGNORE_OTP_RESULT, - ar->fw_features)) && + ar->running_fw->fw_file.fw_features)) && result != 0) { ath10k_err(ar, "otp calibration failed: %d", result); return -EINVAL; @@ -616,46 +772,32 @@ static int ath10k_download_and_run_otp(struct ath10k *ar) return 0; } -static int ath10k_download_fw(struct ath10k *ar, enum ath10k_firmware_mode mode) +static int ath10k_download_fw(struct ath10k *ar) { u32 address, data_len; - const char *mode_name; const void *data; int ret; address = ar->hw_params.patch_load_addr; - switch (mode) { - case ATH10K_FIRMWARE_MODE_NORMAL: - data = ar->firmware_data; - data_len = ar->firmware_len; - mode_name = "normal"; - ret = ath10k_swap_code_seg_configure(ar, - ATH10K_SWAP_CODE_SEG_BIN_TYPE_FW); - if (ret) { - ath10k_err(ar, "failed to configure fw code swap: %d\n", - ret); - return ret; - } - break; - case ATH10K_FIRMWARE_MODE_UTF: - data = ar->testmode.utf_firmware_data; - data_len = ar->testmode.utf_firmware_len; - mode_name = "utf"; - break; - default: - ath10k_err(ar, "unknown firmware mode: %d\n", mode); - return -EINVAL; + data = ar->running_fw->fw_file.firmware_data; + data_len = ar->running_fw->fw_file.firmware_len; + + ret = ath10k_swap_code_seg_configure(ar, &ar->running_fw->fw_file); + if (ret) { + ath10k_err(ar, "failed to configure fw code swap: %d\n", + ret); + return ret; } ath10k_dbg(ar, ATH10K_DBG_BOOT, - "boot uploading firmware image %p len %d mode %s\n", - data, data_len, mode_name); + "boot uploading firmware image %pK len %d\n", + data, data_len); ret = ath10k_bmi_fast_download(ar, address, data, data_len); if (ret) { - ath10k_err(ar, "failed to download %s firmware: %d\n", - mode_name, ret); + ath10k_err(ar, "failed to download firmware: %d\n", + ret); return ret; } @@ -664,42 +806,50 @@ static int ath10k_download_fw(struct ath10k *ar, enum ath10k_firmware_mode mode) static void ath10k_core_free_board_files(struct ath10k *ar) { - if (!IS_ERR(ar->board)) - release_firmware(ar->board); + if (!IS_ERR(ar->normal_mode_fw.board)) + release_firmware(ar->normal_mode_fw.board); - ar->board = NULL; - ar->board_data = NULL; - ar->board_len = 0; + ar->normal_mode_fw.board = NULL; + ar->normal_mode_fw.board_data = NULL; + ar->normal_mode_fw.board_len = 0; } static void ath10k_core_free_firmware_files(struct ath10k *ar) { - if (!IS_ERR(ar->otp)) - release_firmware(ar->otp); - - if (!IS_ERR(ar->firmware)) - release_firmware(ar->firmware); + if (!IS_ERR(ar->normal_mode_fw.fw_file.firmware)) + release_firmware(ar->normal_mode_fw.fw_file.firmware); if (!IS_ERR(ar->cal_file)) release_firmware(ar->cal_file); - ath10k_swap_code_seg_release(ar); + if (!IS_ERR(ar->pre_cal_file)) + release_firmware(ar->pre_cal_file); - ar->otp = NULL; - ar->otp_data = NULL; - ar->otp_len = 0; + ath10k_swap_code_seg_release(ar, &ar->normal_mode_fw.fw_file); - ar->firmware = NULL; - ar->firmware_data = NULL; - ar->firmware_len = 0; + ar->normal_mode_fw.fw_file.otp_data = NULL; + ar->normal_mode_fw.fw_file.otp_len = 0; + + ar->normal_mode_fw.fw_file.firmware = NULL; + ar->normal_mode_fw.fw_file.firmware_data = NULL; + ar->normal_mode_fw.fw_file.firmware_len = 0; ar->cal_file = NULL; + ar->pre_cal_file = NULL; } static int ath10k_fetch_cal_file(struct ath10k *ar) { char filename[100]; + /* pre-cal-<bus>-<id>.bin */ + scnprintf(filename, sizeof(filename), "pre-cal-%s-%s.bin", + ath10k_bus_str(ar->hif.bus), dev_name(ar->dev)); + + ar->pre_cal_file = ath10k_fetch_fw_file(ar, ATH10K_FW_DIR, filename); + if (!IS_ERR(ar->pre_cal_file)) + goto success; + /* cal-<bus>-<id>.bin */ scnprintf(filename, sizeof(filename), "cal-%s-%s.bin", ath10k_bus_str(ar->hif.bus), dev_name(ar->dev)); @@ -708,7 +858,7 @@ static int ath10k_fetch_cal_file(struct ath10k *ar) if (IS_ERR(ar->cal_file)) /* calibration file is optional, don't print any warnings */ return PTR_ERR(ar->cal_file); - +success: ath10k_dbg(ar, ATH10K_DBG_BOOT, "found calibration file %s/%s\n", ATH10K_FW_DIR, filename); @@ -722,14 +872,14 @@ static int ath10k_core_fetch_board_data_api_1(struct ath10k *ar) return -EINVAL; } - ar->board = ath10k_fetch_fw_file(ar, - ar->hw_params.fw.dir, - ar->hw_params.fw.board); - if (IS_ERR(ar->board)) - return PTR_ERR(ar->board); + ar->normal_mode_fw.board = ath10k_fetch_fw_file(ar, + ar->hw_params.fw.dir, + ar->hw_params.fw.board); + if (IS_ERR(ar->normal_mode_fw.board)) + return PTR_ERR(ar->normal_mode_fw.board); - ar->board_data = ar->board->data; - ar->board_len = ar->board->size; + ar->normal_mode_fw.board_data = ar->normal_mode_fw.board->data; + ar->normal_mode_fw.board_len = ar->normal_mode_fw.board->size; return 0; } @@ -789,8 +939,8 @@ static int ath10k_core_parse_bd_ie_board(struct ath10k *ar, "boot found board data for '%s'", boardname); - ar->board_data = board_ie_data; - ar->board_len = board_ie_len; + ar->normal_mode_fw.board_data = board_ie_data; + ar->normal_mode_fw.board_len = board_ie_len; ret = 0; goto out; @@ -823,12 +973,14 @@ static int ath10k_core_fetch_board_data_api_n(struct ath10k *ar, const u8 *data; int ret, ie_id; - ar->board = ath10k_fetch_fw_file(ar, ar->hw_params.fw.dir, filename); - if (IS_ERR(ar->board)) - return PTR_ERR(ar->board); + ar->normal_mode_fw.board = ath10k_fetch_fw_file(ar, + ar->hw_params.fw.dir, + filename); + if (IS_ERR(ar->normal_mode_fw.board)) + return PTR_ERR(ar->normal_mode_fw.board); - data = ar->board->data; - len = ar->board->size; + data = ar->normal_mode_fw.board->data; + len = ar->normal_mode_fw.board->size; /* magic has extra null byte padded */ magic_len = strlen(ATH10K_BOARD_MAGIC) + 1; @@ -895,10 +1047,10 @@ static int ath10k_core_fetch_board_data_api_n(struct ath10k *ar, } out: - if (!ar->board_data || !ar->board_len) { + if (!ar->normal_mode_fw.board_data || !ar->normal_mode_fw.board_len) { ath10k_err(ar, "failed to fetch board data for %s from %s/%s\n", - ar->hw_params.fw.dir, boardname, filename); + boardname, ar->hw_params.fw.dir, filename); ret = -ENODATA; goto err; } @@ -963,51 +1115,8 @@ success: return 0; } -static int ath10k_core_fetch_firmware_api_1(struct ath10k *ar) -{ - int ret = 0; - - if (ar->hw_params.fw.fw == NULL) { - ath10k_err(ar, "firmware file not defined\n"); - return -EINVAL; - } - - ar->firmware = ath10k_fetch_fw_file(ar, - ar->hw_params.fw.dir, - ar->hw_params.fw.fw); - if (IS_ERR(ar->firmware)) { - ret = PTR_ERR(ar->firmware); - ath10k_err(ar, "could not fetch firmware (%d)\n", ret); - goto err; - } - - ar->firmware_data = ar->firmware->data; - ar->firmware_len = ar->firmware->size; - - /* OTP may be undefined. If so, don't fetch it at all */ - if (ar->hw_params.fw.otp == NULL) - return 0; - - ar->otp = ath10k_fetch_fw_file(ar, - ar->hw_params.fw.dir, - ar->hw_params.fw.otp); - if (IS_ERR(ar->otp)) { - ret = PTR_ERR(ar->otp); - ath10k_err(ar, "could not fetch otp (%d)\n", ret); - goto err; - } - - ar->otp_data = ar->otp->data; - ar->otp_len = ar->otp->size; - - return 0; - -err: - ath10k_core_free_firmware_files(ar); - return ret; -} - -static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name) +int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name, + struct ath10k_fw_file *fw_file) { size_t magic_len, len, ie_len; int ie_id, i, index, bit, ret; @@ -1016,15 +1125,17 @@ static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name) __le32 *timestamp, *version; /* first fetch the firmware file (firmware-*.bin) */ - ar->firmware = ath10k_fetch_fw_file(ar, ar->hw_params.fw.dir, name); - if (IS_ERR(ar->firmware)) { + fw_file->firmware = ath10k_fetch_fw_file(ar, ar->hw_params.fw.dir, + name); + if (IS_ERR(fw_file->firmware)) { ath10k_err(ar, "could not fetch firmware file '%s/%s': %ld\n", - ar->hw_params.fw.dir, name, PTR_ERR(ar->firmware)); - return PTR_ERR(ar->firmware); + ar->hw_params.fw.dir, name, + PTR_ERR(fw_file->firmware)); + return PTR_ERR(fw_file->firmware); } - data = ar->firmware->data; - len = ar->firmware->size; + data = fw_file->firmware->data; + len = fw_file->firmware->size; /* magic also includes the null byte, check that as well */ magic_len = strlen(ATH10K_FIRMWARE_MAGIC) + 1; @@ -1067,15 +1178,15 @@ static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name) switch (ie_id) { case ATH10K_FW_IE_FW_VERSION: - if (ie_len > sizeof(ar->hw->wiphy->fw_version) - 1) + if (ie_len > sizeof(fw_file->fw_version) - 1) break; - memcpy(ar->hw->wiphy->fw_version, data, ie_len); - ar->hw->wiphy->fw_version[ie_len] = '\0'; + memcpy(fw_file->fw_version, data, ie_len); + fw_file->fw_version[ie_len] = '\0'; ath10k_dbg(ar, ATH10K_DBG_BOOT, "found fw version %s\n", - ar->hw->wiphy->fw_version); + fw_file->fw_version); break; case ATH10K_FW_IE_TIMESTAMP: if (ie_len != sizeof(u32)) @@ -1102,21 +1213,21 @@ static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name) ath10k_dbg(ar, ATH10K_DBG_BOOT, "Enabling feature bit: %i\n", i); - __set_bit(i, ar->fw_features); + __set_bit(i, fw_file->fw_features); } } ath10k_dbg_dump(ar, ATH10K_DBG_BOOT, "features", "", - ar->fw_features, - sizeof(ar->fw_features)); + fw_file->fw_features, + sizeof(fw_file->fw_features)); break; case ATH10K_FW_IE_FW_IMAGE: ath10k_dbg(ar, ATH10K_DBG_BOOT, "found fw image ie (%zd B)\n", ie_len); - ar->firmware_data = data; - ar->firmware_len = ie_len; + fw_file->firmware_data = data; + fw_file->firmware_len = ie_len; break; case ATH10K_FW_IE_OTP_IMAGE: @@ -1124,8 +1235,8 @@ static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name) "found otp image ie (%zd B)\n", ie_len); - ar->otp_data = data; - ar->otp_len = ie_len; + fw_file->otp_data = data; + fw_file->otp_len = ie_len; break; case ATH10K_FW_IE_WMI_OP_VERSION: @@ -1134,10 +1245,10 @@ static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name) version = (__le32 *)data; - ar->wmi.op_version = le32_to_cpup(version); + fw_file->wmi_op_version = le32_to_cpup(version); ath10k_dbg(ar, ATH10K_DBG_BOOT, "found fw ie wmi op version %d\n", - ar->wmi.op_version); + fw_file->wmi_op_version); break; case ATH10K_FW_IE_HTT_OP_VERSION: if (ie_len != sizeof(u32)) @@ -1145,17 +1256,17 @@ static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name) version = (__le32 *)data; - ar->htt.op_version = le32_to_cpup(version); + fw_file->htt_op_version = le32_to_cpup(version); ath10k_dbg(ar, ATH10K_DBG_BOOT, "found fw ie htt op version %d\n", - ar->htt.op_version); + fw_file->htt_op_version); break; case ATH10K_FW_IE_FW_CODE_SWAP_IMAGE: ath10k_dbg(ar, ATH10K_DBG_BOOT, "found fw code swap image ie (%zd B)\n", ie_len); - ar->swap.firmware_codeswap_data = data; - ar->swap.firmware_codeswap_len = ie_len; + fw_file->codeswap_data = data; + fw_file->codeswap_len = ie_len; break; default: ath10k_warn(ar, "Unknown FW IE: %u\n", @@ -1170,11 +1281,14 @@ static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name) data += ie_len; } - if (!ar->firmware_data || !ar->firmware_len) { - ath10k_warn(ar, "No ATH10K_FW_IE_FW_IMAGE found from '%s/%s', skipping\n", - ar->hw_params.fw.dir, name); - ret = -ENOMEDIUM; - goto err; + if (ar->is_bmi) { + if (!fw_file->firmware_data || + !fw_file->firmware_len) { + ath10k_warn(ar, "No ATH10K_FW_IE_FW_IMAGE found from '%s/%s', skipping\n", + ar->hw_params.fw.dir, name); + ret = -ENOMEDIUM; + goto err; + } } return 0; @@ -1187,47 +1301,117 @@ err: static int ath10k_core_fetch_firmware_files(struct ath10k *ar) { int ret; + struct ath10k_fw_file *fw_file; + + if (!ar->is_bmi) { + fw_file = &ar->normal_mode_fw.fw_file; + fw_file->wmi_op_version = ATH10K_FW_WMI_OP_VERSION_TLV; + fw_file->htt_op_version = ATH10K_FW_HTT_OP_VERSION_TLV; + __set_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT, + fw_file->fw_features); + __set_bit(WMI_SERVICE_WOW, ar->wmi.svc_map); + __set_bit(ATH10K_FW_FEATURE_NO_NWIFI_DECAP_4ADDR_PADDING, + fw_file->fw_features); + return 0; + } - /* calibration file is optional, don't check for any errors */ - ath10k_fetch_cal_file(ar); + if (ar->is_bmi) { + /* calibration file is optional, don't check for any errors */ + ath10k_fetch_cal_file(ar); + } ar->fw_api = 5; ath10k_dbg(ar, ATH10K_DBG_BOOT, "trying fw api %d\n", ar->fw_api); - ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API5_FILE); + ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API5_FILE, + &ar->normal_mode_fw.fw_file); if (ret == 0) goto success; ar->fw_api = 4; ath10k_dbg(ar, ATH10K_DBG_BOOT, "trying fw api %d\n", ar->fw_api); - ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API4_FILE); + ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API4_FILE, + &ar->normal_mode_fw.fw_file); if (ret == 0) goto success; ar->fw_api = 3; ath10k_dbg(ar, ATH10K_DBG_BOOT, "trying fw api %d\n", ar->fw_api); - ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API3_FILE); + ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API3_FILE, + &ar->normal_mode_fw.fw_file); if (ret == 0) goto success; ar->fw_api = 2; ath10k_dbg(ar, ATH10K_DBG_BOOT, "trying fw api %d\n", ar->fw_api); - ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API2_FILE); - if (ret == 0) + ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API2_FILE, + &ar->normal_mode_fw.fw_file); + if (ret) + return ret; + +success: + ath10k_dbg(ar, ATH10K_DBG_BOOT, "using fw api %d\n", ar->fw_api); + + return 0; +} + +static int ath10k_core_pre_cal_download(struct ath10k *ar) +{ + int ret; + + ret = ath10k_download_cal_file(ar, ar->pre_cal_file); + if (ret == 0) { + ar->cal_mode = ATH10K_PRE_CAL_MODE_FILE; goto success; + } - ar->fw_api = 1; - ath10k_dbg(ar, ATH10K_DBG_BOOT, "trying fw api %d\n", ar->fw_api); + ath10k_dbg(ar, ATH10K_DBG_BOOT, + "boot did not find a pre calibration file, try DT next: %d\n", + ret); - ret = ath10k_core_fetch_firmware_api_1(ar); - if (ret) + ret = ath10k_download_cal_dt(ar, "qcom,ath10k-pre-calibration-data"); + if (ret) { + ath10k_dbg(ar, ATH10K_DBG_BOOT, + "unable to load pre cal data from DT: %d\n", ret); return ret; + } + ar->cal_mode = ATH10K_PRE_CAL_MODE_DT; success: - ath10k_dbg(ar, ATH10K_DBG_BOOT, "using fw api %d\n", ar->fw_api); + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot using calibration mode %s\n", + ath10k_cal_mode_str(ar->cal_mode)); + + return 0; +} + +static int ath10k_core_pre_cal_config(struct ath10k *ar) +{ + int ret; + + ret = ath10k_core_pre_cal_download(ar); + if (ret) { + ath10k_dbg(ar, ATH10K_DBG_BOOT, + "failed to load pre cal data: %d\n", ret); + return ret; + } + + ret = ath10k_core_get_board_id_from_otp(ar); + if (ret) { + ath10k_err(ar, "failed to get board id: %d\n", ret); + return ret; + } + + ret = ath10k_download_and_run_otp(ar); + if (ret) { + ath10k_err(ar, "failed to run otp: %d\n", ret); + return ret; + } + + ath10k_dbg(ar, ATH10K_DBG_BOOT, + "pre cal configuration done successfully\n"); return 0; } @@ -1236,7 +1420,15 @@ static int ath10k_download_cal_data(struct ath10k *ar) { int ret; - ret = ath10k_download_cal_file(ar); + ret = ath10k_core_pre_cal_config(ar); + if (ret == 0) + return 0; + + ath10k_dbg(ar, ATH10K_DBG_BOOT, + "pre cal download procedure failed, try cal file: %d\n", + ret); + + ret = ath10k_download_cal_file(ar, ar->cal_file); if (ret == 0) { ar->cal_mode = ATH10K_CAL_MODE_FILE; goto done; @@ -1246,14 +1438,24 @@ static int ath10k_download_cal_data(struct ath10k *ar) "boot did not find a calibration file, try DT next: %d\n", ret); - ret = ath10k_download_cal_dt(ar); + ret = ath10k_download_cal_dt(ar, "qcom,ath10k-calibration-data"); if (ret == 0) { ar->cal_mode = ATH10K_CAL_MODE_DT; goto done; } ath10k_dbg(ar, ATH10K_DBG_BOOT, - "boot did not find DT entry, try OTP next: %d\n", + "boot did not find DT entry, try target EEPROM next: %d\n", + ret); + + ret = ath10k_download_cal_eeprom(ar); + if (ret == 0) { + ar->cal_mode = ATH10K_CAL_MODE_EEPROM; + goto done; + } + + ath10k_dbg(ar, ATH10K_DBG_BOOT, + "boot did not find target EEPROM entry, try OTP next: %d\n", ret); ret = ath10k_download_and_run_otp(ar); @@ -1350,13 +1552,15 @@ static void ath10k_core_restart(struct work_struct *work) ieee80211_stop_queues(ar->hw); ath10k_drain_tx(ar); - complete_all(&ar->scan.started); - complete_all(&ar->scan.completed); - complete_all(&ar->scan.on_channel); - complete_all(&ar->offchan_tx_completed); - complete_all(&ar->install_key_done); - complete_all(&ar->vdev_setup_done); - complete_all(&ar->thermal.wmi_sync); + complete(&ar->scan.started); + complete(&ar->scan.completed); + complete(&ar->scan.on_channel); + complete(&ar->offchan_tx_completed); + complete(&ar->install_key_done); + complete(&ar->vdev_setup_done); + complete(&ar->vdev_delete_done); + complete(&ar->thermal.wmi_sync); + complete(&ar->bss_survey_done); wake_up(&ar->htt.empty_tx_wq); wake_up(&ar->wmi.tx_credits_wq); wake_up(&ar->peer_mapping_wq); @@ -1394,15 +1598,17 @@ static void ath10k_core_restart(struct work_struct *work) static int ath10k_core_init_firmware_features(struct ath10k *ar) { - if (test_bit(ATH10K_FW_FEATURE_WMI_10_2, ar->fw_features) && - !test_bit(ATH10K_FW_FEATURE_WMI_10X, ar->fw_features)) { + struct ath10k_fw_file *fw_file = &ar->normal_mode_fw.fw_file; + + if (test_bit(ATH10K_FW_FEATURE_WMI_10_2, fw_file->fw_features) && + !test_bit(ATH10K_FW_FEATURE_WMI_10X, fw_file->fw_features)) { ath10k_err(ar, "feature bits corrupted: 10.2 feature requires 10.x feature to be set as well"); return -EINVAL; } - if (ar->wmi.op_version >= ATH10K_FW_WMI_OP_VERSION_MAX) { + if (fw_file->wmi_op_version >= ATH10K_FW_WMI_OP_VERSION_MAX) { ath10k_err(ar, "unsupported WMI OP version (max %d): %d\n", - ATH10K_FW_WMI_OP_VERSION_MAX, ar->wmi.op_version); + ATH10K_FW_WMI_OP_VERSION_MAX, fw_file->wmi_op_version); return -EINVAL; } @@ -1414,7 +1620,7 @@ static int ath10k_core_init_firmware_features(struct ath10k *ar) break; case ATH10K_CRYPT_MODE_SW: if (!test_bit(ATH10K_FW_FEATURE_RAW_MODE_SUPPORT, - ar->fw_features)) { + fw_file->fw_features)) { ath10k_err(ar, "cryptmode > 0 requires raw mode support from firmware"); return -EINVAL; } @@ -1433,7 +1639,7 @@ static int ath10k_core_init_firmware_features(struct ath10k *ar) if (rawmode) { if (!test_bit(ATH10K_FW_FEATURE_RAW_MODE_SUPPORT, - ar->fw_features)) { + fw_file->fw_features)) { ath10k_err(ar, "rawmode = 1 requires support from firmware"); return -EINVAL; } @@ -1458,19 +1664,19 @@ static int ath10k_core_init_firmware_features(struct ath10k *ar) /* Backwards compatibility for firmwares without * ATH10K_FW_IE_WMI_OP_VERSION. */ - if (ar->wmi.op_version == ATH10K_FW_WMI_OP_VERSION_UNSET) { - if (test_bit(ATH10K_FW_FEATURE_WMI_10X, ar->fw_features)) { + if (fw_file->wmi_op_version == ATH10K_FW_WMI_OP_VERSION_UNSET) { + if (test_bit(ATH10K_FW_FEATURE_WMI_10X, fw_file->fw_features)) { if (test_bit(ATH10K_FW_FEATURE_WMI_10_2, - ar->fw_features)) - ar->wmi.op_version = ATH10K_FW_WMI_OP_VERSION_10_2; + fw_file->fw_features)) + fw_file->wmi_op_version = ATH10K_FW_WMI_OP_VERSION_10_2; else - ar->wmi.op_version = ATH10K_FW_WMI_OP_VERSION_10_1; + fw_file->wmi_op_version = ATH10K_FW_WMI_OP_VERSION_10_1; } else { - ar->wmi.op_version = ATH10K_FW_WMI_OP_VERSION_MAIN; + fw_file->wmi_op_version = ATH10K_FW_WMI_OP_VERSION_MAIN; } } - switch (ar->wmi.op_version) { + switch (fw_file->wmi_op_version) { case ATH10K_FW_WMI_OP_VERSION_MAIN: ar->max_num_peers = TARGET_NUM_PEERS; ar->max_num_stations = TARGET_NUM_STATIONS; @@ -1483,8 +1689,13 @@ static int ath10k_core_init_firmware_features(struct ath10k *ar) case ATH10K_FW_WMI_OP_VERSION_10_1: case ATH10K_FW_WMI_OP_VERSION_10_2: case ATH10K_FW_WMI_OP_VERSION_10_2_4: - ar->max_num_peers = TARGET_10X_NUM_PEERS; - ar->max_num_stations = TARGET_10X_NUM_STATIONS; + if (ath10k_peer_stats_enabled(ar)) { + ar->max_num_peers = TARGET_10X_TX_STATS_NUM_PEERS; + ar->max_num_stations = TARGET_10X_TX_STATS_NUM_STATIONS; + } else { + ar->max_num_peers = TARGET_10X_NUM_PEERS; + ar->max_num_stations = TARGET_10X_NUM_STATIONS; + } ar->max_num_vdevs = TARGET_10X_NUM_VDEVS; ar->htt.max_num_pending_tx = TARGET_10X_NUM_MSDU_DESC; ar->fw_stats_req_mask = WMI_STAT_PEER; @@ -1495,11 +1706,16 @@ static int ath10k_core_init_firmware_features(struct ath10k *ar) ar->max_num_stations = TARGET_TLV_NUM_STATIONS; ar->max_num_vdevs = TARGET_TLV_NUM_VDEVS; ar->max_num_tdls_vdevs = TARGET_TLV_NUM_TDLS_VDEVS; - ar->htt.max_num_pending_tx = TARGET_TLV_NUM_MSDU_DESC; + if (QCA_REV_WCN3990(ar)) + ar->htt.max_num_pending_tx = + TARGET_HL_1_0_NUM_MSDU_DESC; + else + ar->htt.max_num_pending_tx = TARGET_TLV_NUM_MSDU_DESC; ar->wow.max_num_patterns = TARGET_TLV_NUM_WOW_PATTERNS; ar->fw_stats_req_mask = WMI_STAT_PDEV | WMI_STAT_VDEV | WMI_STAT_PEER; ar->max_spatial_stream = WMI_MAX_SPATIAL_STREAM; + ar->wmi.mgmt_max_num_pending_tx = TARGET_TLV_MGMT_NUM_MSDU_DESC; break; case ATH10K_FW_WMI_OP_VERSION_10_4: ar->max_num_peers = TARGET_10_4_NUM_PEERS; @@ -1507,9 +1723,15 @@ static int ath10k_core_init_firmware_features(struct ath10k *ar) ar->num_active_peers = TARGET_10_4_ACTIVE_PEERS; ar->max_num_vdevs = TARGET_10_4_NUM_VDEVS; ar->num_tids = TARGET_10_4_TGT_NUM_TIDS; - ar->htt.max_num_pending_tx = TARGET_10_4_NUM_MSDU_DESC; - ar->fw_stats_req_mask = WMI_STAT_PEER; - ar->max_spatial_stream = WMI_10_4_MAX_SPATIAL_STREAM; + ar->fw_stats_req_mask = WMI_10_4_STAT_PEER | + WMI_10_4_STAT_PEER_EXTD; + ar->max_spatial_stream = ar->hw_params.max_spatial_stream; + + if (test_bit(ATH10K_FW_FEATURE_PEER_FLOW_CONTROL, + fw_file->fw_features)) + ar->htt.max_num_pending_tx = TARGET_10_4_NUM_MSDU_DESC_PFC; + else + ar->htt.max_num_pending_tx = TARGET_10_4_NUM_MSDU_DESC; break; case ATH10K_FW_WMI_OP_VERSION_UNSET: case ATH10K_FW_WMI_OP_VERSION_MAX: @@ -1520,23 +1742,23 @@ static int ath10k_core_init_firmware_features(struct ath10k *ar) /* Backwards compatibility for firmwares without * ATH10K_FW_IE_HTT_OP_VERSION. */ - if (ar->htt.op_version == ATH10K_FW_HTT_OP_VERSION_UNSET) { - switch (ar->wmi.op_version) { + if (fw_file->htt_op_version == ATH10K_FW_HTT_OP_VERSION_UNSET) { + switch (fw_file->wmi_op_version) { case ATH10K_FW_WMI_OP_VERSION_MAIN: - ar->htt.op_version = ATH10K_FW_HTT_OP_VERSION_MAIN; + fw_file->htt_op_version = ATH10K_FW_HTT_OP_VERSION_MAIN; break; case ATH10K_FW_WMI_OP_VERSION_10_1: case ATH10K_FW_WMI_OP_VERSION_10_2: case ATH10K_FW_WMI_OP_VERSION_10_2_4: - ar->htt.op_version = ATH10K_FW_HTT_OP_VERSION_10_1; + fw_file->htt_op_version = ATH10K_FW_HTT_OP_VERSION_10_1; break; case ATH10K_FW_WMI_OP_VERSION_TLV: - ar->htt.op_version = ATH10K_FW_HTT_OP_VERSION_TLV; + fw_file->htt_op_version = ATH10K_FW_HTT_OP_VERSION_TLV; break; case ATH10K_FW_WMI_OP_VERSION_10_4: case ATH10K_FW_WMI_OP_VERSION_UNSET: case ATH10K_FW_WMI_OP_VERSION_MAX: - WARN_ON(1); + ath10k_err(ar, "htt op version not found from fw meta data"); return -EINVAL; } } @@ -1544,48 +1766,105 @@ static int ath10k_core_init_firmware_features(struct ath10k *ar) return 0; } -int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode) +static int ath10k_core_reset_rx_filter(struct ath10k *ar) +{ + int ret; + int vdev_id; + int vdev_type; + int vdev_subtype; + const u8 *vdev_addr; + + vdev_id = 0; + vdev_type = WMI_VDEV_TYPE_STA; + vdev_subtype = ath10k_wmi_get_vdev_subtype(ar, WMI_VDEV_SUBTYPE_NONE); + vdev_addr = ar->mac_addr; + + ret = ath10k_wmi_vdev_create(ar, vdev_id, vdev_type, vdev_subtype, + vdev_addr); + if (ret) { + ath10k_err(ar, "failed to create dummy vdev: %d\n", ret); + return ret; + } + + ret = ath10k_wmi_vdev_delete(ar, vdev_id); + if (ret) { + ath10k_err(ar, "failed to delete dummy vdev: %d\n", ret); + return ret; + } + + /* WMI and HTT may use separate HIF pipes and are not guaranteed to be + * serialized properly implicitly. + * + * Moreover (most) WMI commands have no explicit acknowledges. It is + * possible to infer it implicitly by poking firmware with echo + * command - getting a reply means all preceding comments have been + * (mostly) processed. + * + * In case of vdev create/delete this is sufficient. + * + * Without this it's possible to end up with a race when HTT Rx ring is + * started before vdev create/delete hack is complete allowing a short + * window of opportunity to receive (and Tx ACK) a bunch of frames. + */ + ret = ath10k_wmi_barrier(ar); + if (ret) { + ath10k_err(ar, "failed to ping firmware: %d\n", ret); + return ret; + } + + return 0; +} + +int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode, + const struct ath10k_fw_components *fw) { int status; + u32 val; lockdep_assert_held(&ar->conf_mutex); clear_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags); - ath10k_bmi_start(ar); + ar->running_fw = fw; - if (ath10k_init_configure_target(ar)) { - status = -EINVAL; - goto err; - } + if (ar->is_bmi) { - status = ath10k_download_cal_data(ar); - if (status) - goto err; + ath10k_bmi_start(ar); - /* Some of of qca988x solutions are having global reset issue - * during target initialization. Bypassing PLL setting before - * downloading firmware and letting the SoC run on REF_CLK is - * fixing the problem. Corresponding firmware change is also needed - * to set the clock source once the target is initialized. - */ - if (test_bit(ATH10K_FW_FEATURE_SUPPORTS_SKIP_CLOCK_INIT, - ar->fw_features)) { - status = ath10k_bmi_write32(ar, hi_skip_clock_init, 1); - if (status) { - ath10k_err(ar, "could not write to skip_clock_init: %d\n", - status); + if (ath10k_init_configure_target(ar)) { + status = -EINVAL; goto err; } - } - status = ath10k_download_fw(ar, mode); - if (status) - goto err; + status = ath10k_download_cal_data(ar); + if (status) + goto err; - status = ath10k_init_uart(ar); - if (status) - goto err; + /* Some of of qca988x solutions are having global reset issue + * during target initialization. Bypassing PLL setting before + * downloading firmware and letting the SoC run on REF_CLK is + * fixing the problem. Corresponding firmware change is also + * needed to set the clock source once the target is + * initialized. + */ + if (test_bit(ATH10K_FW_FEATURE_SUPPORTS_SKIP_CLOCK_INIT, + ar->running_fw->fw_file.fw_features)) { + status = ath10k_bmi_write32(ar, hi_skip_clock_init, 1); + if (status) { + ath10k_err(ar, "skip_clock_init failed: %d\n", + status); + goto err; + } + } + + status = ath10k_download_fw(ar); + if (status) + goto err; + + status = ath10k_init_uart(ar); + if (status) + goto err; + } ar->htc.htc_ops.target_send_suspend_complete = ath10k_send_suspend_complete; @@ -1596,9 +1875,11 @@ int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode) goto err; } - status = ath10k_bmi_done(ar); - if (status) - goto err; + if (ar->is_bmi) { + status = ath10k_bmi_done(ar); + if (status) + goto err; + } status = ath10k_wmi_attach(ar); if (status) { @@ -1656,6 +1937,12 @@ int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode) goto err_hif_stop; } + status = ath10k_pktlog_connect(ar); + if (status) { + ath10k_err(ar, "could not connect pktlog: %d\n", status); + goto err_hif_stop; + } + status = ath10k_htc_start(&ar->htc); if (status) { ath10k_err(ar, "failed to start htc: %d\n", status); @@ -1673,6 +1960,33 @@ int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode) ath10k_dbg(ar, ATH10K_DBG_BOOT, "firmware %s booted\n", ar->hw->wiphy->fw_version); + if (test_bit(WMI_SERVICE_EXT_RES_CFG_SUPPORT, ar->wmi.svc_map)) { + val = 0; + if (ath10k_peer_stats_enabled(ar)) + val = WMI_10_4_PEER_STATS; + + if (test_bit(WMI_SERVICE_BSS_CHANNEL_INFO_64, ar->wmi.svc_map)) + val |= WMI_10_4_BSS_CHANNEL_INFO_64; + + /* 10.4 firmware supports BT-Coex without reloading firmware + * via pdev param. To support Bluetooth coexistence pdev param, + * WMI_COEX_GPIO_SUPPORT of extended resource config should be + * enabled always. + */ + if (test_bit(WMI_SERVICE_COEX_GPIO, ar->wmi.svc_map) && + test_bit(ATH10K_FW_FEATURE_BTCOEX_PARAM, + ar->running_fw->fw_file.fw_features)) + val |= WMI_10_4_COEX_GPIO_SUPPORT; + + status = ath10k_mac_ext_resource_config(ar, val); + if (status) { + ath10k_err(ar, + "failed to send ext resource cfg command : %d\n", + status); + goto err_hif_stop; + } + } + status = ath10k_wmi_cmd_init(ar); if (status) { ath10k_err(ar, "could not send WMI init command (%d)\n", @@ -1686,13 +2000,38 @@ int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode) goto err_hif_stop; } + /* Some firmware revisions do not properly set up hardware rx filter + * registers. + * + * A known example from QCA9880 and 10.2.4 is that MAC_PCU_ADDR1_MASK + * is filled with 0s instead of 1s allowing HW to respond with ACKs to + * any frames that matches MAC_PCU_RX_FILTER which is also + * misconfigured to accept anything. + * + * The ADDR1 is programmed using internal firmware structure field and + * can't be (easily/sanely) reached from the driver explicitly. It is + * possible to implicitly make it correct by creating a dummy vdev and + * then deleting it. + */ + if (!QCA_REV_WCN3990(ar)) { + status = ath10k_core_reset_rx_filter(ar); + if (status) { + ath10k_err(ar, "failed to reset rx filter: %d\n", + status); + goto err_hif_stop; + } + } + status = ath10k_htt_rx_ring_refill(ar); if (status) { ath10k_err(ar, "failed to refill htt rx ring: %d\n", status); goto err_hif_stop; } - ar->free_vdev_map = (1LL << ar->max_num_vdevs) - 1; + if (ar->max_num_vdevs >= 64) + ar->free_vdev_map = 0xFFFFFFFFFFFFFFFFLL; + else + ar->free_vdev_map = (1LL << ar->max_num_vdevs) - 1; INIT_LIST_HEAD(&ar->arvifs); @@ -1755,7 +2094,7 @@ void ath10k_core_stop(struct ath10k *ar) /* try to suspend target */ if (ar->state != ATH10K_STATE_RESTARTING && ar->state != ATH10K_STATE_UTF) - ath10k_wait_for_suspend(ar, WMI_PDEV_SUSPEND_AND_DISABLE_INTR); + ath10k_wait_for_suspend(ar, ar->hw_values->pdev_suspend_option); ath10k_hif_stop(ar); ath10k_htt_tx_free(&ar->htt); @@ -1779,16 +2118,17 @@ static int ath10k_core_probe_fw(struct ath10k *ar) return ret; } - memset(&target_info, 0, sizeof(target_info)); - ret = ath10k_bmi_get_target_info(ar, &target_info); - if (ret) { - ath10k_err(ar, "could not get target info (%d)\n", ret); - goto err_power_down; + if (ar->is_bmi) { + memset(&target_info, 0, sizeof(target_info)); + ret = ath10k_bmi_get_target_info(ar, &target_info); + if (ret) { + ath10k_err(ar, "could not get target info (%d)\n", ret); + goto err_power_down; + } + ar->target_version = target_info.version; + ar->hw->wiphy->hw_version = target_info.version; } - ar->target_version = target_info.version; - ar->hw->wiphy->hw_version = target_info.version; - ret = ath10k_init_hw_params(ar); if (ret) { ath10k_err(ar, "could not get hw params (%d)\n", ret); @@ -1801,17 +2141,37 @@ static int ath10k_core_probe_fw(struct ath10k *ar) goto err_power_down; } - ret = ath10k_core_get_board_id_from_otp(ar); - if (ret && ret != -EOPNOTSUPP) { - ath10k_err(ar, "failed to get board id from otp for qca99x0: %d\n", - ret); - return ret; - } + BUILD_BUG_ON(sizeof(ar->hw->wiphy->fw_version) != + sizeof(ar->normal_mode_fw.fw_file.fw_version)); + memcpy(ar->hw->wiphy->fw_version, + ar->normal_mode_fw.fw_file.fw_version, + sizeof(ar->hw->wiphy->fw_version)); + ath10k_debug_print_hwfw_info(ar); - ret = ath10k_core_fetch_board_file(ar); - if (ret) { - ath10k_err(ar, "failed to fetch board file: %d\n", ret); - goto err_free_firmware_files; + if (ar->is_bmi) { + ret = ath10k_core_pre_cal_download(ar); + if (ret) { + /* pre calibration data download is not necessary + * for all the chipsets. Ignore failures and continue. + */ + ath10k_dbg(ar, ATH10K_DBG_BOOT, + "could not load pre cal data: %d\n", ret); + } + + ret = ath10k_core_get_board_id_from_otp(ar); + if (ret && ret != -EOPNOTSUPP) { + ath10k_err(ar, "failed to get board id from otp: %d\n", + ret); + goto err_free_firmware_files; + } + + ret = ath10k_core_fetch_board_file(ar); + if (ret) { + ath10k_err(ar, "failed to fetch board file: %d\n", ret); + goto err_free_firmware_files; + } + + ath10k_debug_print_board_info(ar); } ret = ath10k_core_init_firmware_features(ar); @@ -1821,22 +2181,26 @@ static int ath10k_core_probe_fw(struct ath10k *ar) goto err_free_firmware_files; } - ret = ath10k_swap_code_seg_init(ar); - if (ret) { - ath10k_err(ar, "failed to initialize code swap segment: %d\n", - ret); - goto err_free_firmware_files; + if (ar->is_bmi) { + ret = ath10k_swap_code_seg_init(ar, + &ar->normal_mode_fw.fw_file); + if (ret) { + ath10k_err(ar, "failed to init code swap segment: %d\n", + ret); + goto err_free_firmware_files; + } } mutex_lock(&ar->conf_mutex); - ret = ath10k_core_start(ar, ATH10K_FIRMWARE_MODE_NORMAL); + ret = ath10k_core_start(ar, ATH10K_FIRMWARE_MODE_NORMAL, + &ar->normal_mode_fw); if (ret) { ath10k_err(ar, "could not init core (%d)\n", ret); goto err_unlock; } - ath10k_print_driver_info(ar); + ath10k_debug_print_boot_info(ar); ath10k_core_stop(ar); mutex_unlock(&ar->conf_mutex); @@ -1861,6 +2225,9 @@ static void ath10k_core_register_work(struct work_struct *work) struct ath10k *ar = container_of(work, struct ath10k, register_work); int status; + /* peer stats are enabled by default */ + set_bit(ATH10K_FLAG_PEER_STATS, &ar->dev_flags); + status = ath10k_core_probe_fw(ar); if (status) { ath10k_err(ar, "could not probe fw (%d)\n", status); @@ -1966,20 +2333,52 @@ struct ath10k *ath10k_core_create(size_t priv_size, struct device *dev, ar->hif.ops = hif_ops; ar->hif.bus = bus; + /* By default, assume bmi is set, as most of the existing + * chip sets are based on this, set to false explicitly + * when current chip set does not support. + */ + ar->is_bmi = true; + switch (hw_rev) { case ATH10K_HW_QCA988X: + case ATH10K_HW_QCA9887: ar->regs = &qca988x_regs; + ar->hw_ce_regs = &qcax_ce_regs; ar->hw_values = &qca988x_values; break; case ATH10K_HW_QCA6174: case ATH10K_HW_QCA9377: ar->regs = &qca6174_regs; + ar->hw_ce_regs = &qcax_ce_regs; ar->hw_values = &qca6174_values; break; case ATH10K_HW_QCA99X0: + case ATH10K_HW_QCA9984: ar->regs = &qca99x0_regs; + ar->hw_ce_regs = &qcax_ce_regs; ar->hw_values = &qca99x0_values; break; + case ATH10K_HW_QCA9888: + ar->regs = &qca99x0_regs; + ar->hw_ce_regs = &qcax_ce_regs; + ar->hw_values = &qca9888_values; + break; + case ATH10K_HW_QCA4019: + ar->regs = &qca4019_regs; + ar->hw_ce_regs = &qcax_ce_regs; + ar->hw_values = &qca4019_values; + break; + case ATH10K_HW_WCN3990: + ar->regs = &wcn3990_regs; + ar->hw_ce_regs = &wcn3990_ce_regs; + ar->hw_values = &wcn3990_values; + /* WCN3990 chip set is non bmi based */ + ar->is_bmi = false; + ar->fw_flags = &wcn3990_fw_flags; + ar->shadow_reg_value = &wcn3990_shadow_reg_value; + ar->shadow_reg_address = &wcn3990_shadow_reg_address; + ar->rri_on_ddr = true; + break; default: ath10k_err(ar, "unsupported core hardware revision %d\n", hw_rev); @@ -1995,7 +2394,10 @@ struct ath10k *ath10k_core_create(size_t priv_size, struct device *dev, init_completion(&ar->install_key_done); init_completion(&ar->vdev_setup_done); + init_completion(&ar->vdev_delete_done); init_completion(&ar->thermal.wmi_sync); + init_completion(&ar->bss_survey_done); + init_completion(&ar->peer_delete_done); INIT_DELAYED_WORK(&ar->scan.timeout, ath10k_scan_timeout_work); @@ -2009,7 +2411,10 @@ struct ath10k *ath10k_core_create(size_t priv_size, struct device *dev, mutex_init(&ar->conf_mutex); spin_lock_init(&ar->data_lock); + spin_lock_init(&ar->txqs_lock); + spin_lock_init(&ar->datapath_rx_stat_lock); + INIT_LIST_HEAD(&ar->txqs); INIT_LIST_HEAD(&ar->peers); init_waitqueue_head(&ar->peer_mapping_wq); init_waitqueue_head(&ar->htt.empty_tx_wq); @@ -2025,6 +2430,8 @@ struct ath10k *ath10k_core_create(size_t priv_size, struct device *dev, INIT_WORK(&ar->register_work, ath10k_core_register_work); INIT_WORK(&ar->restart_work, ath10k_core_restart); + init_dummy_netdev(&ar->napi_dev); + ret = ath10k_debug_create(ar); if (ret) goto err_free_aux_wq; @@ -2058,5 +2465,5 @@ void ath10k_core_destroy(struct ath10k *ar) EXPORT_SYMBOL(ath10k_core_destroy); MODULE_AUTHOR("Qualcomm Atheros"); -MODULE_DESCRIPTION("Core module for QCA988X PCIe devices."); +MODULE_DESCRIPTION("Core module for Qualcomm Atheros 802.11ac wireless LAN cards."); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h index 257836a0cfbc..bc13e9135152 100644 --- a/drivers/net/wireless/ath/ath10k/core.h +++ b/drivers/net/wireless/ath/ath10k/core.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2005-2011 Atheros Communications Inc. - * Copyright (c) 2011-2013 Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, 2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,6 +24,8 @@ #include <linux/pci.h> #include <linux/uuid.h> #include <linux/time.h> +#include <linux/inetdevice.h> +#include <soc/qcom/socinfo.h> #include "htt.h" #include "htc.h" @@ -44,8 +46,8 @@ #define ATH10K_SCAN_ID 0 #define WMI_READY_TIMEOUT (5 * HZ) -#define ATH10K_FLUSH_TIMEOUT_HZ (5*HZ) -#define ATH10K_CONNECTION_LOSS_HZ (3*HZ) +#define ATH10K_FLUSH_TIMEOUT_HZ (5 * HZ) +#define ATH10K_CONNECTION_LOSS_HZ (3 * HZ) #define ATH10K_NUM_CHANS 39 /* Antenna noise floor */ @@ -65,10 +67,30 @@ #define ATH10K_KEEPALIVE_MAX_IDLE 3895 #define ATH10K_KEEPALIVE_MAX_UNRESPONSIVE 3900 +/* NAPI poll budget */ +#define ATH10K_NAPI_BUDGET 64 +#define ATH10K_NAPI_QUOTA_LIMIT 60 + +#define ATH10K_RX_MCS_MIN 0 +#define ATH10K_RX_HT_MCS_MAX 32 +#define ATH10K_RX_VHT_RATEIDX_MAX 9 +#define ATH10K_RX_VHT_MCS_MAX 20 /* For 2x2 */ +#define ATH10K_RX_NSS_MIN 0 +#define ATH10K_RX_NSS_MAX 5 + +enum ath10k_datapath_rx_band { + ATH10K_BAND_MIN, + ATH10K_BAND_2GHZ = ATH10K_BAND_MIN, + ATH10K_BAND_5GHZ, + ATH10K_BAND_MAX, +}; + struct ath10k; enum ath10k_bus { ATH10K_BUS_PCI, + ATH10K_BUS_AHB, + ATH10K_BUS_SNOC, }; static inline const char *ath10k_bus_str(enum ath10k_bus bus) @@ -76,31 +98,30 @@ static inline const char *ath10k_bus_str(enum ath10k_bus bus) switch (bus) { case ATH10K_BUS_PCI: return "pci"; + case ATH10K_BUS_AHB: + return "ahb"; + case ATH10K_BUS_SNOC: + return "snoc"; } return "unknown"; } +enum ath10k_skb_flags { + ATH10K_SKB_F_NO_HWCRYPT = BIT(0), + ATH10K_SKB_F_DTIM_ZERO = BIT(1), + ATH10K_SKB_F_DELIVER_CAB = BIT(2), + ATH10K_SKB_F_MGMT = BIT(3), + ATH10K_SKB_F_QOS = BIT(4), +}; + struct ath10k_skb_cb { dma_addr_t paddr; + u8 flags; u8 eid; - u8 vdev_id; - enum ath10k_hw_txrx_mode txmode; - bool is_protected; - - struct { - u8 tid; - u16 freq; - bool is_offchan; - bool nohwcrypt; - struct ath10k_htt_txbuf *txbuf; - u32 txbuf_paddr; - } __packed htt; - - struct { - bool dtim_zero; - bool deliver_cab; - } bcn; + u16 msdu_id; + struct ieee80211_vif *vif; + struct ieee80211_txq *txq; } __packed; struct ath10k_skb_rxcb { @@ -141,17 +162,22 @@ struct ath10k_mem_chunk { }; struct ath10k_wmi { - enum ath10k_fw_wmi_op_version op_version; enum ath10k_htc_ep_id eid; struct completion service_ready; struct completion unified_ready; + struct completion barrier; wait_queue_head_t tx_credits_wq; DECLARE_BITMAP(svc_map, WMI_SERVICE_MAX); struct wmi_cmd_map *cmd; struct wmi_vdev_param_map *vdev_param; struct wmi_pdev_param_map *pdev_param; const struct wmi_ops *ops; + const struct wmi_peer_flags_map *peer_flags; + u32 mgmt_max_num_pending_tx; + struct idr mgmt_pending_tx; + /* Protects access to mgmt_pending_tx, mgmt_max_num_pending_tx */ + spinlock_t mgmt_tx_lock; u32 num_mem_chunks; u32 rx_decap_mode; struct ath10k_mem_chunk mem_chunks[WMI_MAX_MEM_REQS]; @@ -164,6 +190,14 @@ struct ath10k_fw_stats_peer { u32 peer_rssi; u32 peer_tx_rate; u32 peer_rx_rate; /* 10x only */ + u32 rx_duration; +}; + +struct ath10k_fw_extd_stats_peer { + struct list_head list; + + u8 peer_macaddr[ETH_ALEN]; + u32 rx_duration; }; struct ath10k_fw_stats_vdev { @@ -190,10 +224,10 @@ struct ath10k_fw_stats_pdev { /* PDEV stats */ s32 ch_noise_floor; - u32 tx_frame_count; - u32 rx_frame_count; - u32 rx_clear_count; - u32 cycle_count; + u32 tx_frame_count; /* Cycles spent transmitting frames */ + u32 rx_frame_count; /* Cycles spent receiving frames */ + u32 rx_clear_count; /* Total channel busy time, evidently */ + u32 cycle_count; /* Total on-channel time */ u32 phy_err_count; u32 chan_tx_power; u32 ack_rx_bad; @@ -257,9 +291,11 @@ struct ath10k_fw_stats_pdev { }; struct ath10k_fw_stats { + bool extended; struct list_head pdevs; struct list_head vdevs; struct list_head peers; + struct list_head peers_extd; }; #define ATH10K_TPC_TABLE_TYPE_FLAG 1 @@ -298,6 +334,9 @@ struct ath10k_dfs_stats { struct ath10k_peer { struct list_head list; + struct ieee80211_vif *vif; + struct ieee80211_sta *sta; + int vdev_id; u8 addr[ETH_ALEN]; DECLARE_BITMAP(peer_ids, ATH10K_MAX_NUM_PEER_IDS); @@ -306,6 +345,12 @@ struct ath10k_peer { struct ieee80211_key_conf *keys[WMI_MAX_KEY_INDEX + 1]; }; +struct ath10k_txq { + struct list_head list; + unsigned long num_fw_queued; + unsigned long num_push_allowed; +}; + struct ath10k_sta { struct ath10k_vif *arvif; @@ -314,16 +359,19 @@ struct ath10k_sta { u32 bw; u32 nss; u32 smps; + u16 peer_id; struct work_struct update_wk; #ifdef CONFIG_MAC80211_DEBUGFS /* protected by conf_mutex */ bool aggr_mode; + u64 rx_duration; #endif }; -#define ATH10K_VDEV_SETUP_TIMEOUT_HZ (5*HZ) +#define ATH10K_VDEV_SETUP_TIMEOUT_HZ (5 * HZ) +#define ATH10K_VDEV_DELETE_TIMEOUT_HZ (5 * HZ) enum ath10k_beacon_state { ATH10K_BEACON_SCHEDULED = 0, @@ -335,6 +383,7 @@ struct ath10k_vif { struct list_head list; u32 vdev_id; + u16 peer_id; enum wmi_vdev_type vdev_type; enum wmi_vdev_subtype vdev_subtype; u32 beacon_interval; @@ -386,6 +435,9 @@ struct ath10k_vif { struct work_struct ap_csa_work; struct delayed_work connection_loss_work; struct cfg80211_bitrate_mask bitrate_mask; + struct wmi_ns_arp_offload_req arp_offload; + struct wmi_ns_arp_offload_req ns_offload; + struct wmi_gtk_rekey_data gtk_rekey_data; }; struct ath10k_vif_iter { @@ -420,11 +472,13 @@ struct ath10k_debug { struct completion tpc_complete; /* protected by conf_mutex */ - u32 fw_dbglog_mask; + u64 fw_dbglog_mask; u32 fw_dbglog_level; u32 pktlog_filter; + enum ath10k_htc_ep_id eid; u32 reg_addr; u32 nf_cal_period; + void *cal_data; struct ath10k_fw_crash_data *fw_crash_data; }; @@ -512,6 +566,32 @@ enum ath10k_fw_features { /* Firmware Supports Adaptive CCA*/ ATH10K_FW_FEATURE_SUPPORTS_ADAPTIVE_CCA = 11, + /* Firmware supports management frame protection */ + ATH10K_FW_FEATURE_MFP_SUPPORT = 12, + + /* Firmware supports pull-push model where host shares it's software + * queue state with firmware and firmware generates fetch requests + * telling host which queues to dequeue tx from. + * + * Primary function of this is improved MU-MIMO performance with + * multiple clients. + */ + ATH10K_FW_FEATURE_PEER_FLOW_CONTROL = 13, + + /* Firmware supports BT-Coex without reloading firmware via pdev param. + * To support Bluetooth coexistence pdev param, WMI_COEX_GPIO_SUPPORT of + * extended resource config should be enabled always. This firmware IE + * is used to configure WMI_COEX_GPIO_SUPPORT. + */ + ATH10K_FW_FEATURE_BTCOEX_PARAM = 14, + + /* Older firmware with HTT delivers incorrect tx status for null func + * frames to driver, but this fixed in 10.2 and 10.4 firmware versions. + * Also this workaround results in reporting of incorrect null func + * status for 10.4. This flag is used to skip the workaround. + */ + ATH10K_FW_FEATURE_SKIP_NULL_FUNC_WAR = 15, + /* keep last */ ATH10K_FW_FEATURE_COUNT, }; @@ -534,12 +614,21 @@ enum ath10k_dev_flags { /* Disable HW crypto engine */ ATH10K_FLAG_HW_CRYPTO_DISABLED, + + /* Bluetooth coexistance enabled */ + ATH10K_FLAG_BTCOEX, + + /* Per Station statistics service */ + ATH10K_FLAG_PEER_STATS, }; enum ath10k_cal_mode { ATH10K_CAL_MODE_FILE, ATH10K_CAL_MODE_OTP, ATH10K_CAL_MODE_DT, + ATH10K_PRE_CAL_MODE_FILE, + ATH10K_PRE_CAL_MODE_DT, + ATH10K_CAL_MODE_EEPROM, }; enum ath10k_crypt_mode { @@ -558,6 +647,12 @@ static inline const char *ath10k_cal_mode_str(enum ath10k_cal_mode mode) return "otp"; case ATH10K_CAL_MODE_DT: return "dt"; + case ATH10K_PRE_CAL_MODE_FILE: + return "pre-cal-file"; + case ATH10K_PRE_CAL_MODE_DT: + return "pre-cal-dt"; + case ATH10K_CAL_MODE_EEPROM: + return "eeprom"; } return "unknown"; @@ -591,11 +686,68 @@ enum ath10k_tx_pause_reason { ATH10K_TX_PAUSE_MAX, }; +struct fw_flag { + u32 flags; +}; + +struct ath10k_fw_file { + const struct firmware *firmware; + + char fw_version[ETHTOOL_FWVERS_LEN]; + + DECLARE_BITMAP(fw_features, ATH10K_FW_FEATURE_COUNT); + + enum ath10k_fw_wmi_op_version wmi_op_version; + enum ath10k_fw_htt_op_version htt_op_version; + + const void *firmware_data; + size_t firmware_len; + + const void *otp_data; + size_t otp_len; + + const void *codeswap_data; + size_t codeswap_len; + + /* The original idea of struct ath10k_fw_file was that it only + * contains struct firmware and pointers to various parts (actual + * firmware binary, otp, metadata etc) of the file. This seg_info + * is actually created separate but as this is used similarly as + * the other firmware components it's more convenient to have it + * here. + */ + struct ath10k_swap_code_seg_info *firmware_swap_code_seg_info; +}; + +struct ath10k_fw_components { + const struct firmware *board; + const void *board_data; + size_t board_len; + + struct ath10k_fw_file fw_file; +}; + +struct datapath_rx_stats { + u32 no_of_packets; + u32 short_gi_pkts; + u32 ht_rate_indx[ATH10K_RX_HT_MCS_MAX + 1]; + u32 vht_rate_indx[ATH10K_RX_VHT_MCS_MAX + 1]; + u32 ht_rate_packets; + u32 vht_rate_packets; + u32 legacy_pkt; + u32 nss[ATH10K_RX_NSS_MAX + 1]; + u32 num_pkts_40Mhz; + u32 num_pkts_80Mhz; + u32 band[ATH10K_BAND_MAX + 1]; +}; + struct ath10k { struct ath_common ath_common; struct ieee80211_hw *hw; + struct ieee80211_ops *ops; struct device *dev; u8 mac_addr[ETH_ALEN]; + u8 base_mac_addr[ETH_ALEN]; enum ath10k_hw_rev hw_rev; u16 dev_id; @@ -615,9 +767,7 @@ struct ath10k { u32 max_spatial_stream; /* protected by conf_mutex */ bool ani_enabled; - - DECLARE_BITMAP(fw_features, ATH10K_FW_FEATURE_COUNT); - + bool sifs_burst_enabled; bool p2p; struct { @@ -628,75 +778,29 @@ struct ath10k { struct completion target_suspend; const struct ath10k_hw_regs *regs; + const struct ath10k_hw_ce_regs *hw_ce_regs; const struct ath10k_hw_values *hw_values; + struct ath10k_shadow_reg_value *shadow_reg_value; + struct ath10k_shadow_reg_address *shadow_reg_address; struct ath10k_bmi bmi; struct ath10k_wmi wmi; struct ath10k_htc htc; struct ath10k_htt htt; - struct ath10k_hw_params { - u32 id; - u16 dev_id; - const char *name; - u32 patch_load_addr; - int uart_pin; - u32 otp_exe_param; - - /* This is true if given HW chip has a quirky Cycle Counter - * wraparound which resets to 0x7fffffff instead of 0. All - * other CC related counters (e.g. Rx Clear Count) are divided - * by 2 so they never wraparound themselves. - */ - bool has_shifted_cc_wraparound; - - /* Some of chip expects fragment descriptor to be continuous - * memory for any TX operation. Set continuous_frag_desc flag - * for the hardware which have such requirement. - */ - bool continuous_frag_desc; - - u32 channel_counters_freq_hz; - - /* Mgmt tx descriptors threshold for limiting probe response - * frames. - */ - u32 max_probe_resp_desc_thres; - - struct ath10k_hw_params_fw { - const char *dir; - const char *fw; - const char *otp; - const char *board; - size_t board_size; - size_t board_ext_size; - } fw; - - /* Number of bytes used for alignment in rx_hdr_status */ - int decap_align_bytes; - - } hw_params; - - const struct firmware *board; - const void *board_data; - size_t board_len; + struct ath10k_hw_params hw_params; - const struct firmware *otp; - const void *otp_data; - size_t otp_len; + /* contains the firmware images used with ATH10K_FIRMWARE_MODE_NORMAL */ + struct ath10k_fw_components normal_mode_fw; - const struct firmware *firmware; - const void *firmware_data; - size_t firmware_len; + /* READ-ONLY images of the running firmware, which can be either + * normal or UTF. Do not modify, release etc! + */ + const struct ath10k_fw_components *running_fw; + const struct firmware *pre_cal_file; const struct firmware *cal_file; struct { - const void *firmware_codeswap_data; - size_t firmware_codeswap_len; - struct ath10k_swap_code_seg_info *firmware_swap_code_seg_info; - } swap; - - struct { u32 vendor; u32 device; u32 subsystem_vendor; @@ -736,6 +840,9 @@ struct ath10k { /* current operating channel definition */ struct cfg80211_chan_def chandef; + /* currently configured operating channel in firmware */ + struct ieee80211_channel *tgt_oper_chan; + unsigned long long free_vdev_map; struct ath10k_vif *monitor_arvif; bool monitor; @@ -756,6 +863,7 @@ struct ath10k { struct completion install_key_done; struct completion vdev_setup_done; + struct completion vdev_delete_done; struct workqueue_struct *workqueue; /* Auxiliary workqueue */ @@ -766,9 +874,13 @@ struct ath10k { /* protects shared structure data */ spinlock_t data_lock; + /* protects: ar->txqs, artxq->list */ + spinlock_t txqs_lock; + struct list_head txqs; struct list_head arvifs; struct list_head peers; + struct ath10k_peer *peer_map[ATH10K_MAX_NUM_PEER_IDS]; wait_queue_head_t peer_mapping_wq; /* protected by conf_mutex */ @@ -811,6 +923,7 @@ struct ath10k { * avoid reporting garbage data. */ bool ch_info_can_report_survey; + struct completion bss_survey_done; struct dfs_pattern_detector *dfs_detector; @@ -818,8 +931,6 @@ struct ath10k { #ifdef CONFIG_ATH10K_DEBUGFS struct ath10k_debug debug; -#endif - struct { /* relay(fs) channel for spectral scan */ struct rchan *rfs_chan_spec_scan; @@ -828,16 +939,15 @@ struct ath10k { enum ath10k_spectral_mode mode; struct ath10k_spec_scan config; } spectral; + struct datapath_rx_stats *rx_stats; +#endif + /* prevent concurrency histogram for receiving data packet */ + spinlock_t datapath_rx_stat_lock; struct { /* protected by conf_mutex */ - const struct firmware *utf; - char utf_version[32]; - const void *utf_firmware_data; - size_t utf_firmware_len; - DECLARE_BITMAP(orig_fw_features, ATH10K_FW_FEATURE_COUNT); - enum ath10k_fw_wmi_op_version orig_wmi_op_version; - enum ath10k_fw_wmi_op_version op_version; + struct ath10k_fw_components utf_mode_fw; + /* protected by data_lock */ bool utf_monitor; } testmode; @@ -852,10 +962,29 @@ struct ath10k { struct ath10k_thermal thermal; struct ath10k_wow wow; + /* NAPI */ + struct net_device napi_dev; + struct napi_struct napi; + + struct fw_flag *fw_flags; + /* set for bmi chip sets */ + struct completion peer_delete_done; + bool is_bmi; + enum ieee80211_sta_state sta_state; + bool rri_on_ddr; /* must be last */ u8 drv_priv[0] __aligned(sizeof(void *)); }; +static inline bool ath10k_peer_stats_enabled(struct ath10k *ar) +{ + if (test_bit(ATH10K_FLAG_PEER_STATS, &ar->dev_flags) && + test_bit(WMI_SERVICE_PEER_STATS, ar->wmi.svc_map)) + return true; + + return false; +} + struct ath10k *ath10k_core_create(size_t priv_size, struct device *dev, enum ath10k_bus bus, enum ath10k_hw_rev hw_rev, @@ -864,8 +993,11 @@ void ath10k_core_destroy(struct ath10k *ar); void ath10k_core_get_fw_features_str(struct ath10k *ar, char *buf, size_t max_len); +int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name, + struct ath10k_fw_file *fw_file); -int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode); +int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode, + const struct ath10k_fw_components *fw_components); int ath10k_wait_for_suspend(struct ath10k *ar, u32 suspend_opt); void ath10k_core_stop(struct ath10k *ar); int ath10k_core_register(struct ath10k *ar, u32 chip_id); diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c index 30c357567054..0836a81b93e0 100644 --- a/drivers/net/wireless/ath/ath10k/debug.c +++ b/drivers/net/wireless/ath/ath10k/debug.c @@ -19,15 +19,20 @@ #include <linux/debugfs.h> #include <linux/vmalloc.h> #include <linux/utsname.h> +#include <linux/crc32.h> +#include <linux/firmware.h> #include "core.h" #include "debug.h" #include "hif.h" +#include "htt.h" #include "wmi-ops.h" /* ms */ #define ATH10K_DEBUG_HTT_STATS_INTERVAL 1000 +#define ATH10K_DEBUG_CAL_DATA_LEN 12064 + #define ATH10K_FW_CRASH_DUMP_VERSION 1 /** @@ -122,43 +127,73 @@ void ath10k_info(struct ath10k *ar, const char *fmt, ...) } EXPORT_SYMBOL(ath10k_info); -void ath10k_print_driver_info(struct ath10k *ar) +void ath10k_debug_print_hwfw_info(struct ath10k *ar) { + const struct firmware *firmware; char fw_features[128] = {}; - char boardinfo[100]; + u32 crc = 0; ath10k_core_get_fw_features_str(ar, fw_features, sizeof(fw_features)); - if (ar->id.bmi_ids_valid) - scnprintf(boardinfo, sizeof(boardinfo), "bmi %d:%d", - ar->id.bmi_chip_id, ar->id.bmi_board_id); - else - scnprintf(boardinfo, sizeof(boardinfo), "sub %04x:%04x", - ar->id.subsystem_vendor, ar->id.subsystem_device); - - ath10k_info(ar, "%s (0x%08x, 0x%08x %s) fw %s fwapi %d bdapi %d htt-ver %d.%d wmi-op %d htt-op %d cal %s max-sta %d raw %d hwcrypto %d features %s\n", + ath10k_info(ar, "%s target 0x%08x chip_id 0x%08x sub %04x:%04x", ar->hw_params.name, ar->target_version, ar->chip_id, - boardinfo, + ar->id.subsystem_vendor, ar->id.subsystem_device); + + ath10k_info(ar, "kconfig debug %d debugfs %d tracing %d dfs %d testmode %d\n", + IS_ENABLED(CONFIG_ATH10K_DEBUG), + IS_ENABLED(CONFIG_ATH10K_DEBUGFS), + IS_ENABLED(CONFIG_ATH10K_TRACING), + IS_ENABLED(CONFIG_ATH10K_DFS_CERTIFIED), + IS_ENABLED(CONFIG_NL80211_TESTMODE)); + + firmware = ar->normal_mode_fw.fw_file.firmware; + if (firmware) + crc = crc32_le(0, firmware->data, firmware->size); + + ath10k_info(ar, "firmware ver %s api %d features %s crc32 %08x\n", ar->hw->wiphy->fw_version, ar->fw_api, + fw_features, + crc); +} + +void ath10k_debug_print_board_info(struct ath10k *ar) +{ + char boardinfo[100]; + + if (ar->id.bmi_ids_valid) + scnprintf(boardinfo, sizeof(boardinfo), "%d:%d", + ar->id.bmi_chip_id, ar->id.bmi_board_id); + else + scnprintf(boardinfo, sizeof(boardinfo), "N/A"); + + ath10k_info(ar, "board_file api %d bmi_id %s crc32 %08x", ar->bd_api, + boardinfo, + crc32_le(0, ar->normal_mode_fw.board->data, + ar->normal_mode_fw.board->size)); +} + +void ath10k_debug_print_boot_info(struct ath10k *ar) +{ + ath10k_info(ar, "htt-ver %d.%d wmi-op %d htt-op %d cal %s max-sta %d raw %d hwcrypto %d\n", ar->htt.target_version_major, ar->htt.target_version_minor, - ar->wmi.op_version, - ar->htt.op_version, + ar->normal_mode_fw.fw_file.wmi_op_version, + ar->normal_mode_fw.fw_file.htt_op_version, ath10k_cal_mode_str(ar->cal_mode), ar->max_num_stations, test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags), - !test_bit(ATH10K_FLAG_HW_CRYPTO_DISABLED, &ar->dev_flags), - fw_features); - ath10k_info(ar, "debug %d debugfs %d tracing %d dfs %d testmode %d\n", - config_enabled(CONFIG_ATH10K_DEBUG), - config_enabled(CONFIG_ATH10K_DEBUGFS), - config_enabled(CONFIG_ATH10K_TRACING), - config_enabled(CONFIG_ATH10K_DFS_CERTIFIED), - config_enabled(CONFIG_NL80211_TESTMODE)); + !test_bit(ATH10K_FLAG_HW_CRYPTO_DISABLED, &ar->dev_flags)); +} + +void ath10k_print_driver_info(struct ath10k *ar) +{ + ath10k_debug_print_hwfw_info(ar); + ath10k_debug_print_board_info(ar); + ath10k_debug_print_boot_info(ar); } EXPORT_SYMBOL(ath10k_print_driver_info); @@ -251,7 +286,7 @@ static const struct file_operations fops_wmi_services = { .llseek = default_llseek, }; -static void ath10k_debug_fw_stats_pdevs_free(struct list_head *head) +static void ath10k_fw_stats_pdevs_free(struct list_head *head) { struct ath10k_fw_stats_pdev *i, *tmp; @@ -261,7 +296,7 @@ static void ath10k_debug_fw_stats_pdevs_free(struct list_head *head) } } -static void ath10k_debug_fw_stats_vdevs_free(struct list_head *head) +static void ath10k_fw_stats_vdevs_free(struct list_head *head) { struct ath10k_fw_stats_vdev *i, *tmp; @@ -271,7 +306,7 @@ static void ath10k_debug_fw_stats_vdevs_free(struct list_head *head) } } -static void ath10k_debug_fw_stats_peers_free(struct list_head *head) +static void ath10k_fw_stats_peers_free(struct list_head *head) { struct ath10k_fw_stats_peer *i, *tmp; @@ -281,13 +316,25 @@ static void ath10k_debug_fw_stats_peers_free(struct list_head *head) } } +static void ath10k_fw_extd_stats_peers_free(struct list_head *head) +{ + struct ath10k_fw_extd_stats_peer *i, *tmp; + + list_for_each_entry_safe(i, tmp, head, list) { + list_del(&i->list); + kfree(i); + } +} + static void ath10k_debug_fw_stats_reset(struct ath10k *ar) { spin_lock_bh(&ar->data_lock); ar->debug.fw_stats_done = false; - ath10k_debug_fw_stats_pdevs_free(&ar->debug.fw_stats.pdevs); - ath10k_debug_fw_stats_vdevs_free(&ar->debug.fw_stats.vdevs); - ath10k_debug_fw_stats_peers_free(&ar->debug.fw_stats.peers); + ar->debug.fw_stats.extended = false; + ath10k_fw_stats_pdevs_free(&ar->debug.fw_stats.pdevs); + ath10k_fw_stats_vdevs_free(&ar->debug.fw_stats.vdevs); + ath10k_fw_stats_peers_free(&ar->debug.fw_stats.peers); + ath10k_fw_extd_stats_peers_free(&ar->debug.fw_stats.peers_extd); spin_unlock_bh(&ar->data_lock); } @@ -302,6 +349,7 @@ void ath10k_debug_fw_stats_process(struct ath10k *ar, struct sk_buff *skb) INIT_LIST_HEAD(&stats.pdevs); INIT_LIST_HEAD(&stats.vdevs); INIT_LIST_HEAD(&stats.peers); + INIT_LIST_HEAD(&stats.peers_extd); spin_lock_bh(&ar->data_lock); ret = ath10k_wmi_pull_fw_stats(ar, skb, &stats); @@ -321,9 +369,13 @@ void ath10k_debug_fw_stats_process(struct ath10k *ar, struct sk_buff *skb) * b) consume stat update events until another one with pdev stats is * delivered which is treated as end-of-data and is itself discarded */ + if (ath10k_peer_stats_enabled(ar)) + ath10k_sta_update_rx_duration(ar, &stats); if (ar->debug.fw_stats_done) { - ath10k_warn(ar, "received unsolicited stats update event\n"); + if (!ath10k_peer_stats_enabled(ar)) + ath10k_warn(ar, "received unsolicited stats update event\n"); + goto free; } @@ -347,17 +399,21 @@ void ath10k_debug_fw_stats_process(struct ath10k *ar, struct sk_buff *skb) /* Although this is unlikely impose a sane limit to * prevent firmware from DoS-ing the host. */ + ath10k_fw_stats_peers_free(&ar->debug.fw_stats.peers); ath10k_warn(ar, "dropping fw peer stats\n"); goto free; } if (num_vdevs >= BITS_PER_LONG) { + ath10k_fw_stats_vdevs_free(&ar->debug.fw_stats.vdevs); ath10k_warn(ar, "dropping fw vdev stats\n"); goto free; } list_splice_tail_init(&stats.peers, &ar->debug.fw_stats.peers); list_splice_tail_init(&stats.vdevs, &ar->debug.fw_stats.vdevs); + list_splice_tail_init(&stats.peers_extd, + &ar->debug.fw_stats.peers_extd); } complete(&ar->debug.fw_stats_complete); @@ -366,9 +422,10 @@ free: /* In some cases lists have been spliced and cleared. Free up * resources if that is not the case. */ - ath10k_debug_fw_stats_pdevs_free(&stats.pdevs); - ath10k_debug_fw_stats_vdevs_free(&stats.vdevs); - ath10k_debug_fw_stats_peers_free(&stats.peers); + ath10k_fw_stats_pdevs_free(&stats.pdevs); + ath10k_fw_stats_vdevs_free(&stats.vdevs); + ath10k_fw_stats_peers_free(&stats.peers); + ath10k_fw_extd_stats_peers_free(&stats.peers_extd); spin_unlock_bh(&ar->data_lock); } @@ -481,6 +538,230 @@ static const struct file_operations fops_fw_stats = { .llseek = default_llseek, }; +static inline int is_vht_rate_valid(u32 rate_indx) +{ + if ((rate_indx >= ATH10K_RX_MCS_MIN) && + (rate_indx <= ATH10K_RX_VHT_RATEIDX_MAX)) + return 1; + else + return 0; +} + +void fill_datapath_stats(struct ath10k *ar, struct ieee80211_rx_status *status) +{ + struct datapath_rx_stats *stat_cnt = ar->rx_stats; + + spin_lock_bh(&ar->datapath_rx_stat_lock); + + stat_cnt->no_of_packets += 1; + if (!(stat_cnt->no_of_packets)) { + memset(stat_cnt, 0, sizeof(*stat_cnt)); + stat_cnt->no_of_packets += 1; + } + + if (status->flag & RX_FLAG_SHORT_GI) + stat_cnt->short_gi_pkts += 1; + + if ((status->vht_nss >= ATH10K_RX_NSS_MIN) && + (status->vht_nss < ATH10K_RX_NSS_MAX)) { + stat_cnt->nss[status->vht_nss] += 1; + if (status->flag & RX_FLAG_VHT) { + stat_cnt->vht_rate_packets += 1; + if (is_vht_rate_valid(status->rate_idx)) { + stat_cnt->vht_rate_indx[((status->vht_nss - 1) * + 10) + status->rate_idx] += 1; + } else { + /*if we get index other than (>=0 and <=9)*/ + stat_cnt->vht_rate_indx[ATH10K_RX_VHT_MCS_MAX] += 1; + } + } else if (status->flag & RX_FLAG_HT) { + stat_cnt->ht_rate_packets += 1; + if ((status->rate_idx >= ATH10K_RX_MCS_MIN) && + (status->rate_idx < ATH10K_RX_HT_MCS_MAX)) + stat_cnt->ht_rate_indx[status->rate_idx] += 1; + else { + /*if we get index other than (>=0 and <=31)*/ + stat_cnt->ht_rate_indx[ATH10K_RX_HT_MCS_MAX] += 1; + } + } else { + /* if pkt is other than HT and VHT */ + stat_cnt->legacy_pkt += 1; + } + } else { + stat_cnt->nss[ATH10K_RX_NSS_MAX] += 1; + } + + if (status->flag & RX_FLAG_40MHZ) + stat_cnt->num_pkts_40Mhz += 1; + if (status->vht_flag & RX_VHT_FLAG_80MHZ) + stat_cnt->num_pkts_80Mhz += 1; + if ((status->band >= ATH10K_BAND_MIN) && + (status->band < ATH10K_BAND_MAX)) { + stat_cnt->band[status->band] += 1; + } else { + /*if band is other than 0,1 */ + stat_cnt->band[ATH10K_BAND_MAX] += 1; + } + + spin_unlock_bh(&ar->datapath_rx_stat_lock); +} + +size_t get_datapath_stat(char *buf, struct ath10k *ar) +{ + u8 i; + struct datapath_rx_stats *stat_cnt = ar->rx_stats; + size_t j = 0; + + spin_lock(&ar->datapath_rx_stat_lock); + + j = snprintf(buf, ATH10K_DATAPATH_BUF_SIZE, "\nNo of packets: %u\t" + "No of short_gi packets: %u\n" + "\nHT Packets: %u \t VHT Packets: %u\n" + "\n40Mhz Packets: %u \t 80Mhz Packets: %u\n" + "\n2.4GHz: %u \t 5GHz: %u \t band-error: %u\n\n", + stat_cnt->no_of_packets, + stat_cnt->short_gi_pkts, + stat_cnt->ht_rate_packets, + stat_cnt->vht_rate_packets, + stat_cnt->num_pkts_40Mhz, + stat_cnt->num_pkts_80Mhz, + stat_cnt->band[ATH10K_BAND_2GHZ], + stat_cnt->band[ATH10K_BAND_5GHZ], + stat_cnt->band[ATH10K_BAND_MAX]); + + for (i = 0; i <= ATH10K_RX_NSS_MAX; i++) { + j += snprintf(buf + j, (ATH10K_DATAPATH_BUF_SIZE - j), + "NSS-%u: %u\t", i, stat_cnt->nss[i]); + } + + j += snprintf(buf + j, (ATH10K_DATAPATH_BUF_SIZE - j), + "\n\n----HT Rate index------\n"); + + for (i = ATH10K_RX_MCS_MIN; i < ATH10K_RX_HT_MCS_MAX; + i += 4) { + j += snprintf(buf + j, (ATH10K_DATAPATH_BUF_SIZE - j), + "ht_rate_indx[%02u]: %10u\tht_rate_indx[%02u]: %10u\t" + "ht_rate_indx[%02u]: %10u\tht_rate_indx[%02u]: %10u\n", + i, stat_cnt->ht_rate_indx[i], + i + 1, stat_cnt->ht_rate_indx[i + 1], + i + 2, stat_cnt->ht_rate_indx[i + 2], + i + 3, stat_cnt->ht_rate_indx[i + 3]); + } + + j += snprintf(buf + j, (ATH10K_DATAPATH_BUF_SIZE - j), + "ht_rate_indx[OOB]: %10u\n", + stat_cnt->ht_rate_indx[ATH10K_RX_HT_MCS_MAX]); + + j += snprintf(buf + j, (ATH10K_DATAPATH_BUF_SIZE - j), + "\n----VHT Rate index------\n"); + + for (i = ATH10K_RX_MCS_MIN; + i <= ATH10K_RX_VHT_RATEIDX_MAX; i++) { + j += snprintf(buf + j, (ATH10K_DATAPATH_BUF_SIZE - j), + "vht_rate_indx[%02u]: %10u\tvht_rate_indx[%02u]: %10u\n", + i, stat_cnt->vht_rate_indx[i], + i + 10, stat_cnt->vht_rate_indx[i + 10]); + } + + j += snprintf(buf + j, (ATH10K_DATAPATH_BUF_SIZE - j), + "vht_rate_indx[%02u]: %10u\n", + i + 10, stat_cnt->vht_rate_indx[i + 10]); + + j += snprintf(buf + j, (ATH10K_DATAPATH_BUF_SIZE - j), + "\nnumber of pkt other than HT and VHT(legacy) : %u\n" + "----------------------\n", + stat_cnt->legacy_pkt); + + spin_unlock(&ar->datapath_rx_stat_lock); + + return j; +} + +static int ath10k_datapath_stats_open(struct inode *inode, struct file *file) +{ + struct ath10k *ar = inode->i_private; + int ret; + + spin_lock(&ar->datapath_rx_stat_lock); + + if (ar->state != ATH10K_STATE_ON) { + ret = -ENETDOWN; + goto err_unlock; + } + + file->private_data = ar; + + spin_unlock(&ar->datapath_rx_stat_lock); + return 0; + +err_unlock: + spin_unlock(&ar->datapath_rx_stat_lock); + return ret; +} + +static ssize_t ath10k_datapath_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath10k *ar = file->private_data; + size_t buf_len; + unsigned int ret; + void *buf = NULL; + + buf = vmalloc(ATH10K_DATAPATH_BUF_SIZE); + if (!buf) + return 0; + + buf_len = get_datapath_stat(buf, ar); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, buf_len); + vfree(buf); + + return ret; +} + +static ssize_t ath10k_datapath_stats_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath10k *ar = file->private_data; + u32 filter; + int ret; + + if (kstrtouint_from_user(ubuf, count, 0, &filter)) + return -EINVAL; + + spin_lock(&ar->datapath_rx_stat_lock); + + if (ar->state != ATH10K_STATE_ON) { + ret = count; + goto err_unlock; + } + + if (!filter) + memset(ar->rx_stats, 0, sizeof(*ar->rx_stats)); + + ret = count; + +err_unlock: + spin_unlock(&ar->datapath_rx_stat_lock); + return ret; +} + +static int ath10k_datapath_stats_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations fops_datapath_stats = { + .open = ath10k_datapath_stats_open, + .read = ath10k_datapath_stats_read, + .write = ath10k_datapath_stats_write, + .release = ath10k_datapath_stats_release, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + static ssize_t ath10k_debug_fw_reset_stats_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) @@ -571,25 +852,23 @@ static ssize_t ath10k_write_simulate_fw_crash(struct file *file, char buf[32]; int ret; - mutex_lock(&ar->conf_mutex); - simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); /* make sure that buf is null terminated */ buf[sizeof(buf) - 1] = 0; + /* drop the possible '\n' from the end */ + if (buf[count - 1] == '\n') + buf[count - 1] = 0; + + mutex_lock(&ar->conf_mutex); + if (ar->state != ATH10K_STATE_ON && ar->state != ATH10K_STATE_RESTARTED) { ret = -ENETDOWN; goto exit; } - /* drop the possible '\n' from the end */ - if (buf[count - 1] == '\n') { - buf[count - 1] = 0; - count--; - } - if (!strcmp(buf, "soft")) { ath10k_info(ar, "simulating soft firmware crash\n"); ret = ath10k_wmi_force_fw_hang(ar, WMI_FORCE_FW_HANG_ASSERT, 0); @@ -1114,7 +1393,7 @@ static ssize_t ath10k_read_htt_max_amsdu_ampdu(struct file *file, { struct ath10k *ar = file->private_data; char buf[64]; - u8 amsdu = 3, ampdu = 64; + u8 amsdu, ampdu; unsigned int len; mutex_lock(&ar->conf_mutex); @@ -1176,9 +1455,9 @@ static ssize_t ath10k_read_fw_dbglog(struct file *file, { struct ath10k *ar = file->private_data; unsigned int len; - char buf[64]; + char buf[96]; - len = scnprintf(buf, sizeof(buf), "0x%08x %u\n", + len = scnprintf(buf, sizeof(buf), "0x%16llx %u\n", ar->debug.fw_dbglog_mask, ar->debug.fw_dbglog_level); return simple_read_from_buffer(user_buf, count, ppos, buf, len); @@ -1190,15 +1469,16 @@ static ssize_t ath10k_write_fw_dbglog(struct file *file, { struct ath10k *ar = file->private_data; int ret; - char buf[64]; - unsigned int log_level, mask; + char buf[96]; + unsigned int log_level; + u64 mask; simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); /* make sure that buf is null terminated */ buf[sizeof(buf) - 1] = 0; - ret = sscanf(buf, "%x %u", &mask, &log_level); + ret = sscanf(buf, "%llx %u", &mask, &log_level); if (!ret) return -EINVAL; @@ -1398,74 +1678,68 @@ static const struct file_operations fops_fw_dbglog = { .llseek = default_llseek, }; -static int ath10k_debug_cal_data_open(struct inode *inode, struct file *file) +static int ath10k_debug_cal_data_fetch(struct ath10k *ar) { - struct ath10k *ar = inode->i_private; - void *buf; u32 hi_addr; __le32 addr; int ret; - mutex_lock(&ar->conf_mutex); - - if (ar->state != ATH10K_STATE_ON && - ar->state != ATH10K_STATE_UTF) { - ret = -ENETDOWN; - goto err; - } + lockdep_assert_held(&ar->conf_mutex); - buf = vmalloc(QCA988X_CAL_DATA_LEN); - if (!buf) { - ret = -ENOMEM; - goto err; - } + if (WARN_ON(ar->hw_params.cal_data_len > ATH10K_DEBUG_CAL_DATA_LEN)) + return -EINVAL; hi_addr = host_interest_item_address(HI_ITEM(hi_board_data)); ret = ath10k_hif_diag_read(ar, hi_addr, &addr, sizeof(addr)); if (ret) { - ath10k_warn(ar, "failed to read hi_board_data address: %d\n", ret); - goto err_vfree; + ath10k_warn(ar, "failed to read hi_board_data address: %d\n", + ret); + return ret; } - ret = ath10k_hif_diag_read(ar, le32_to_cpu(addr), buf, - QCA988X_CAL_DATA_LEN); + ret = ath10k_hif_diag_read(ar, le32_to_cpu(addr), ar->debug.cal_data, + ar->hw_params.cal_data_len); if (ret) { ath10k_warn(ar, "failed to read calibration data: %d\n", ret); - goto err_vfree; + return ret; } - file->private_data = buf; + return 0; +} - mutex_unlock(&ar->conf_mutex); +static int ath10k_debug_cal_data_open(struct inode *inode, struct file *file) +{ + struct ath10k *ar = inode->i_private; - return 0; + mutex_lock(&ar->conf_mutex); -err_vfree: - vfree(buf); + if (ar->state == ATH10K_STATE_ON || + ar->state == ATH10K_STATE_UTF) { + ath10k_debug_cal_data_fetch(ar); + } -err: + file->private_data = ar; mutex_unlock(&ar->conf_mutex); - return ret; + return 0; } static ssize_t ath10k_debug_cal_data_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { - void *buf = file->private_data; + struct ath10k *ar = file->private_data; - return simple_read_from_buffer(user_buf, count, ppos, - buf, QCA988X_CAL_DATA_LEN); -} + mutex_lock(&ar->conf_mutex); -static int ath10k_debug_cal_data_release(struct inode *inode, - struct file *file) -{ - vfree(file->private_data); + count = simple_read_from_buffer(user_buf, count, ppos, + ar->debug.cal_data, + ar->hw_params.cal_data_len); - return 0; + mutex_unlock(&ar->conf_mutex); + + return count; } static ssize_t ath10k_write_ani_enable(struct file *file, @@ -1523,10 +1797,67 @@ static const struct file_operations fops_ani_enable = { .llseek = default_llseek, }; +static ssize_t ath10k_write_sifs_burst_enable(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath10k *ar = file->private_data; + int ret; + u8 enable; + + if (kstrtou8_from_user(user_buf, count, 0, &enable)) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + + if (ar->sifs_burst_enabled == enable) { + ret = count; + goto exit; + } + + ret = ath10k_wmi_pdev_set_param(ar, ar->wmi.pdev_param->burst_enable, + enable); + if (ret) { + ath10k_warn(ar, "sifs_burst_enable failed: %d\n", ret); + goto exit; + } + ar->sifs_burst_enabled = enable; + + ret = count; + +exit: + mutex_unlock(&ar->conf_mutex); + + return ret; +} + +static ssize_t ath10k_read_sifs_burst_enable(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath10k *ar = file->private_data; + char buf[32]; + int len; + bool ret = false; + + if (ar->sifs_burst_enabled) + ret = true; + + len = scnprintf(buf, sizeof(buf), "%d\n", ret); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_sifs_burst_enable = { + .read = ath10k_read_sifs_burst_enable, + .write = ath10k_write_sifs_burst_enable, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + static const struct file_operations fops_cal_data = { .open = ath10k_debug_cal_data_open, .read = ath10k_debug_cal_data_read, - .release = ath10k_debug_cal_data_release, .owner = THIS_MODULE, .llseek = default_llseek, }; @@ -1861,7 +2192,7 @@ int ath10k_debug_start(struct ath10k *ar) ath10k_warn(ar, "failed to disable pktlog: %d\n", ret); } - if (ar->debug.nf_cal_period) { + if (ar->debug.nf_cal_period && !QCA_REV_WCN3990(ar)) { ret = ath10k_wmi_pdev_set_param(ar, ar->wmi.pdev_param->cal_period, ar->debug.nf_cal_period); @@ -1878,6 +2209,9 @@ void ath10k_debug_stop(struct ath10k *ar) { lockdep_assert_held(&ar->conf_mutex); + if (!QCA_REV_WCN3990(ar)) + ath10k_debug_cal_data_fetch(ar); + /* Must not use _sync to avoid deadlock, we do that in * ath10k_debug_destroy(). The check for htt_stats_mask is to avoid * warning from del_timer(). */ @@ -2088,17 +2422,410 @@ static const struct file_operations fops_quiet_period = { .open = simple_open }; +static ssize_t ath10k_write_btcoex(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath10k *ar = file->private_data; + char buf[32]; + size_t buf_size; + int ret; + bool val; + u32 pdev_param; + + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, ubuf, buf_size)) + return -EFAULT; + + buf[buf_size] = '\0'; + + if (strtobool(buf, &val) != 0) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + + if (ar->state != ATH10K_STATE_ON && + ar->state != ATH10K_STATE_RESTARTED) { + ret = -ENETDOWN; + goto exit; + } + + if (!(test_bit(ATH10K_FLAG_BTCOEX, &ar->dev_flags) ^ val)) { + ret = count; + goto exit; + } + + pdev_param = ar->wmi.pdev_param->enable_btcoex; + if (test_bit(ATH10K_FW_FEATURE_BTCOEX_PARAM, + ar->running_fw->fw_file.fw_features)) { + ret = ath10k_wmi_pdev_set_param(ar, pdev_param, val); + if (ret) { + ath10k_warn(ar, "failed to enable btcoex: %d\n", ret); + ret = count; + goto exit; + } + } else { + ath10k_info(ar, "restarting firmware due to btcoex change"); + queue_work(ar->workqueue, &ar->restart_work); + } + + if (val) + set_bit(ATH10K_FLAG_BTCOEX, &ar->dev_flags); + else + clear_bit(ATH10K_FLAG_BTCOEX, &ar->dev_flags); + + ret = count; + +exit: + mutex_unlock(&ar->conf_mutex); + + return ret; +} + +static ssize_t ath10k_read_btcoex(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char buf[32]; + struct ath10k *ar = file->private_data; + int len = 0; + + mutex_lock(&ar->conf_mutex); + len = scnprintf(buf, sizeof(buf) - len, "%d\n", + test_bit(ATH10K_FLAG_BTCOEX, &ar->dev_flags)); + mutex_unlock(&ar->conf_mutex); + + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static const struct file_operations fops_btcoex = { + .read = ath10k_read_btcoex, + .write = ath10k_write_btcoex, + .open = simple_open +}; + +static ssize_t ath10k_write_peer_stats(struct file *file, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ath10k *ar = file->private_data; + char buf[32]; + size_t buf_size; + int ret; + bool val; + + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, ubuf, buf_size)) + return -EFAULT; + + buf[buf_size] = '\0'; + + if (strtobool(buf, &val) != 0) + return -EINVAL; + + mutex_lock(&ar->conf_mutex); + + if (ar->state != ATH10K_STATE_ON && + ar->state != ATH10K_STATE_RESTARTED) { + ret = -ENETDOWN; + goto exit; + } + + if (!(test_bit(ATH10K_FLAG_PEER_STATS, &ar->dev_flags) ^ val)) { + ret = count; + goto exit; + } + + if (val) + set_bit(ATH10K_FLAG_PEER_STATS, &ar->dev_flags); + else + clear_bit(ATH10K_FLAG_PEER_STATS, &ar->dev_flags); + + ath10k_info(ar, "restarting firmware due to Peer stats change"); + + queue_work(ar->workqueue, &ar->restart_work); + ret = count; + +exit: + mutex_unlock(&ar->conf_mutex); + return ret; +} + +static ssize_t ath10k_read_peer_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) + +{ + char buf[32]; + struct ath10k *ar = file->private_data; + int len = 0; + + mutex_lock(&ar->conf_mutex); + len = scnprintf(buf, sizeof(buf) - len, "%d\n", + test_bit(ATH10K_FLAG_PEER_STATS, &ar->dev_flags)); + mutex_unlock(&ar->conf_mutex); + + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static const struct file_operations fops_peer_stats = { + .read = ath10k_read_peer_stats, + .write = ath10k_write_peer_stats, + .open = simple_open +}; + +static ssize_t ath10k_debug_fw_checksums_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath10k *ar = file->private_data; + unsigned int len = 0, buf_len = 4096; + ssize_t ret_cnt; + char *buf; + + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&ar->conf_mutex); + + len += scnprintf(buf + len, buf_len - len, + "firmware-N.bin\t\t%08x\n", + crc32_le(0, ar->normal_mode_fw.fw_file.firmware->data, + ar->normal_mode_fw.fw_file.firmware->size)); + len += scnprintf(buf + len, buf_len - len, + "athwlan\t\t\t%08x\n", + crc32_le(0, ar->normal_mode_fw.fw_file.firmware_data, + ar->normal_mode_fw.fw_file.firmware_len)); + len += scnprintf(buf + len, buf_len - len, + "otp\t\t\t%08x\n", + crc32_le(0, ar->normal_mode_fw.fw_file.otp_data, + ar->normal_mode_fw.fw_file.otp_len)); + len += scnprintf(buf + len, buf_len - len, + "codeswap\t\t%08x\n", + crc32_le(0, ar->normal_mode_fw.fw_file.codeswap_data, + ar->normal_mode_fw.fw_file.codeswap_len)); + len += scnprintf(buf + len, buf_len - len, + "board-N.bin\t\t%08x\n", + crc32_le(0, ar->normal_mode_fw.board->data, + ar->normal_mode_fw.board->size)); + len += scnprintf(buf + len, buf_len - len, + "board\t\t\t%08x\n", + crc32_le(0, ar->normal_mode_fw.board_data, + ar->normal_mode_fw.board_len)); + + ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + mutex_unlock(&ar->conf_mutex); + + kfree(buf); + return ret_cnt; +} + +static const struct file_operations fops_fw_checksums = { + .read = ath10k_debug_fw_checksums_read, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static struct txctl_frm_hdr frm_hdr; + +static void ath10k_extract_frame_header(u8 *addr1, u8 *addr2, u8 *addr3) +{ + frm_hdr.bssid_tail = (addr1[IEEE80211_ADDR_LEN - 2] << BITS_PER_BYTE) + | (addr1[IEEE80211_ADDR_LEN - 1]); + frm_hdr.sa_tail = (addr2[IEEE80211_ADDR_LEN - 2] << BITS_PER_BYTE) + | (addr2[IEEE80211_ADDR_LEN - 1]); + frm_hdr.da_tail = (addr3[IEEE80211_ADDR_LEN - 2] << BITS_PER_BYTE) + | (addr3[IEEE80211_ADDR_LEN - 1]); +} + +static void ath10k_process_ieee_hdr(void *data) +{ + u8 dir; + struct ieee80211_frame *wh; + + if (!data) + return; + + wh = (struct ieee80211_frame *)(data); + frm_hdr.framectrl = *(u_int16_t *)(wh->i_fc); + frm_hdr.seqctrl = *(u_int16_t *)(wh->i_seq); + dir = (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK); + + if (dir == IEEE80211_FC1_DIR_TODS) + ath10k_extract_frame_header(wh->i_addr1, wh->i_addr2, + wh->i_addr3); + else if (dir == IEEE80211_FC1_DIR_FROMDS) + ath10k_extract_frame_header(wh->i_addr2, wh->i_addr3, + wh->i_addr1); + else + ath10k_extract_frame_header(wh->i_addr3, wh->i_addr2, + wh->i_addr1); +} + +static void ath10k_pktlog_process_rx(struct ath10k *ar, struct sk_buff *skb) +{ + struct ath10k_pktlog_hdr *hdr = (void *)skb->data; + struct ath_pktlog_txctl pktlog_tx_ctrl; + + switch (hdr->log_type) { + case ATH10K_PKTLOG_TYPE_TX_CTRL: { + spin_lock_bh(&ar->htt.tx_lock); + + memcpy((void *)(&pktlog_tx_ctrl.hdr), (void *)hdr, + sizeof(pktlog_tx_ctrl.hdr)); + pktlog_tx_ctrl.frm_hdr = frm_hdr; + memcpy((void *)pktlog_tx_ctrl.txdesc_ctl, (void *)hdr->payload, + __le16_to_cpu(hdr->size)); + pktlog_tx_ctrl.hdr.size = sizeof(pktlog_tx_ctrl) - + sizeof(pktlog_tx_ctrl.hdr); + + spin_unlock_bh(&ar->htt.tx_lock); + + trace_ath10k_htt_pktlog(ar, (void *)&pktlog_tx_ctrl, + sizeof(pktlog_tx_ctrl)); + break; + } + case ATH10K_PKTLOG_TYPE_TX_MSDU_ID: + break; + case ATH10K_PKTLOG_TYPE_TX_FRM_HDR: { + ath10k_process_ieee_hdr((void *)(hdr->payload)); + trace_ath10k_htt_pktlog(ar, hdr, sizeof(*hdr) + + __le16_to_cpu(hdr->size)); + break; + } + case ATH10K_PKTLOG_TYPE_RX_STAT: + case ATH10K_PKTLOG_TYPE_RC_FIND: + case ATH10K_PKTLOG_TYPE_RC_UPDATE: + case ATH10K_PKTLOG_TYPE_DBG_PRINT: + case ATH10K_PKTLOG_TYPE_TX_STAT: + case ATH10K_PKTLOG_TYPE_SW_EVENT: + trace_ath10k_htt_pktlog(ar, hdr, sizeof(*hdr) + + __le16_to_cpu(hdr->size)); + break; + case ATH10K_PKTLOG_TYPE_TX_VIRT_ADDR: { + u32 desc_id = (u32)*((u32 *)(hdr->payload)); + struct sk_buff *msdu; + + spin_lock_bh(&ar->htt.tx_lock); + msdu = ath10k_htt_tx_find_msdu_by_id(&ar->htt, desc_id); + + if (!msdu) { + ath10k_info(ar, + "Failed to get msdu, id: %d\n", + desc_id); + spin_unlock_bh(&ar->htt.tx_lock); + return; + } + ath10k_process_ieee_hdr((void *)msdu->data); + spin_unlock_bh(&ar->htt.tx_lock); + trace_ath10k_htt_pktlog(ar, hdr, sizeof(*hdr) + + __le16_to_cpu(hdr->size)); + break; + } + } +} + +int ath10k_rx_record_pktlog(struct ath10k *ar, struct sk_buff *skb) +{ + struct sk_buff *pktlog_skb; + struct ath_pktlog_hdr *pl_hdr; + struct ath_pktlog_rx_info *pktlog_rx_info; + struct htt_rx_desc *rx_desc = (void *)skb->data - sizeof(*rx_desc); + + if (!ar->debug.pktlog_filter) + return 0; + + pktlog_skb = dev_alloc_skb(sizeof(struct ath_pktlog_hdr) + + sizeof(struct htt_rx_desc) - + sizeof(struct htt_host_fw_desc_base)); + if (!pktlog_skb) + return -ENOMEM; + + pktlog_rx_info = (struct ath_pktlog_rx_info *)pktlog_skb->data; + pl_hdr = &pktlog_rx_info->pl_hdr; + + pl_hdr->flags = (1 << ATH10K_PKTLOG_FLG_FRM_TYPE_REMOTE_S); + pl_hdr->missed_cnt = 0; + pl_hdr->mac_id = 0; + pl_hdr->log_type = ATH10K_PKTLOG_TYPE_RX_STAT; + pl_hdr->flags |= ATH10K_PKTLOG_HDR_SIZE_16; + pl_hdr->size = sizeof(*rx_desc) - + sizeof(struct htt_host_fw_desc_base); + + pl_hdr->timestamp = + cpu_to_le32(rx_desc->ppdu_end.wcn3990.rx_pkt_end.phy_timestamp_1); + + pl_hdr->type_specific_data = 0xDEADAA; + memcpy((void *)pktlog_rx_info + sizeof(struct ath_pktlog_hdr), + (void *)rx_desc + sizeof(struct htt_host_fw_desc_base), + pl_hdr->size); + + ath10k_pktlog_process_rx(ar, pktlog_skb); + dev_kfree_skb_any(pktlog_skb); + return 0; +} + +static void ath10k_pktlog_htc_tx_complete(struct ath10k *ar, + struct sk_buff *skb) +{ + ath10k_info(ar, "PKTLOG htc completed\n"); +} + +int ath10k_pktlog_connect(struct ath10k *ar) +{ + int status; + struct ath10k_htc_svc_conn_req conn_req; + struct ath10k_htc_svc_conn_resp conn_resp; + + memset(&conn_req, 0, sizeof(conn_req)); + memset(&conn_resp, 0, sizeof(conn_resp)); + + conn_req.ep_ops.ep_tx_complete = ath10k_pktlog_htc_tx_complete; + conn_req.ep_ops.ep_rx_complete = ath10k_pktlog_process_rx; + conn_req.ep_ops.ep_tx_credits = NULL; + + /* connect to control service */ + conn_req.service_id = ATH10K_HTC_SVC_ID_HTT_LOG_MSG; + status = ath10k_htc_connect_service(&ar->htc, &conn_req, &conn_resp); + if (status) { + ath10k_warn(ar, "failed to connect to PKTLOG service: %d\n", + status); + return status; + } + + ar->debug.eid = conn_resp.eid; + + return 0; +} + int ath10k_debug_create(struct ath10k *ar) { ar->debug.fw_crash_data = vzalloc(sizeof(*ar->debug.fw_crash_data)); if (!ar->debug.fw_crash_data) return -ENOMEM; + ar->debug.cal_data = vzalloc(ATH10K_DEBUG_CAL_DATA_LEN); + if (!ar->debug.cal_data) + goto err_cal_data; + + ar->rx_stats = vzalloc(sizeof(*ar->rx_stats)); + if (!ar->rx_stats) + goto err_rx_stats; + INIT_LIST_HEAD(&ar->debug.fw_stats.pdevs); INIT_LIST_HEAD(&ar->debug.fw_stats.vdevs); INIT_LIST_HEAD(&ar->debug.fw_stats.peers); + INIT_LIST_HEAD(&ar->debug.fw_stats.peers_extd); return 0; + +err_rx_stats: + vfree(ar->debug.cal_data); + +err_cal_data: + vfree(ar->debug.fw_crash_data); + return -ENOMEM; } void ath10k_debug_destroy(struct ath10k *ar) @@ -2106,6 +2833,12 @@ void ath10k_debug_destroy(struct ath10k *ar) vfree(ar->debug.fw_crash_data); ar->debug.fw_crash_data = NULL; + vfree(ar->debug.cal_data); + ar->debug.cal_data = NULL; + + vfree(ar->rx_stats); + ar->rx_stats = NULL; + ath10k_debug_fw_stats_reset(ar); kfree(ar->debug.tpc_stats); @@ -2128,6 +2861,9 @@ int ath10k_debug_register(struct ath10k *ar) init_completion(&ar->debug.tpc_complete); init_completion(&ar->debug.fw_stats_complete); + debugfs_create_file("datapath_rx_stats", S_IRUSR, ar->debug.debugfs_phy, + ar, &fops_datapath_stats); + debugfs_create_file("fw_stats", S_IRUSR, ar->debug.debugfs_phy, ar, &fops_fw_stats); @@ -2137,8 +2873,8 @@ int ath10k_debug_register(struct ath10k *ar) debugfs_create_file("wmi_services", S_IRUSR, ar->debug.debugfs_phy, ar, &fops_wmi_services); - debugfs_create_file("simulate_fw_crash", S_IRUSR, ar->debug.debugfs_phy, - ar, &fops_simulate_fw_crash); + debugfs_create_file("simulate_fw_crash", S_IRUSR | S_IWUSR, + ar->debug.debugfs_phy, ar, &fops_simulate_fw_crash); debugfs_create_file("fw_crash_dump", S_IRUSR, ar->debug.debugfs_phy, ar, &fops_fw_crash_dump); @@ -2155,26 +2891,32 @@ int ath10k_debug_register(struct ath10k *ar) debugfs_create_file("chip_id", S_IRUSR, ar->debug.debugfs_phy, ar, &fops_chip_id); - debugfs_create_file("htt_stats_mask", S_IRUSR, ar->debug.debugfs_phy, - ar, &fops_htt_stats_mask); + debugfs_create_file("htt_stats_mask", S_IRUSR | S_IWUSR, + ar->debug.debugfs_phy, ar, &fops_htt_stats_mask); debugfs_create_file("htt_max_amsdu_ampdu", S_IRUSR | S_IWUSR, ar->debug.debugfs_phy, ar, &fops_htt_max_amsdu_ampdu); - debugfs_create_file("fw_dbglog", S_IRUSR, ar->debug.debugfs_phy, - ar, &fops_fw_dbglog); + debugfs_create_file("fw_dbglog", S_IRUSR | S_IWUSR, + ar->debug.debugfs_phy, ar, &fops_fw_dbglog); - debugfs_create_file("cal_data", S_IRUSR, ar->debug.debugfs_phy, - ar, &fops_cal_data); + if (!QCA_REV_WCN3990(ar)) { + debugfs_create_file("cal_data", S_IRUSR, ar->debug.debugfs_phy, + ar, &fops_cal_data); + + debugfs_create_file("nf_cal_period", S_IRUSR | S_IWUSR, + ar->debug.debugfs_phy, ar, + &fops_nf_cal_period); + } debugfs_create_file("ani_enable", S_IRUSR | S_IWUSR, ar->debug.debugfs_phy, ar, &fops_ani_enable); - debugfs_create_file("nf_cal_period", S_IRUSR | S_IWUSR, - ar->debug.debugfs_phy, ar, &fops_nf_cal_period); + debugfs_create_file("sifs_burst_enable", S_IRUSR | S_IWUSR, + ar->debug.debugfs_phy, ar, &fops_sifs_burst_enable); - if (config_enabled(CONFIG_ATH10K_DFS_CERTIFIED)) { + if (IS_ENABLED(CONFIG_ATH10K_DFS_CERTIFIED)) { debugfs_create_file("dfs_simulate_radar", S_IWUSR, ar->debug.debugfs_phy, ar, &fops_simulate_radar); @@ -2197,6 +2939,18 @@ int ath10k_debug_register(struct ath10k *ar) debugfs_create_file("tpc_stats", S_IRUSR, ar->debug.debugfs_phy, ar, &fops_tpc_stats); + if (test_bit(WMI_SERVICE_COEX_GPIO, ar->wmi.svc_map)) + debugfs_create_file("btcoex", S_IRUGO | S_IWUSR, + ar->debug.debugfs_phy, ar, &fops_btcoex); + + if (test_bit(WMI_SERVICE_PEER_STATS, ar->wmi.svc_map)) + debugfs_create_file("peer_stats", S_IRUGO | S_IWUSR, + ar->debug.debugfs_phy, ar, + &fops_peer_stats); + + debugfs_create_file("fw_checksums", S_IRUSR, + ar->debug.debugfs_phy, ar, &fops_fw_checksums); + return 0; } diff --git a/drivers/net/wireless/ath/ath10k/debug.h b/drivers/net/wireless/ath/ath10k/debug.h index 7de780c4ec8d..eb47720bbf91 100644 --- a/drivers/net/wireless/ath/ath10k/debug.h +++ b/drivers/net/wireless/ath/ath10k/debug.h @@ -37,6 +37,8 @@ enum ath10k_debug_mask { ATH10K_DBG_TESTMODE = 0x00001000, ATH10K_DBG_WMI_PRINT = 0x00002000, ATH10K_DBG_PCI_PS = 0x00004000, + ATH10K_DBG_AHB = 0x00008000, + ATH10K_DBG_SNOC = 0x00010000, ATH10K_DBG_ANY = 0xffffffff, }; @@ -55,14 +57,97 @@ enum ath10k_dbg_aggr_mode { ATH10K_DBG_AGGR_MODE_MAX, }; +#define IEEE80211_FC1_DIR_MASK 0x03 +#define IEEE80211_FC1_DIR_NODS 0x00 /* STA->STA */ +#define IEEE80211_FC1_DIR_TODS 0x01 /* STA->AP */ +#define IEEE80211_FC1_DIR_FROMDS 0x02 /* AP ->STA */ +#define IEEE80211_FC1_DIR_DSTODS 0x03 /* AP ->AP */ +#define IEEE80211_ADDR_LEN 6 /* size of 802.11 address */ + +#define MAX_PKT_INFO_MSDU_ID 192 +#define MSDU_ID_INFO_ID_OFFSET \ + ((MAX_PKT_INFO_MSDU_ID >> 3) + 4) + +#define PKTLOG_MAX_TXCTL_WORDS 57 /* +2 words for bitmap */ +#define HTT_TX_MSDU_LEN_MASK 0xffff + +struct txctl_frm_hdr { + __le16 framectrl; /* frame control field from header */ + __le16 seqctrl; /* frame control field from header */ + __le16 bssid_tail; /* last two octets of bssid */ + __le16 sa_tail; /* last two octets of SA */ + __le16 da_tail; /* last two octets of DA */ + __le16 resvd; +} __packed; + +struct ath_pktlog_hdr { + __le16 flags; + __le16 missed_cnt; + u8 log_type; + u8 mac_id; + __le16 size; + __le32 timestamp; + __le32 type_specific_data; +} __packed; + +/* generic definitions for IEEE 802.11 frames */ +struct ieee80211_frame { + u8 i_fc[2]; + u8 i_dur[2]; + union { + struct { + u8 i_addr1[IEEE80211_ADDR_LEN]; + u8 i_addr2[IEEE80211_ADDR_LEN]; + u8 i_addr3[IEEE80211_ADDR_LEN]; + }; + u8 i_addr_all[3 * IEEE80211_ADDR_LEN]; + }; + u8 i_seq[2]; +} __packed; + +struct fw_pktlog_msdu_info { + __le32 num_msdu; + u8 bound_bmap[MAX_PKT_INFO_MSDU_ID >> 3]; + __le16 id[MAX_PKT_INFO_MSDU_ID]; +} __packed; + +struct ath_pktlog_txctl { + struct ath_pktlog_hdr hdr; + struct txctl_frm_hdr frm_hdr; + __le32 txdesc_ctl[PKTLOG_MAX_TXCTL_WORDS]; +} __packed; + +struct ath_pktlog_msdu_id { + struct ath_pktlog_hdr hdr; + struct fw_pktlog_msdu_info msdu_info; +} __packed; + +struct ath_pktlog_rx_info { + struct ath_pktlog_hdr pl_hdr; + struct rx_attention attention; + struct rx_frag_info frag_info; + struct rx_mpdu_start mpdu_start; + struct rx_msdu_start msdu_start; + struct rx_msdu_end msdu_end; + struct rx_mpdu_end mpdu_end; + struct rx_ppdu_start ppdu_start; + struct rx_ppdu_end ppdu_end; + u8 rx_hdr_status[RX_HTT_HDR_STATUS_LEN]; +} __packed; + /* FIXME: How to calculate the buffer size sanely? */ -#define ATH10K_FW_STATS_BUF_SIZE (1024*1024) +#define ATH10K_FW_STATS_BUF_SIZE (1024 * 1024) +#define ATH10K_DATAPATH_BUF_SIZE (1024 * 1024) extern unsigned int ath10k_debug_mask; __printf(2, 3) void ath10k_info(struct ath10k *ar, const char *fmt, ...); __printf(2, 3) void ath10k_err(struct ath10k *ar, const char *fmt, ...); __printf(2, 3) void ath10k_warn(struct ath10k *ar, const char *fmt, ...); + +void ath10k_debug_print_hwfw_info(struct ath10k *ar); +void ath10k_debug_print_board_info(struct ath10k *ar); +void ath10k_debug_print_boot_info(struct ath10k *ar); void ath10k_print_driver_info(struct ath10k *ar); #ifdef CONFIG_ATH10K_DEBUGFS @@ -79,6 +164,7 @@ struct ath10k_fw_crash_data * ath10k_debug_get_new_fw_crash_data(struct ath10k *ar); void ath10k_debug_dbglog_add(struct ath10k *ar, u8 *buffer, int len); +int ath10k_rx_record_pktlog(struct ath10k *ar, struct sk_buff *skb); #define ATH10K_DFS_STAT_INC(ar, c) (ar->debug.dfs_stats.c++) void ath10k_debug_get_et_strings(struct ieee80211_hw *hw, @@ -89,6 +175,9 @@ int ath10k_debug_get_et_sset_count(struct ieee80211_hw *hw, void ath10k_debug_get_et_stats(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ethtool_stats *stats, u64 *data); +void fill_datapath_stats(struct ath10k *ar, struct ieee80211_rx_status *status); +size_t get_datapath_stat(char *buf, struct ath10k *ar); +int ath10k_pktlog_connect(struct ath10k *ar); #else static inline int ath10k_debug_start(struct ath10k *ar) { @@ -133,12 +222,32 @@ static inline void ath10k_debug_dbglog_add(struct ath10k *ar, u8 *buffer, { } +static inline int ath10k_rx_record_pktlog(struct ath10k *ar, + struct sk_buff *skb) +{ + return 0; +} + static inline struct ath10k_fw_crash_data * ath10k_debug_get_new_fw_crash_data(struct ath10k *ar) { return NULL; } +static inline void fill_datapath_stats(struct ath10k *ar, + struct ieee80211_rx_status *status) +{ +} + +static inline size_t get_datapath_stat(char *buf, struct ath10k *ar) +{ + return 0; +} + +static inline int ath10k_pktlog_connect(struct ath10k *ar) +{ + return 0; +} #define ATH10K_DFS_STAT_INC(ar, c) do { } while (0) #define ath10k_debug_get_et_strings NULL @@ -149,6 +258,17 @@ ath10k_debug_get_new_fw_crash_data(struct ath10k *ar) #ifdef CONFIG_MAC80211_DEBUGFS void ath10k_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct dentry *dir); +void ath10k_sta_update_rx_duration(struct ath10k *ar, + struct ath10k_fw_stats *stats); +void ath10k_sta_statistics(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct station_info *sinfo); +#else +static inline +void ath10k_sta_update_rx_duration(struct ath10k *ar, + struct ath10k_fw_stats *stats) +{ +} #endif /* CONFIG_MAC80211_DEBUGFS */ #ifdef CONFIG_ATH10K_DEBUG diff --git a/drivers/net/wireless/ath/ath10k/debugfs_sta.c b/drivers/net/wireless/ath/ath10k/debugfs_sta.c index 95b5c49374e0..9955fea0802a 100644 --- a/drivers/net/wireless/ath/ath10k/debugfs_sta.c +++ b/drivers/net/wireless/ath/ath10k/debugfs_sta.c @@ -18,6 +18,67 @@ #include "wmi-ops.h" #include "debug.h" +static void ath10k_sta_update_extd_stats_rx_duration(struct ath10k *ar, + struct ath10k_fw_stats *stats) +{ + struct ath10k_fw_extd_stats_peer *peer; + struct ieee80211_sta *sta; + struct ath10k_sta *arsta; + + rcu_read_lock(); + list_for_each_entry(peer, &stats->peers_extd, list) { + sta = ieee80211_find_sta_by_ifaddr(ar->hw, peer->peer_macaddr, + NULL); + if (!sta) + continue; + arsta = (struct ath10k_sta *)sta->drv_priv; + arsta->rx_duration += (u64)peer->rx_duration; + } + rcu_read_unlock(); +} + +static void ath10k_sta_update_stats_rx_duration(struct ath10k *ar, + struct ath10k_fw_stats *stats) +{ + struct ath10k_fw_stats_peer *peer; + struct ieee80211_sta *sta; + struct ath10k_sta *arsta; + + rcu_read_lock(); + list_for_each_entry(peer, &stats->peers, list) { + sta = ieee80211_find_sta_by_ifaddr(ar->hw, peer->peer_macaddr, + NULL); + if (!sta) + continue; + arsta = (struct ath10k_sta *)sta->drv_priv; + arsta->rx_duration += (u64)peer->rx_duration; + } + rcu_read_unlock(); +} + +void ath10k_sta_update_rx_duration(struct ath10k *ar, + struct ath10k_fw_stats *stats) +{ + if (stats->extended) + ath10k_sta_update_extd_stats_rx_duration(ar, stats); + else + ath10k_sta_update_stats_rx_duration(ar, stats); +} + +void ath10k_sta_statistics(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct station_info *sinfo) +{ + struct ath10k_sta *arsta = (struct ath10k_sta *)sta->drv_priv; + struct ath10k *ar = arsta->arvif->ar; + + if (!ath10k_peer_stats_enabled(ar)) + return; + + sinfo->rx_duration = arsta->rx_duration; + sinfo->filled |= 1ULL << NL80211_STA_INFO_RX_DURATION; +} + static ssize_t ath10k_dbg_sta_read_aggr_mode(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) diff --git a/drivers/net/wireless/ath/ath10k/hif.h b/drivers/net/wireless/ath/ath10k/hif.h index 89e7076c919f..65723124985e 100644 --- a/drivers/net/wireless/ath/ath10k/hif.h +++ b/drivers/net/wireless/ath/ath10k/hif.h @@ -26,7 +26,7 @@ struct ath10k_hif_sg_item { u16 transfer_id; void *transfer_context; /* NULL = tx completion callback not called */ void *vaddr; /* for debugging mostly */ - u32 paddr; + dma_addr_t paddr; u16 len; }; @@ -87,6 +87,10 @@ struct ath10k_hif_ops { int (*suspend)(struct ath10k *ar); int (*resume)(struct ath10k *ar); + + /* fetch calibration data from target eeprom */ + int (*fetch_cal_eeprom)(struct ath10k *ar, void **data, + size_t *data_len); }; static inline int ath10k_hif_tx_sg(struct ath10k *ar, u8 pipe_id, @@ -202,4 +206,14 @@ static inline void ath10k_hif_write32(struct ath10k *ar, ar->hif.ops->write32(ar, address, data); } +static inline int ath10k_hif_fetch_cal_eeprom(struct ath10k *ar, + void **data, + size_t *data_len) +{ + if (!ar->hif.ops->fetch_cal_eeprom) + return -EOPNOTSUPP; + + return ar->hif.ops->fetch_cal_eeprom(ar, data, data_len); +} + #endif /* _HIF_H_ */ diff --git a/drivers/net/wireless/ath/ath10k/htc.c b/drivers/net/wireless/ath/ath10k/htc.c index 5b3c6bcf9598..aa20ebbefe94 100644 --- a/drivers/net/wireless/ath/ath10k/htc.c +++ b/drivers/net/wireless/ath/ath10k/htc.c @@ -44,7 +44,7 @@ static struct sk_buff *ath10k_htc_build_tx_ctrl_skb(void *ar) skb_cb = ATH10K_SKB_CB(skb); memset(skb_cb, 0, sizeof(*skb_cb)); - ath10k_dbg(ar, ATH10K_DBG_HTC, "%s: skb %p\n", __func__, skb); + ath10k_dbg(ar, ATH10K_DBG_HTC, "%s: skb %pK\n", __func__, skb); return skb; } @@ -62,7 +62,7 @@ static void ath10k_htc_notify_tx_completion(struct ath10k_htc_ep *ep, { struct ath10k *ar = ep->htc->ar; - ath10k_dbg(ar, ATH10K_DBG_HTC, "%s: ep %d skb %p\n", __func__, + ath10k_dbg(ar, ATH10K_DBG_HTC, "%s: ep %d skb %pK\n", __func__, ep->eid, skb); ath10k_htc_restore_tx_skb(ep->htc, skb); @@ -86,7 +86,8 @@ static void ath10k_htc_prepare_tx_skb(struct ath10k_htc_ep *ep, hdr->eid = ep->eid; hdr->len = __cpu_to_le16(skb->len - sizeof(*hdr)); hdr->flags = 0; - hdr->flags |= ATH10K_HTC_FLAG_NEED_CREDIT_UPDATE; + if (ep->tx_credit_flow_enabled) + hdr->flags |= ATH10K_HTC_FLAG_NEED_CREDIT_UPDATE; spin_lock_bh(&ep->htc->tx_lock); hdr->seq_no = ep->seq_no++; @@ -404,7 +405,7 @@ void ath10k_htc_rx_completion_handler(struct ath10k *ar, struct sk_buff *skb) goto out; } - ath10k_dbg(ar, ATH10K_DBG_HTC, "htc rx completion ep %d skb %p\n", + ath10k_dbg(ar, ATH10K_DBG_HTC, "htc rx completion ep %d skb %pK\n", eid, skb); ep->ep_ops.ep_rx_complete(ar, skb); @@ -451,8 +452,16 @@ static const char *htc_service_name(enum ath10k_htc_svc_id id) return "NMI Data"; case ATH10K_HTC_SVC_ID_HTT_DATA_MSG: return "HTT Data"; + case ATH10K_HTC_SVC_ID_HTT_DATA2_MSG: + return "HTT Data"; + case ATH10K_HTC_SVC_ID_HTT_DATA3_MSG: + return "HTT Data"; + case ATH10K_HTC_SVC_ID_HTT_IPA_MSG: + return "IPA"; case ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS: return "RAW"; + case ATH10K_HTC_SVC_ID_HTT_LOG_MSG: + return "PKTLOG"; } return "Unknown"; diff --git a/drivers/net/wireless/ath/ath10k/htc.h b/drivers/net/wireless/ath/ath10k/htc.h index e70aa38e6e05..5f28099b2c88 100644 --- a/drivers/net/wireless/ath/ath10k/htc.h +++ b/drivers/net/wireless/ath/ath10k/htc.h @@ -22,7 +22,6 @@ #include <linux/list.h> #include <linux/bug.h> #include <linux/skbuff.h> -#include <linux/semaphore.h> #include <linux/timer.h> struct ath10k; @@ -223,7 +222,8 @@ enum ath10k_htc_svc_gid { ATH10K_HTC_SVC_GRP_WMI = 1, ATH10K_HTC_SVC_GRP_NMI = 2, ATH10K_HTC_SVC_GRP_HTT = 3, - + ATH10K_IPA_SERVICE_GROUP = 5, + ATH10K_LOG_SERVICE_GROUP = 6, ATH10K_HTC_SVC_GRP_TEST = 254, ATH10K_HTC_SVC_GRP_LAST = 255, }; @@ -247,7 +247,10 @@ enum ath10k_htc_svc_id { ATH10K_HTC_SVC_ID_NMI_DATA = SVC(ATH10K_HTC_SVC_GRP_NMI, 1), ATH10K_HTC_SVC_ID_HTT_DATA_MSG = SVC(ATH10K_HTC_SVC_GRP_HTT, 0), - + ATH10K_HTC_SVC_ID_HTT_DATA2_MSG = SVC(ATH10K_HTC_SVC_GRP_HTT, 1), + ATH10K_HTC_SVC_ID_HTT_DATA3_MSG = SVC(ATH10K_HTC_SVC_GRP_HTT, 2), + ATH10K_HTC_SVC_ID_HTT_IPA_MSG = SVC(ATH10K_IPA_SERVICE_GROUP, 0), + ATH10K_HTC_SVC_ID_HTT_LOG_MSG = SVC(ATH10K_LOG_SERVICE_GROUP, 0), /* raw stream service (i.e. flash, tcmd, calibration apps) */ ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS = SVC(ATH10K_HTC_SVC_GRP_TEST, 0), }; @@ -297,10 +300,10 @@ struct ath10k_htc_svc_conn_resp { #define ATH10K_NUM_CONTROL_TX_BUFFERS 2 #define ATH10K_HTC_MAX_LEN 4096 #define ATH10K_HTC_MAX_CTRL_MSG_LEN 256 -#define ATH10K_HTC_WAIT_TIMEOUT_HZ (1*HZ) +#define ATH10K_HTC_WAIT_TIMEOUT_HZ (1 * HZ) #define ATH10K_HTC_CONTROL_BUFFER_SIZE (ATH10K_HTC_MAX_CTRL_MSG_LEN + \ sizeof(struct ath10k_htc_hdr)) -#define ATH10K_HTC_CONN_SVC_TIMEOUT_HZ (1*HZ) +#define ATH10K_HTC_CONN_SVC_TIMEOUT_HZ (1 * HZ) struct ath10k_htc_ep { struct ath10k_htc *htc; diff --git a/drivers/net/wireless/ath/ath10k/htt.c b/drivers/net/wireless/ath/ath10k/htt.c index 3e6ba63dfdff..130cd9502021 100644 --- a/drivers/net/wireless/ath/ath10k/htt.c +++ b/drivers/net/wireless/ath/ath10k/htt.c @@ -131,12 +131,12 @@ static const enum htt_t2h_msg_type htt_10_4_t2h_msg_types[] = { [HTT_10_4_T2H_MSG_TYPE_AGGR_CONF] = HTT_T2H_MSG_TYPE_AGGR_CONF, [HTT_10_4_T2H_MSG_TYPE_TX_FETCH_IND] = HTT_T2H_MSG_TYPE_TX_FETCH_IND, - [HTT_10_4_T2H_MSG_TYPE_TX_FETCH_CONF] = - HTT_T2H_MSG_TYPE_TX_FETCH_CONF, + [HTT_10_4_T2H_MSG_TYPE_TX_FETCH_CONFIRM] = + HTT_T2H_MSG_TYPE_TX_FETCH_CONFIRM, [HTT_10_4_T2H_MSG_TYPE_STATS_NOUPLOAD] = HTT_T2H_MSG_TYPE_STATS_NOUPLOAD, - [HTT_10_4_T2H_MSG_TYPE_TX_LOW_LATENCY_IND] = - HTT_T2H_MSG_TYPE_TX_LOW_LATENCY_IND, + [HTT_10_4_T2H_MSG_TYPE_TX_MODE_SWITCH_IND] = + HTT_T2H_MSG_TYPE_TX_MODE_SWITCH_IND, }; int ath10k_htt_connect(struct ath10k_htt *htt) @@ -149,7 +149,7 @@ int ath10k_htt_connect(struct ath10k_htt *htt) memset(&conn_resp, 0, sizeof(conn_resp)); conn_req.ep_ops.ep_tx_complete = ath10k_htt_htc_tx_complete; - conn_req.ep_ops.ep_rx_complete = ath10k_htt_t2h_msg_handler; + conn_req.ep_ops.ep_rx_complete = ath10k_htt_htc_t2h_msg_handler; /* connect to control service */ conn_req.service_id = ATH10K_HTC_SVC_ID_HTT_DATA_MSG; @@ -183,7 +183,7 @@ int ath10k_htt_init(struct ath10k *ar) 8 + /* llc snap */ 2; /* ip4 dscp or ip6 priority */ - switch (ar->htt.op_version) { + switch (ar->running_fw->fw_file.htt_op_version) { case ATH10K_FW_HTT_OP_VERSION_10_4: ar->htt.t2h_msg_types = htt_10_4_t2h_msg_types; ar->htt.t2h_msg_types_max = HTT_10_4_T2H_NUM_MSGS; @@ -208,7 +208,7 @@ int ath10k_htt_init(struct ath10k *ar) return 0; } -#define HTT_TARGET_VERSION_TIMEOUT_HZ (3*HZ) +#define HTT_TARGET_VERSION_TIMEOUT_HZ (3 * HZ) static int ath10k_htt_verify_version(struct ath10k_htt *htt) { diff --git a/drivers/net/wireless/ath/ath10k/htt.h b/drivers/net/wireless/ath/ath10k/htt.h index 2bad50e520b5..6377c4ef427c 100644 --- a/drivers/net/wireless/ath/ath10k/htt.h +++ b/drivers/net/wireless/ath/ath10k/htt.h @@ -22,6 +22,7 @@ #include <linux/interrupt.h> #include <linux/dmapool.h> #include <linux/hashtable.h> +#include <linux/kfifo.h> #include <net/mac80211.h> #include "htc.h" @@ -52,6 +53,7 @@ enum htt_h2t_msg_type { /* host-to-target */ /* This command is used for sending management frames in HTT < 3.0. * HTT >= 3.0 uses TX_FRM for everything. */ HTT_H2T_MSG_TYPE_MGMT_TX = 7, + HTT_H2T_MSG_TYPE_TX_FETCH_RESP = 11, HTT_H2T_NUM_MSGS /* keep this last */ }; @@ -97,7 +99,11 @@ struct htt_data_tx_desc_frag { } __packed; struct htt_msdu_ext_desc { +#ifdef CONFIG_ATH10K_SNOC + __le32 tso_flag[5]; +#else __le32 tso_flag[3]; +#endif __le16 ip_identification; u8 flags; u8 reserved; @@ -116,6 +122,19 @@ struct htt_msdu_ext_desc { | HTT_MSDU_EXT_DESC_FLAG_TCP_IPV4_CSUM_ENABLE \ | HTT_MSDU_EXT_DESC_FLAG_TCP_IPV6_CSUM_ENABLE) +#define HTT_TX_IPV4_CSUM_EN BIT(16) +#define HTT_TX_UDP_IPV4_CSUM_EN BIT(17) +#define HTT_TX_UDP_IPV6_CSUM_EN BIT(18) +#define HTT_TX_TCP_IPV4_CSUM_EN BIT(19) +#define HTT_TX_TCP_IPV6_CSUM_EN BIT(20) +#define HTT_TX_PARTIAL_CSUM_EN BIT(21) + +#define HTT_TX_CHECKSUM_ENABLE (HTT_TX_IPV4_CSUM_EN \ + | HTT_TX_UDP_IPV4_CSUM_EN \ + | HTT_TX_UDP_IPV6_CSUM_EN \ + | HTT_TX_TCP_IPV4_CSUM_EN \ + | HTT_TX_TCP_IPV6_CSUM_EN) + enum htt_data_tx_desc_flags0 { HTT_DATA_TX_DESC_FLAGS0_MAC_HDR_PRESENT = 1 << 0, HTT_DATA_TX_DESC_FLAGS0_NO_AGGR = 1 << 1, @@ -165,9 +184,19 @@ struct htt_data_tx_desc { __le16 flags1; /* %HTT_DATA_TX_DESC_FLAGS1_ */ __le16 len; __le16 id; +#ifdef CONFIG_ATH10K_SNOC + __le32 frags_paddr_lo; + __le32 frags_paddr_hi; +#else __le32 frags_paddr; - __le16 peerid; - __le16 freq; +#endif + union { + __le32 peerid; + struct { + __le16 peerid; + __le16 freq; + } __packed offchan_tx; + } __packed; u8 prefetch[0]; /* start of frame, for FW classification engine */ } __packed; @@ -194,8 +223,15 @@ enum htt_rx_ring_flags { #define HTT_RX_RING_SIZE_MAX 2048 struct htt_rx_ring_setup_ring { +#ifdef CONFIG_ATH10K_SNOC + __le32 fw_idx_shadow_reg_paddr_low; + __le32 fw_idx_shadow_reg_paddr_high; + __le32 rx_ring_base_paddr_low; + __le32 rx_ring_base_paddr_high; +#else __le32 fw_idx_shadow_reg_paddr; __le32 rx_ring_base_paddr; +#endif __le16 rx_ring_len; /* in 4-byte words */ __le16 rx_ring_bufsize; /* rx skb size - in bytes */ __le16 flags; /* %HTT_RX_RING_FLAGS_ */ @@ -408,10 +444,10 @@ enum htt_10_4_t2h_msg_type { HTT_10_4_T2H_MSG_TYPE_EN_STATS = 0x14, HTT_10_4_T2H_MSG_TYPE_AGGR_CONF = 0x15, HTT_10_4_T2H_MSG_TYPE_TX_FETCH_IND = 0x16, - HTT_10_4_T2H_MSG_TYPE_TX_FETCH_CONF = 0x17, + HTT_10_4_T2H_MSG_TYPE_TX_FETCH_CONFIRM = 0x17, HTT_10_4_T2H_MSG_TYPE_STATS_NOUPLOAD = 0x18, /* 0x19 to 0x2f are reserved */ - HTT_10_4_T2H_MSG_TYPE_TX_LOW_LATENCY_IND = 0x30, + HTT_10_4_T2H_MSG_TYPE_TX_MODE_SWITCH_IND = 0x30, /* keep this last */ HTT_10_4_T2H_NUM_MSGS }; @@ -444,8 +480,8 @@ enum htt_t2h_msg_type { HTT_T2H_MSG_TYPE_TEST, HTT_T2H_MSG_TYPE_EN_STATS, HTT_T2H_MSG_TYPE_TX_FETCH_IND, - HTT_T2H_MSG_TYPE_TX_FETCH_CONF, - HTT_T2H_MSG_TYPE_TX_LOW_LATENCY_IND, + HTT_T2H_MSG_TYPE_TX_FETCH_CONFIRM, + HTT_T2H_MSG_TYPE_TX_MODE_SWITCH_IND, /* keep this last */ HTT_T2H_NUM_MSGS }; @@ -478,10 +514,10 @@ struct htt_mgmt_tx_completion { __le32 status; } __packed; -#define HTT_RX_INDICATION_INFO0_EXT_TID_MASK (0x3F) +#define HTT_RX_INDICATION_INFO0_EXT_TID_MASK (0x1F) #define HTT_RX_INDICATION_INFO0_EXT_TID_LSB (0) -#define HTT_RX_INDICATION_INFO0_FLUSH_VALID (1 << 6) -#define HTT_RX_INDICATION_INFO0_RELEASE_VALID (1 << 7) +#define HTT_RX_INDICATION_INFO0_FLUSH_VALID (1 << 5) +#define HTT_RX_INDICATION_INFO0_RELEASE_VALID (1 << 6) #define HTT_RX_INDICATION_INFO1_FLUSH_START_SEQNO_MASK 0x0000003F #define HTT_RX_INDICATION_INFO1_FLUSH_START_SEQNO_LSB 0 @@ -517,6 +553,9 @@ struct htt_rx_indication_hdr { #define HTT_RX_INDICATION_INFO2_SERVICE_MASK 0xFF000000 #define HTT_RX_INDICATION_INFO2_SERVICE_LSB 24 +#define HTT_WCN3990_PADDR_MASK 0x1F +#define HTT_WCN3990_ARCH_PADDR_MASK 0x1FFFFFFFFF + enum htt_rx_legacy_rate { HTT_RX_OFDM_48 = 0, HTT_RX_OFDM_24 = 1, @@ -588,7 +627,7 @@ enum htt_rx_mpdu_status { /* only accept EAPOL frames */ HTT_RX_IND_MPDU_STATUS_UNAUTH_PEER, HTT_RX_IND_MPDU_STATUS_OUT_OF_SYNC, - /* Non-data in promiscous mode */ + /* Non-data in promiscuous mode */ HTT_RX_IND_MPDU_STATUS_MGMT_CTRL, HTT_RX_IND_MPDU_STATUS_TKIP_MIC_ERR, HTT_RX_IND_MPDU_STATUS_DECRYPT_ERR, @@ -839,7 +878,11 @@ struct htt_rx_offload_ind { } __packed; struct htt_rx_in_ord_msdu_desc { +#ifdef CONFIG_ATH10K_SNOC + __le64 msdu_paddr; +#else __le32 msdu_paddr; +#endif __le16 msdu_len; u8 fw_desc; u8 reserved; @@ -893,7 +936,7 @@ struct htt_rx_in_ord_ind { * Purpose: indicate how many 32-bit integers follow the message header * - NUM_CHARS * Bits 31:16 - * Purpose: indicate how many 8-bit charaters follow the series of integers + * Purpose: indicate how many 8-bit characters follow the series of integers */ struct htt_rx_test { u8 num_ints; @@ -1035,10 +1078,10 @@ struct htt_dbg_stats_wal_tx_stats { /* illegal rate phy errors */ __le32 illgl_rate_phy_err; - /* wal pdev continous xretry */ + /* wal pdev continuous xretry */ __le32 pdev_cont_xretry; - /* wal pdev continous xretry */ + /* wal pdev continuous xretry */ __le32 pdev_tx_timeout; /* wal pdev resets */ @@ -1301,16 +1344,175 @@ struct htt_frag_desc_bank_id { * so we use a conservatively safe value for now */ #define HTT_FRAG_DESC_BANK_MAX 4 -#define HTT_FRAG_DESC_BANK_CFG_INFO_PDEV_ID_MASK 0x03 -#define HTT_FRAG_DESC_BANK_CFG_INFO_PDEV_ID_LSB 0 -#define HTT_FRAG_DESC_BANK_CFG_INFO_SWAP (1 << 2) +#define HTT_FRAG_DESC_BANK_CFG_INFO_PDEV_ID_MASK 0x03 +#define HTT_FRAG_DESC_BANK_CFG_INFO_PDEV_ID_LSB 0 +#define HTT_FRAG_DESC_BANK_CFG_INFO_SWAP BIT(2) +#define HTT_FRAG_DESC_BANK_CFG_INFO_Q_STATE_VALID BIT(3) +#define HTT_FRAG_DESC_BANK_CFG_INFO_Q_STATE_DEPTH_TYPE_MASK BIT(4) +#define HTT_FRAG_DESC_BANK_CFG_INFO_Q_STATE_DEPTH_TYPE_LSB 4 + +enum htt_q_depth_type { + HTT_Q_DEPTH_TYPE_BYTES = 0, + HTT_Q_DEPTH_TYPE_MSDUS = 1, +}; + +#define HTT_TX_Q_STATE_NUM_PEERS (TARGET_10_4_NUM_QCACHE_PEERS_MAX + \ + TARGET_10_4_NUM_VDEVS) +#define HTT_TX_Q_STATE_NUM_TIDS 8 +#define HTT_TX_Q_STATE_ENTRY_SIZE 1 +#define HTT_TX_Q_STATE_ENTRY_MULTIPLIER 0 + +/** + * htt_q_state_conf - part of htt_frag_desc_bank_cfg for host q state config + * + * Defines host q state format and behavior. See htt_q_state. + * + * @record_size: Defines the size of each host q entry in bytes. In practice + * however firmware (at least 10.4.3-00191) ignores this host + * configuration value and uses hardcoded value of 1. + * @record_multiplier: This is valid only when q depth type is MSDUs. It + * defines the exponent for the power of 2 multiplication. + */ +struct htt_q_state_conf { + __le32 paddr; + __le16 num_peers; + __le16 num_tids; + u8 record_size; + u8 record_multiplier; + u8 pad[2]; +} __packed; + +struct bank_base_addr { + __le32 low; + __le32 high; +}; struct htt_frag_desc_bank_cfg { u8 info; /* HTT_FRAG_DESC_BANK_CFG_INFO_ */ u8 num_banks; u8 desc_size; +#ifdef CONFIG_ATH10K_SNOC + struct bank_base_addr bank_base_addrs[HTT_FRAG_DESC_BANK_MAX]; +#else __le32 bank_base_addrs[HTT_FRAG_DESC_BANK_MAX]; +#endif struct htt_frag_desc_bank_id bank_id[HTT_FRAG_DESC_BANK_MAX]; + struct htt_q_state_conf q_state; +} __packed; + +#define HTT_TX_Q_STATE_ENTRY_COEFFICIENT 128 +#define HTT_TX_Q_STATE_ENTRY_FACTOR_MASK 0x3f +#define HTT_TX_Q_STATE_ENTRY_FACTOR_LSB 0 +#define HTT_TX_Q_STATE_ENTRY_EXP_MASK 0xc0 +#define HTT_TX_Q_STATE_ENTRY_EXP_LSB 6 + +/** + * htt_q_state - shared between host and firmware via DMA + * + * This structure is used for the host to expose it's software queue state to + * firmware so that its rate control can schedule fetch requests for optimized + * performance. This is most notably used for MU-MIMO aggregation when multiple + * MU clients are connected. + * + * @count: Each element defines the host queue depth. When q depth type was + * configured as HTT_Q_DEPTH_TYPE_BYTES then each entry is defined as: + * FACTOR * 128 * 8^EXP (see HTT_TX_Q_STATE_ENTRY_FACTOR_MASK and + * HTT_TX_Q_STATE_ENTRY_EXP_MASK). When q depth type was configured as + * HTT_Q_DEPTH_TYPE_MSDUS the number of packets is scaled by 2 ** + * record_multiplier (see htt_q_state_conf). + * @map: Used by firmware to quickly check which host queues are not empty. It + * is a bitmap simply saying. + * @seq: Used by firmware to quickly check if the host queues were updated + * since it last checked. + * + * FIXME: Is the q_state map[] size calculation really correct? + */ +struct htt_q_state { + u8 count[HTT_TX_Q_STATE_NUM_TIDS][HTT_TX_Q_STATE_NUM_PEERS]; + u32 map[HTT_TX_Q_STATE_NUM_TIDS][(HTT_TX_Q_STATE_NUM_PEERS + 31) / 32]; + __le32 seq; +} __packed; + +#define HTT_TX_FETCH_RECORD_INFO_PEER_ID_MASK 0x0fff +#define HTT_TX_FETCH_RECORD_INFO_PEER_ID_LSB 0 +#define HTT_TX_FETCH_RECORD_INFO_TID_MASK 0xf000 +#define HTT_TX_FETCH_RECORD_INFO_TID_LSB 12 + +struct htt_tx_fetch_record { + __le16 info; /* HTT_TX_FETCH_IND_RECORD_INFO_ */ + __le16 num_msdus; + __le32 num_bytes; +} __packed; + +struct htt_tx_fetch_ind { + u8 pad0; + __le16 fetch_seq_num; + __le32 token; + __le16 num_resp_ids; + __le16 num_records; + struct htt_tx_fetch_record records[0]; + __le32 resp_ids[0]; /* ath10k_htt_get_tx_fetch_ind_resp_ids() */ +} __packed; + +static inline void * +ath10k_htt_get_tx_fetch_ind_resp_ids(struct htt_tx_fetch_ind *ind) +{ + return (void *)&ind->records[le16_to_cpu(ind->num_records)]; +} + +struct htt_tx_fetch_resp { + u8 pad0; + __le16 resp_id; + __le16 fetch_seq_num; + __le16 num_records; + __le32 token; + struct htt_tx_fetch_record records[0]; +} __packed; + +struct htt_tx_fetch_confirm { + u8 pad0; + __le16 num_resp_ids; + __le32 resp_ids[0]; +} __packed; + +enum htt_tx_mode_switch_mode { + HTT_TX_MODE_SWITCH_PUSH = 0, + HTT_TX_MODE_SWITCH_PUSH_PULL = 1, +}; + +#define HTT_TX_MODE_SWITCH_IND_INFO0_ENABLE BIT(0) +#define HTT_TX_MODE_SWITCH_IND_INFO0_NUM_RECORDS_MASK 0xfffe +#define HTT_TX_MODE_SWITCH_IND_INFO0_NUM_RECORDS_LSB 1 + +#define HTT_TX_MODE_SWITCH_IND_INFO1_MODE_MASK 0x0003 +#define HTT_TX_MODE_SWITCH_IND_INFO1_MODE_LSB 0 +#define HTT_TX_MODE_SWITCH_IND_INFO1_THRESHOLD_MASK 0xfffc +#define HTT_TX_MODE_SWITCH_IND_INFO1_THRESHOLD_LSB 2 + +#define HTT_TX_MODE_SWITCH_RECORD_INFO0_PEER_ID_MASK 0x0fff +#define HTT_TX_MODE_SWITCH_RECORD_INFO0_PEER_ID_LSB 0 +#define HTT_TX_MODE_SWITCH_RECORD_INFO0_TID_MASK 0xf000 +#define HTT_TX_MODE_SWITCH_RECORD_INFO0_TID_LSB 12 + +struct htt_tx_mode_switch_record { + __le16 info0; /* HTT_TX_MODE_SWITCH_RECORD_INFO0_ */ + __le16 num_max_msdus; +} __packed; + +struct htt_tx_mode_switch_ind { + u8 pad0; + __le16 info0; /* HTT_TX_MODE_SWITCH_IND_INFO0_ */ + __le16 info1; /* HTT_TX_MODE_SWITCH_IND_INFO1_ */ + u8 pad1[2]; + struct htt_tx_mode_switch_record records[0]; +} __packed; + +struct htt_channel_change { + u8 pad[3]; + __le32 freq; + __le32 center_freq1; + __le32 center_freq2; + __le32 phymode; } __packed; union htt_rx_pn_t { @@ -1318,10 +1520,10 @@ union htt_rx_pn_t { u32 pn24; /* TKIP or CCMP: 48-bit PN */ - u_int64_t pn48; + u64 pn48; /* WAPI: 128-bit PN */ - u_int64_t pn128[2]; + u64 pn128[2]; }; struct htt_cmd { @@ -1335,6 +1537,7 @@ struct htt_cmd { struct htt_oob_sync_req oob_sync_req; struct htt_aggr_conf aggr_conf; struct htt_frag_desc_bank_cfg frag_desc_bank_cfg; + struct htt_tx_fetch_resp tx_fetch_resp; }; } __packed; @@ -1359,16 +1562,25 @@ struct htt_resp { struct htt_rx_pn_ind rx_pn_ind; struct htt_rx_offload_ind rx_offload_ind; struct htt_rx_in_ord_ind rx_in_ord_ind; + struct htt_tx_fetch_ind tx_fetch_ind; + struct htt_tx_fetch_confirm tx_fetch_confirm; + struct htt_tx_mode_switch_ind tx_mode_switch_ind; + struct htt_channel_change chan_change; }; } __packed; /*** host side structures follow ***/ struct htt_tx_done { - u32 msdu_id; - bool discard; - bool no_ack; - bool success; + u16 msdu_id; + u16 status; +}; + +enum htt_tx_compl_state { + HTT_TX_COMPL_STATE_NONE, + HTT_TX_COMPL_STATE_ACK, + HTT_TX_COMPL_STATE_NOACK, + HTT_TX_COMPL_STATE_DISCARD, }; struct htt_peer_map_event { @@ -1395,7 +1607,6 @@ struct ath10k_htt { u8 target_version_major; u8 target_version_minor; struct completion target_version_received; - enum ath10k_fw_htt_op_version op_version; u8 max_num_amsdu; u8 max_num_ampdu; @@ -1433,7 +1644,11 @@ struct ath10k_htt { * rx buffers the host SW provides for the MAC HW to * fill. */ +#ifdef CONFIG_ATH10K_SNOC + __le64 *paddrs_ring; +#else __le32 *paddrs_ring; +#endif /* * Base address of ring, as a "physical" device address @@ -1489,17 +1704,19 @@ struct ath10k_htt { struct idr pending_tx; wait_queue_head_t empty_tx_wq; + /* FIFO for storing tx done status {ack, no-ack, discard} and msdu id */ + DECLARE_KFIFO_PTR(txdone_fifo, struct htt_tx_done); + /* set if host-fw communication goes haywire * used to avoid further failures */ bool rx_confused; - struct tasklet_struct rx_replenish_task; + atomic_t num_mpdus_ready; /* This is used to group tx/rx completions separately and process them * in batches to reduce cache stalls */ - struct tasklet_struct txrx_compl_task; - struct sk_buff_head tx_compl_q; struct sk_buff_head rx_compl_q; struct sk_buff_head rx_in_ord_compl_q; + struct sk_buff_head tx_fetch_ind_q; /* rx_status template */ struct ieee80211_rx_status rx_status; @@ -1513,20 +1730,36 @@ struct ath10k_htt { dma_addr_t paddr; struct ath10k_htt_txbuf *vaddr; } txbuf; + + struct { + bool enabled; + struct htt_q_state *vaddr; + dma_addr_t paddr; + u16 num_push_allowed; + u16 num_peers; + u16 num_tids; + enum htt_tx_mode_switch_mode mode; + enum htt_q_depth_type type; + } tx_q_state; }; #define RX_HTT_HDR_STATUS_LEN 64 -/* This structure layout is programmed via rx ring setup - * so that FW knows how to transfer the rx descriptor to the host. - * Buffers like this are placed on the rx ring. */ -struct htt_rx_desc { +struct htt_host_fw_desc_base { union { /* This field is filled on the host using the msdu buffer * from htt_rx_indication */ struct fw_rx_desc_base fw_desc; u32 pad; } __packed; +}; + +/* This structure layout is programmed via rx ring setup + * so that FW knows how to transfer the rx descriptor to the host. + * Buffers like this are placed on the rx ring. + */ +struct htt_rx_desc { + struct htt_host_fw_desc_base fw_desc_base; struct { struct rx_attention attention; struct rx_frag_info frag_info; @@ -1555,7 +1788,7 @@ struct htt_rx_desc { /* Refill a bunch of RX buffers for each refill round so that FW/HW can handle * aggregated traffic more nicely. */ -#define ATH10K_HTT_MAX_NUM_REFILL 16 +#define ATH10K_HTT_MAX_NUM_REFILL 100 /* * DMA_MAP expects the buffer to be an integral number of cache lines. @@ -1583,7 +1816,8 @@ int ath10k_htt_rx_ring_refill(struct ath10k *ar); void ath10k_htt_rx_free(struct ath10k_htt *htt); void ath10k_htt_htc_tx_complete(struct ath10k *ar, struct sk_buff *skb); -void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb); +void ath10k_htt_htc_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb); +bool ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb); int ath10k_htt_h2t_ver_req_msg(struct ath10k_htt *htt); int ath10k_htt_h2t_stats_req(struct ath10k_htt *htt, u8 mask, u64 cookie); int ath10k_htt_send_frag_desc_bank_cfg(struct ath10k_htt *htt); @@ -1592,11 +1826,33 @@ int ath10k_htt_h2t_aggr_cfg_msg(struct ath10k_htt *htt, u8 max_subfrms_ampdu, u8 max_subfrms_amsdu); void ath10k_htt_hif_tx_complete(struct ath10k *ar, struct sk_buff *skb); +int ath10k_htt_tx_fetch_resp(struct ath10k *ar, + __le32 token, + __le16 fetch_seq_num, + struct htt_tx_fetch_record *records, + size_t num_records); + +void ath10k_htt_tx_txq_update(struct ieee80211_hw *hw, + struct ieee80211_txq *txq); +void ath10k_htt_tx_txq_recalc(struct ieee80211_hw *hw, + struct ieee80211_txq *txq); +void ath10k_htt_tx_txq_sync(struct ath10k *ar); +void ath10k_htt_tx_dec_pending(struct ath10k_htt *htt); +int ath10k_htt_tx_inc_pending(struct ath10k_htt *htt); +void ath10k_htt_tx_mgmt_dec_pending(struct ath10k_htt *htt); +int ath10k_htt_tx_mgmt_inc_pending(struct ath10k_htt *htt, bool is_mgmt, + bool is_presp); -void __ath10k_htt_tx_dec_pending(struct ath10k_htt *htt, bool limit_mgmt_desc); int ath10k_htt_tx_alloc_msdu_id(struct ath10k_htt *htt, struct sk_buff *skb); void ath10k_htt_tx_free_msdu_id(struct ath10k_htt *htt, u16 msdu_id); int ath10k_htt_mgmt_tx(struct ath10k_htt *htt, struct sk_buff *); -int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *); +int ath10k_htt_tx(struct ath10k_htt *htt, + enum ath10k_hw_txrx_mode txmode, + struct sk_buff *msdu); +void ath10k_htt_rx_pktlog_completion_handler(struct ath10k *ar, + struct sk_buff *skb); +int ath10k_htt_txrx_compl_task(struct ath10k *ar, int budget); +struct sk_buff *ath10k_htt_tx_find_msdu_by_id(struct ath10k_htt *htt, + u16 msdu_id); #endif diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c index b32c47fe926d..184da610f9da 100644 --- a/drivers/net/wireless/ath/ath10k/htt_rx.c +++ b/drivers/net/wireless/ath/ath10k/htt_rx.c @@ -27,15 +27,17 @@ #define HTT_RX_RING_SIZE HTT_RX_RING_SIZE_MAX #define HTT_RX_RING_FILL_LEVEL (((HTT_RX_RING_SIZE) / 2) - 1) +#define HTT_RX_RING_FILL_LEVEL_DUAL_MAC (HTT_RX_RING_SIZE - 1) /* when under memory pressure rx ring refill may fail and needs a retry */ #define HTT_RX_RING_REFILL_RETRY_MS 50 +#define HTT_RX_RING_REFILL_RESCHED_MS 5 + static int ath10k_htt_rx_get_csum_state(struct sk_buff *skb); -static void ath10k_htt_txrx_compl_task(unsigned long ptr); static struct sk_buff * -ath10k_htt_rx_find_skb_paddr(struct ath10k *ar, u32 paddr) +ath10k_htt_rx_find_skb_paddr(struct ath10k *ar, u64 paddr) { struct ath10k_skb_rxcb *rxcb; @@ -128,13 +130,16 @@ static int __ath10k_htt_rx_ring_fill_n(struct ath10k_htt *htt, int num) rxcb = ATH10K_SKB_RXCB(skb); rxcb->paddr = paddr; htt->rx_ring.netbufs_ring[idx] = skb; - htt->rx_ring.paddrs_ring[idx] = __cpu_to_le32(paddr); + if (QCA_REV_WCN3990(htt->ar)) + htt->rx_ring.paddrs_ring[idx] = __cpu_to_le64(paddr); + else + htt->rx_ring.paddrs_ring[idx] = __cpu_to_le32(paddr); htt->rx_ring.fill_cnt++; if (htt->rx_ring.in_ord_rx) { hash_add(htt->rx_ring.skb_table, &ATH10K_SKB_RXCB(skb)->hlist, - (u32)paddr); + paddr); } num--; @@ -192,7 +197,8 @@ static void ath10k_htt_rx_msdu_buff_replenish(struct ath10k_htt *htt) mod_timer(&htt->rx_ring.refill_retry_timer, jiffies + msecs_to_jiffies(HTT_RX_RING_REFILL_RETRY_MS)); } else if (num_deficit > 0) { - tasklet_schedule(&htt->rx_replenish_task); + mod_timer(&htt->rx_ring.refill_retry_timer, jiffies + + msecs_to_jiffies(HTT_RX_RING_REFILL_RESCHED_MS)); } spin_unlock_bh(&htt->rx_ring.lock); } @@ -223,12 +229,10 @@ int ath10k_htt_rx_ring_refill(struct ath10k *ar) void ath10k_htt_rx_free(struct ath10k_htt *htt) { del_timer_sync(&htt->rx_ring.refill_retry_timer); - tasklet_kill(&htt->rx_replenish_task); - tasklet_kill(&htt->txrx_compl_task); - skb_queue_purge(&htt->tx_compl_q); skb_queue_purge(&htt->rx_compl_q); skb_queue_purge(&htt->rx_in_ord_compl_q); + skb_queue_purge(&htt->tx_fetch_ind_q); ath10k_htt_rx_ring_free(htt); @@ -281,7 +285,6 @@ static inline struct sk_buff *ath10k_htt_rx_netbuf_pop(struct ath10k_htt *htt) /* return: < 0 fatal error, 0 - non chained msdu, 1 chained msdu */ static int ath10k_htt_rx_amsdu_pop(struct ath10k_htt *htt, - u8 **fw_desc, int *fw_desc_len, struct sk_buff_head *amsdu) { struct ath10k *ar = htt->ar; @@ -323,48 +326,6 @@ static int ath10k_htt_rx_amsdu_pop(struct ath10k_htt *htt, return -EIO; } - /* - * Copy the FW rx descriptor for this MSDU from the rx - * indication message into the MSDU's netbuf. HL uses the - * same rx indication message definition as LL, and simply - * appends new info (fields from the HW rx desc, and the - * MSDU payload itself). So, the offset into the rx - * indication message only has to account for the standard - * offset of the per-MSDU FW rx desc info within the - * message, and how many bytes of the per-MSDU FW rx desc - * info have already been consumed. (And the endianness of - * the host, since for a big-endian host, the rx ind - * message contents, including the per-MSDU rx desc bytes, - * were byteswapped during upload.) - */ - if (*fw_desc_len > 0) { - rx_desc->fw_desc.info0 = **fw_desc; - /* - * The target is expected to only provide the basic - * per-MSDU rx descriptors. Just to be sure, verify - * that the target has not attached extension data - * (e.g. LRO flow ID). - */ - - /* or more, if there's extension data */ - (*fw_desc)++; - (*fw_desc_len)--; - } else { - /* - * When an oversized AMSDU happened, FW will lost - * some of MSDU status - in this case, the FW - * descriptors provided will be less than the - * actual MSDUs inside this MPDU. Mark the FW - * descriptors so that it will still deliver to - * upper stack, if no CRC error for this MPDU. - * - * FIX THIS - the FW descriptors are actually for - * MSDUs in the end of this A-MSDU instead of the - * beginning. - */ - rx_desc->fw_desc.info0 = 0; - } - msdu_len_invalid = !!(__le32_to_cpu(rx_desc->attention.flags) & (RX_ATTENTION_FLAGS_MPDU_LENGTH_ERR | RX_ATTENTION_FLAGS_MSDU_LENGTH_ERR)); @@ -423,15 +384,8 @@ static int ath10k_htt_rx_amsdu_pop(struct ath10k_htt *htt, return msdu_chaining; } -static void ath10k_htt_rx_replenish_task(unsigned long ptr) -{ - struct ath10k_htt *htt = (struct ath10k_htt *)ptr; - - ath10k_htt_rx_msdu_buff_replenish(htt); -} - static struct sk_buff *ath10k_htt_rx_pop_paddr(struct ath10k_htt *htt, - u32 paddr) + u64 paddr) { struct ath10k *ar = htt->ar; struct ath10k_skb_rxcb *rxcb; @@ -466,7 +420,7 @@ static int ath10k_htt_rx_pop_paddr_list(struct ath10k_htt *htt, struct sk_buff *msdu; int msdu_count; bool is_offload; - u32 paddr; + u64 paddr; lockdep_assert_held(&htt->rx_ring.lock); @@ -474,8 +428,12 @@ static int ath10k_htt_rx_pop_paddr_list(struct ath10k_htt *htt, is_offload = !!(ev->info & HTT_RX_IN_ORD_IND_INFO_OFFLOAD_MASK); while (msdu_count--) { +#ifdef CONFIG_ATH10K_SNOC + paddr = __le64_to_cpu(msdu_desc->msdu_paddr); + paddr &= HTT_WCN3990_ARCH_PADDR_MASK; +#else paddr = __le32_to_cpu(msdu_desc->msdu_paddr); - +#endif msdu = ath10k_htt_rx_pop_paddr(htt, paddr); if (!msdu) { __skb_queue_purge(list); @@ -521,7 +479,15 @@ int ath10k_htt_rx_alloc(struct ath10k_htt *htt) */ htt->rx_ring.size = HTT_RX_RING_SIZE; htt->rx_ring.size_mask = htt->rx_ring.size - 1; - htt->rx_ring.fill_level = HTT_RX_RING_FILL_LEVEL; + + switch (ar->hw_rev) { + case ATH10K_HW_WCN3990: + htt->rx_ring.fill_level = HTT_RX_RING_FILL_LEVEL_DUAL_MAC; + break; + default: + htt->rx_ring.fill_level = HTT_RX_RING_FILL_LEVEL; + break; + } if (!is_power_of_2(htt->rx_ring.size)) { ath10k_warn(ar, "htt rx ring size is not power of 2\n"); @@ -536,7 +502,7 @@ int ath10k_htt_rx_alloc(struct ath10k_htt *htt) size = htt->rx_ring.size * sizeof(htt->rx_ring.paddrs_ring); - vaddr = dma_alloc_coherent(htt->ar->dev, size, &paddr, GFP_DMA); + vaddr = dma_alloc_coherent(htt->ar->dev, size, &paddr, GFP_KERNEL); if (!vaddr) goto err_dma_ring; @@ -545,7 +511,7 @@ int ath10k_htt_rx_alloc(struct ath10k_htt *htt) vaddr = dma_alloc_coherent(htt->ar->dev, sizeof(*htt->rx_ring.alloc_idx.vaddr), - &paddr, GFP_DMA); + &paddr, GFP_KERNEL); if (!vaddr) goto err_dma_idx; @@ -563,15 +529,10 @@ int ath10k_htt_rx_alloc(struct ath10k_htt *htt) htt->rx_ring.sw_rd_idx.msdu_payld = 0; hash_init(htt->rx_ring.skb_table); - tasklet_init(&htt->rx_replenish_task, ath10k_htt_rx_replenish_task, - (unsigned long)htt); - - skb_queue_head_init(&htt->tx_compl_q); skb_queue_head_init(&htt->rx_compl_q); skb_queue_head_init(&htt->rx_in_ord_compl_q); - - tasklet_init(&htt->txrx_compl_task, ath10k_htt_txrx_compl_task, - (unsigned long)htt); + skb_queue_head_init(&htt->tx_fetch_ind_q); + atomic_set(&htt->num_mpdus_ready, 0); ath10k_dbg(ar, ATH10K_DBG_BOOT, "htt rx ring size %d fill_level %d\n", htt->rx_ring.size, htt->rx_ring.fill_level); @@ -603,6 +564,11 @@ static int ath10k_htt_rx_crypto_param_len(struct ath10k *ar, return IEEE80211_TKIP_IV_LEN; case HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2: return IEEE80211_CCMP_HDR_LEN; + case HTT_RX_MPDU_ENCRYPT_AES_CCM256_WPA2: + return IEEE80211_CCMP_256_HDR_LEN; + case HTT_RX_MPDU_ENCRYPT_AES_GCMP_WPA2: + case HTT_RX_MPDU_ENCRYPT_AES_GCMP256_WPA2: + return IEEE80211_GCMP_HDR_LEN; case HTT_RX_MPDU_ENCRYPT_WEP128: case HTT_RX_MPDU_ENCRYPT_WAPI: break; @@ -628,6 +594,11 @@ static int ath10k_htt_rx_crypto_tail_len(struct ath10k *ar, return IEEE80211_TKIP_ICV_LEN; case HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2: return IEEE80211_CCMP_MIC_LEN; + case HTT_RX_MPDU_ENCRYPT_AES_CCM256_WPA2: + return IEEE80211_CCMP_256_MIC_LEN; + case HTT_RX_MPDU_ENCRYPT_AES_GCMP_WPA2: + case HTT_RX_MPDU_ENCRYPT_AES_GCMP256_WPA2: + return IEEE80211_GCMP_MIC_LEN; case HTT_RX_MPDU_ENCRYPT_WEP128: case HTT_RX_MPDU_ENCRYPT_WAPI: break; @@ -674,7 +645,7 @@ static void ath10k_htt_rx_h_rates(struct ath10k *ar, rate &= ~RX_PPDU_START_RATE_FLAG; sband = &ar->mac.sbands[status->band]; - status->rate_idx = ath10k_mac_hw_rate_to_idx(sband, rate); + status->rate_idx = ath10k_mac_hw_rate_to_idx(sband, rate, cck); break; case HTT_RX_HT: case HTT_RX_HT_WITH_TXBF: @@ -798,7 +769,7 @@ ath10k_htt_rx_h_peer_channel(struct ath10k *ar, struct htt_rx_desc *rxd) if (WARN_ON_ONCE(!arvif)) return NULL; - if (WARN_ON(ath10k_mac_vif_chan(arvif->vif, &def))) + if (ath10k_mac_vif_chan(arvif->vif, &def)) return NULL; return def.chan; @@ -860,6 +831,8 @@ static bool ath10k_htt_rx_h_channel(struct ath10k *ar, ch = ath10k_htt_rx_h_vdev_channel(ar, vdev_id); if (!ch) ch = ath10k_htt_rx_h_any_channel(ar); + if (!ch) + ch = ar->tgt_oper_chan; spin_unlock_bh(&ar->data_lock); if (!ch) @@ -977,9 +950,9 @@ static void ath10k_process_rx(struct ath10k *ar, status = IEEE80211_SKB_RXCB(skb); *status = *rx_status; - + fill_datapath_stats(ar, status); ath10k_dbg(ar, ATH10K_DBG_DATA, - "rx skb %p len %u peer %pM %s %s sn %u %s%s%s%s%s %srate_idx %u vht_nss %u freq %u band %u flag 0x%llx fcs-err %i mic-err %i amsdu-more %i\n", + "rx skb %pK len %u peer %pM %s %s sn %u %s%s%s%s%s %srate_idx %u vht_nss %u freq %u band %u flag 0x%llx fcs-err %i mic-err %i amsdu-more %i\n", skb, skb->len, ieee80211_get_SA(hdr), @@ -987,7 +960,8 @@ static void ath10k_process_rx(struct ath10k *ar, is_multicast_ether_addr(ieee80211_get_DA(hdr)) ? "mcast" : "ucast", (__le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4, - status->flag == 0 ? "legacy" : "", + (status->flag & (RX_FLAG_HT | RX_FLAG_VHT)) == 0 ? + "legacy" : "", status->flag & RX_FLAG_HT ? "ht" : "", status->flag & RX_FLAG_VHT ? "vht" : "", status->flag & RX_FLAG_40MHZ ? "40" : "", @@ -1005,7 +979,8 @@ static void ath10k_process_rx(struct ath10k *ar, trace_ath10k_rx_hdr(ar, skb->data, skb->len); trace_ath10k_rx_payload(ar, skb->data, skb->len); - ieee80211_rx(ar->hw, skb); + ath10k_rx_record_pktlog(ar, skb); + ieee80211_rx_napi(ar->hw, NULL, skb, &ar->napi); } static int ath10k_htt_rx_nwifi_hdrlen(struct ath10k *ar, @@ -1014,7 +989,7 @@ static int ath10k_htt_rx_nwifi_hdrlen(struct ath10k *ar, int len = ieee80211_hdrlen(hdr->frame_control); if (!test_bit(ATH10K_FW_FEATURE_NO_NWIFI_DECAP_4ADDR_PADDING, - ar->fw_features)) + ar->running_fw->fw_file.fw_features)) len = round_up(len, 4); return len; @@ -1093,17 +1068,20 @@ static void ath10k_htt_rx_h_undecap_raw(struct ath10k *ar, } /* MMIC */ - if (!ieee80211_has_morefrags(hdr->frame_control) && + if ((status->flag & RX_FLAG_MMIC_STRIPPED) && + !ieee80211_has_morefrags(hdr->frame_control) && enctype == HTT_RX_MPDU_ENCRYPT_TKIP_WPA) skb_trim(msdu, msdu->len - 8); /* Head */ - hdr_len = ieee80211_hdrlen(hdr->frame_control); - crypto_len = ath10k_htt_rx_crypto_param_len(ar, enctype); + if (status->flag & RX_FLAG_IV_STRIPPED) { + hdr_len = ieee80211_hdrlen(hdr->frame_control); + crypto_len = ath10k_htt_rx_crypto_param_len(ar, enctype); - memmove((void *)msdu->data + crypto_len, - (void *)msdu->data, hdr_len); - skb_pull(msdu, crypto_len); + memmove((void *)msdu->data + crypto_len, + (void *)msdu->data, hdr_len); + skb_pull(msdu, crypto_len); + } } static void ath10k_htt_rx_h_undecap_nwifi(struct ath10k *ar, @@ -1113,9 +1091,11 @@ static void ath10k_htt_rx_h_undecap_nwifi(struct ath10k *ar, enum htt_rx_mpdu_encrypt_type enctype) { struct ieee80211_hdr *hdr; + struct htt_rx_desc *rxd; size_t hdr_len; u8 da[ETH_ALEN]; u8 sa[ETH_ALEN]; + int l3_pad_bytes; int bytes_aligned = ar->hw_params.decap_align_bytes; /* Delivered decapped frame: @@ -1130,7 +1110,13 @@ static void ath10k_htt_rx_h_undecap_nwifi(struct ath10k *ar, */ /* pull decapped header and copy SA & DA */ - hdr = (struct ieee80211_hdr *)msdu->data; + rxd = (void *)msdu->data - sizeof(*rxd); + + l3_pad_bytes = ath10k_rx_desc_get_l3_pad_bytes(&ar->hw_params, rxd); + skb_put(msdu, l3_pad_bytes); + + hdr = (struct ieee80211_hdr *)(msdu->data + l3_pad_bytes); + hdr_len = ath10k_htt_rx_nwifi_hdrlen(ar, hdr); ether_addr_copy(da, ieee80211_get_DA(hdr)); ether_addr_copy(sa, ieee80211_get_SA(hdr)); @@ -1205,6 +1191,8 @@ static void ath10k_htt_rx_h_undecap_eth(struct ath10k *ar, void *rfc1042; u8 da[ETH_ALEN]; u8 sa[ETH_ALEN]; + int l3_pad_bytes; + struct htt_rx_desc *rxd; int bytes_aligned = ar->hw_params.decap_align_bytes; /* Delivered decapped frame: @@ -1216,6 +1204,11 @@ static void ath10k_htt_rx_h_undecap_eth(struct ath10k *ar, if (WARN_ON_ONCE(!rfc1042)) return; + rxd = (void *)msdu->data - sizeof(*rxd); + l3_pad_bytes = ath10k_rx_desc_get_l3_pad_bytes(&ar->hw_params, rxd); + skb_put(msdu, l3_pad_bytes); + skb_pull(msdu, l3_pad_bytes); + /* pull decapped header and copy SA & DA */ eth = (struct ethhdr *)msdu->data; ether_addr_copy(da, eth->h_dest); @@ -1255,6 +1248,8 @@ static void ath10k_htt_rx_h_undecap_snap(struct ath10k *ar, { struct ieee80211_hdr *hdr; size_t hdr_len; + int l3_pad_bytes; + struct htt_rx_desc *rxd; int bytes_aligned = ar->hw_params.decap_align_bytes; /* Delivered decapped frame: @@ -1263,7 +1258,11 @@ static void ath10k_htt_rx_h_undecap_snap(struct ath10k *ar, * [payload] */ - skb_pull(msdu, sizeof(struct amsdu_subframe_hdr)); + rxd = (void *)msdu->data - sizeof(*rxd); + l3_pad_bytes = ath10k_rx_desc_get_l3_pad_bytes(&ar->hw_params, rxd); + + skb_put(msdu, l3_pad_bytes); + skb_pull(msdu, sizeof(struct amsdu_subframe_hdr) + l3_pad_bytes); hdr = (struct ieee80211_hdr *)first_hdr; hdr_len = ieee80211_hdrlen(hdr->frame_control); @@ -1376,6 +1375,7 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar, bool has_tkip_err; bool has_peer_idx_invalid; bool is_decrypted; + bool is_mgmt; u32 attention; if (skb_queue_empty(amsdu)) @@ -1384,6 +1384,9 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar, first = skb_peek(amsdu); rxd = (void *)first->data - sizeof(*rxd); + is_mgmt = !!(rxd->attention.flags & + __cpu_to_le32(RX_ATTENTION_FLAGS_MGMT_TYPE)); + enctype = MS(__le32_to_cpu(rxd->mpdu_start.info0), RX_MPDU_START_INFO0_ENCRYPT_TYPE); @@ -1427,6 +1430,7 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar, RX_FLAG_MMIC_ERROR | RX_FLAG_DECRYPTED | RX_FLAG_IV_STRIPPED | + RX_FLAG_ONLY_MONITOR | RX_FLAG_MMIC_STRIPPED); if (has_fcs_err) @@ -1435,9 +1439,19 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar, if (has_tkip_err) status->flag |= RX_FLAG_MMIC_ERROR; + /* Firmware reports all necessary management frames via WMI already. + * They are not reported to monitor interfaces at all so pass the ones + * coming via HTT to monitor interfaces instead. This simplifies + * matters a lot. + */ + if (is_mgmt) + status->flag |= RX_FLAG_ONLY_MONITOR; + if (is_decrypted) { - status->flag |= RX_FLAG_DECRYPTED | - RX_FLAG_MMIC_STRIPPED; + status->flag |= RX_FLAG_DECRYPTED; + + if (likely(!is_mgmt)) + status->flag |= RX_FLAG_MMIC_STRIPPED; if (fill_crypt_header) status->flag |= RX_FLAG_MIC_STRIPPED | @@ -1457,6 +1471,8 @@ static void ath10k_htt_rx_h_mpdu(struct ath10k *ar, */ if (!is_decrypted) continue; + if (is_mgmt) + continue; if (fill_crypt_header) continue; @@ -1570,14 +1586,6 @@ static bool ath10k_htt_rx_amsdu_allowed(struct ath10k *ar, struct sk_buff_head *amsdu, struct ieee80211_rx_status *rx_status) { - struct sk_buff *msdu; - struct htt_rx_desc *rxd; - bool is_mgmt; - bool has_fcs_err; - - msdu = skb_peek(amsdu); - rxd = (void *)msdu->data - sizeof(*rxd); - /* FIXME: It might be a good idea to do some fuzzy-testing to drop * invalid/dangerous frames. */ @@ -1587,23 +1595,6 @@ static bool ath10k_htt_rx_amsdu_allowed(struct ath10k *ar, return false; } - is_mgmt = !!(rxd->attention.flags & - __cpu_to_le32(RX_ATTENTION_FLAGS_MGMT_TYPE)); - has_fcs_err = !!(rxd->attention.flags & - __cpu_to_le32(RX_ATTENTION_FLAGS_FCS_ERR)); - - /* Management frames are handled via WMI events. The pros of such - * approach is that channel is explicitly provided in WMI events - * whereas HTT doesn't provide channel information for Rxed frames. - * - * However some firmware revisions don't report corrupted frames via - * WMI so don't drop them. - */ - if (is_mgmt && !has_fcs_err) { - ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx mgmt ctrl\n"); - return false; - } - if (test_bit(ATH10K_CAC_RUNNING, &ar->dev_flags)) { ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx cac running\n"); return false; @@ -1625,25 +1616,50 @@ static void ath10k_htt_rx_h_filter(struct ath10k *ar, __skb_queue_purge(amsdu); } -static void ath10k_htt_rx_handler(struct ath10k_htt *htt, - struct htt_rx_indication *rx) +static int ath10k_htt_rx_handle_amsdu(struct ath10k_htt *htt) { struct ath10k *ar = htt->ar; struct ieee80211_rx_status *rx_status = &htt->rx_status; - struct htt_rx_indication_mpdu_range *mpdu_ranges; struct sk_buff_head amsdu; - int num_mpdu_ranges; - int fw_desc_len; - u8 *fw_desc; - int i, ret, mpdu_count = 0; + int ret, num_msdus; - lockdep_assert_held(&htt->rx_ring.lock); + __skb_queue_head_init(&amsdu); - if (htt->rx_confused) - return; + spin_lock_bh(&htt->rx_ring.lock); + if (htt->rx_confused) { + spin_unlock_bh(&htt->rx_ring.lock); + return -EIO; + } + ret = ath10k_htt_rx_amsdu_pop(htt, &amsdu); + spin_unlock_bh(&htt->rx_ring.lock); - fw_desc_len = __le16_to_cpu(rx->prefix.fw_rx_desc_bytes); - fw_desc = (u8 *)&rx->fw_desc; + if (ret < 0) { + ath10k_warn(ar, "rx ring became corrupted: %d\n", ret); + __skb_queue_purge(&amsdu); + /* FIXME: It's probably a good idea to reboot the + * device instead of leaving it inoperable. + */ + htt->rx_confused = true; + return ret; + } + + num_msdus = skb_queue_len(&amsdu); + ath10k_htt_rx_h_ppdu(ar, &amsdu, rx_status, 0xffff); + ath10k_htt_rx_h_unchain(ar, &amsdu, ret > 0); + ath10k_htt_rx_h_filter(ar, &amsdu, rx_status); + ath10k_htt_rx_h_mpdu(ar, &amsdu, rx_status, true); + ath10k_htt_rx_h_deliver(ar, &amsdu, rx_status); + + return num_msdus; +} + +static void ath10k_htt_rx_proc_rx_ind(struct ath10k_htt *htt, + struct htt_rx_indication *rx) +{ + struct ath10k *ar = htt->ar; + struct htt_rx_indication_mpdu_range *mpdu_ranges; + int num_mpdu_ranges; + int i, mpdu_count = 0; num_mpdu_ranges = MS(__le32_to_cpu(rx->hdr.info1), HTT_RX_INDICATION_INFO1_NUM_MPDU_RANGES); @@ -1657,80 +1673,10 @@ static void ath10k_htt_rx_handler(struct ath10k_htt *htt, for (i = 0; i < num_mpdu_ranges; i++) mpdu_count += mpdu_ranges[i].mpdu_count; - while (mpdu_count--) { - __skb_queue_head_init(&amsdu); - ret = ath10k_htt_rx_amsdu_pop(htt, &fw_desc, - &fw_desc_len, &amsdu); - if (ret < 0) { - ath10k_warn(ar, "rx ring became corrupted: %d\n", ret); - __skb_queue_purge(&amsdu); - /* FIXME: It's probably a good idea to reboot the - * device instead of leaving it inoperable. - */ - htt->rx_confused = true; - break; - } - - ath10k_htt_rx_h_ppdu(ar, &amsdu, rx_status, 0xffff); - ath10k_htt_rx_h_unchain(ar, &amsdu, ret > 0); - ath10k_htt_rx_h_filter(ar, &amsdu, rx_status); - ath10k_htt_rx_h_mpdu(ar, &amsdu, rx_status, true); - ath10k_htt_rx_h_deliver(ar, &amsdu, rx_status); - } - - tasklet_schedule(&htt->rx_replenish_task); -} - -static void ath10k_htt_rx_frag_handler(struct ath10k_htt *htt, - struct htt_rx_fragment_indication *frag) -{ - struct ath10k *ar = htt->ar; - struct ieee80211_rx_status *rx_status = &htt->rx_status; - struct sk_buff_head amsdu; - int ret; - u8 *fw_desc; - int fw_desc_len; - - fw_desc_len = __le16_to_cpu(frag->fw_rx_desc_bytes); - fw_desc = (u8 *)frag->fw_msdu_rx_desc; - - __skb_queue_head_init(&amsdu); - - spin_lock_bh(&htt->rx_ring.lock); - ret = ath10k_htt_rx_amsdu_pop(htt, &fw_desc, &fw_desc_len, - &amsdu); - spin_unlock_bh(&htt->rx_ring.lock); - - tasklet_schedule(&htt->rx_replenish_task); - - ath10k_dbg(ar, ATH10K_DBG_HTT_DUMP, "htt rx frag ahead\n"); - - if (ret) { - ath10k_warn(ar, "failed to pop amsdu from httr rx ring for fragmented rx %d\n", - ret); - __skb_queue_purge(&amsdu); - return; - } - - if (skb_queue_len(&amsdu) != 1) { - ath10k_warn(ar, "failed to pop frag amsdu: too many msdus\n"); - __skb_queue_purge(&amsdu); - return; - } - - ath10k_htt_rx_h_ppdu(ar, &amsdu, rx_status, 0xffff); - ath10k_htt_rx_h_filter(ar, &amsdu, rx_status); - ath10k_htt_rx_h_mpdu(ar, &amsdu, rx_status, true); - ath10k_htt_rx_h_deliver(ar, &amsdu, rx_status); - - if (fw_desc_len > 0) { - ath10k_dbg(ar, ATH10K_DBG_HTT, - "expecting more fragmented rx in one indication %d\n", - fw_desc_len); - } + atomic_add(mpdu_count, &htt->num_mpdus_ready); } -static void ath10k_htt_rx_frm_tx_compl(struct ath10k *ar, +static void ath10k_htt_rx_tx_compl_ind(struct ath10k *ar, struct sk_buff *skb) { struct ath10k_htt *htt = &ar->htt; @@ -1742,19 +1688,19 @@ static void ath10k_htt_rx_frm_tx_compl(struct ath10k *ar, switch (status) { case HTT_DATA_TX_STATUS_NO_ACK: - tx_done.no_ack = true; + tx_done.status = HTT_TX_COMPL_STATE_NOACK; break; case HTT_DATA_TX_STATUS_OK: - tx_done.success = true; + tx_done.status = HTT_TX_COMPL_STATE_ACK; break; case HTT_DATA_TX_STATUS_DISCARD: case HTT_DATA_TX_STATUS_POSTPONE: case HTT_DATA_TX_STATUS_DOWNLOAD_FAIL: - tx_done.discard = true; + tx_done.status = HTT_TX_COMPL_STATE_DISCARD; break; default: ath10k_warn(ar, "unhandled tx completion status %d\n", status); - tx_done.discard = true; + tx_done.status = HTT_TX_COMPL_STATE_DISCARD; break; } @@ -1764,7 +1710,20 @@ static void ath10k_htt_rx_frm_tx_compl(struct ath10k *ar, for (i = 0; i < resp->data_tx_completion.num_msdus; i++) { msdu_id = resp->data_tx_completion.msdus[i]; tx_done.msdu_id = __le16_to_cpu(msdu_id); - ath10k_txrx_tx_unref(htt, &tx_done); + + /* kfifo_put: In practice firmware shouldn't fire off per-CE + * interrupt and main interrupt (MSI/-X range case) for the same + * HTC service so it should be safe to use kfifo_put w/o lock. + * + * From kfifo_put() documentation: + * Note that with only one concurrent reader and one concurrent + * writer, you don't need extra locking to use these macro. + */ + if (!kfifo_put(&htt->txdone_fifo, tx_done)) { + ath10k_warn(ar, "txdone fifo overrun, msdu_id %d status %d\n", + tx_done.msdu_id, tx_done.status); + ath10k_txrx_tx_unref(htt, &tx_done); + } } } @@ -1848,7 +1807,8 @@ static void ath10k_htt_rx_delba(struct ath10k *ar, struct htt_resp *resp) spin_unlock_bh(&ar->data_lock); } -static int ath10k_htt_rx_extract_amsdu(struct sk_buff_head *list, +static int ath10k_htt_rx_extract_amsdu(struct ath10k *ar, + struct sk_buff_head *list, struct sk_buff_head *amsdu) { struct sk_buff *msdu; @@ -1869,6 +1829,9 @@ static int ath10k_htt_rx_extract_amsdu(struct sk_buff_head *list, break; } + if (QCA_REV_WCN3990(ar)) + return 0; + msdu = skb_peek_tail(amsdu); rxd = (void *)msdu->data - sizeof(*rxd); if (!(rxd->msdu_end.common.info0 & @@ -1899,14 +1862,15 @@ static void ath10k_htt_rx_h_rx_offload_prot(struct ieee80211_rx_status *status, RX_FLAG_MMIC_STRIPPED; } -static void ath10k_htt_rx_h_rx_offload(struct ath10k *ar, - struct sk_buff_head *list) +static int ath10k_htt_rx_h_rx_offload(struct ath10k *ar, + struct sk_buff_head *list) { struct ath10k_htt *htt = &ar->htt; struct ieee80211_rx_status *status = &htt->rx_status; struct htt_rx_offload_msdu *rx; struct sk_buff *msdu; size_t offset; + int num_msdu = 0; while ((msdu = __skb_dequeue(list))) { /* Offloaded frames don't have Rx descriptor. Instead they have @@ -1946,10 +1910,12 @@ static void ath10k_htt_rx_h_rx_offload(struct ath10k *ar, ath10k_htt_rx_h_rx_offload_prot(status, msdu); ath10k_htt_rx_h_channel(ar, status, NULL, rx->vdev_id); ath10k_process_rx(ar, status, msdu); + num_msdu++; } + return num_msdu; } -static void ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) +static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) { struct ath10k_htt *htt = &ar->htt; struct htt_resp *resp = (void *)skb->data; @@ -1962,12 +1928,12 @@ static void ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) u8 tid; bool offload; bool frag; - int ret; + int ret, num_msdus = 0; lockdep_assert_held(&htt->rx_ring.lock); if (htt->rx_confused) - return; + return -EIO; skb_pull(skb, sizeof(resp->hdr)); skb_pull(skb, sizeof(resp->rx_in_ord_ind)); @@ -1986,7 +1952,7 @@ static void ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) if (skb->len < msdu_count * sizeof(*resp->rx_in_ord_ind.msdu_descs)) { ath10k_warn(ar, "dropping invalid in order rx indication\n"); - return; + return -EINVAL; } /* The event can deliver more than 1 A-MSDU. Each A-MSDU is later @@ -1997,18 +1963,18 @@ static void ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) if (ret < 0) { ath10k_warn(ar, "failed to pop paddr list: %d\n", ret); htt->rx_confused = true; - return; + return -EIO; } /* Offloaded frames are very different and need to be handled * separately. */ if (offload) - ath10k_htt_rx_h_rx_offload(ar, &list); + num_msdus = ath10k_htt_rx_h_rx_offload(ar, &list); while (!skb_queue_empty(&list)) { __skb_queue_head_init(&amsdu); - ret = ath10k_htt_rx_extract_amsdu(&list, &amsdu); + ret = ath10k_htt_rx_extract_amsdu(ar, &list, &amsdu); switch (ret) { case 0: /* Note: The in-order indication may report interleaved @@ -2017,6 +1983,7 @@ static void ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) * better to report something than nothing though. This * should still give an idea about rx rate to the user. */ + num_msdus += skb_queue_len(&amsdu); ath10k_htt_rx_h_ppdu(ar, &amsdu, status, vdev_id); ath10k_htt_rx_h_filter(ar, &amsdu, status); ath10k_htt_rx_h_mpdu(ar, &amsdu, status, false); @@ -2029,14 +1996,299 @@ static void ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) ath10k_warn(ar, "failed to extract amsdu: %d\n", ret); htt->rx_confused = true; __skb_queue_purge(&list); - return; + return -EIO; + } + } + return num_msdus; +} + +static void ath10k_htt_rx_tx_fetch_resp_id_confirm(struct ath10k *ar, + const __le32 *resp_ids, + int num_resp_ids) +{ + int i; + u32 resp_id; + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx tx fetch confirm num_resp_ids %d\n", + num_resp_ids); + + for (i = 0; i < num_resp_ids; i++) { + resp_id = le32_to_cpu(resp_ids[i]); + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx tx fetch confirm resp_id %u\n", + resp_id); + + /* TODO: free resp_id */ + } +} + +static void ath10k_htt_rx_tx_fetch_ind(struct ath10k *ar, struct sk_buff *skb) +{ + struct ieee80211_hw *hw = ar->hw; + struct ieee80211_txq *txq; + struct htt_resp *resp = (struct htt_resp *)skb->data; + struct htt_tx_fetch_record *record; + size_t len; + size_t max_num_bytes; + size_t max_num_msdus; + size_t num_bytes; + size_t num_msdus; + const __le32 *resp_ids; + u16 num_records; + u16 num_resp_ids; + u16 peer_id; + u8 tid; + int ret; + int i; + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx tx fetch ind\n"); + + len = sizeof(resp->hdr) + sizeof(resp->tx_fetch_ind); + if (unlikely(skb->len < len)) { + ath10k_warn(ar, "received corrupted tx_fetch_ind event: buffer too short\n"); + return; + } + + num_records = le16_to_cpu(resp->tx_fetch_ind.num_records); + num_resp_ids = le16_to_cpu(resp->tx_fetch_ind.num_resp_ids); + + len += sizeof(resp->tx_fetch_ind.records[0]) * num_records; + len += sizeof(resp->tx_fetch_ind.resp_ids[0]) * num_resp_ids; + + if (unlikely(skb->len < len)) { + ath10k_warn(ar, "received corrupted tx_fetch_ind event: too many records/resp_ids\n"); + return; + } + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx tx fetch ind num records %hu num resps %hu seq %hu\n", + num_records, num_resp_ids, + le16_to_cpu(resp->tx_fetch_ind.fetch_seq_num)); + + if (!ar->htt.tx_q_state.enabled) { + ath10k_warn(ar, "received unexpected tx_fetch_ind event: not enabled\n"); + return; + } + + if (ar->htt.tx_q_state.mode == HTT_TX_MODE_SWITCH_PUSH) { + ath10k_warn(ar, "received unexpected tx_fetch_ind event: in push mode\n"); + return; + } + + rcu_read_lock(); + + for (i = 0; i < num_records; i++) { + record = &resp->tx_fetch_ind.records[i]; + peer_id = MS(le16_to_cpu(record->info), + HTT_TX_FETCH_RECORD_INFO_PEER_ID); + tid = MS(le16_to_cpu(record->info), + HTT_TX_FETCH_RECORD_INFO_TID); + max_num_msdus = le16_to_cpu(record->num_msdus); + max_num_bytes = le32_to_cpu(record->num_bytes); + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx tx fetch record %i peer_id %hu tid %hhu msdus %zu bytes %zu\n", + i, peer_id, tid, max_num_msdus, max_num_bytes); + + if (unlikely(peer_id >= ar->htt.tx_q_state.num_peers) || + unlikely(tid >= ar->htt.tx_q_state.num_tids)) { + ath10k_warn(ar, "received out of range peer_id %hu tid %hhu\n", + peer_id, tid); + continue; } + + spin_lock_bh(&ar->data_lock); + txq = ath10k_mac_txq_lookup(ar, peer_id, tid); + spin_unlock_bh(&ar->data_lock); + + /* It is okay to release the lock and use txq because RCU read + * lock is held. + */ + + if (unlikely(!txq)) { + ath10k_warn(ar, "failed to lookup txq for peer_id %hu tid %hhu\n", + peer_id, tid); + continue; + } + + num_msdus = 0; + num_bytes = 0; + + while (num_msdus < max_num_msdus && + num_bytes < max_num_bytes) { + ret = ath10k_mac_tx_push_txq(hw, txq); + if (ret < 0) + break; + + num_msdus++; + num_bytes += ret; + } + + record->num_msdus = cpu_to_le16(num_msdus); + record->num_bytes = cpu_to_le32(num_bytes); + + ath10k_htt_tx_txq_recalc(hw, txq); + } + + rcu_read_unlock(); + + resp_ids = ath10k_htt_get_tx_fetch_ind_resp_ids(&resp->tx_fetch_ind); + ath10k_htt_rx_tx_fetch_resp_id_confirm(ar, resp_ids, num_resp_ids); + + ret = ath10k_htt_tx_fetch_resp(ar, + resp->tx_fetch_ind.token, + resp->tx_fetch_ind.fetch_seq_num, + resp->tx_fetch_ind.records, + num_records); + if (unlikely(ret)) { + ath10k_warn(ar, "failed to submit tx fetch resp for token 0x%08x: %d\n", + le32_to_cpu(resp->tx_fetch_ind.token), ret); + /* FIXME: request fw restart */ } - tasklet_schedule(&htt->rx_replenish_task); + ath10k_htt_tx_txq_sync(ar); } -void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) +static void ath10k_htt_rx_tx_fetch_confirm(struct ath10k *ar, + struct sk_buff *skb) +{ + const struct htt_resp *resp = (void *)skb->data; + size_t len; + int num_resp_ids; + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx tx fetch confirm\n"); + + len = sizeof(resp->hdr) + sizeof(resp->tx_fetch_confirm); + if (unlikely(skb->len < len)) { + ath10k_warn(ar, "received corrupted tx_fetch_confirm event: buffer too short\n"); + return; + } + + num_resp_ids = le16_to_cpu(resp->tx_fetch_confirm.num_resp_ids); + len += sizeof(resp->tx_fetch_confirm.resp_ids[0]) * num_resp_ids; + + if (unlikely(skb->len < len)) { + ath10k_warn(ar, "received corrupted tx_fetch_confirm event: resp_ids buffer overflow\n"); + return; + } + + ath10k_htt_rx_tx_fetch_resp_id_confirm(ar, + resp->tx_fetch_confirm.resp_ids, + num_resp_ids); +} + +static void ath10k_htt_rx_tx_mode_switch_ind(struct ath10k *ar, + struct sk_buff *skb) +{ + const struct htt_resp *resp = (void *)skb->data; + const struct htt_tx_mode_switch_record *record; + struct ieee80211_txq *txq; + struct ath10k_txq *artxq; + size_t len; + size_t num_records; + enum htt_tx_mode_switch_mode mode; + bool enable; + u16 info0; + u16 info1; + u16 threshold; + u16 peer_id; + u8 tid; + int i; + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx tx mode switch ind\n"); + + len = sizeof(resp->hdr) + sizeof(resp->tx_mode_switch_ind); + if (unlikely(skb->len < len)) { + ath10k_warn(ar, "received corrupted tx_mode_switch_ind event: buffer too short\n"); + return; + } + + info0 = le16_to_cpu(resp->tx_mode_switch_ind.info0); + info1 = le16_to_cpu(resp->tx_mode_switch_ind.info1); + + enable = !!(info0 & HTT_TX_MODE_SWITCH_IND_INFO0_ENABLE); + num_records = MS(info0, HTT_TX_MODE_SWITCH_IND_INFO1_THRESHOLD); + mode = MS(info1, HTT_TX_MODE_SWITCH_IND_INFO1_MODE); + threshold = MS(info1, HTT_TX_MODE_SWITCH_IND_INFO1_THRESHOLD); + + ath10k_dbg(ar, ATH10K_DBG_HTT, + "htt rx tx mode switch ind info0 0x%04hx info1 0x%04hx enable %d num records %zd mode %d threshold %hu\n", + info0, info1, enable, num_records, mode, threshold); + + len += sizeof(resp->tx_mode_switch_ind.records[0]) * num_records; + + if (unlikely(skb->len < len)) { + ath10k_warn(ar, "received corrupted tx_mode_switch_mode_ind event: too many records\n"); + return; + } + + switch (mode) { + case HTT_TX_MODE_SWITCH_PUSH: + case HTT_TX_MODE_SWITCH_PUSH_PULL: + break; + default: + ath10k_warn(ar, "received invalid tx_mode_switch_mode_ind mode %d, ignoring\n", + mode); + return; + } + + if (!enable) + return; + + ar->htt.tx_q_state.enabled = enable; + ar->htt.tx_q_state.mode = mode; + ar->htt.tx_q_state.num_push_allowed = threshold; + + rcu_read_lock(); + + for (i = 0; i < num_records; i++) { + record = &resp->tx_mode_switch_ind.records[i]; + info0 = le16_to_cpu(record->info0); + peer_id = MS(info0, HTT_TX_MODE_SWITCH_RECORD_INFO0_PEER_ID); + tid = MS(info0, HTT_TX_MODE_SWITCH_RECORD_INFO0_TID); + + if (unlikely(peer_id >= ar->htt.tx_q_state.num_peers) || + unlikely(tid >= ar->htt.tx_q_state.num_tids)) { + ath10k_warn(ar, "received out of range peer_id %hu tid %hhu\n", + peer_id, tid); + continue; + } + + spin_lock_bh(&ar->data_lock); + txq = ath10k_mac_txq_lookup(ar, peer_id, tid); + spin_unlock_bh(&ar->data_lock); + + /* It is okay to release the lock and use txq because RCU read + * lock is held. + */ + + if (unlikely(!txq)) { + ath10k_warn(ar, "failed to lookup txq for peer_id %hu tid %hhu\n", + peer_id, tid); + continue; + } + + spin_lock_bh(&ar->htt.tx_lock); + artxq = (void *)txq->drv_priv; + artxq->num_push_allowed = le16_to_cpu(record->num_max_msdus); + spin_unlock_bh(&ar->htt.tx_lock); + } + + rcu_read_unlock(); + + ath10k_mac_tx_push_pending(ar); +} + +void ath10k_htt_htc_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) +{ + bool release; + + release = ath10k_htt_t2h_msg_handler(ar, skb); + + /* Free the indication buffer */ + if (release) + dev_kfree_skb_any(skb); +} + +bool ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) { struct ath10k_htt *htt = &ar->htt; struct htt_resp *resp = (struct htt_resp *)skb->data; @@ -2052,8 +2304,7 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) if (resp->hdr.msg_type >= ar->htt.t2h_msg_types_max) { ath10k_dbg(ar, ATH10K_DBG_HTT, "htt rx, unsupported msg_type: 0x%0X\n max: 0x%0X", resp->hdr.msg_type, ar->htt.t2h_msg_types_max); - dev_kfree_skb_any(skb); - return; + return true; } type = ar->htt.t2h_msg_types[resp->hdr.msg_type]; @@ -2065,11 +2316,8 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) break; } case HTT_T2H_MSG_TYPE_RX_IND: - spin_lock_bh(&htt->rx_ring.lock); - __skb_queue_tail(&htt->rx_compl_q, skb); - spin_unlock_bh(&htt->rx_ring.lock); - tasklet_schedule(&htt->txrx_compl_task); - return; + ath10k_htt_rx_proc_rx_ind(htt, &resp->rx_ind); + break; case HTT_T2H_MSG_TYPE_PEER_MAP: { struct htt_peer_map_event ev = { .vdev_id = resp->peer_map.vdev_id, @@ -2090,28 +2338,31 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) struct htt_tx_done tx_done = {}; int status = __le32_to_cpu(resp->mgmt_tx_completion.status); - tx_done.msdu_id = - __le32_to_cpu(resp->mgmt_tx_completion.desc_id); + tx_done.msdu_id = __le32_to_cpu(resp->mgmt_tx_completion.desc_id); switch (status) { case HTT_MGMT_TX_STATUS_OK: - tx_done.success = true; + tx_done.status = HTT_TX_COMPL_STATE_ACK; break; case HTT_MGMT_TX_STATUS_RETRY: - tx_done.no_ack = true; + tx_done.status = HTT_TX_COMPL_STATE_NOACK; break; case HTT_MGMT_TX_STATUS_DROP: - tx_done.discard = true; + tx_done.status = HTT_TX_COMPL_STATE_DISCARD; break; } - ath10k_txrx_tx_unref(htt, &tx_done); + status = ath10k_txrx_tx_unref(htt, &tx_done); + if (!status) { + spin_lock_bh(&htt->tx_lock); + ath10k_htt_tx_mgmt_dec_pending(htt); + spin_unlock_bh(&htt->tx_lock); + } break; } case HTT_T2H_MSG_TYPE_TX_COMPL_IND: - skb_queue_tail(&htt->tx_compl_q, skb); - tasklet_schedule(&htt->txrx_compl_task); - return; + ath10k_htt_rx_tx_compl_ind(htt->ar, skb); + break; case HTT_T2H_MSG_TYPE_SEC_IND: { struct ath10k *ar = htt->ar; struct htt_security_indication *ev = &resp->security_indication; @@ -2127,7 +2378,7 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) case HTT_T2H_MSG_TYPE_RX_FRAG_IND: { ath10k_dbg_dump(ar, ATH10K_DBG_HTT_DUMP, NULL, "htt event: ", skb->data, skb->len); - ath10k_htt_rx_frag_handler(htt, &resp->rx_frag_ind); + atomic_inc(&htt->num_mpdus_ready); break; } case HTT_T2H_MSG_TYPE_TEST: @@ -2150,12 +2401,10 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) ath10k_htt_rx_delba(ar, resp); break; case HTT_T2H_MSG_TYPE_PKTLOG: { - struct ath10k_pktlog_hdr *hdr = - (struct ath10k_pktlog_hdr *)resp->pktlog_msg.payload; - trace_ath10k_htt_pktlog(ar, resp->pktlog_msg.payload, - sizeof(*hdr) + - __le16_to_cpu(hdr->size)); + skb->len - + offsetof(struct htt_resp, + pktlog_msg.payload)); break; } case HTT_T2H_MSG_TYPE_RX_FLUSH: { @@ -2165,22 +2414,41 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) break; } case HTT_T2H_MSG_TYPE_RX_IN_ORD_PADDR_IND: { - spin_lock_bh(&htt->rx_ring.lock); __skb_queue_tail(&htt->rx_in_ord_compl_q, skb); - spin_unlock_bh(&htt->rx_ring.lock); - tasklet_schedule(&htt->txrx_compl_task); - return; + return false; } case HTT_T2H_MSG_TYPE_TX_CREDIT_UPDATE_IND: break; - case HTT_T2H_MSG_TYPE_CHAN_CHANGE: + case HTT_T2H_MSG_TYPE_CHAN_CHANGE: { + u32 phymode = __le32_to_cpu(resp->chan_change.phymode); + u32 freq = __le32_to_cpu(resp->chan_change.freq); + + ar->tgt_oper_chan = + __ieee80211_get_channel(ar->hw->wiphy, freq); + ath10k_dbg(ar, ATH10K_DBG_HTT, + "htt chan change freq %u phymode %s\n", + freq, ath10k_wmi_phymode_str(phymode)); break; + } case HTT_T2H_MSG_TYPE_AGGR_CONF: break; + case HTT_T2H_MSG_TYPE_TX_FETCH_IND: { + struct sk_buff *tx_fetch_ind = skb_copy(skb, GFP_ATOMIC); + + if (!tx_fetch_ind) { + ath10k_warn(ar, "failed to copy htt tx fetch ind\n"); + break; + } + skb_queue_tail(&htt->tx_fetch_ind_q, tx_fetch_ind); + break; + } + case HTT_T2H_MSG_TYPE_TX_FETCH_CONFIRM: + ath10k_htt_rx_tx_fetch_confirm(ar, skb); + break; + case HTT_T2H_MSG_TYPE_TX_MODE_SWITCH_IND: + ath10k_htt_rx_tx_mode_switch_ind(ar, skb); + break; case HTT_T2H_MSG_TYPE_EN_STATS: - case HTT_T2H_MSG_TYPE_TX_FETCH_IND: - case HTT_T2H_MSG_TYPE_TX_FETCH_CONF: - case HTT_T2H_MSG_TYPE_TX_LOW_LATENCY_IND: default: ath10k_warn(ar, "htt event (%d) not handled\n", resp->hdr.msg_type); @@ -2188,34 +2456,116 @@ void ath10k_htt_t2h_msg_handler(struct ath10k *ar, struct sk_buff *skb) skb->data, skb->len); break; }; + return true; +} +EXPORT_SYMBOL(ath10k_htt_t2h_msg_handler); - /* Free the indication buffer */ +void ath10k_htt_rx_pktlog_completion_handler(struct ath10k *ar, + struct sk_buff *skb) +{ + trace_ath10k_htt_pktlog(ar, skb->data, skb->len); dev_kfree_skb_any(skb); } -EXPORT_SYMBOL(ath10k_htt_t2h_msg_handler); +EXPORT_SYMBOL(ath10k_htt_rx_pktlog_completion_handler); -static void ath10k_htt_txrx_compl_task(unsigned long ptr) +int ath10k_htt_txrx_compl_task(struct ath10k *ar, int budget) { - struct ath10k_htt *htt = (struct ath10k_htt *)ptr; - struct ath10k *ar = htt->ar; - struct htt_resp *resp; + struct ath10k_htt *htt = &ar->htt; + struct htt_tx_done tx_done = {}; + struct sk_buff_head tx_ind_q; struct sk_buff *skb; + unsigned long flags; + int quota = 0, done, num_rx_msdus; + bool resched_napi = false; + + __skb_queue_head_init(&tx_ind_q); + + /* Since in-ord-ind can deliver more than 1 A-MSDU in single event, + * process it first to utilize full available quota. + */ + while (quota < budget) { + if (skb_queue_empty(&htt->rx_in_ord_compl_q)) + break; + + skb = __skb_dequeue(&htt->rx_in_ord_compl_q); + if (!skb) { + resched_napi = true; + goto exit; + } + + spin_lock_bh(&htt->rx_ring.lock); + num_rx_msdus = ath10k_htt_rx_in_ord_ind(ar, skb); + spin_unlock_bh(&htt->rx_ring.lock); + if (num_rx_msdus < 0) { + resched_napi = true; + goto exit; + } - while ((skb = skb_dequeue(&htt->tx_compl_q))) { - ath10k_htt_rx_frm_tx_compl(htt->ar, skb); dev_kfree_skb_any(skb); + if (num_rx_msdus > 0) + quota += num_rx_msdus; + + if ((quota > ATH10K_NAPI_QUOTA_LIMIT) && + !skb_queue_empty(&htt->rx_in_ord_compl_q)) { + resched_napi = true; + goto exit; + } } - spin_lock_bh(&htt->rx_ring.lock); - while ((skb = __skb_dequeue(&htt->rx_compl_q))) { - resp = (struct htt_resp *)skb->data; - ath10k_htt_rx_handler(htt, &resp->rx_ind); - dev_kfree_skb_any(skb); + while (quota < budget) { + /* no more data to receive */ + if (!atomic_read(&htt->num_mpdus_ready)) + break; + + num_rx_msdus = ath10k_htt_rx_handle_amsdu(htt); + if (num_rx_msdus < 0) { + resched_napi = true; + goto exit; + } + + quota += num_rx_msdus; + atomic_dec(&htt->num_mpdus_ready); + if ((quota > ATH10K_NAPI_QUOTA_LIMIT) && + atomic_read(&htt->num_mpdus_ready)) { + resched_napi = true; + goto exit; + } } - while ((skb = __skb_dequeue(&htt->rx_in_ord_compl_q))) { - ath10k_htt_rx_in_ord_ind(ar, skb); + /* From NAPI documentation: + * The napi poll() function may also process TX completions, in which + * case if it processes the entire TX ring then it should count that + * work as the rest of the budget. + */ + if ((quota < budget) && !kfifo_is_empty(&htt->txdone_fifo)) + quota = budget; + + /* kfifo_get: called only within txrx_tasklet so it's neatly serialized. + * From kfifo_get() documentation: + * Note that with only one concurrent reader and one concurrent writer, + * you don't need extra locking to use these macro. + */ + while (kfifo_get(&htt->txdone_fifo, &tx_done)) + ath10k_txrx_tx_unref(htt, &tx_done); + + ath10k_mac_tx_push_pending(ar); + + spin_lock_irqsave(&htt->tx_fetch_ind_q.lock, flags); + skb_queue_splice_init(&htt->tx_fetch_ind_q, &tx_ind_q); + spin_unlock_irqrestore(&htt->tx_fetch_ind_q.lock, flags); + + while ((skb = __skb_dequeue(&tx_ind_q))) { + ath10k_htt_rx_tx_fetch_ind(ar, skb); dev_kfree_skb_any(skb); } - spin_unlock_bh(&htt->rx_ring.lock); + +exit: + ath10k_htt_rx_msdu_buff_replenish(htt); + /* In case of rx failure or more data to read, report budget + * to reschedule NAPI poll + */ + done = resched_napi ? budget : quota; + + return done; } +EXPORT_SYMBOL(ath10k_htt_txrx_compl_task); diff --git a/drivers/net/wireless/ath/ath10k/htt_tx.c b/drivers/net/wireless/ath/ath10k/htt_tx.c index 16823970dbfd..6579eb2b410c 100644 --- a/drivers/net/wireless/ath/ath10k/htt_tx.c +++ b/drivers/net/wireless/ath/ath10k/htt_tx.c @@ -22,53 +22,185 @@ #include "txrx.h" #include "debug.h" -void __ath10k_htt_tx_dec_pending(struct ath10k_htt *htt, bool limit_mgmt_desc) +static u8 ath10k_htt_tx_txq_calc_size(size_t count) { - if (limit_mgmt_desc) - htt->num_pending_mgmt_tx--; + int exp; + int factor; + + exp = 0; + factor = count >> 7; + + while (factor >= 64 && exp < 4) { + factor >>= 3; + exp++; + } + + if (exp == 4) + return 0xff; + + if (count > 0) + factor = max(1, factor); + + return SM(exp, HTT_TX_Q_STATE_ENTRY_EXP) | + SM(factor, HTT_TX_Q_STATE_ENTRY_FACTOR); +} + +static void __ath10k_htt_tx_txq_recalc(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct ath10k *ar = hw->priv; + struct ath10k_sta *arsta; + struct ath10k_vif *arvif = (void *)txq->vif->drv_priv; + unsigned long frame_cnt; + unsigned long byte_cnt; + int idx; + u32 bit; + u16 peer_id; + u8 tid; + u8 count; + + lockdep_assert_held(&ar->htt.tx_lock); + + if (!ar->htt.tx_q_state.enabled) + return; + + if (ar->htt.tx_q_state.mode != HTT_TX_MODE_SWITCH_PUSH_PULL) + return; + + if (txq->sta) { + arsta = (void *)txq->sta->drv_priv; + peer_id = arsta->peer_id; + } else { + peer_id = arvif->peer_id; + } + + tid = txq->tid; + bit = BIT(peer_id % 32); + idx = peer_id / 32; + + ieee80211_txq_get_depth(txq, &frame_cnt, &byte_cnt); + count = ath10k_htt_tx_txq_calc_size(byte_cnt); + + if (unlikely(peer_id >= ar->htt.tx_q_state.num_peers) || + unlikely(tid >= ar->htt.tx_q_state.num_tids)) { + ath10k_warn(ar, "refusing to update txq for peer_id %hu tid %hhu due to out of bounds\n", + peer_id, tid); + return; + } + + ar->htt.tx_q_state.vaddr->count[tid][peer_id] = count; + ar->htt.tx_q_state.vaddr->map[tid][idx] &= ~bit; + ar->htt.tx_q_state.vaddr->map[tid][idx] |= count ? bit : 0; + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt tx txq state update peer_id %hu tid %hhu count %hhu\n", + peer_id, tid, count); +} + +static void __ath10k_htt_tx_txq_sync(struct ath10k *ar) +{ + u32 seq; + size_t size; + + lockdep_assert_held(&ar->htt.tx_lock); + + if (!ar->htt.tx_q_state.enabled) + return; + + if (ar->htt.tx_q_state.mode != HTT_TX_MODE_SWITCH_PUSH_PULL) + return; + + seq = le32_to_cpu(ar->htt.tx_q_state.vaddr->seq); + seq++; + ar->htt.tx_q_state.vaddr->seq = cpu_to_le32(seq); + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt tx txq state update commit seq %u\n", + seq); + + size = sizeof(*ar->htt.tx_q_state.vaddr); + dma_sync_single_for_device(ar->dev, + ar->htt.tx_q_state.paddr, + size, + DMA_TO_DEVICE); +} + +void ath10k_htt_tx_txq_recalc(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct ath10k *ar = hw->priv; + + spin_lock_bh(&ar->htt.tx_lock); + __ath10k_htt_tx_txq_recalc(hw, txq); + spin_unlock_bh(&ar->htt.tx_lock); +} + +void ath10k_htt_tx_txq_sync(struct ath10k *ar) +{ + spin_lock_bh(&ar->htt.tx_lock); + __ath10k_htt_tx_txq_sync(ar); + spin_unlock_bh(&ar->htt.tx_lock); +} + +void ath10k_htt_tx_txq_update(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct ath10k *ar = hw->priv; + + spin_lock_bh(&ar->htt.tx_lock); + __ath10k_htt_tx_txq_recalc(hw, txq); + __ath10k_htt_tx_txq_sync(ar); + spin_unlock_bh(&ar->htt.tx_lock); +} + +void ath10k_htt_tx_dec_pending(struct ath10k_htt *htt) +{ + lockdep_assert_held(&htt->tx_lock); htt->num_pending_tx--; if (htt->num_pending_tx == htt->max_num_pending_tx - 1) ath10k_mac_tx_unlock(htt->ar, ATH10K_TX_PAUSE_Q_FULL); } -static void ath10k_htt_tx_dec_pending(struct ath10k_htt *htt, - bool limit_mgmt_desc) +int ath10k_htt_tx_inc_pending(struct ath10k_htt *htt) { - spin_lock_bh(&htt->tx_lock); - __ath10k_htt_tx_dec_pending(htt, limit_mgmt_desc); - spin_unlock_bh(&htt->tx_lock); + lockdep_assert_held(&htt->tx_lock); + + if (htt->num_pending_tx >= htt->max_num_pending_tx) + return -EBUSY; + + htt->num_pending_tx++; + if (htt->num_pending_tx == htt->max_num_pending_tx) + ath10k_mac_tx_lock(htt->ar, ATH10K_TX_PAUSE_Q_FULL); + + return 0; } -static int ath10k_htt_tx_inc_pending(struct ath10k_htt *htt, - bool limit_mgmt_desc, bool is_probe_resp) +int ath10k_htt_tx_mgmt_inc_pending(struct ath10k_htt *htt, bool is_mgmt, + bool is_presp) { struct ath10k *ar = htt->ar; - int ret = 0; - spin_lock_bh(&htt->tx_lock); + lockdep_assert_held(&htt->tx_lock); - if (htt->num_pending_tx >= htt->max_num_pending_tx) { - ret = -EBUSY; - goto exit; - } + if (!is_mgmt || !ar->hw_params.max_probe_resp_desc_thres) + return 0; - if (limit_mgmt_desc) { - if (is_probe_resp && (htt->num_pending_mgmt_tx > - ar->hw_params.max_probe_resp_desc_thres)) { - ret = -EBUSY; - goto exit; - } - htt->num_pending_mgmt_tx++; - } + if (is_presp && + ar->hw_params.max_probe_resp_desc_thres < htt->num_pending_mgmt_tx) + return -EBUSY; - htt->num_pending_tx++; - if (htt->num_pending_tx == htt->max_num_pending_tx) - ath10k_mac_tx_lock(htt->ar, ATH10K_TX_PAUSE_Q_FULL); + htt->num_pending_mgmt_tx++; -exit: - spin_unlock_bh(&htt->tx_lock); - return ret; + return 0; +} + +void ath10k_htt_tx_mgmt_dec_pending(struct ath10k_htt *htt) +{ + lockdep_assert_held(&htt->tx_lock); + + if (!htt->ar->hw_params.max_probe_resp_desc_thres) + return; + + htt->num_pending_mgmt_tx--; } int ath10k_htt_tx_alloc_msdu_id(struct ath10k_htt *htt, struct sk_buff *skb) @@ -86,6 +218,27 @@ int ath10k_htt_tx_alloc_msdu_id(struct ath10k_htt *htt, struct sk_buff *skb) return ret; } +struct sk_buff *ath10k_htt_tx_find_msdu_by_id(struct ath10k_htt *htt, + u16 msdu_id) +{ + struct ath10k *ar; + struct sk_buff *ret; + + if (!htt) + return NULL; + + ar = htt->ar; + + lockdep_assert_held(&htt->tx_lock); + + ret = (struct sk_buff *)idr_find(&htt->pending_tx, msdu_id); + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt tx find msdu by msdu_id %s\n", + !ret ? "Failed" : "Success"); + + return ret; +} + void ath10k_htt_tx_free_msdu_id(struct ath10k_htt *htt, u16 msdu_id) { struct ath10k *ar = htt->ar; @@ -97,6 +250,87 @@ void ath10k_htt_tx_free_msdu_id(struct ath10k_htt *htt, u16 msdu_id) idr_remove(&htt->pending_tx, msdu_id); } +static void ath10k_htt_tx_free_cont_frag_desc(struct ath10k_htt *htt) +{ + size_t size; + + if (!htt->frag_desc.vaddr) + return; + + size = htt->max_num_pending_tx * sizeof(struct htt_msdu_ext_desc); + + dma_free_coherent(htt->ar->dev, + size, + htt->frag_desc.vaddr, + htt->frag_desc.paddr); +} + +static int ath10k_htt_tx_alloc_cont_frag_desc(struct ath10k_htt *htt) +{ + struct ath10k *ar = htt->ar; + size_t size; + + if (!ar->hw_params.continuous_frag_desc) + return 0; + + size = htt->max_num_pending_tx * sizeof(struct htt_msdu_ext_desc); + htt->frag_desc.vaddr = dma_alloc_coherent(ar->dev, size, + &htt->frag_desc.paddr, + GFP_KERNEL); + if (!htt->frag_desc.vaddr) { + ath10k_err(ar, "failed to alloc fragment desc memory\n"); + return -ENOMEM; + } + + return 0; +} + +static void ath10k_htt_tx_free_txq(struct ath10k_htt *htt) +{ + struct ath10k *ar = htt->ar; + size_t size; + + if (!test_bit(ATH10K_FW_FEATURE_PEER_FLOW_CONTROL, + ar->running_fw->fw_file.fw_features)) + return; + + size = sizeof(*htt->tx_q_state.vaddr); + + dma_unmap_single(ar->dev, htt->tx_q_state.paddr, size, DMA_TO_DEVICE); + kfree(htt->tx_q_state.vaddr); +} + +static int ath10k_htt_tx_alloc_txq(struct ath10k_htt *htt) +{ + struct ath10k *ar = htt->ar; + size_t size; + int ret; + + if (!test_bit(ATH10K_FW_FEATURE_PEER_FLOW_CONTROL, + ar->running_fw->fw_file.fw_features)) + return 0; + + htt->tx_q_state.num_peers = HTT_TX_Q_STATE_NUM_PEERS; + htt->tx_q_state.num_tids = HTT_TX_Q_STATE_NUM_TIDS; + htt->tx_q_state.type = HTT_Q_DEPTH_TYPE_BYTES; + + size = sizeof(*htt->tx_q_state.vaddr); + htt->tx_q_state.vaddr = kzalloc(size, GFP_KERNEL); + if (!htt->tx_q_state.vaddr) + return -ENOMEM; + + htt->tx_q_state.paddr = dma_map_single(ar->dev, htt->tx_q_state.vaddr, + size, DMA_TO_DEVICE); + ret = dma_mapping_error(ar->dev, htt->tx_q_state.paddr); + if (ret) { + ath10k_warn(ar, "failed to dma map tx_q_state: %d\n", ret); + kfree(htt->tx_q_state.vaddr); + return -EIO; + } + + return 0; +} + int ath10k_htt_tx_alloc(struct ath10k_htt *htt) { struct ath10k *ar = htt->ar; @@ -111,36 +345,49 @@ int ath10k_htt_tx_alloc(struct ath10k_htt *htt) size = htt->max_num_pending_tx * sizeof(struct ath10k_htt_txbuf); htt->txbuf.vaddr = dma_alloc_coherent(ar->dev, size, &htt->txbuf.paddr, - GFP_DMA); + GFP_KERNEL); if (!htt->txbuf.vaddr) { ath10k_err(ar, "failed to alloc tx buffer\n"); ret = -ENOMEM; goto free_idr_pending_tx; } - if (!ar->hw_params.continuous_frag_desc) - goto skip_frag_desc_alloc; - - size = htt->max_num_pending_tx * sizeof(struct htt_msdu_ext_desc); - htt->frag_desc.vaddr = dma_alloc_coherent(ar->dev, size, - &htt->frag_desc.paddr, - GFP_DMA); - if (!htt->frag_desc.vaddr) { - ath10k_warn(ar, "failed to alloc fragment desc memory\n"); - ret = -ENOMEM; + ret = ath10k_htt_tx_alloc_cont_frag_desc(htt); + if (ret) { + ath10k_err(ar, "failed to alloc cont frag desc: %d\n", ret); goto free_txbuf; } -skip_frag_desc_alloc: + ret = ath10k_htt_tx_alloc_txq(htt); + if (ret) { + ath10k_err(ar, "failed to alloc txq: %d\n", ret); + goto free_frag_desc; + } + + size = roundup_pow_of_two(htt->max_num_pending_tx); + ret = kfifo_alloc(&htt->txdone_fifo, size, GFP_KERNEL); + if (ret) { + ath10k_err(ar, "failed to alloc txdone fifo: %d\n", ret); + goto free_txq; + } + return 0; +free_txq: + ath10k_htt_tx_free_txq(htt); + +free_frag_desc: + ath10k_htt_tx_free_cont_frag_desc(htt); + free_txbuf: size = htt->max_num_pending_tx * sizeof(struct ath10k_htt_txbuf); dma_free_coherent(htt->ar->dev, size, htt->txbuf.vaddr, htt->txbuf.paddr); + free_idr_pending_tx: idr_destroy(&htt->pending_tx); + return ret; } @@ -152,8 +399,8 @@ static int ath10k_htt_tx_clean_up_pending(int msdu_id, void *skb, void *ctx) ath10k_dbg(ar, ATH10K_DBG_HTT, "force cleanup msdu_id %hu\n", msdu_id); - tx_done.discard = 1; tx_done.msdu_id = msdu_id; + tx_done.status = HTT_TX_COMPL_STATE_DISCARD; ath10k_txrx_tx_unref(htt, &tx_done); @@ -174,12 +421,10 @@ void ath10k_htt_tx_free(struct ath10k_htt *htt) htt->txbuf.paddr); } - if (htt->frag_desc.vaddr) { - size = htt->max_num_pending_tx * - sizeof(struct htt_msdu_ext_desc); - dma_free_coherent(htt->ar->dev, size, htt->frag_desc.vaddr, - htt->frag_desc.paddr); - } + ath10k_htt_tx_free_txq(htt); + ath10k_htt_tx_free_cont_frag_desc(htt); + WARN_ON(!kfifo_is_empty(&htt->txdone_fifo)); + kfifo_free(&htt->txdone_fifo); } void ath10k_htt_htc_tx_complete(struct ath10k *ar, struct sk_buff *skb) @@ -263,12 +508,103 @@ int ath10k_htt_h2t_stats_req(struct ath10k_htt *htt, u8 mask, u64 cookie) return 0; } +#ifdef CONFIG_ATH10K_SNOC +static inline +void ath10k_htt_fill_rx_ring_cfg(struct ath10k_htt *htt, + struct htt_rx_ring_setup_ring *ring) +{ + ring->fw_idx_shadow_reg_paddr_low = + __cpu_to_le32(htt->rx_ring.alloc_idx.paddr); + ring->fw_idx_shadow_reg_paddr_high = 0; + ring->rx_ring_base_paddr_low = __cpu_to_le32(htt->rx_ring.base_paddr); + ring->rx_ring_base_paddr_high = upper_32_bits(htt->rx_ring.base_paddr) & + HTT_WCN3990_PADDR_MASK; +} + +static inline void ath10k_htt_fill_frags(struct htt_data_tx_desc_frag *frags, + struct sk_buff *msdu, + struct ath10k_skb_cb *skb_cb) +{ + frags[0].tword_addr.paddr_lo = __cpu_to_le32(skb_cb->paddr); + frags[0].tword_addr.paddr_hi = upper_32_bits(skb_cb->paddr) & + HTT_WCN3990_PADDR_MASK; + frags[0].tword_addr.len_16 = __cpu_to_le16(msdu->len); + frags[1].tword_addr.paddr_lo = 0; + frags[1].tword_addr.paddr_hi = 0; + frags[1].tword_addr.len_16 = 0; +} + +static inline void ath10k_htt_fill_frag_desc(struct ath10k_htt_txbuf *txbuf, + dma_addr_t frags_paddr) +{ + txbuf->cmd_tx.frags_paddr_lo = __cpu_to_le32(frags_paddr); + txbuf->cmd_tx.frags_paddr_hi = upper_32_bits(frags_paddr) & + HTT_WCN3990_PADDR_MASK; +} + +static inline +void ath10k_htt_set_bank_base_addr(struct htt_frag_desc_bank_cfg *cfg, + dma_addr_t paddr) +{ + cfg->bank_base_addrs[0].low = __cpu_to_le32(paddr); + cfg->bank_base_addrs[0].high = upper_32_bits(paddr) & + HTT_WCN3990_PADDR_MASK; +} + +static inline +u32 ath10k_htt_get_paddr_hi(dma_addr_t paddr) +{ + return(upper_32_bits(paddr) & + HTT_WCN3990_PADDR_MASK); +} +#else +static inline void ath10k_htt_fill_frags(struct htt_data_tx_desc_frag *frags, + struct sk_buff *msdu, + struct ath10k_skb_cb *skb_cb) +{ + frags[0].dword_addr.paddr = __cpu_to_le32(skb_cb->paddr); + frags[0].dword_addr.len = __cpu_to_le32(msdu->len); + frags[1].dword_addr.paddr = 0; + frags[1].dword_addr.len = 0; +} + +static inline void ath10k_htt_fill_frag_desc(struct ath10k_htt_txbuf *txbuf, + dma_addr_t frags_paddr) +{ + txbuf->cmd_tx.frags_paddr = __cpu_to_le32(frags_paddr); +} + +static inline +void ath10k_htt_set_bank_base_addr(struct htt_frag_desc_bank_cfg *cfg, + dma_addr_t paddr) +{ + cfg->bank_base_addrs[0] = __cpu_to_le32(paddr); +} + +static inline +void ath10k_htt_fill_rx_ring_cfg(struct ath10k_htt *htt, + struct htt_rx_ring_setup_ring *ring) +{ + ring->fw_idx_shadow_reg_paddr = + __cpu_to_le32(htt->rx_ring.alloc_idx.paddr); + ring->rx_ring_base_paddr = __cpu_to_le32(htt->rx_ring.base_paddr); +} + +static inline +u32 ath10k_htt_get_paddr_hi(dma_addr_t paddr) +{ + return 0; +} +#endif + int ath10k_htt_send_frag_desc_bank_cfg(struct ath10k_htt *htt) { struct ath10k *ar = htt->ar; struct sk_buff *skb; struct htt_cmd *cmd; + struct htt_frag_desc_bank_cfg *cfg; int ret, size; + u8 info; if (!ar->hw_params.continuous_frag_desc) return 0; @@ -286,14 +622,31 @@ int ath10k_htt_send_frag_desc_bank_cfg(struct ath10k_htt *htt) skb_put(skb, size); cmd = (struct htt_cmd *)skb->data; cmd->hdr.msg_type = HTT_H2T_MSG_TYPE_FRAG_DESC_BANK_CFG; - cmd->frag_desc_bank_cfg.info = 0; - cmd->frag_desc_bank_cfg.num_banks = 1; - cmd->frag_desc_bank_cfg.desc_size = sizeof(struct htt_msdu_ext_desc); - cmd->frag_desc_bank_cfg.bank_base_addrs[0] = - __cpu_to_le32(htt->frag_desc.paddr); - cmd->frag_desc_bank_cfg.bank_id[0].bank_min_id = 0; - cmd->frag_desc_bank_cfg.bank_id[0].bank_max_id = - __cpu_to_le16(htt->max_num_pending_tx - 1); + + info = 0; + info |= SM(htt->tx_q_state.type, + HTT_FRAG_DESC_BANK_CFG_INFO_Q_STATE_DEPTH_TYPE); + + if (test_bit(ATH10K_FW_FEATURE_PEER_FLOW_CONTROL, + ar->running_fw->fw_file.fw_features)) + info |= HTT_FRAG_DESC_BANK_CFG_INFO_Q_STATE_VALID; + + cfg = &cmd->frag_desc_bank_cfg; + cfg->info = info; + cfg->num_banks = 1; + cfg->desc_size = sizeof(struct htt_msdu_ext_desc); + ath10k_htt_set_bank_base_addr(cfg, htt->frag_desc.paddr); + cfg->bank_id[0].bank_min_id = 0; + cfg->bank_id[0].bank_max_id = __cpu_to_le16(htt->max_num_pending_tx - + 1); + + cfg->q_state.paddr = cpu_to_le32(htt->tx_q_state.paddr); + cfg->q_state.num_peers = cpu_to_le16(htt->tx_q_state.num_peers); + cfg->q_state.num_tids = cpu_to_le16(htt->tx_q_state.num_tids); + cfg->q_state.record_size = HTT_TX_Q_STATE_ENTRY_SIZE; + cfg->q_state.record_multiplier = HTT_TX_Q_STATE_ENTRY_MULTIPLIER; + + ath10k_dbg(ar, ATH10K_DBG_HTT, "htt frag desc bank cmd\n"); ret = ath10k_htc_send(&htt->ar->htc, htt->eid, skb); if (ret) { @@ -360,9 +713,7 @@ int ath10k_htt_send_rx_ring_cfg_ll(struct ath10k_htt *htt) fw_idx = __le32_to_cpu(*htt->rx_ring.alloc_idx.vaddr); - ring->fw_idx_shadow_reg_paddr = - __cpu_to_le32(htt->rx_ring.alloc_idx.paddr); - ring->rx_ring_base_paddr = __cpu_to_le32(htt->rx_ring.base_paddr); + ath10k_htt_fill_rx_ring_cfg(htt, ring); ring->rx_ring_len = __cpu_to_le16(htt->rx_ring.size); ring->rx_ring_bufsize = __cpu_to_le16(HTT_RX_BUF_SIZE); ring->flags = __cpu_to_le16(flags); @@ -439,6 +790,86 @@ int ath10k_htt_h2t_aggr_cfg_msg(struct ath10k_htt *htt, return 0; } +int ath10k_htt_tx_fetch_resp(struct ath10k *ar, + __le32 token, + __le16 fetch_seq_num, + struct htt_tx_fetch_record *records, + size_t num_records) +{ + struct sk_buff *skb; + struct htt_cmd *cmd; + const u16 resp_id = 0; + int len = 0; + int ret; + + /* Response IDs are echo-ed back only for host driver convienence + * purposes. They aren't used for anything in the driver yet so use 0. + */ + + len += sizeof(cmd->hdr); + len += sizeof(cmd->tx_fetch_resp); + len += sizeof(cmd->tx_fetch_resp.records[0]) * num_records; + + skb = ath10k_htc_alloc_skb(ar, len); + if (!skb) + return -ENOMEM; + + skb_put(skb, len); + cmd = (struct htt_cmd *)skb->data; + cmd->hdr.msg_type = HTT_H2T_MSG_TYPE_TX_FETCH_RESP; + cmd->tx_fetch_resp.resp_id = cpu_to_le16(resp_id); + cmd->tx_fetch_resp.fetch_seq_num = fetch_seq_num; + cmd->tx_fetch_resp.num_records = cpu_to_le16(num_records); + cmd->tx_fetch_resp.token = token; + + memcpy(cmd->tx_fetch_resp.records, records, + sizeof(records[0]) * num_records); + + ret = ath10k_htc_send(&ar->htc, ar->htt.eid, skb); + if (ret) { + ath10k_warn(ar, "failed to submit htc command: %d\n", ret); + goto err_free_skb; + } + + return 0; + +err_free_skb: + dev_kfree_skb_any(skb); + + return ret; +} + +static u8 ath10k_htt_tx_get_vdev_id(struct ath10k *ar, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ath10k_skb_cb *cb = ATH10K_SKB_CB(skb); + struct ath10k_vif *arvif; + + if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) { + return ar->scan.vdev_id; + } else if (cb->vif) { + arvif = (void *)cb->vif->drv_priv; + return arvif->vdev_id; + } else if (ar->monitor_started) { + return ar->monitor_vdev_id; + } else { + return 0; + } +} + +static u8 ath10k_htt_tx_get_tid(struct sk_buff *skb, bool is_eth) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + struct ath10k_skb_cb *cb = ATH10K_SKB_CB(skb); + + if (!is_eth && ieee80211_is_mgmt(hdr->frame_control)) + return HTT_DATA_TX_EXT_TID_MGMT; + else if (cb->flags & ATH10K_SKB_F_QOS) + return skb->priority % IEEE80211_QOS_CTL_TID_MASK; + else + return HTT_DATA_TX_EXT_TID_NON_QOS_MCAST_BCAST; +} + int ath10k_htt_mgmt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) { struct ath10k *ar = htt->ar; @@ -446,25 +877,11 @@ int ath10k_htt_mgmt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) struct sk_buff *txdesc = NULL; struct htt_cmd *cmd; struct ath10k_skb_cb *skb_cb = ATH10K_SKB_CB(msdu); - u8 vdev_id = skb_cb->vdev_id; + u8 vdev_id = ath10k_htt_tx_get_vdev_id(ar, msdu); int len = 0; int msdu_id = -1; int res; struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)msdu->data; - bool limit_mgmt_desc = false; - bool is_probe_resp = false; - - if (ar->hw_params.max_probe_resp_desc_thres) { - limit_mgmt_desc = true; - - if (ieee80211_is_probe_resp(hdr->frame_control)) - is_probe_resp = true; - } - - res = ath10k_htt_tx_inc_pending(htt, limit_mgmt_desc, is_probe_resp); - - if (res) - goto err; len += sizeof(cmd->hdr); len += sizeof(cmd->mgmt_tx); @@ -473,10 +890,17 @@ int ath10k_htt_mgmt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) res = ath10k_htt_tx_alloc_msdu_id(htt, msdu); spin_unlock_bh(&htt->tx_lock); if (res < 0) - goto err_tx_dec; + goto err; msdu_id = res; + if ((ieee80211_is_action(hdr->frame_control) || + ieee80211_is_deauth(hdr->frame_control) || + ieee80211_is_disassoc(hdr->frame_control)) && + ieee80211_has_protected(hdr->frame_control)) { + skb_put(msdu, IEEE80211_CCMP_MIC_LEN); + } + txdesc = ath10k_htc_alloc_skb(ar, len); if (!txdesc) { res = -ENOMEM; @@ -503,8 +927,6 @@ int ath10k_htt_mgmt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) memcpy(cmd->mgmt_tx.hdr, msdu->data, min_t(int, msdu->len, HTT_MGMT_FRM_HDR_DOWNLOAD_LEN)); - skb_cb->htt.txbuf = NULL; - res = ath10k_htc_send(&htt->ar->htc, htt->eid, txdesc); if (res) goto err_unmap_msdu; @@ -519,65 +941,55 @@ err_free_msdu_id: spin_lock_bh(&htt->tx_lock); ath10k_htt_tx_free_msdu_id(htt, msdu_id); spin_unlock_bh(&htt->tx_lock); -err_tx_dec: - ath10k_htt_tx_dec_pending(htt, limit_mgmt_desc); err: return res; } -int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) +int ath10k_htt_tx(struct ath10k_htt *htt, enum ath10k_hw_txrx_mode txmode, + struct sk_buff *msdu) { struct ath10k *ar = htt->ar; struct device *dev = ar->dev; struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)msdu->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(msdu); struct ath10k_skb_cb *skb_cb = ATH10K_SKB_CB(msdu); struct ath10k_hif_sg_item sg_items[2]; + struct ath10k_htt_txbuf *txbuf; struct htt_data_tx_desc_frag *frags; - u8 vdev_id = skb_cb->vdev_id; - u8 tid = skb_cb->htt.tid; + bool is_eth = (txmode == ATH10K_HW_TXRX_ETHERNET); + u8 vdev_id = ath10k_htt_tx_get_vdev_id(ar, msdu); + u8 tid = ath10k_htt_tx_get_tid(msdu, is_eth); int prefetch_len; int res; u8 flags0 = 0; u16 msdu_id, flags1 = 0; - u32 frags_paddr = 0; + u16 freq = 0; + dma_addr_t frags_paddr = 0; + u32 txbuf_paddr; struct htt_msdu_ext_desc *ext_desc = NULL; - bool limit_mgmt_desc = false; - bool is_probe_resp = false; - - if (unlikely(ieee80211_is_mgmt(hdr->frame_control)) && - ar->hw_params.max_probe_resp_desc_thres) { - limit_mgmt_desc = true; - - if (ieee80211_is_probe_resp(hdr->frame_control)) - is_probe_resp = true; - } - - res = ath10k_htt_tx_inc_pending(htt, limit_mgmt_desc, is_probe_resp); - if (res) - goto err; spin_lock_bh(&htt->tx_lock); res = ath10k_htt_tx_alloc_msdu_id(htt, msdu); spin_unlock_bh(&htt->tx_lock); if (res < 0) - goto err_tx_dec; + goto err; msdu_id = res; prefetch_len = min(htt->prefetch_len, msdu->len); prefetch_len = roundup(prefetch_len, 4); - skb_cb->htt.txbuf = &htt->txbuf.vaddr[msdu_id]; - skb_cb->htt.txbuf_paddr = htt->txbuf.paddr + - (sizeof(struct ath10k_htt_txbuf) * msdu_id); + txbuf = &htt->txbuf.vaddr[msdu_id]; + txbuf_paddr = htt->txbuf.paddr + + (sizeof(struct ath10k_htt_txbuf) * msdu_id); if ((ieee80211_is_action(hdr->frame_control) || ieee80211_is_deauth(hdr->frame_control) || ieee80211_is_disassoc(hdr->frame_control)) && ieee80211_has_protected(hdr->frame_control)) { skb_put(msdu, IEEE80211_CCMP_MIC_LEN); - } else if (!skb_cb->htt.nohwcrypt && - skb_cb->txmode == ATH10K_HW_TXRX_RAW && + } else if (!(skb_cb->flags & ATH10K_SKB_F_NO_HWCRYPT) && + txmode == ATH10K_HW_TXRX_RAW && ieee80211_has_protected(hdr->frame_control)) { skb_put(msdu, IEEE80211_CCMP_MIC_LEN); } @@ -590,7 +1002,10 @@ int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) goto err_free_msdu_id; } - switch (skb_cb->txmode) { + if (unlikely(info->flags & IEEE80211_TX_CTL_TX_OFFCHAN)) + freq = ar->scan.roc_freq; + + switch (txmode) { case ATH10K_HW_TXRX_RAW: case ATH10K_HW_TXRX_NATIVE_WIFI: flags0 |= HTT_DATA_TX_DESC_FLAGS0_MAC_HDR_PRESENT; @@ -604,22 +1019,18 @@ int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) ext_desc = &htt->frag_desc.vaddr[msdu_id]; frags[0].tword_addr.paddr_lo = __cpu_to_le32(skb_cb->paddr); - frags[0].tword_addr.paddr_hi = 0; + frags[0].tword_addr.paddr_hi = + ath10k_htt_get_paddr_hi(skb_cb->paddr); frags[0].tword_addr.len_16 = __cpu_to_le16(msdu->len); frags_paddr = htt->frag_desc.paddr + (sizeof(struct htt_msdu_ext_desc) * msdu_id); } else { - frags = skb_cb->htt.txbuf->frags; - frags[0].dword_addr.paddr = - __cpu_to_le32(skb_cb->paddr); - frags[0].dword_addr.len = __cpu_to_le32(msdu->len); - frags[1].dword_addr.paddr = 0; - frags[1].dword_addr.len = 0; - - frags_paddr = skb_cb->htt.txbuf_paddr; + frags = txbuf->frags; + ath10k_htt_fill_frags(frags, msdu, skb_cb); + frags_paddr = txbuf_paddr; } - flags0 |= SM(skb_cb->txmode, HTT_DATA_TX_DESC_FLAGS0_PKT_TYPE); + flags0 |= SM(txmode, HTT_DATA_TX_DESC_FLAGS0_PKT_TYPE); break; case ATH10K_HW_TXRX_MGMT: flags0 |= SM(ATH10K_HW_TXRX_MGMT, @@ -646,17 +1057,13 @@ int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) * avoid extra memory allocations, compress data structures and thus * improve performance. */ - skb_cb->htt.txbuf->htc_hdr.eid = htt->eid; - skb_cb->htt.txbuf->htc_hdr.len = __cpu_to_le16( - sizeof(skb_cb->htt.txbuf->cmd_hdr) + - sizeof(skb_cb->htt.txbuf->cmd_tx) + - prefetch_len); - skb_cb->htt.txbuf->htc_hdr.flags = 0; + txbuf->htc_hdr.eid = htt->eid; + txbuf->htc_hdr.len = __cpu_to_le16(sizeof(txbuf->cmd_hdr) + + sizeof(txbuf->cmd_tx) + + prefetch_len); + txbuf->htc_hdr.flags = 0; - if (skb_cb->htt.nohwcrypt) - flags0 |= HTT_DATA_TX_DESC_FLAGS0_NO_ENCRYPT; - - if (!skb_cb->is_protected) + if (skb_cb->flags & ATH10K_SKB_F_NO_HWCRYPT) flags0 |= HTT_DATA_TX_DESC_FLAGS0_NO_ENCRYPT; flags1 |= SM((u16)vdev_id, HTT_DATA_TX_DESC_FLAGS1_VDEV_ID); @@ -665,8 +1072,14 @@ int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) !test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags)) { flags1 |= HTT_DATA_TX_DESC_FLAGS1_CKSUM_L3_OFFLOAD; flags1 |= HTT_DATA_TX_DESC_FLAGS1_CKSUM_L4_OFFLOAD; - if (ar->hw_params.continuous_frag_desc) + if (ar->hw_params.continuous_frag_desc) { ext_desc->flags |= HTT_MSDU_CHECKSUM_ENABLE; + if (QCA_REV_WCN3990(ar)) { + memset(ext_desc->tso_flag, 0, + sizeof(ext_desc->tso_flag)); + ext_desc->tso_flag[3] |= HTT_TX_CHECKSUM_ENABLE; + } + } } /* Prevent firmware from sending up tx inspection requests. There's @@ -675,20 +1088,29 @@ int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) */ flags1 |= HTT_DATA_TX_DESC_FLAGS1_POSTPONED; - skb_cb->htt.txbuf->cmd_hdr.msg_type = HTT_H2T_MSG_TYPE_TX_FRM; - skb_cb->htt.txbuf->cmd_tx.flags0 = flags0; - skb_cb->htt.txbuf->cmd_tx.flags1 = __cpu_to_le16(flags1); - skb_cb->htt.txbuf->cmd_tx.len = __cpu_to_le16(msdu->len); - skb_cb->htt.txbuf->cmd_tx.id = __cpu_to_le16(msdu_id); - skb_cb->htt.txbuf->cmd_tx.frags_paddr = __cpu_to_le32(frags_paddr); - skb_cb->htt.txbuf->cmd_tx.peerid = __cpu_to_le16(HTT_INVALID_PEERID); - skb_cb->htt.txbuf->cmd_tx.freq = __cpu_to_le16(skb_cb->htt.freq); + txbuf->cmd_hdr.msg_type = HTT_H2T_MSG_TYPE_TX_FRM; + txbuf->cmd_tx.flags0 = flags0; + txbuf->cmd_tx.flags1 = __cpu_to_le16(flags1); + txbuf->cmd_tx.len = __cpu_to_le16(msdu->len); + txbuf->cmd_tx.id = __cpu_to_le16(msdu_id); + + /* fill fragment descriptor */ + ath10k_htt_fill_frag_desc(txbuf, frags_paddr); + if (ath10k_mac_tx_frm_has_freq(ar)) { + txbuf->cmd_tx.offchan_tx.peerid = + __cpu_to_le16(HTT_INVALID_PEERID); + txbuf->cmd_tx.offchan_tx.freq = + __cpu_to_le16(freq); + } else { + txbuf->cmd_tx.peerid = + __cpu_to_le32(HTT_INVALID_PEERID); + } trace_ath10k_htt_tx(ar, msdu_id, msdu->len, vdev_id, tid); ath10k_dbg(ar, ATH10K_DBG_HTT, - "htt tx flags0 %hhu flags1 %hu len %d id %hu frags_paddr %08x, msdu_paddr %08x vdev %hhu tid %hhu freq %hu\n", - flags0, flags1, msdu->len, msdu_id, frags_paddr, - (u32)skb_cb->paddr, vdev_id, tid, skb_cb->htt.freq); + "htt tx flags0 %hhu flags1 %hu len %d id %hu frags_paddr %pad, msdu_paddr %pad vdev %hhu tid %hhu freq %hu\n", + flags0, flags1, msdu->len, msdu_id, &frags_paddr, + &skb_cb->paddr, vdev_id, tid, freq); ath10k_dbg_dump(ar, ATH10K_DBG_HTT_DUMP, NULL, "htt tx msdu: ", msdu->data, msdu->len); trace_ath10k_tx_hdr(ar, msdu->data, msdu->len); @@ -696,12 +1118,12 @@ int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) sg_items[0].transfer_id = 0; sg_items[0].transfer_context = NULL; - sg_items[0].vaddr = &skb_cb->htt.txbuf->htc_hdr; - sg_items[0].paddr = skb_cb->htt.txbuf_paddr + - sizeof(skb_cb->htt.txbuf->frags); - sg_items[0].len = sizeof(skb_cb->htt.txbuf->htc_hdr) + - sizeof(skb_cb->htt.txbuf->cmd_hdr) + - sizeof(skb_cb->htt.txbuf->cmd_tx); + sg_items[0].vaddr = &txbuf->htc_hdr; + sg_items[0].paddr = txbuf_paddr + + sizeof(txbuf->frags); + sg_items[0].len = sizeof(txbuf->htc_hdr) + + sizeof(txbuf->cmd_hdr) + + sizeof(txbuf->cmd_tx); sg_items[1].transfer_id = 0; sg_items[1].transfer_context = NULL; @@ -720,11 +1142,7 @@ int ath10k_htt_tx(struct ath10k_htt *htt, struct sk_buff *msdu) err_unmap_msdu: dma_unmap_single(dev, skb_cb->paddr, msdu->len, DMA_TO_DEVICE); err_free_msdu_id: - spin_lock_bh(&htt->tx_lock); ath10k_htt_tx_free_msdu_id(htt, msdu_id); - spin_unlock_bh(&htt->tx_lock); -err_tx_dec: - ath10k_htt_tx_dec_pending(htt, limit_mgmt_desc); err: return res; } diff --git a/drivers/net/wireless/ath/ath10k/hw.c b/drivers/net/wireless/ath/ath10k/hw.c index 7b84d08a5154..558214cef688 100644 --- a/drivers/net/wireless/ath/ath10k/hw.c +++ b/drivers/net/wireless/ath/ath10k/hw.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2014-2015, 2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,7 +19,6 @@ #include "hw.h" const struct ath10k_hw_regs qca988x_regs = { - .rtc_state_cold_reset_mask = 0x00000400, .rtc_soc_base_address = 0x00004000, .rtc_wmac_base_address = 0x00005000, .soc_core_base_address = 0x00009000, @@ -46,7 +45,6 @@ const struct ath10k_hw_regs qca988x_regs = { }; const struct ath10k_hw_regs qca6174_regs = { - .rtc_state_cold_reset_mask = 0x00002000, .rtc_soc_base_address = 0x00000800, .rtc_wmac_base_address = 0x00001000, .soc_core_base_address = 0x0003a000, @@ -73,7 +71,6 @@ const struct ath10k_hw_regs qca6174_regs = { }; const struct ath10k_hw_regs qca99x0_regs = { - .rtc_state_cold_reset_mask = 0x00000400, .rtc_soc_base_address = 0x00080000, .rtc_wmac_base_address = 0x00000000, .soc_core_base_address = 0x00082000, @@ -88,7 +85,7 @@ const struct ath10k_hw_regs qca99x0_regs = { .ce7_base_address = 0x0004bc00, /* Note: qca99x0 supports upto 12 Copy Engines. Other than address of * CE0 and CE1 no other copy engine is directly referred in the code. - * It is not really neccessary to assign address for newly supported + * It is not really necessary to assign address for newly supported * CEs in this address table. * Copy Engine Address * CE8 0x0004c000 @@ -109,7 +106,368 @@ const struct ath10k_hw_regs qca99x0_regs = { .pcie_intr_clr_address = 0x00000010, }; +const struct ath10k_hw_regs qca4019_regs = { + .rtc_soc_base_address = 0x00080000, + .soc_core_base_address = 0x00082000, + .ce_wrapper_base_address = 0x0004d000, + .ce0_base_address = 0x0004a000, + .ce1_base_address = 0x0004a400, + .ce2_base_address = 0x0004a800, + .ce3_base_address = 0x0004ac00, + .ce4_base_address = 0x0004b000, + .ce5_base_address = 0x0004b400, + .ce6_base_address = 0x0004b800, + .ce7_base_address = 0x0004bc00, + /* qca4019 supports upto 12 copy engines. Since base address + * of ce8 to ce11 are not directly referred in the code, + * no need have them in separate members in this table. + * Copy Engine Address + * CE8 0x0004c000 + * CE9 0x0004c400 + * CE10 0x0004c800 + * CE11 0x0004cc00 + */ + .soc_reset_control_si0_rst_mask = 0x00000001, + .soc_reset_control_ce_rst_mask = 0x00000100, + .soc_chip_id_address = 0x000000ec, + .fw_indicator_address = 0x0004f00c, + .ce_wrap_intr_sum_host_msi_lsb = 0x0000000c, + .ce_wrap_intr_sum_host_msi_mask = 0x00fff000, + .pcie_intr_fw_mask = 0x00100000, + .pcie_intr_ce_mask_all = 0x000fff00, + .pcie_intr_clr_address = 0x00000010, +}; + +const struct ath10k_hw_regs wcn3990_regs = { + .rtc_soc_base_address = 0x00000000, + .rtc_wmac_base_address = 0x00000000, + .soc_core_base_address = 0x00000000, + .ce_wrapper_base_address = 0x0024C000, + .soc_global_reset_address = 0x00000008, + .ce0_base_address = 0x00240000, + .ce1_base_address = 0x00241000, + .ce2_base_address = 0x00242000, + .ce3_base_address = 0x00243000, + .ce4_base_address = 0x00244000, + .ce5_base_address = 0x00245000, + .ce6_base_address = 0x00246000, + .ce7_base_address = 0x00247000, + .ce8_base_address = 0x00248000, + .ce9_base_address = 0x00249000, + .ce10_base_address = 0x0024A000, + .ce11_base_address = 0x0024B000, + .soc_chip_id_address = 0x000000f0, + .soc_reset_control_si0_rst_mask = 0x00000001, + .soc_reset_control_ce_rst_mask = 0x00000100, + .ce_wrap_intr_sum_host_msi_lsb = 0x0000000c, + .ce_wrap_intr_sum_host_msi_mask = 0x00fff000, + .pcie_intr_fw_mask = 0x00100000, +}; + +static unsigned int +ath10k_set_ring_byte(unsigned int offset, + struct ath10k_hw_ce_regs_addr_map *addr_map) +{ + return (((0 | (offset)) << addr_map->lsb) & addr_map->mask); +} + +static unsigned int +ath10k_get_ring_byte(unsigned int offset, + struct ath10k_hw_ce_regs_addr_map *addr_map) +{ + return (((offset) & addr_map->mask) >> (addr_map->lsb)); +} + +struct ath10k_hw_ce_regs_addr_map wcn3990_src_ring = { + .msb = 0x00000010, + .lsb = 0x00000010, + .mask = 0x00020000, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_regs_addr_map wcn3990_dst_ring = { + .msb = 0x00000012, + .lsb = 0x00000012, + .mask = 0x00040000, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_regs_addr_map wcn3990_dmax = { + .msb = 0x00000000, + .lsb = 0x00000000, + .mask = 0x0000ffff, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_ctrl1 wcn3990_ctrl1 = { + .addr = 0x00000018, + .src_ring = &wcn3990_src_ring, + .dst_ring = &wcn3990_dst_ring, + .dmax = &wcn3990_dmax, +}; + +struct ath10k_hw_ce_regs_addr_map wcn3990_host_ie_cc = { + .mask = 0x00000001, +}; + +struct ath10k_hw_ce_host_ie wcn3990_host_ie = { + .copy_complete = &wcn3990_host_ie_cc, +}; + +struct ath10k_hw_ce_host_wm_regs wcn3990_wm_reg = { + .dstr_lmask = 0x00000010, + .dstr_hmask = 0x00000008, + .srcr_lmask = 0x00000004, + .srcr_hmask = 0x00000002, + .cc_mask = 0x00000001, + .wm_mask = 0x0000001E, + .addr = 0x00000030, +}; + +struct ath10k_hw_ce_misc_regs wcn3990_misc_reg = { + .axi_err = 0x00000100, + .dstr_add_err = 0x00000200, + .srcr_len_err = 0x00000100, + .dstr_mlen_vio = 0x00000080, + .dstr_overflow = 0x00000040, + .srcr_overflow = 0x00000020, + .err_mask = 0x000003E0, + .addr = 0x00000038, +}; + +struct ath10k_hw_ce_regs_addr_map wcn3990_src_wm_low = { + .msb = 0x00000000, + .lsb = 0x00000010, + .mask = 0xffff0000, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_regs_addr_map wcn3990_src_wm_high = { + .msb = 0x0000000f, + .lsb = 0x00000000, + .mask = 0x0000ffff, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_dst_src_wm_regs wcn3990_wm_src_ring = { + .addr = 0x0000004c, + .low_rst = 0x00000000, + .high_rst = 0x00000000, + .wm_low = &wcn3990_src_wm_low, + .wm_high = &wcn3990_src_wm_high, +}; + +struct ath10k_hw_ce_regs_addr_map wcn3990_dst_wm_low = { + .lsb = 0x00000010, + .mask = 0xffff0000, + .set = &ath10k_set_ring_byte, +}; + +struct ath10k_hw_ce_regs_addr_map wcn3990_dst_wm_high = { + .msb = 0x0000000f, + .lsb = 0x00000000, + .mask = 0x0000ffff, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_dst_src_wm_regs wcn3990_wm_dst_ring = { + .addr = 0x00000050, + .low_rst = 0x00000000, + .high_rst = 0x00000000, + .wm_low = &wcn3990_dst_wm_low, + .wm_high = &wcn3990_dst_wm_high, +}; + +static struct ath10k_hw_ce_ctrl1_upd wcn3990_ctrl1_upd = { + .shift = 19, + .mask = 0x00080000, + .enable = 0x00000000, +}; + +struct ath10k_hw_ce_regs wcn3990_ce_regs = { + .sr_base_addr = 0x00000000, + .sr_size_addr = 0x00000008, + .dr_base_addr = 0x0000000c, + .dr_size_addr = 0x00000014, + .misc_ie_addr = 0x00000034, + .sr_wr_index_addr = 0x0000003c, + .dst_wr_index_addr = 0x00000040, + .current_srri_addr = 0x00000044, + .current_drri_addr = 0x00000048, + .ddr_addr_for_rri_low = 0x00000004, + .ddr_addr_for_rri_high = 0x00000008, + .ce_rri_low = 0x0024C004, + .ce_rri_high = 0x0024C008, + .host_ie_addr = 0x0000002c, + .ctrl1_regs = &wcn3990_ctrl1, + .host_ie = &wcn3990_host_ie, + .wm_regs = &wcn3990_wm_reg, + .misc_regs = &wcn3990_misc_reg, + .wm_srcr = &wcn3990_wm_src_ring, + .wm_dstr = &wcn3990_wm_dst_ring, + .upd = &wcn3990_ctrl1_upd, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_src_ring = { + .msb = 0x00000010, + .lsb = 0x00000010, + .mask = 0x00010000, + .set = &ath10k_set_ring_byte, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_dst_ring = { + .msb = 0x00000011, + .lsb = 0x00000011, + .mask = 0x00020000, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_dmax = { + .msb = 0x0000000f, + .lsb = 0x00000000, + .mask = 0x0000ffff, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_ctrl1 qcax_ctrl1 = { + .addr = 0x00000010, + .hw_mask = 0x0007ffff, + .sw_mask = 0x0007ffff, + .hw_wr_mask = 0x00000000, + .sw_wr_mask = 0x0007ffff, + .reset_mask = 0xffffffff, + .reset = 0x00000080, + .src_ring = &qcax_src_ring, + .dst_ring = &qcax_dst_ring, + .dmax = &qcax_dmax, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_cmd_halt_status = { + .msb = 0x00000003, + .lsb = 0x00000003, + .mask = 0x00000008, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_cmd_halt qcax_cmd_halt = { + .msb = 0x00000000, + .mask = 0x00000001, + .status_reset = 0x00000000, + .status = &qcax_cmd_halt_status, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_host_ie_cc = { + .msb = 0x00000000, + .lsb = 0x00000000, + .mask = 0x00000001, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_host_ie qcax_host_ie = { + .copy_complete_reset = 0x00000000, + .copy_complete = &qcax_host_ie_cc, +}; + +struct ath10k_hw_ce_host_wm_regs qcax_wm_reg = { + .dstr_lmask = 0x00000010, + .dstr_hmask = 0x00000008, + .srcr_lmask = 0x00000004, + .srcr_hmask = 0x00000002, + .cc_mask = 0x00000001, + .wm_mask = 0x0000001E, + .addr = 0x00000030, +}; + +struct ath10k_hw_ce_misc_regs qcax_misc_reg = { + .axi_err = 0x00000400, + .dstr_add_err = 0x00000200, + .srcr_len_err = 0x00000100, + .dstr_mlen_vio = 0x00000080, + .dstr_overflow = 0x00000040, + .srcr_overflow = 0x00000020, + .err_mask = 0x000007E0, + .addr = 0x00000038, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_src_wm_low = { + .msb = 0x0000001f, + .lsb = 0x00000010, + .mask = 0xffff0000, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_src_wm_high = { + .msb = 0x0000000f, + .lsb = 0x00000000, + .mask = 0x0000ffff, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_dst_src_wm_regs qcax_wm_src_ring = { + .addr = 0x0000004c, + .low_rst = 0x00000000, + .high_rst = 0x00000000, + .wm_low = &qcax_src_wm_low, + .wm_high = &qcax_src_wm_high, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_dst_wm_low = { + .lsb = 0x00000010, + .mask = 0xffff0000, + .set = &ath10k_set_ring_byte, +}; + +struct ath10k_hw_ce_regs_addr_map qcax_dst_wm_high = { + .msb = 0x0000000f, + .lsb = 0x00000000, + .mask = 0x0000ffff, + .set = &ath10k_set_ring_byte, + .get = &ath10k_get_ring_byte, +}; + +struct ath10k_hw_ce_dst_src_wm_regs qcax_wm_dst_ring = { + .addr = 0x00000050, + .low_rst = 0x00000000, + .high_rst = 0x00000000, + .wm_low = &qcax_dst_wm_low, + .wm_high = &qcax_dst_wm_high, +}; + +struct ath10k_hw_ce_regs qcax_ce_regs = { + .sr_base_addr = 0x00000000, + .sr_size_addr = 0x00000004, + .dr_base_addr = 0x00000008, + .dr_size_addr = 0x0000000c, + .ce_cmd_addr = 0x00000018, + .misc_ie_addr = 0x00000034, + .sr_wr_index_addr = 0x0000003c, + .dst_wr_index_addr = 0x00000040, + .current_srri_addr = 0x00000044, + .current_drri_addr = 0x00000048, + .host_ie_addr = 0x0000002c, + .ctrl1_regs = &qcax_ctrl1, + .cmd_halt = &qcax_cmd_halt, + .host_ie = &qcax_host_ie, + .wm_regs = &qcax_wm_reg, + .misc_regs = &qcax_misc_reg, + .wm_srcr = &qcax_wm_src_ring, + .wm_dstr = &qcax_wm_dst_ring, +}; + const struct ath10k_hw_values qca988x_values = { + .pdev_suspend_option = WMI_PDEV_SUSPEND_AND_DISABLE_INTR, .rtc_state_val_on = 3, .ce_count = 8, .msi_assign_ce_max = 7, @@ -119,6 +477,7 @@ const struct ath10k_hw_values qca988x_values = { }; const struct ath10k_hw_values qca6174_values = { + .pdev_suspend_option = WMI_PDEV_SUSPEND_AND_DISABLE_INTR, .rtc_state_val_on = 3, .ce_count = 8, .msi_assign_ce_max = 7, @@ -128,6 +487,7 @@ const struct ath10k_hw_values qca6174_values = { }; const struct ath10k_hw_values qca99x0_values = { + .pdev_suspend_option = WMI_PDEV_SUSPEND_AND_DISABLE_INTR, .rtc_state_val_on = 5, .ce_count = 12, .msi_assign_ce_max = 12, @@ -136,22 +496,143 @@ const struct ath10k_hw_values qca99x0_values = { .ce_desc_meta_data_lsb = 4, }; +const struct ath10k_hw_values qca9888_values = { + .pdev_suspend_option = WMI_PDEV_SUSPEND_AND_DISABLE_INTR, + .rtc_state_val_on = 3, + .ce_count = 12, + .msi_assign_ce_max = 12, + .num_target_ce_config_wlan = 10, + .ce_desc_meta_data_mask = 0xFFF0, + .ce_desc_meta_data_lsb = 4, +}; + +const struct ath10k_hw_values qca4019_values = { + .pdev_suspend_option = WMI_PDEV_SUSPEND_AND_DISABLE_INTR, + .ce_count = 12, + .num_target_ce_config_wlan = 10, + .ce_desc_meta_data_mask = 0xFFF0, + .ce_desc_meta_data_lsb = 4, +}; + +const struct ath10k_hw_values wcn3990_values = { + .pdev_suspend_option = WMI_PDEV_SUSPEND, + .rtc_state_val_on = 5, + .ce_count = 12, + .msi_assign_ce_max = 12, + .num_target_ce_config_wlan = 12, + .ce_desc_meta_data_mask = 0xFFF0, + .ce_desc_meta_data_lsb = 4, + .default_listen_interval = 1, +}; + +struct fw_flag wcn3990_fw_flags = { + .flags = 0x82E, +}; + +struct ath10k_shadow_reg_value wcn3990_shadow_reg_value = { + .shadow_reg_value_0 = 0x00032000, + .shadow_reg_value_1 = 0x00032004, + .shadow_reg_value_2 = 0x00032008, + .shadow_reg_value_3 = 0x0003200C, + .shadow_reg_value_4 = 0x00032010, + .shadow_reg_value_5 = 0x00032014, + .shadow_reg_value_6 = 0x00032018, + .shadow_reg_value_7 = 0x0003201C, + .shadow_reg_value_8 = 0x00032020, + .shadow_reg_value_9 = 0x00032024, + .shadow_reg_value_10 = 0x00032028, + .shadow_reg_value_11 = 0x0003202C, + .shadow_reg_value_12 = 0x00032030, + .shadow_reg_value_13 = 0x00032034, + .shadow_reg_value_14 = 0x00032038, + .shadow_reg_value_15 = 0x0003203C, + .shadow_reg_value_16 = 0x00032040, + .shadow_reg_value_17 = 0x00032044, + .shadow_reg_value_18 = 0x00032048, + .shadow_reg_value_19 = 0x0003204C, + .shadow_reg_value_20 = 0x00032050, + .shadow_reg_value_21 = 0x00032054, + .shadow_reg_value_22 = 0x00032058, + .shadow_reg_value_23 = 0x0003205C +}; + +struct ath10k_shadow_reg_address wcn3990_shadow_reg_address = { + .shadow_reg_address_0 = 0x00030020, + .shadow_reg_address_1 = 0x00030024, + .shadow_reg_address_2 = 0x00030028, + .shadow_reg_address_3 = 0x0003002C, + .shadow_reg_address_4 = 0x00030030, + .shadow_reg_address_5 = 0x00030034, + .shadow_reg_address_6 = 0x00030038, + .shadow_reg_address_7 = 0x0003003C, + .shadow_reg_address_8 = 0x00030040, + .shadow_reg_address_9 = 0x00030044, + .shadow_reg_address_10 = 0x00030048, + .shadow_reg_address_11 = 0x0003004C, + .shadow_reg_address_12 = 0x00030050, + .shadow_reg_address_13 = 0x00030054, + .shadow_reg_address_14 = 0x00030058, + .shadow_reg_address_15 = 0x0003005C, + .shadow_reg_address_16 = 0x00030060, + .shadow_reg_address_17 = 0x00030064, + .shadow_reg_address_18 = 0x00030068, + .shadow_reg_address_19 = 0x0003006C, + .shadow_reg_address_20 = 0x00030070, + .shadow_reg_address_21 = 0x00030074, + .shadow_reg_address_22 = 0x00030078, + .shadow_reg_address_23 = 0x0003007C +}; + void ath10k_hw_fill_survey_time(struct ath10k *ar, struct survey_info *survey, u32 cc, u32 rcc, u32 cc_prev, u32 rcc_prev) { u32 cc_fix = 0; + u32 rcc_fix = 0; + enum ath10k_hw_cc_wraparound_type wraparound_type; survey->filled |= SURVEY_INFO_TIME | SURVEY_INFO_TIME_BUSY; - if (ar->hw_params.has_shifted_cc_wraparound && cc < cc_prev) { - cc_fix = 0x7fffffff; - survey->filled &= ~SURVEY_INFO_TIME_BUSY; + wraparound_type = ar->hw_params.cc_wraparound_type; + + if (cc < cc_prev || rcc < rcc_prev) { + switch (wraparound_type) { + case ATH10K_HW_CC_WRAP_SHIFTED_ALL: + if (cc < cc_prev) { + cc_fix = 0x7fffffff; + survey->filled &= ~SURVEY_INFO_TIME_BUSY; + } + break; + case ATH10K_HW_CC_WRAP_SHIFTED_EACH: + if (cc < cc_prev) + cc_fix = 0x7fffffff; + + if (rcc < rcc_prev) + rcc_fix = 0x7fffffff; + break; + case ATH10K_HW_CC_WRAP_DISABLED: + break; + } } cc -= cc_prev - cc_fix; - rcc -= rcc_prev; + rcc -= rcc_prev - rcc_fix; survey->time = CCNT_TO_MSEC(ar, cc); survey->time_busy = CCNT_TO_MSEC(ar, rcc); } + +const struct ath10k_hw_ops qca988x_ops = { +}; + +static int ath10k_qca99x0_rx_desc_get_l3_pad_bytes(struct htt_rx_desc *rxd) +{ + return MS(__le32_to_cpu(rxd->msdu_end.qca99x0.info1), + RX_MSDU_END_INFO1_L3_HDR_PAD); +} + +const struct ath10k_hw_ops qca99x0_ops = { + .rx_desc_get_l3_pad_bytes = ath10k_qca99x0_rx_desc_get_l3_pad_bytes, +}; + +const struct ath10k_hw_ops wcn3990_ops = {0}; diff --git a/drivers/net/wireless/ath/ath10k/hw.h b/drivers/net/wireless/ath/ath10k/hw.h index 713c2bcea178..2c5f2ba6322c 100644 --- a/drivers/net/wireless/ath/ath10k/hw.h +++ b/drivers/net/wireless/ath/ath10k/hw.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2005-2011 Atheros Communications Inc. - * Copyright (c) 2011-2013 Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013,2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,7 +26,10 @@ #define QCA6164_2_1_DEVICE_ID (0x0041) #define QCA6174_2_1_DEVICE_ID (0x003e) #define QCA99X0_2_0_DEVICE_ID (0x0040) +#define QCA9888_2_0_DEVICE_ID (0x0056) +#define QCA9984_1_0_DEVICE_ID (0x0046) #define QCA9377_1_0_DEVICE_ID (0x0042) +#define QCA9887_1_0_DEVICE_ID (0x0050) /* QCA988X 1.0 definitions (unsupported) */ #define QCA988X_HW_1_0_CHIP_ID_REV 0x0 @@ -35,11 +38,16 @@ #define QCA988X_HW_2_0_VERSION 0x4100016c #define QCA988X_HW_2_0_CHIP_ID_REV 0x2 #define QCA988X_HW_2_0_FW_DIR ATH10K_FW_DIR "/QCA988X/hw2.0" -#define QCA988X_HW_2_0_FW_FILE "firmware.bin" -#define QCA988X_HW_2_0_OTP_FILE "otp.bin" #define QCA988X_HW_2_0_BOARD_DATA_FILE "board.bin" #define QCA988X_HW_2_0_PATCH_LOAD_ADDR 0x1234 +/* QCA9887 1.0 definitions */ +#define QCA9887_HW_1_0_VERSION 0x4100016d +#define QCA9887_HW_1_0_CHIP_ID_REV 0 +#define QCA9887_HW_1_0_FW_DIR ATH10K_FW_DIR "/QCA9887/hw1.0" +#define QCA9887_HW_1_0_BOARD_DATA_FILE "board.bin" +#define QCA9887_HW_1_0_PATCH_LOAD_ADDR 0x1234 + /* QCA6174 target BMI version signatures */ #define QCA6174_HW_1_0_VERSION 0x05000000 #define QCA6174_HW_1_1_VERSION 0x05000001 @@ -76,14 +84,10 @@ enum qca9377_chip_id_rev { }; #define QCA6174_HW_2_1_FW_DIR "ath10k/QCA6174/hw2.1" -#define QCA6174_HW_2_1_FW_FILE "firmware.bin" -#define QCA6174_HW_2_1_OTP_FILE "otp.bin" #define QCA6174_HW_2_1_BOARD_DATA_FILE "board.bin" #define QCA6174_HW_2_1_PATCH_LOAD_ADDR 0x1234 #define QCA6174_HW_3_0_FW_DIR "ath10k/QCA6174/hw3.0" -#define QCA6174_HW_3_0_FW_FILE "firmware.bin" -#define QCA6174_HW_3_0_OTP_FILE "otp.bin" #define QCA6174_HW_3_0_BOARD_DATA_FILE "board.bin" #define QCA6174_HW_3_0_PATCH_LOAD_ADDR 0x1234 @@ -94,18 +98,40 @@ enum qca9377_chip_id_rev { #define QCA99X0_HW_2_0_DEV_VERSION 0x01000000 #define QCA99X0_HW_2_0_CHIP_ID_REV 0x1 #define QCA99X0_HW_2_0_FW_DIR ATH10K_FW_DIR "/QCA99X0/hw2.0" -#define QCA99X0_HW_2_0_FW_FILE "firmware.bin" -#define QCA99X0_HW_2_0_OTP_FILE "otp.bin" #define QCA99X0_HW_2_0_BOARD_DATA_FILE "board.bin" #define QCA99X0_HW_2_0_PATCH_LOAD_ADDR 0x1234 +/* QCA9984 1.0 defines */ +#define QCA9984_HW_1_0_DEV_VERSION 0x1000000 +#define QCA9984_HW_DEV_TYPE 0xa +#define QCA9984_HW_1_0_CHIP_ID_REV 0x0 +#define QCA9984_HW_1_0_FW_DIR ATH10K_FW_DIR "/QCA9984/hw1.0" +#define QCA9984_HW_1_0_BOARD_DATA_FILE "board.bin" +#define QCA9984_HW_1_0_PATCH_LOAD_ADDR 0x1234 + +/* QCA9888 2.0 defines */ +#define QCA9888_HW_2_0_DEV_VERSION 0x1000000 +#define QCA9888_HW_DEV_TYPE 0xc +#define QCA9888_HW_2_0_CHIP_ID_REV 0x0 +#define QCA9888_HW_2_0_FW_DIR ATH10K_FW_DIR "/QCA9888/hw2.0" +#define QCA9888_HW_2_0_BOARD_DATA_FILE "board.bin" +#define QCA9888_HW_2_0_PATCH_LOAD_ADDR 0x1234 + /* QCA9377 1.0 definitions */ #define QCA9377_HW_1_0_FW_DIR ATH10K_FW_DIR "/QCA9377/hw1.0" -#define QCA9377_HW_1_0_FW_FILE "firmware.bin" -#define QCA9377_HW_1_0_OTP_FILE "otp.bin" #define QCA9377_HW_1_0_BOARD_DATA_FILE "board.bin" #define QCA9377_HW_1_0_PATCH_LOAD_ADDR 0x1234 +/* QCA4019 1.0 definitions */ +#define QCA4019_HW_1_0_DEV_VERSION 0x01000000 +#define QCA4019_HW_1_0_FW_DIR ATH10K_FW_DIR "/QCA4019/hw1.0" +#define QCA4019_HW_1_0_BOARD_DATA_FILE "board.bin" +#define QCA4019_HW_1_0_PATCH_LOAD_ADDR 0x1234 + +/* WCN3990 1.0 definitions */ +#define WCN3990_HW_1_0_DEV_VERSION ATH10K_HW_WCN3990 +#define WCN3990_HW_1_0_FW_DIR "/etc/firmware" + #define ATH10K_FW_API2_FILE "firmware-2.bin" #define ATH10K_FW_API3_FILE "firmware-3.bin" @@ -126,8 +152,6 @@ enum qca9377_chip_id_rev { #define REG_DUMP_COUNT_QCA988X 60 -#define QCA988X_CAL_DATA_LEN 2116 - struct ath10k_fw_ie { __le32 id; __le32 len; @@ -199,14 +223,19 @@ enum ath10k_hw_rev { ATH10K_HW_QCA988X, ATH10K_HW_QCA6174, ATH10K_HW_QCA99X0, + ATH10K_HW_QCA9888, + ATH10K_HW_QCA9984, ATH10K_HW_QCA9377, + ATH10K_HW_QCA4019, + ATH10K_HW_QCA9887, + ATH10K_HW_WCN3990, }; struct ath10k_hw_regs { - u32 rtc_state_cold_reset_mask; u32 rtc_soc_base_address; u32 rtc_wmac_base_address; u32 soc_core_base_address; + u32 soc_global_reset_address; u32 ce_wrapper_base_address; u32 ce0_base_address; u32 ce1_base_address; @@ -216,6 +245,10 @@ struct ath10k_hw_regs { u32 ce5_base_address; u32 ce6_base_address; u32 ce7_base_address; + u32 ce8_base_address; + u32 ce9_base_address; + u32 ce10_base_address; + u32 ce11_base_address; u32 soc_reset_control_si0_rst_mask; u32 soc_reset_control_ce_rst_mask; u32 soc_chip_id_address; @@ -232,29 +265,142 @@ struct ath10k_hw_regs { extern const struct ath10k_hw_regs qca988x_regs; extern const struct ath10k_hw_regs qca6174_regs; extern const struct ath10k_hw_regs qca99x0_regs; +extern const struct ath10k_hw_regs qca4019_regs; +extern const struct ath10k_hw_regs wcn3990_regs; + +struct ath10k_hw_ce_regs_addr_map { + u32 msb; + u32 lsb; + u32 mask; + unsigned int (*set)(unsigned int offset, + struct ath10k_hw_ce_regs_addr_map *addr_map); + unsigned int (*get)(unsigned int offset, + struct ath10k_hw_ce_regs_addr_map *addr_map); +}; + +struct ath10k_hw_ce_ctrl1 { + u32 addr; + u32 hw_mask; + u32 sw_mask; + u32 hw_wr_mask; + u32 sw_wr_mask; + u32 reset_mask; + u32 reset; + struct ath10k_hw_ce_regs_addr_map *src_ring; + struct ath10k_hw_ce_regs_addr_map *dst_ring; + struct ath10k_hw_ce_regs_addr_map *dmax; +}; + +struct ath10k_hw_ce_cmd_halt { + u32 status_reset; + u32 msb; + u32 mask; + struct ath10k_hw_ce_regs_addr_map *status; +}; + +struct ath10k_hw_ce_host_ie { + u32 copy_complete_reset; + struct ath10k_hw_ce_regs_addr_map *copy_complete; +}; + +struct ath10k_hw_ce_host_wm_regs { + u32 dstr_lmask; + u32 dstr_hmask; + u32 srcr_lmask; + u32 srcr_hmask; + u32 cc_mask; + u32 wm_mask; + u32 addr; +}; + +struct ath10k_hw_ce_misc_regs { + u32 axi_err; + u32 dstr_add_err; + u32 srcr_len_err; + u32 dstr_mlen_vio; + u32 dstr_overflow; + u32 srcr_overflow; + u32 err_mask; + u32 addr; +}; + +struct ath10k_hw_ce_dst_src_wm_regs { + u32 addr; + u32 low_rst; + u32 high_rst; + struct ath10k_hw_ce_regs_addr_map *wm_low; + struct ath10k_hw_ce_regs_addr_map *wm_high; +}; + +struct ath10k_hw_ce_ctrl1_upd { + u32 shift; + u32 mask; + u32 enable; +}; + +struct ath10k_hw_ce_regs { + u32 sr_base_addr; + u32 sr_size_addr; + u32 dr_base_addr; + u32 dr_size_addr; + u32 ce_cmd_addr; + u32 misc_ie_addr; + u32 sr_wr_index_addr; + u32 dst_wr_index_addr; + u32 current_srri_addr; + u32 current_drri_addr; + u32 ddr_addr_for_rri_low; + u32 ddr_addr_for_rri_high; + u32 ce_rri_low; + u32 ce_rri_high; + u32 host_ie_addr; + struct ath10k_hw_ce_host_wm_regs *wm_regs; + struct ath10k_hw_ce_misc_regs *misc_regs; + struct ath10k_hw_ce_ctrl1 *ctrl1_regs; + struct ath10k_hw_ce_cmd_halt *cmd_halt; + struct ath10k_hw_ce_host_ie *host_ie; + struct ath10k_hw_ce_dst_src_wm_regs *wm_srcr; + struct ath10k_hw_ce_dst_src_wm_regs *wm_dstr; + struct ath10k_hw_ce_ctrl1_upd *upd; +}; + +extern struct ath10k_hw_ce_regs wcn3990_ce_regs; +extern struct ath10k_hw_ce_regs qcax_ce_regs; + +extern struct fw_flag wcn3990_fw_flags; struct ath10k_hw_values { + u32 pdev_suspend_option; u32 rtc_state_val_on; u8 ce_count; u8 msi_assign_ce_max; u8 num_target_ce_config_wlan; u16 ce_desc_meta_data_mask; u8 ce_desc_meta_data_lsb; + u8 default_listen_interval; }; extern const struct ath10k_hw_values qca988x_values; extern const struct ath10k_hw_values qca6174_values; extern const struct ath10k_hw_values qca99x0_values; +extern const struct ath10k_hw_values qca9888_values; +extern const struct ath10k_hw_values qca4019_values; +extern const struct ath10k_hw_values wcn3990_values; void ath10k_hw_fill_survey_time(struct ath10k *ar, struct survey_info *survey, u32 cc, u32 rcc, u32 cc_prev, u32 rcc_prev); #define QCA_REV_988X(ar) ((ar)->hw_rev == ATH10K_HW_QCA988X) +#define QCA_REV_9887(ar) ((ar)->hw_rev == ATH10K_HW_QCA9887) #define QCA_REV_6174(ar) ((ar)->hw_rev == ATH10K_HW_QCA6174) #define QCA_REV_99X0(ar) ((ar)->hw_rev == ATH10K_HW_QCA99X0) +#define QCA_REV_9888(ar) ((ar)->hw_rev == ATH10K_HW_QCA9888) +#define QCA_REV_9984(ar) ((ar)->hw_rev == ATH10K_HW_QCA9984) #define QCA_REV_9377(ar) ((ar)->hw_rev == ATH10K_HW_QCA9377) +#define QCA_REV_40XX(ar) ((ar)->hw_rev == ATH10K_HW_QCA4019) +#define QCA_REV_WCN3990(ar) ((ar)->hw_rev == ATH10K_HW_WCN3990) -/* Known pecularities: +/* Known peculiarities: * - raw appears in nwifi decap, raw and nwifi appear in ethernet decap * - raw have FCS, nwifi doesn't * - ethernet frames have 802.11 header decapped and parts (base hdr, cipher @@ -277,15 +423,6 @@ enum ath10k_mcast2ucast_mode { ATH10K_MCAST2UCAST_ENABLED = 1, }; -struct ath10k_pktlog_hdr { - __le16 flags; - __le16 missed_cnt; - __le16 log_type; - __le16 size; - __le32 timestamp; - u8 payload[0]; -} __packed; - enum ath10k_hw_rate_ofdm { ATH10K_HW_RATE_OFDM_48M = 0, ATH10K_HW_RATE_OFDM_24M, @@ -307,6 +444,111 @@ enum ath10k_hw_rate_cck { ATH10K_HW_RATE_CCK_SP_2M, }; +enum ath10k_hw_rate_rev2_cck { + ATH10K_HW_RATE_REV2_CCK_LP_1M = 1, + ATH10K_HW_RATE_REV2_CCK_LP_2M, + ATH10K_HW_RATE_REV2_CCK_LP_5_5M, + ATH10K_HW_RATE_REV2_CCK_LP_11M, + ATH10K_HW_RATE_REV2_CCK_SP_2M, + ATH10K_HW_RATE_REV2_CCK_SP_5_5M, + ATH10K_HW_RATE_REV2_CCK_SP_11M, +}; + +enum ath10k_hw_cc_wraparound_type { + ATH10K_HW_CC_WRAP_DISABLED = 0, + + /* This type is when the HW chip has a quirky Cycle Counter + * wraparound which resets to 0x7fffffff instead of 0. All + * other CC related counters (e.g. Rx Clear Count) are divided + * by 2 so they never wraparound themselves. + */ + ATH10K_HW_CC_WRAP_SHIFTED_ALL = 1, + + /* Each hw counter wrapsaround independently. When the + * counter overflows the repestive counter is right shifted + * by 1, i.e reset to 0x7fffffff, and other counters will be + * running unaffected. In this type of wraparound, it should + * be possible to report accurate Rx busy time unlike the + * first type. + */ + ATH10K_HW_CC_WRAP_SHIFTED_EACH = 2, +}; + +struct ath10k_hw_params { + u32 id; + u16 dev_id; + const char *name; + u32 patch_load_addr; + int uart_pin; + u32 otp_exe_param; + + /* Type of hw cycle counter wraparound logic, for more info + * refer enum ath10k_hw_cc_wraparound_type. + */ + enum ath10k_hw_cc_wraparound_type cc_wraparound_type; + + /* Some of chip expects fragment descriptor to be continuous + * memory for any TX operation. Set continuous_frag_desc flag + * for the hardware which have such requirement. + */ + bool continuous_frag_desc; + + /* CCK hardware rate table mapping for the newer chipsets + * like QCA99X0, QCA4019 got revised. The CCK h/w rate values + * are in a proper order with respect to the rate/preamble + */ + bool cck_rate_map_rev2; + + u32 channel_counters_freq_hz; + + /* Mgmt tx descriptors threshold for limiting probe response + * frames. + */ + u32 max_probe_resp_desc_thres; + + u32 tx_chain_mask; + u32 rx_chain_mask; + u32 max_spatial_stream; + u32 cal_data_len; + + struct ath10k_hw_params_fw { + const char *dir; + const char *board; + size_t board_size; + size_t board_ext_size; + } fw; + + /* qca99x0 family chips deliver broadcast/multicast management + * frames encrypted and expect software do decryption. + */ + bool sw_decrypt_mcast_mgmt; + + const struct ath10k_hw_ops *hw_ops; + + /* Number of bytes used for alignment in rx_hdr_status of rx desc. */ + int decap_align_bytes; +}; + +struct htt_rx_desc; + +/* Defines needed for Rx descriptor abstraction */ +struct ath10k_hw_ops { + int (*rx_desc_get_l3_pad_bytes)(struct htt_rx_desc *rxd); +}; + +extern const struct ath10k_hw_ops qca988x_ops; +extern const struct ath10k_hw_ops qca99x0_ops; +extern const struct ath10k_hw_ops wcn3990_ops; + +static inline int +ath10k_rx_desc_get_l3_pad_bytes(struct ath10k_hw_params *hw, + struct htt_rx_desc *rxd) +{ + if (hw->hw_ops->rx_desc_get_l3_pad_bytes) + return hw->hw_ops->rx_desc_get_l3_pad_bytes(rxd); + return 0; +} + /* Target specific defines for MAIN firmware */ #define TARGET_NUM_VDEVS 8 #define TARGET_NUM_PEER_AST 2 @@ -348,14 +590,19 @@ enum ath10k_hw_rate_cck { #define TARGET_10X_MAC_AGGR_DELIM 0 #define TARGET_10X_AST_SKID_LIMIT 128 #define TARGET_10X_NUM_STATIONS 128 +#define TARGET_10X_TX_STATS_NUM_STATIONS 118 #define TARGET_10X_NUM_PEERS ((TARGET_10X_NUM_STATIONS) + \ (TARGET_10X_NUM_VDEVS)) +#define TARGET_10X_TX_STATS_NUM_PEERS ((TARGET_10X_TX_STATS_NUM_STATIONS) + \ + (TARGET_10X_NUM_VDEVS)) #define TARGET_10X_NUM_OFFLOAD_PEERS 0 #define TARGET_10X_NUM_OFFLOAD_REORDER_BUFS 0 #define TARGET_10X_NUM_PEER_KEYS 2 #define TARGET_10X_NUM_TIDS_MAX 256 #define TARGET_10X_NUM_TIDS min((TARGET_10X_NUM_TIDS_MAX), \ (TARGET_10X_NUM_PEERS) * 2) +#define TARGET_10X_TX_STATS_NUM_TIDS min((TARGET_10X_NUM_TIDS_MAX), \ + (TARGET_10X_TX_STATS_NUM_PEERS) * 2) #define TARGET_10X_TX_CHAIN_MASK (BIT(0) | BIT(1) | BIT(2)) #define TARGET_10X_RX_CHAIN_MASK (BIT(0) | BIT(1) | BIT(2)) #define TARGET_10X_RX_TIMEOUT_LO_PRI 100 @@ -385,6 +632,14 @@ enum ath10k_hw_rate_cck { #define TARGET_TLV_NUM_TIDS ((TARGET_TLV_NUM_PEERS) * 2) #define TARGET_TLV_NUM_MSDU_DESC (1024 + 32) #define TARGET_TLV_NUM_WOW_PATTERNS 22 +/* FW supports max 50 outstanding mgmt cmds */ +#define TARGET_TLV_MGMT_NUM_MSDU_DESC (50) + +/* Target specific defines for WMI-HL-1.0 firmware */ +#define TARGET_HL_10_TLV_NUM_PEERS 14 +#define TARGET_HL_10_TLV_AST_SKID_LIMIT 6 +#define TARGET_HL_10_TLV_NUM_WDS_ENTRIES 2 +#define TARGET_HL_1_0_NUM_MSDU_DESC (3600) /* Diagnostic Window */ #define CE_DIAG_PIPE 7 @@ -400,15 +655,14 @@ enum ath10k_hw_rate_cck { #define TARGET_10_4_NUM_QCACHE_PEERS_MAX 512 #define TARGET_10_4_QCACHE_ACTIVE_PEERS 50 +#define TARGET_10_4_QCACHE_ACTIVE_PEERS_PFC 35 #define TARGET_10_4_NUM_OFFLOAD_PEERS 0 #define TARGET_10_4_NUM_OFFLOAD_REORDER_BUFFS 0 #define TARGET_10_4_NUM_PEER_KEYS 2 #define TARGET_10_4_TGT_NUM_TIDS ((TARGET_10_4_NUM_PEERS) * 2) +#define TARGET_10_4_NUM_MSDU_DESC (1024 + 400) +#define TARGET_10_4_NUM_MSDU_DESC_PFC 2500 #define TARGET_10_4_AST_SKID_LIMIT 32 -#define TARGET_10_4_TX_CHAIN_MASK (BIT(0) | BIT(1) | \ - BIT(2) | BIT(3)) -#define TARGET_10_4_RX_CHAIN_MASK (BIT(0) | BIT(1) | \ - BIT(2) | BIT(3)) /* 100 ms for video, best-effort, and background */ #define TARGET_10_4_RX_TIMEOUT_LO_PRI 100 @@ -434,7 +688,6 @@ enum ath10k_hw_rate_cck { #define TARGET_10_4_RX_SKIP_DEFRAG_TIMEOUT_DUP_DETECTION_CHECK 1 #define TARGET_10_4_VOW_CONFIG 0 #define TARGET_10_4_GTK_OFFLOAD_MAX_VDEV 3 -#define TARGET_10_4_NUM_MSDU_DESC (1024 + 400) #define TARGET_10_4_11AC_TX_MAX_FRAGS 2 #define TARGET_10_4_MAX_PEER_EXT_STATS 16 #define TARGET_10_4_SMART_ANT_CAP 0 @@ -468,7 +721,6 @@ enum ath10k_hw_rate_cck { /* as of IP3.7.1 */ #define RTC_STATE_V_ON ar->hw_values->rtc_state_val_on -#define RTC_STATE_COLD_RESET_MASK ar->regs->rtc_state_cold_reset_mask #define RTC_STATE_V_LSB 0 #define RTC_STATE_V_MASK 0x00000007 #define RTC_STATE_ADDRESS 0x0000 @@ -531,7 +783,10 @@ enum ath10k_hw_rate_cck { #define WLAN_SYSTEM_SLEEP_DISABLE_MASK 0x00000001 #define WLAN_GPIO_PIN0_ADDRESS 0x00000028 +#define WLAN_GPIO_PIN0_CONFIG_LSB 11 #define WLAN_GPIO_PIN0_CONFIG_MASK 0x00007800 +#define WLAN_GPIO_PIN0_PAD_PULL_LSB 5 +#define WLAN_GPIO_PIN0_PAD_PULL_MASK 0x00000060 #define WLAN_GPIO_PIN1_ADDRESS 0x0000002c #define WLAN_GPIO_PIN1_CONFIG_MASK 0x00007800 #define WLAN_GPIO_PIN10_ADDRESS 0x00000050 @@ -544,6 +799,8 @@ enum ath10k_hw_rate_cck { #define CLOCK_GPIO_BT_CLK_OUT_EN_MASK 0 #define SI_CONFIG_OFFSET 0x00000000 +#define SI_CONFIG_ERR_INT_LSB 19 +#define SI_CONFIG_ERR_INT_MASK 0x00080000 #define SI_CONFIG_BIDIR_OD_DATA_LSB 18 #define SI_CONFIG_BIDIR_OD_DATA_MASK 0x00040000 #define SI_CONFIG_I2C_LSB 16 @@ -557,7 +814,9 @@ enum ath10k_hw_rate_cck { #define SI_CONFIG_DIVIDER_LSB 0 #define SI_CONFIG_DIVIDER_MASK 0x0000000f #define SI_CS_OFFSET 0x00000004 +#define SI_CS_DONE_ERR_LSB 10 #define SI_CS_DONE_ERR_MASK 0x00000400 +#define SI_CS_DONE_INT_LSB 9 #define SI_CS_DONE_INT_MASK 0x00000200 #define SI_CS_START_LSB 8 #define SI_CS_START_MASK 0x00000100 @@ -586,6 +845,7 @@ enum ath10k_hw_rate_cck { #define FW_INDICATOR_ADDRESS ar->regs->fw_indicator_address #define FW_IND_EVENT_PENDING 1 #define FW_IND_INITIALIZED 2 +#define FW_IND_HOST_READY 0x80000000 /* HOST_REG interrupt from firmware */ #define PCIE_INTR_FIRMWARE_MASK ar->regs->pcie_intr_fw_mask @@ -607,7 +867,10 @@ enum ath10k_hw_rate_cck { #define GPIO_BASE_ADDRESS WLAN_GPIO_BASE_ADDRESS #define GPIO_PIN0_OFFSET WLAN_GPIO_PIN0_ADDRESS #define GPIO_PIN1_OFFSET WLAN_GPIO_PIN1_ADDRESS +#define GPIO_PIN0_CONFIG_LSB WLAN_GPIO_PIN0_CONFIG_LSB #define GPIO_PIN0_CONFIG_MASK WLAN_GPIO_PIN0_CONFIG_MASK +#define GPIO_PIN0_PAD_PULL_LSB WLAN_GPIO_PIN0_PAD_PULL_LSB +#define GPIO_PIN0_PAD_PULL_MASK WLAN_GPIO_PIN0_PAD_PULL_MASK #define GPIO_PIN1_CONFIG_MASK WLAN_GPIO_PIN1_CONFIG_MASK #define SI_BASE_ADDRESS WLAN_SI_BASE_ADDRESS #define SCRATCH_BASE_ADDRESS SOC_CORE_BASE_ADDRESS @@ -662,6 +925,108 @@ enum ath10k_hw_rate_cck { #define WINDOW_READ_ADDR_ADDRESS MISSING #define WINDOW_WRITE_ADDR_ADDRESS MISSING +#define QCA9887_1_0_I2C_SDA_GPIO_PIN 5 +#define QCA9887_1_0_I2C_SDA_PIN_CONFIG 3 +#define QCA9887_1_0_SI_CLK_GPIO_PIN 17 +#define QCA9887_1_0_SI_CLK_PIN_CONFIG 3 +#define QCA9887_1_0_GPIO_ENABLE_W1TS_LOW_ADDRESS 0x00000010 + +#define QCA9887_EEPROM_SELECT_READ 0xa10000a0 +#define QCA9887_EEPROM_ADDR_HI_MASK 0x0000ff00 +#define QCA9887_EEPROM_ADDR_HI_LSB 8 +#define QCA9887_EEPROM_ADDR_LO_MASK 0x00ff0000 +#define QCA9887_EEPROM_ADDR_LO_LSB 16 + #define RTC_STATE_V_GET(x) (((x) & RTC_STATE_V_MASK) >> RTC_STATE_V_LSB) +struct ath10k_shadow_reg_value { + u32 shadow_reg_value_0; + u32 shadow_reg_value_1; + u32 shadow_reg_value_2; + u32 shadow_reg_value_3; + u32 shadow_reg_value_4; + u32 shadow_reg_value_5; + u32 shadow_reg_value_6; + u32 shadow_reg_value_7; + u32 shadow_reg_value_8; + u32 shadow_reg_value_9; + u32 shadow_reg_value_10; + u32 shadow_reg_value_11; + u32 shadow_reg_value_12; + u32 shadow_reg_value_13; + u32 shadow_reg_value_14; + u32 shadow_reg_value_15; + u32 shadow_reg_value_16; + u32 shadow_reg_value_17; + u32 shadow_reg_value_18; + u32 shadow_reg_value_19; + u32 shadow_reg_value_20; + u32 shadow_reg_value_21; + u32 shadow_reg_value_22; + u32 shadow_reg_value_23; +}; + +struct ath10k_shadow_reg_address { + u32 shadow_reg_address_0; + u32 shadow_reg_address_1; + u32 shadow_reg_address_2; + u32 shadow_reg_address_3; + u32 shadow_reg_address_4; + u32 shadow_reg_address_5; + u32 shadow_reg_address_6; + u32 shadow_reg_address_7; + u32 shadow_reg_address_8; + u32 shadow_reg_address_9; + u32 shadow_reg_address_10; + u32 shadow_reg_address_11; + u32 shadow_reg_address_12; + u32 shadow_reg_address_13; + u32 shadow_reg_address_14; + u32 shadow_reg_address_15; + u32 shadow_reg_address_16; + u32 shadow_reg_address_17; + u32 shadow_reg_address_18; + u32 shadow_reg_address_19; + u32 shadow_reg_address_20; + u32 shadow_reg_address_21; + u32 shadow_reg_address_22; + u32 shadow_reg_address_23; +}; + +extern struct ath10k_shadow_reg_value wcn3990_shadow_reg_value; +extern struct ath10k_shadow_reg_address wcn3990_shadow_reg_address; + +#define ATH10K_PKTLOG_HDR_SIZE_16 0x8000 + +enum { + ATH10k_PKTLOG_FLG_FRM_TYPE_LOCAL_S = 0, + ATH10K_PKTLOG_FLG_FRM_TYPE_REMOTE_S, + ATH10K_PKTLOG_FLG_FRM_TYPE_CLONE_S, + ATH10K_PKTLOG_FLG_FRM_TYPE_CBF_S, + ATH10K_PKTLOG_FLG_FRM_TYPE_UNKNOWN_S +}; + +enum ath10k_pktlog_type { + ATH10K_PKTLOG_TYPE_TX_CTRL = 1, + ATH10K_PKTLOG_TYPE_TX_STAT, + ATH10K_PKTLOG_TYPE_TX_MSDU_ID, + ATH10K_PKTLOG_TYPE_TX_FRM_HDR, + ATH10K_PKTLOG_TYPE_RX_STAT, + ATH10K_PKTLOG_TYPE_RC_FIND, + ATH10K_PKTLOG_TYPE_RC_UPDATE, + ATH10K_PKTLOG_TYPE_TX_VIRT_ADDR, + ATH10K_PKTLOG_TYPE_DBG_PRINT, + ATH10K_PKTLOG_TYPE_SW_EVENT, + ATH10K_PKTLOG_TYPE_MAX, +}; + +struct ath10k_pktlog_hdr { + __le16 flags; + __le16 missed_cnt; + __le16 log_type; + __le16 size; + __le32 timestamp; + u8 payload[0]; +} __packed; + #endif /* _HW_H_ */ diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c index 4644357d291a..db0476c44b41 100644 --- a/drivers/net/wireless/ath/ath10k/mac.c +++ b/drivers/net/wireless/ath/ath10k/mac.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2005-2011 Atheros Communications Inc. - * Copyright (c) 2011-2013 Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, 2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -62,6 +62,32 @@ static struct ieee80211_rate ath10k_rates[] = { { .bitrate = 540, .hw_value = ATH10K_HW_RATE_OFDM_54M }, }; +static struct ieee80211_rate ath10k_rates_rev2[] = { + { .bitrate = 10, + .hw_value = ATH10K_HW_RATE_REV2_CCK_LP_1M }, + { .bitrate = 20, + .hw_value = ATH10K_HW_RATE_REV2_CCK_LP_2M, + .hw_value_short = ATH10K_HW_RATE_REV2_CCK_SP_2M, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, + .hw_value = ATH10K_HW_RATE_REV2_CCK_LP_5_5M, + .hw_value_short = ATH10K_HW_RATE_REV2_CCK_SP_5_5M, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, + .hw_value = ATH10K_HW_RATE_REV2_CCK_LP_11M, + .hw_value_short = ATH10K_HW_RATE_REV2_CCK_SP_11M, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + + { .bitrate = 60, .hw_value = ATH10K_HW_RATE_OFDM_6M }, + { .bitrate = 90, .hw_value = ATH10K_HW_RATE_OFDM_9M }, + { .bitrate = 120, .hw_value = ATH10K_HW_RATE_OFDM_12M }, + { .bitrate = 180, .hw_value = ATH10K_HW_RATE_OFDM_18M }, + { .bitrate = 240, .hw_value = ATH10K_HW_RATE_OFDM_24M }, + { .bitrate = 360, .hw_value = ATH10K_HW_RATE_OFDM_36M }, + { .bitrate = 480, .hw_value = ATH10K_HW_RATE_OFDM_48M }, + { .bitrate = 540, .hw_value = ATH10K_HW_RATE_OFDM_54M }, +}; + #define ATH10K_MAC_FIRST_OFDM_RATE_IDX 4 #define ath10k_a_rates (ath10k_rates + ATH10K_MAC_FIRST_OFDM_RATE_IDX) @@ -70,6 +96,9 @@ static struct ieee80211_rate ath10k_rates[] = { #define ath10k_g_rates (ath10k_rates + 0) #define ath10k_g_rates_size (ARRAY_SIZE(ath10k_rates)) +#define ath10k_g_rates_rev2 (ath10k_rates_rev2 + 0) +#define ath10k_g_rates_rev2_size (ARRAY_SIZE(ath10k_rates_rev2)) + static bool ath10k_mac_bitrate_is_cck(int bitrate) { switch (bitrate) { @@ -90,7 +119,7 @@ static u8 ath10k_mac_bitrate_to_rate(int bitrate) } u8 ath10k_mac_hw_rate_to_idx(const struct ieee80211_supported_band *sband, - u8 hw_rate) + u8 hw_rate, bool cck) { const struct ieee80211_rate *rate; int i; @@ -98,6 +127,9 @@ u8 ath10k_mac_hw_rate_to_idx(const struct ieee80211_supported_band *sband, for (i = 0; i < sband->n_bitrates; i++) { rate = &sband->bitrates[i]; + if (ath10k_mac_bitrate_is_cck(rate->bitrate) != cck) + continue; + if (rate->hw_value == hw_rate) return i; else if (rate->flags & IEEE80211_RATE_SHORT_PREAMBLE && @@ -154,6 +186,26 @@ ath10k_mac_max_vht_nss(const u16 vht_mcs_mask[NL80211_VHT_NSS_MAX]) return 1; } +int ath10k_mac_ext_resource_config(struct ath10k *ar, u32 val) +{ + enum wmi_host_platform_type platform_type; + int ret; + + if (test_bit(WMI_SERVICE_TX_MODE_DYNAMIC, ar->wmi.svc_map)) + platform_type = WMI_HOST_PLATFORM_LOW_PERF; + else + platform_type = WMI_HOST_PLATFORM_HIGH_PERF; + + ret = ath10k_wmi_ext_resource_config(ar, platform_type, val); + + if (ret && ret != -EOPNOTSUPP) { + ath10k_warn(ar, "failed to configure ext resource: %d\n", ret); + return ret; + } + + return 0; +} + /**********/ /* Crypto */ /**********/ @@ -247,7 +299,8 @@ static int ath10k_install_peer_wep_keys(struct ath10k_vif *arvif, lockdep_assert_held(&ar->conf_mutex); if (WARN_ON(arvif->vif->type != NL80211_IFTYPE_AP && - arvif->vif->type != NL80211_IFTYPE_ADHOC)) + arvif->vif->type != NL80211_IFTYPE_ADHOC && + arvif->vif->type != NL80211_IFTYPE_MESH_POINT)) return -EINVAL; spin_lock_bh(&ar->data_lock); @@ -445,10 +498,10 @@ static int ath10k_mac_vif_update_wep_key(struct ath10k_vif *arvif, lockdep_assert_held(&ar->conf_mutex); list_for_each_entry(peer, &ar->peers, list) { - if (!memcmp(peer->addr, arvif->vif->addr, ETH_ALEN)) + if (ether_addr_equal(peer->addr, arvif->vif->addr)) continue; - if (!memcmp(peer->addr, arvif->bssid, ETH_ALEN)) + if (ether_addr_equal(peer->addr, arvif->bssid)) continue; if (peer->keys[key->keyidx] == key) @@ -614,10 +667,45 @@ ath10k_mac_get_any_chandef_iter(struct ieee80211_hw *hw, *def = &conf->def; } -static int ath10k_peer_create(struct ath10k *ar, u32 vdev_id, const u8 *addr, +static int ath10k_peer_delete(struct ath10k *ar, u32 vdev_id, const u8 *addr) +{ + int ret; + unsigned long time_left; + + lockdep_assert_held(&ar->conf_mutex); + + ret = ath10k_wmi_peer_delete(ar, vdev_id, addr); + if (ret) + return ret; + + ret = ath10k_wait_for_peer_deleted(ar, vdev_id, addr); + if (ret) + return ret; + + if (QCA_REV_WCN3990(ar)) { + time_left = wait_for_completion_timeout(&ar->peer_delete_done, + 50 * HZ); + + if (time_left == 0) { + ath10k_warn(ar, "Timeout in receiving peer delete response\n"); + return -ETIMEDOUT; + } + } + + ar->num_peers--; + + return 0; +} + +static int ath10k_peer_create(struct ath10k *ar, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + u32 vdev_id, + const u8 *addr, enum wmi_peer_type peer_type) { struct ath10k_vif *arvif; + struct ath10k_peer *peer; int num_peers = 0; int ret; @@ -646,6 +734,22 @@ static int ath10k_peer_create(struct ath10k *ar, u32 vdev_id, const u8 *addr, return ret; } + spin_lock_bh(&ar->data_lock); + + peer = ath10k_peer_find(ar, vdev_id, addr); + if (!peer) { + spin_unlock_bh(&ar->data_lock); + ath10k_warn(ar, "failed to find peer %pM on vdev %i after creation\n", + addr, vdev_id); + ath10k_peer_delete(ar, vdev_id, addr); + return -ENOENT; + } + + peer->vif = vif; + peer->sta = sta; + + spin_unlock_bh(&ar->data_lock); + ar->num_peers++; return 0; @@ -705,28 +809,11 @@ static int ath10k_mac_set_rts(struct ath10k_vif *arvif, u32 value) return ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, vdev_param, value); } -static int ath10k_peer_delete(struct ath10k *ar, u32 vdev_id, const u8 *addr) -{ - int ret; - - lockdep_assert_held(&ar->conf_mutex); - - ret = ath10k_wmi_peer_delete(ar, vdev_id, addr); - if (ret) - return ret; - - ret = ath10k_wait_for_peer_deleted(ar, vdev_id, addr); - if (ret) - return ret; - - ar->num_peers--; - - return 0; -} - static void ath10k_peer_cleanup(struct ath10k *ar, u32 vdev_id) { struct ath10k_peer *peer, *tmp; + int peer_id; + int i; lockdep_assert_held(&ar->conf_mutex); @@ -738,6 +825,22 @@ static void ath10k_peer_cleanup(struct ath10k *ar, u32 vdev_id) ath10k_warn(ar, "removing stale peer %pM from vdev_id %d\n", peer->addr, vdev_id); + for_each_set_bit(peer_id, peer->peer_ids, + ATH10K_MAX_NUM_PEER_IDS) { + ar->peer_map[peer_id] = NULL; + } + + /* Double check that peer is properly un-referenced from + * the peer_map + */ + for (i = 0; i < ARRAY_SIZE(ar->peer_map); i++) { + if (ar->peer_map[i] == peer) { + ath10k_warn(ar, "removing stale peer_map entry for %pM (ptr %pK idx %d)\n", + peer->addr, peer, i); + ar->peer_map[i] = NULL; + } + } + list_del(&peer->list); kfree(peer); ar->num_peers--; @@ -748,6 +851,7 @@ static void ath10k_peer_cleanup(struct ath10k *ar, u32 vdev_id) static void ath10k_peer_cleanup_all(struct ath10k *ar) { struct ath10k_peer *peer, *tmp; + int i; lockdep_assert_held(&ar->conf_mutex); @@ -756,6 +860,10 @@ static void ath10k_peer_cleanup_all(struct ath10k *ar) list_del(&peer->list); kfree(peer); } + + for (i = 0; i < ARRAY_SIZE(ar->peer_map); i++) + ar->peer_map[i] = NULL; + spin_unlock_bh(&ar->data_lock); ar->num_peers = 0; @@ -886,6 +994,7 @@ static int ath10k_monitor_vdev_start(struct ath10k *ar, int vdev_id) arg.channel.max_antenna_gain = channel->max_antenna_gain * 2; reinit_completion(&ar->vdev_setup_done); + reinit_completion(&ar->vdev_delete_done); ret = ath10k_wmi_vdev_start(ar, &arg); if (ret) { @@ -935,6 +1044,7 @@ static int ath10k_monitor_vdev_stop(struct ath10k *ar) ar->monitor_vdev_id, ret); reinit_completion(&ar->vdev_setup_done); + reinit_completion(&ar->vdev_delete_done); ret = ath10k_wmi_vdev_stop(ar, ar->monitor_vdev_id); if (ret) @@ -1271,6 +1381,7 @@ static int ath10k_vdev_stop(struct ath10k_vif *arvif) lockdep_assert_held(&ar->conf_mutex); reinit_completion(&ar->vdev_setup_done); + reinit_completion(&ar->vdev_delete_done); ret = ath10k_wmi_vdev_stop(ar, arvif->vdev_id); if (ret) { @@ -1306,7 +1417,12 @@ static int ath10k_vdev_start_restart(struct ath10k_vif *arvif, lockdep_assert_held(&ar->conf_mutex); + /* Clear arp and ns offload cache */ + memset(&arvif->arp_offload, 0, sizeof(arvif->arp_offload)); + memset(&arvif->ns_offload, 0, sizeof(arvif->ns_offload)); + reinit_completion(&ar->vdev_setup_done); + reinit_completion(&ar->vdev_delete_done); arg.vdev_id = arvif->vdev_id; arg.dtim_period = arvif->dtim_period; @@ -1384,10 +1500,7 @@ static int ath10k_mac_setup_bcn_p2p_ie(struct ath10k_vif *arvif, const u8 *p2p_ie; int ret; - if (arvif->vdev_type != WMI_VDEV_TYPE_AP) - return 0; - - if (arvif->vdev_subtype != WMI_VDEV_SUBTYPE_P2P_GO) + if (arvif->vif->type != NL80211_IFTYPE_AP || !arvif->vif->p2p) return 0; mgmt = (void *)bcn->data; @@ -1754,7 +1867,7 @@ static int ath10k_mac_vif_setup_ps(struct ath10k_vif *arvif) if (enable_ps && ath10k_mac_num_vifs_started(ar) > 1 && !test_bit(ATH10K_FW_FEATURE_MULTI_VIF_PS_SUPPORT, - ar->fw_features)) { + ar->running_fw->fw_file.fw_features)) { ath10k_warn(ar, "refusing to enable ps on vdev %i: not supported by fw\n", arvif->vdev_id); enable_ps = false; @@ -1990,7 +2103,7 @@ static void ath10k_peer_assoc_h_basic(struct ath10k *ar, ether_addr_copy(arg->addr, sta->addr); arg->vdev_id = arvif->vdev_id; arg->peer_aid = aid; - arg->peer_flags |= WMI_PEER_AUTH; + arg->peer_flags |= arvif->ar->wmi.peer_flags->auth; arg->peer_listen_intval = ath10k_peer_assoc_h_listen_intval(ar, vif); arg->peer_num_spatial_streams = 1; arg->peer_caps = vif->bss_conf.assoc_capability; @@ -1998,6 +2111,7 @@ static void ath10k_peer_assoc_h_basic(struct ath10k *ar, static void ath10k_peer_assoc_h_crypto(struct ath10k *ar, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, struct wmi_peer_assoc_complete_arg *arg) { struct ieee80211_bss_conf *info = &vif->bss_conf; @@ -2032,12 +2146,18 @@ static void ath10k_peer_assoc_h_crypto(struct ath10k *ar, /* FIXME: base on RSN IE/WPA IE is a correct idea? */ if (rsnie || wpaie) { ath10k_dbg(ar, ATH10K_DBG_WMI, "%s: rsn ie found\n", __func__); - arg->peer_flags |= WMI_PEER_NEED_PTK_4_WAY; + arg->peer_flags |= ar->wmi.peer_flags->need_ptk_4_way; } if (wpaie) { ath10k_dbg(ar, ATH10K_DBG_WMI, "%s: wpa ie found\n", __func__); - arg->peer_flags |= WMI_PEER_NEED_GTK_2_WAY; + arg->peer_flags |= ar->wmi.peer_flags->need_gtk_2_way; + } + + if (sta->mfp && + test_bit(ATH10K_FW_FEATURE_MFP_SUPPORT, + ar->running_fw->fw_file.fw_features)) { + arg->peer_flags |= ar->wmi.peer_flags->pmf; } } @@ -2134,7 +2254,7 @@ static void ath10k_peer_assoc_h_ht(struct ath10k *ar, ath10k_peer_assoc_h_vht_masked(vht_mcs_mask)) return; - arg->peer_flags |= WMI_PEER_HT; + arg->peer_flags |= ar->wmi.peer_flags->ht; arg->peer_max_mpdu = (1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + ht_cap->ampdu_factor)) - 1; @@ -2145,10 +2265,10 @@ static void ath10k_peer_assoc_h_ht(struct ath10k *ar, arg->peer_rate_caps |= WMI_RC_HT_FLAG; if (ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) - arg->peer_flags |= WMI_PEER_LDPC; + arg->peer_flags |= ar->wmi.peer_flags->ldbc; if (sta->bandwidth >= IEEE80211_STA_RX_BW_40) { - arg->peer_flags |= WMI_PEER_40MHZ; + arg->peer_flags |= ar->wmi.peer_flags->bw40; arg->peer_rate_caps |= WMI_RC_CW40_FLAG; } @@ -2162,7 +2282,7 @@ static void ath10k_peer_assoc_h_ht(struct ath10k *ar, if (ht_cap->cap & IEEE80211_HT_CAP_TX_STBC) { arg->peer_rate_caps |= WMI_RC_TX_STBC_FLAG; - arg->peer_flags |= WMI_PEER_STBC; + arg->peer_flags |= ar->wmi.peer_flags->stbc; } if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC) { @@ -2170,7 +2290,7 @@ static void ath10k_peer_assoc_h_ht(struct ath10k *ar, stbc = stbc >> IEEE80211_HT_CAP_RX_STBC_SHIFT; stbc = stbc << WMI_RC_RX_STBC_FLAG_S; arg->peer_rate_caps |= stbc; - arg->peer_flags |= WMI_PEER_STBC; + arg->peer_flags |= ar->wmi.peer_flags->stbc; } if (ht_cap->mcs.rx_mask[1] && ht_cap->mcs.rx_mask[2]) @@ -2351,10 +2471,10 @@ static void ath10k_peer_assoc_h_vht(struct ath10k *ar, if (ath10k_peer_assoc_h_vht_masked(vht_mcs_mask)) return; - arg->peer_flags |= WMI_PEER_VHT; + arg->peer_flags |= ar->wmi.peer_flags->vht; if (def.chan->band == IEEE80211_BAND_2GHZ) - arg->peer_flags |= WMI_PEER_VHT_2G; + arg->peer_flags |= ar->wmi.peer_flags->vht_2g; arg->peer_vht_caps = vht_cap->cap; @@ -2371,7 +2491,7 @@ static void ath10k_peer_assoc_h_vht(struct ath10k *ar, ampdu_factor)) - 1); if (sta->bandwidth == IEEE80211_STA_RX_BW_80) - arg->peer_flags |= WMI_PEER_80MHZ; + arg->peer_flags |= ar->wmi.peer_flags->bw80; arg->peer_vht_rates.rx_max_rate = __le16_to_cpu(vht_cap->vht_mcs.rx_highest); @@ -2396,27 +2516,28 @@ static void ath10k_peer_assoc_h_qos(struct ath10k *ar, switch (arvif->vdev_type) { case WMI_VDEV_TYPE_AP: if (sta->wme) - arg->peer_flags |= WMI_PEER_QOS; + arg->peer_flags |= arvif->ar->wmi.peer_flags->qos; if (sta->wme && sta->uapsd_queues) { - arg->peer_flags |= WMI_PEER_APSD; + arg->peer_flags |= arvif->ar->wmi.peer_flags->apsd; arg->peer_rate_caps |= WMI_RC_UAPSD_FLAG; } break; case WMI_VDEV_TYPE_STA: if (vif->bss_conf.qos) - arg->peer_flags |= WMI_PEER_QOS; + arg->peer_flags |= arvif->ar->wmi.peer_flags->qos; break; case WMI_VDEV_TYPE_IBSS: if (sta->wme) - arg->peer_flags |= WMI_PEER_QOS; + arg->peer_flags |= arvif->ar->wmi.peer_flags->qos; break; default: break; } ath10k_dbg(ar, ATH10K_DBG_MAC, "mac peer %pM qos %d\n", - sta->addr, !!(arg->peer_flags & WMI_PEER_QOS)); + sta->addr, !!(arg->peer_flags & + arvif->ar->wmi.peer_flags->qos)); } static bool ath10k_mac_sta_has_ofdm_only(struct ieee80211_sta *sta) @@ -2509,7 +2630,7 @@ static int ath10k_peer_assoc_prepare(struct ath10k *ar, memset(arg, 0, sizeof(*arg)); ath10k_peer_assoc_h_basic(ar, vif, sta, arg); - ath10k_peer_assoc_h_crypto(ar, vif, arg); + ath10k_peer_assoc_h_crypto(ar, vif, sta, arg); ath10k_peer_assoc_h_rates(ar, vif, sta, arg); ath10k_peer_assoc_h_ht(ar, vif, sta, arg); ath10k_peer_assoc_h_vht(ar, vif, sta, arg); @@ -2721,16 +2842,18 @@ static void ath10k_bss_disassoc(struct ieee80211_hw *hw, ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id); if (ret) - ath10k_warn(ar, "faield to down vdev %i: %d\n", + ath10k_warn(ar, "failed to down vdev %i: %d\n", arvif->vdev_id, ret); arvif->def_wep_key_idx = -1; - ret = ath10k_mac_vif_recalc_txbf(ar, vif, vht_cap); - if (ret) { - ath10k_warn(ar, "failed to recalc txbf for vdev %i: %d\n", - arvif->vdev_id, ret); - return; + if (!QCA_REV_WCN3990(ar)) { + ret = ath10k_mac_vif_recalc_txbf(ar, vif, vht_cap); + if (ret) { + ath10k_warn(ar, "failed to recalc txbf for vdev %i: %d\n", + arvif->vdev_id, ret); + return; + } } arvif->is_up = false; @@ -2884,7 +3007,7 @@ static int ath10k_update_channel_list(struct ath10k *ar) if (channel->flags & IEEE80211_CHAN_DISABLED) continue; - ch->allow_ht = true; + ch->allow_ht = true; /* FIXME: when should we really allow VHT? */ ch->allow_vht = true; @@ -2974,7 +3097,7 @@ static void ath10k_regd_update(struct ath10k *ar) regpair = ar->ath_common.regulatory.regpair; - if (config_enabled(CONFIG_ATH10K_DFS_CERTIFIED) && ar->dfs_detector) { + if (IS_ENABLED(CONFIG_ATH10K_DFS_CERTIFIED) && ar->dfs_detector) { nl_dfs_reg = ar->dfs_detector->region; wmi_dfs_reg = ath10k_mac_get_dfs_region(nl_dfs_reg); } else { @@ -3003,7 +3126,7 @@ static void ath10k_reg_notifier(struct wiphy *wiphy, ath_reg_notifier_apply(wiphy, request, &ar->ath_common.regulatory); - if (config_enabled(CONFIG_ATH10K_DFS_CERTIFIED) && ar->dfs_detector) { + if (IS_ENABLED(CONFIG_ATH10K_DFS_CERTIFIED) && ar->dfs_detector) { ath10k_dbg(ar, ATH10K_DBG_REGULATORY, "dfs region 0x%x\n", request->dfs_region); result = ar->dfs_detector->set_dfs_domain(ar->dfs_detector, @@ -3023,6 +3146,13 @@ static void ath10k_reg_notifier(struct wiphy *wiphy, /* TX handlers */ /***************/ +enum ath10k_mac_tx_path { + ATH10K_MAC_TX_HTT, + ATH10K_MAC_TX_HTT_MGMT, + ATH10K_MAC_TX_WMI_MGMT, + ATH10K_MAC_TX_UNKNOWN, +}; + void ath10k_mac_tx_lock(struct ath10k *ar, int reason) { lockdep_assert_held(&ar->htt.tx_lock); @@ -3149,35 +3279,11 @@ void ath10k_mac_handle_tx_pause_vdev(struct ath10k *ar, u32 vdev_id, spin_unlock_bh(&ar->htt.tx_lock); } -static u8 ath10k_tx_h_get_tid(struct ieee80211_hdr *hdr) -{ - if (ieee80211_is_mgmt(hdr->frame_control)) - return HTT_DATA_TX_EXT_TID_MGMT; - - if (!ieee80211_is_data_qos(hdr->frame_control)) - return HTT_DATA_TX_EXT_TID_NON_QOS_MCAST_BCAST; - - if (!is_unicast_ether_addr(ieee80211_get_DA(hdr))) - return HTT_DATA_TX_EXT_TID_NON_QOS_MCAST_BCAST; - - return ieee80211_get_qos_ctl(hdr)[0] & IEEE80211_QOS_CTL_TID_MASK; -} - -static u8 ath10k_tx_h_get_vdev_id(struct ath10k *ar, struct ieee80211_vif *vif) -{ - if (vif) - return ath10k_vif_to_arvif(vif)->vdev_id; - - if (ar->monitor_started) - return ar->monitor_vdev_id; - - ath10k_warn(ar, "failed to resolve vdev id\n"); - return 0; -} - static enum ath10k_hw_txrx_mode -ath10k_tx_h_get_txmode(struct ath10k *ar, struct ieee80211_vif *vif, - struct ieee80211_sta *sta, struct sk_buff *skb) +ath10k_mac_tx_h_get_txmode(struct ath10k *ar, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct sk_buff *skb) { const struct ieee80211_hdr *hdr = (void *)skb->data; __le16 fc = hdr->frame_control; @@ -3206,7 +3312,10 @@ ath10k_tx_h_get_txmode(struct ath10k *ar, struct ieee80211_vif *vif, */ if (ar->htt.target_version_major < 3 && (ieee80211_is_nullfunc(fc) || ieee80211_is_qos_nullfunc(fc)) && - !test_bit(ATH10K_FW_FEATURE_HAS_WMI_MGMT_TX, ar->fw_features)) + !test_bit(ATH10K_FW_FEATURE_HAS_WMI_MGMT_TX, + ar->running_fw->fw_file.fw_features) && + !test_bit(ATH10K_FW_FEATURE_SKIP_NULL_FUNC_WAR, + ar->running_fw->fw_file.fw_features)) return ATH10K_HW_TXRX_MGMT; /* Workaround: @@ -3227,14 +3336,22 @@ ath10k_tx_h_get_txmode(struct ath10k *ar, struct ieee80211_vif *vif, } static bool ath10k_tx_h_use_hwcrypto(struct ieee80211_vif *vif, - struct sk_buff *skb) { - struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct sk_buff *skb) +{ + const struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + const struct ieee80211_hdr *hdr = (void *)skb->data; const u32 mask = IEEE80211_TX_INTFL_DONT_ENCRYPT | IEEE80211_TX_CTL_INJECTED; + + if (!ieee80211_has_protected(hdr->frame_control)) + return false; + if ((info->flags & mask) == mask) return false; + if (vif) return !ath10k_vif_to_arvif(vif)->nohwcrypt; + return true; } @@ -3261,7 +3378,7 @@ static void ath10k_tx_h_nwifi(struct ieee80211_hw *hw, struct sk_buff *skb) */ hdr = (void *)skb->data; if (ieee80211_is_qos_nullfunc(hdr->frame_control)) - cb->htt.tid = HTT_DATA_TX_EXT_TID_NON_QOS_MCAST_BCAST; + cb->flags &= ~ATH10K_SKB_F_QOS; hdr->frame_control &= ~__cpu_to_le16(IEEE80211_STYPE_QOS_DATA); } @@ -3301,8 +3418,7 @@ static void ath10k_tx_h_add_p2p_noa_ie(struct ath10k *ar, struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif); /* This is case only for P2P_GO */ - if (arvif->vdev_type != WMI_VDEV_TYPE_AP || - arvif->vdev_subtype != WMI_VDEV_SUBTYPE_P2P_GO) + if (vif->type != NL80211_IFTYPE_AP || !vif->p2p) return; if (unlikely(ieee80211_is_probe_resp(hdr->frame_control))) { @@ -3317,7 +3433,29 @@ static void ath10k_tx_h_add_p2p_noa_ie(struct ath10k *ar, } } -static bool ath10k_mac_need_offchan_tx_work(struct ath10k *ar) +static void ath10k_mac_tx_h_fill_cb(struct ath10k *ar, + struct ieee80211_vif *vif, + struct ieee80211_txq *txq, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + struct ath10k_skb_cb *cb = ATH10K_SKB_CB(skb); + + cb->flags = 0; + if (!ath10k_tx_h_use_hwcrypto(vif, skb)) + cb->flags |= ATH10K_SKB_F_NO_HWCRYPT; + + if (ieee80211_is_mgmt(hdr->frame_control)) + cb->flags |= ATH10K_SKB_F_MGMT; + + if (ieee80211_is_data_qos(hdr->frame_control)) + cb->flags |= ATH10K_SKB_F_QOS; + + cb->vif = vif; + cb->txq = txq; +} + +bool ath10k_mac_tx_frm_has_freq(struct ath10k *ar) { /* FIXME: Not really sure since when the behaviour changed. At some * point new firmware stopped requiring creation of peer entries for @@ -3325,8 +3463,9 @@ static bool ath10k_mac_need_offchan_tx_work(struct ath10k *ar) * tx credit replenishment and reliability). Assuming it's at least 3.4 * because that's when the `freq` was introduced to TX_FRM HTT command. */ - return !(ar->htt.target_version_major >= 3 && - ar->htt.target_version_minor >= 4); + return (ar->htt.target_version_major >= 3 && + ar->htt.target_version_minor >= 4 && + ar->running_fw->fw_file.htt_op_version == ATH10K_FW_HTT_OP_VERSION_TLV); } static int ath10k_mac_tx_wmi_mgmt(struct ath10k *ar, struct sk_buff *skb) @@ -3351,26 +3490,52 @@ unlock: return ret; } -static void ath10k_mac_tx(struct ath10k *ar, struct sk_buff *skb) +static enum ath10k_mac_tx_path +ath10k_mac_tx_h_get_txpath(struct ath10k *ar, + struct sk_buff *skb, + enum ath10k_hw_txrx_mode txmode) { - struct ath10k_skb_cb *cb = ATH10K_SKB_CB(skb); - struct ath10k_htt *htt = &ar->htt; - int ret = 0; - - switch (cb->txmode) { + switch (txmode) { case ATH10K_HW_TXRX_RAW: case ATH10K_HW_TXRX_NATIVE_WIFI: case ATH10K_HW_TXRX_ETHERNET: - ret = ath10k_htt_tx(htt, skb); - break; + return ATH10K_MAC_TX_HTT; case ATH10K_HW_TXRX_MGMT: if (test_bit(ATH10K_FW_FEATURE_HAS_WMI_MGMT_TX, - ar->fw_features)) - ret = ath10k_mac_tx_wmi_mgmt(ar, skb); + ar->running_fw->fw_file.fw_features) || + test_bit(WMI_SERVICE_MGMT_TX_WMI, + ar->wmi.svc_map)) + return ATH10K_MAC_TX_WMI_MGMT; else if (ar->htt.target_version_major >= 3) - ret = ath10k_htt_tx(htt, skb); + return ATH10K_MAC_TX_HTT; else - ret = ath10k_htt_mgmt_tx(htt, skb); + return ATH10K_MAC_TX_HTT_MGMT; + } + + return ATH10K_MAC_TX_UNKNOWN; +} + +static int ath10k_mac_tx_submit(struct ath10k *ar, + enum ath10k_hw_txrx_mode txmode, + enum ath10k_mac_tx_path txpath, + struct sk_buff *skb) +{ + struct ath10k_htt *htt = &ar->htt; + int ret = -EINVAL; + + switch (txpath) { + case ATH10K_MAC_TX_HTT: + ret = ath10k_htt_tx(htt, txmode, skb); + break; + case ATH10K_MAC_TX_HTT_MGMT: + ret = ath10k_htt_mgmt_tx(htt, skb); + break; + case ATH10K_MAC_TX_WMI_MGMT: + ret = ath10k_mac_tx_wmi_mgmt(ar, skb); + break; + case ATH10K_MAC_TX_UNKNOWN: + WARN_ON_ONCE(1); + ret = -EINVAL; break; } @@ -3379,6 +3544,65 @@ static void ath10k_mac_tx(struct ath10k *ar, struct sk_buff *skb) ret); ieee80211_free_txskb(ar->hw, skb); } + + return ret; +} + +/* This function consumes the sk_buff regardless of return value as far as + * caller is concerned so no freeing is necessary afterwards. + */ +static int ath10k_mac_tx(struct ath10k *ar, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ath10k_hw_txrx_mode txmode, + enum ath10k_mac_tx_path txpath, + struct sk_buff *skb) +{ + struct ieee80211_hw *hw = ar->hw; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + int ret; + + skb_orphan(skb); + /* We should disable CCK RATE due to P2P */ + if (info->flags & IEEE80211_TX_CTL_NO_CCK_RATE) + ath10k_dbg(ar, ATH10K_DBG_MAC, "IEEE80211_TX_CTL_NO_CCK_RATE\n"); + + switch (txmode) { + case ATH10K_HW_TXRX_MGMT: + case ATH10K_HW_TXRX_NATIVE_WIFI: + ath10k_tx_h_nwifi(hw, skb); + ath10k_tx_h_add_p2p_noa_ie(ar, vif, skb); + ath10k_tx_h_seq_no(vif, skb); + break; + case ATH10K_HW_TXRX_ETHERNET: + ath10k_tx_h_8023(skb); + break; + case ATH10K_HW_TXRX_RAW: + if (!test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags)) { + WARN_ON_ONCE(1); + ieee80211_free_txskb(hw, skb); + return -ENOTSUPP; + } + } + + if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) { + if (!ath10k_mac_tx_frm_has_freq(ar)) { + ath10k_dbg(ar, ATH10K_DBG_MAC, "queued offchannel skb %pK\n", + skb); + + skb_queue_tail(&ar->offchan_tx_queue, skb); + ieee80211_queue_work(hw, &ar->offchan_tx_work); + return 0; + } + } + + ret = ath10k_mac_tx_submit(ar, txmode, txpath, skb); + if (ret) { + ath10k_warn(ar, "failed to submit frame: %d\n", ret); + return ret; + } + + return 0; } void ath10k_offchan_tx_purge(struct ath10k *ar) @@ -3398,7 +3622,12 @@ void ath10k_offchan_tx_work(struct work_struct *work) { struct ath10k *ar = container_of(work, struct ath10k, offchan_tx_work); struct ath10k_peer *peer; + struct ath10k_vif *arvif; + enum ath10k_hw_txrx_mode txmode; + enum ath10k_mac_tx_path txpath; struct ieee80211_hdr *hdr; + struct ieee80211_vif *vif; + struct ieee80211_sta *sta; struct sk_buff *skb; const u8 *peer_addr; int vdev_id; @@ -3420,14 +3649,14 @@ void ath10k_offchan_tx_work(struct work_struct *work) mutex_lock(&ar->conf_mutex); - ath10k_dbg(ar, ATH10K_DBG_MAC, "mac offchannel skb %p\n", + ath10k_dbg(ar, ATH10K_DBG_MAC, "mac offchannel skb %pK\n", skb); hdr = (struct ieee80211_hdr *)skb->data; peer_addr = ieee80211_get_DA(hdr); - vdev_id = ATH10K_SKB_CB(skb)->vdev_id; spin_lock_bh(&ar->data_lock); + vdev_id = ar->scan.vdev_id; peer = ath10k_peer_find(ar, vdev_id, peer_addr); spin_unlock_bh(&ar->data_lock); @@ -3437,7 +3666,8 @@ void ath10k_offchan_tx_work(struct work_struct *work) peer_addr, vdev_id); if (!peer) { - ret = ath10k_peer_create(ar, vdev_id, peer_addr, + ret = ath10k_peer_create(ar, NULL, NULL, vdev_id, + peer_addr, WMI_PEER_TYPE_DEFAULT); if (ret) ath10k_warn(ar, "failed to create peer %pM on vdev %d: %d\n", @@ -3450,12 +3680,33 @@ void ath10k_offchan_tx_work(struct work_struct *work) ar->offchan_tx_skb = skb; spin_unlock_bh(&ar->data_lock); - ath10k_mac_tx(ar, skb); + /* It's safe to access vif and sta - conf_mutex guarantees that + * sta_state() and remove_interface() are locked exclusively + * out wrt to this offchannel worker. + */ + arvif = ath10k_get_arvif(ar, vdev_id); + if (arvif) { + vif = arvif->vif; + sta = ieee80211_find_sta(vif, peer_addr); + } else { + vif = NULL; + sta = NULL; + } + + txmode = ath10k_mac_tx_h_get_txmode(ar, vif, sta, skb); + txpath = ath10k_mac_tx_h_get_txpath(ar, skb, txmode); + + ret = ath10k_mac_tx(ar, vif, sta, txmode, txpath, skb); + if (ret) { + ath10k_warn(ar, "failed to transmit offchannel frame: %d\n", + ret); + /* not serious */ + } time_left = wait_for_completion_timeout(&ar->offchan_tx_completed, 3 * HZ); if (time_left == 0) - ath10k_warn(ar, "timed out waiting for offchannel skb %p\n", + ath10k_warn(ar, "timed out waiting for offchannel skb %pK\n", skb); if (!peer && tmp_peer_created) { @@ -3486,6 +3737,7 @@ void ath10k_mgmt_over_wmi_tx_work(struct work_struct *work) { struct ath10k *ar = container_of(work, struct ath10k, wmi_mgmt_tx_work); struct sk_buff *skb; + dma_addr_t paddr; int ret; for (;;) { @@ -3493,13 +3745,222 @@ void ath10k_mgmt_over_wmi_tx_work(struct work_struct *work) if (!skb) break; - ret = ath10k_wmi_mgmt_tx(ar, skb); + if (QCA_REV_WCN3990(ar)) { + paddr = dma_map_single(ar->dev, skb->data, + skb->len, DMA_TO_DEVICE); + if (!paddr) + continue; + ret = ath10k_wmi_mgmt_tx_send(ar, skb, paddr); + if (ret) { + ath10k_warn(ar, "failed to transmit management frame by ref via WMI: %d\n", + ret); + dma_unmap_single(ar->dev, paddr, skb->len, + DMA_FROM_DEVICE); + ieee80211_free_txskb(ar->hw, skb); + } + } else { + ret = ath10k_wmi_mgmt_tx(ar, skb); + if (ret) { + ath10k_warn(ar, "failed to transmit management frame via WMI: %d\n", + ret); + ieee80211_free_txskb(ar->hw, skb); + } + } + } +} + +static void ath10k_mac_txq_init(struct ieee80211_txq *txq) +{ + struct ath10k_txq *artxq; + + if (!txq) + return; + + artxq = (void *)txq->drv_priv; + INIT_LIST_HEAD(&artxq->list); +} + +static void ath10k_mac_txq_unref(struct ath10k *ar, struct ieee80211_txq *txq) +{ + struct ath10k_txq *artxq; + struct ath10k_skb_cb *cb; + struct sk_buff *msdu; + int msdu_id; + + if (!txq) + return; + + artxq = (void *)txq->drv_priv; + spin_lock_bh(&ar->txqs_lock); + if (!list_empty(&artxq->list)) + list_del_init(&artxq->list); + spin_unlock_bh(&ar->txqs_lock); + + spin_lock_bh(&ar->htt.tx_lock); + idr_for_each_entry(&ar->htt.pending_tx, msdu, msdu_id) { + cb = ATH10K_SKB_CB(msdu); + if (cb->txq == txq) + cb->txq = NULL; + } + spin_unlock_bh(&ar->htt.tx_lock); +} + +struct ieee80211_txq *ath10k_mac_txq_lookup(struct ath10k *ar, + u16 peer_id, + u8 tid) +{ + struct ath10k_peer *peer; + + lockdep_assert_held(&ar->data_lock); + + peer = ar->peer_map[peer_id]; + if (!peer) + return NULL; + + if (peer->sta) + return peer->sta->txq[tid]; + else if (peer->vif) + return peer->vif->txq; + else + return NULL; +} + +static bool ath10k_mac_tx_can_push(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct ath10k *ar = hw->priv; + struct ath10k_txq *artxq = (void *)txq->drv_priv; + + /* No need to get locks */ + + if (ar->htt.tx_q_state.mode == HTT_TX_MODE_SWITCH_PUSH) + return true; + + if (ar->htt.num_pending_tx < ar->htt.tx_q_state.num_push_allowed) + return true; + + if (artxq->num_fw_queued < artxq->num_push_allowed) + return true; + + return false; +} + +int ath10k_mac_tx_push_txq(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct ath10k *ar = hw->priv; + struct ath10k_htt *htt = &ar->htt; + struct ath10k_txq *artxq = (void *)txq->drv_priv; + struct ieee80211_vif *vif = txq->vif; + struct ieee80211_sta *sta = txq->sta; + enum ath10k_hw_txrx_mode txmode; + enum ath10k_mac_tx_path txpath; + struct sk_buff *skb; + struct ieee80211_hdr *hdr; + size_t skb_len; + bool is_mgmt, is_presp; + int ret; + + spin_lock_bh(&ar->htt.tx_lock); + ret = ath10k_htt_tx_inc_pending(htt); + spin_unlock_bh(&ar->htt.tx_lock); + + if (ret) + return ret; + + skb = ieee80211_tx_dequeue(hw, txq); + if (!skb) { + spin_lock_bh(&ar->htt.tx_lock); + ath10k_htt_tx_dec_pending(htt); + spin_unlock_bh(&ar->htt.tx_lock); + + return -ENOENT; + } + + ath10k_mac_tx_h_fill_cb(ar, vif, txq, skb); + + skb_len = skb->len; + txmode = ath10k_mac_tx_h_get_txmode(ar, vif, sta, skb); + txpath = ath10k_mac_tx_h_get_txpath(ar, skb, txmode); + is_mgmt = (txpath == ATH10K_MAC_TX_HTT_MGMT); + + if (is_mgmt) { + hdr = (struct ieee80211_hdr *)skb->data; + is_presp = ieee80211_is_probe_resp(hdr->frame_control); + + spin_lock_bh(&ar->htt.tx_lock); + ret = ath10k_htt_tx_mgmt_inc_pending(htt, is_mgmt, is_presp); + if (ret) { - ath10k_warn(ar, "failed to transmit management frame via WMI: %d\n", - ret); - ieee80211_free_txskb(ar->hw, skb); + ath10k_htt_tx_dec_pending(htt); + spin_unlock_bh(&ar->htt.tx_lock); + return ret; + } + spin_unlock_bh(&ar->htt.tx_lock); + } + + ret = ath10k_mac_tx(ar, vif, sta, txmode, txpath, skb); + if (unlikely(ret)) { + ath10k_warn(ar, "failed to push frame: %d\n", ret); + + spin_lock_bh(&ar->htt.tx_lock); + ath10k_htt_tx_dec_pending(htt); + if (is_mgmt) + ath10k_htt_tx_mgmt_dec_pending(htt); + spin_unlock_bh(&ar->htt.tx_lock); + + return ret; + } + + spin_lock_bh(&ar->htt.tx_lock); + artxq->num_fw_queued++; + spin_unlock_bh(&ar->htt.tx_lock); + + return skb_len; +} + +void ath10k_mac_tx_push_pending(struct ath10k *ar) +{ + struct ieee80211_hw *hw = ar->hw; + struct ieee80211_txq *txq; + struct ath10k_txq *artxq; + struct ath10k_txq *last; + int ret; + int max; + + if (ar->htt.num_pending_tx >= (ar->htt.max_num_pending_tx / 2)) + return; + + spin_lock_bh(&ar->txqs_lock); + rcu_read_lock(); + + last = list_last_entry(&ar->txqs, struct ath10k_txq, list); + while (!list_empty(&ar->txqs)) { + artxq = list_first_entry(&ar->txqs, struct ath10k_txq, list); + txq = container_of((void *)artxq, struct ieee80211_txq, + drv_priv); + + /* Prevent aggressive sta/tid taking over tx queue */ + max = 16; + ret = 0; + while (ath10k_mac_tx_can_push(hw, txq) && max--) { + ret = ath10k_mac_tx_push_txq(hw, txq); + if (ret < 0) + break; } + + list_del_init(&artxq->list); + if (ret != -ENOENT) + list_add_tail(&artxq->list, &ar->txqs); + + ath10k_htt_tx_txq_update(hw, txq); + + if (artxq == last || (ret < 0 && ret != -ENOENT)) + break; } + + rcu_read_unlock(); + spin_unlock_bh(&ar->txqs_lock); } /************/ @@ -3525,9 +3986,10 @@ void __ath10k_scan_finish(struct ath10k *ar) case ATH10K_SCAN_STARTING: ar->scan.state = ATH10K_SCAN_IDLE; ar->scan_channel = NULL; + ar->scan.roc_freq = 0; ath10k_offchan_tx_purge(ar); cancel_delayed_work(&ar->scan.timeout); - complete_all(&ar->scan.completed); + complete(&ar->scan.completed); break; } } @@ -3556,7 +4018,7 @@ static int ath10k_scan_stop(struct ath10k *ar) goto out; } - ret = wait_for_completion_timeout(&ar->scan.completed, 3*HZ); + ret = wait_for_completion_timeout(&ar->scan.completed, 3 * HZ); if (ret == 0) { ath10k_warn(ar, "failed to receive scan abortion completion: timed out\n"); ret = -ETIMEDOUT; @@ -3636,7 +4098,7 @@ static int ath10k_start_scan(struct ath10k *ar, if (ret) return ret; - ret = wait_for_completion_timeout(&ar->scan.started, 1*HZ); + ret = wait_for_completion_timeout(&ar->scan.started, 1 * HZ); if (ret == 0) { ret = ath10k_scan_stop(ar); if (ret) @@ -3663,67 +4125,100 @@ static int ath10k_start_scan(struct ath10k *ar, /* mac80211 callbacks */ /**********************/ -static void ath10k_tx(struct ieee80211_hw *hw, - struct ieee80211_tx_control *control, - struct sk_buff *skb) +static void ath10k_mac_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) { struct ath10k *ar = hw->priv; + struct ath10k_htt *htt = &ar->htt; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_vif *vif = info->control.vif; struct ieee80211_sta *sta = control->sta; - struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; - __le16 fc = hdr->frame_control; + struct ieee80211_txq *txq = NULL; + struct ieee80211_hdr *hdr = (void *)skb->data; + enum ath10k_hw_txrx_mode txmode; + enum ath10k_mac_tx_path txpath; + bool is_htt; + bool is_mgmt; + bool is_presp; + int ret; - /* We should disable CCK RATE due to P2P */ - if (info->flags & IEEE80211_TX_CTL_NO_CCK_RATE) - ath10k_dbg(ar, ATH10K_DBG_MAC, "IEEE80211_TX_CTL_NO_CCK_RATE\n"); + ath10k_mac_tx_h_fill_cb(ar, vif, txq, skb); - ATH10K_SKB_CB(skb)->htt.is_offchan = false; - ATH10K_SKB_CB(skb)->htt.freq = 0; - ATH10K_SKB_CB(skb)->htt.tid = ath10k_tx_h_get_tid(hdr); - ATH10K_SKB_CB(skb)->htt.nohwcrypt = !ath10k_tx_h_use_hwcrypto(vif, skb); - ATH10K_SKB_CB(skb)->vdev_id = ath10k_tx_h_get_vdev_id(ar, vif); - ATH10K_SKB_CB(skb)->txmode = ath10k_tx_h_get_txmode(ar, vif, sta, skb); - ATH10K_SKB_CB(skb)->is_protected = ieee80211_has_protected(fc); + txmode = ath10k_mac_tx_h_get_txmode(ar, vif, sta, skb); + txpath = ath10k_mac_tx_h_get_txpath(ar, skb, txmode); + is_htt = (txpath == ATH10K_MAC_TX_HTT || + txpath == ATH10K_MAC_TX_HTT_MGMT); + is_mgmt = (txpath == ATH10K_MAC_TX_HTT_MGMT); - switch (ATH10K_SKB_CB(skb)->txmode) { - case ATH10K_HW_TXRX_MGMT: - case ATH10K_HW_TXRX_NATIVE_WIFI: - ath10k_tx_h_nwifi(hw, skb); - ath10k_tx_h_add_p2p_noa_ie(ar, vif, skb); - ath10k_tx_h_seq_no(vif, skb); - break; - case ATH10K_HW_TXRX_ETHERNET: - ath10k_tx_h_8023(skb); - break; - case ATH10K_HW_TXRX_RAW: - if (!test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags)) { - WARN_ON_ONCE(1); - ieee80211_free_txskb(hw, skb); + if (is_htt) { + spin_lock_bh(&ar->htt.tx_lock); + is_presp = ieee80211_is_probe_resp(hdr->frame_control); + + ret = ath10k_htt_tx_inc_pending(htt); + if (ret) { + ath10k_warn(ar, "failed to increase tx pending count: %d, dropping\n", + ret); + spin_unlock_bh(&ar->htt.tx_lock); + ieee80211_free_txskb(ar->hw, skb); + return; + } + + ret = ath10k_htt_tx_mgmt_inc_pending(htt, is_mgmt, is_presp); + if (ret) { + ath10k_dbg(ar, ATH10K_DBG_MAC, "failed to increase tx mgmt pending count: %d, dropping\n", + ret); + ath10k_htt_tx_dec_pending(htt); + spin_unlock_bh(&ar->htt.tx_lock); + ieee80211_free_txskb(ar->hw, skb); return; } + spin_unlock_bh(&ar->htt.tx_lock); } - if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) { - spin_lock_bh(&ar->data_lock); - ATH10K_SKB_CB(skb)->htt.freq = ar->scan.roc_freq; - ATH10K_SKB_CB(skb)->vdev_id = ar->scan.vdev_id; - spin_unlock_bh(&ar->data_lock); + ret = ath10k_mac_tx(ar, vif, sta, txmode, txpath, skb); + if (ret) { + ath10k_warn(ar, "failed to transmit frame: %d\n", ret); + if (is_htt) { + spin_lock_bh(&ar->htt.tx_lock); + ath10k_htt_tx_dec_pending(htt); + if (is_mgmt) + ath10k_htt_tx_mgmt_dec_pending(htt); + spin_unlock_bh(&ar->htt.tx_lock); + } + return; + } +} - if (ath10k_mac_need_offchan_tx_work(ar)) { - ATH10K_SKB_CB(skb)->htt.freq = 0; - ATH10K_SKB_CB(skb)->htt.is_offchan = true; +static void ath10k_mac_op_wake_tx_queue(struct ieee80211_hw *hw, + struct ieee80211_txq *txq) +{ + struct ath10k *ar = hw->priv; + struct ath10k_txq *artxq = (void *)txq->drv_priv; + struct ieee80211_txq *f_txq; + struct ath10k_txq *f_artxq; + int ret = 0; + int max = 16; - ath10k_dbg(ar, ATH10K_DBG_MAC, "queued offchannel skb %p\n", - skb); + spin_lock_bh(&ar->txqs_lock); + if (list_empty(&artxq->list)) + list_add_tail(&artxq->list, &ar->txqs); - skb_queue_tail(&ar->offchan_tx_queue, skb); - ieee80211_queue_work(hw, &ar->offchan_tx_work); - return; - } + f_artxq = list_first_entry(&ar->txqs, struct ath10k_txq, list); + f_txq = container_of((void *)f_artxq, struct ieee80211_txq, drv_priv); + list_del_init(&f_artxq->list); + + while (ath10k_mac_tx_can_push(hw, f_txq) && max--) { + ret = ath10k_mac_tx_push_txq(hw, f_txq); + if (ret) + break; } + if (ret != -ENOENT) + list_add_tail(&f_artxq->list, &ar->txqs); + spin_unlock_bh(&ar->txqs_lock); - ath10k_mac_tx(ar, skb); + ath10k_htt_tx_txq_update(hw, f_txq); + ath10k_htt_tx_txq_update(hw, txq); } /* Must not be called with conf_mutex held as workers can use that also. */ @@ -3863,6 +4358,9 @@ static struct ieee80211_sta_vht_cap ath10k_create_vht_cap(struct ath10k *ar) mcs_map |= IEEE80211_VHT_MCS_NOT_SUPPORTED << (i * 2); } + if (ar->cfg_tx_chainmask <= 1) + vht_cap.cap &= ~IEEE80211_VHT_CAP_TXSTBC; + vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(mcs_map); vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(mcs_map); @@ -3882,7 +4380,8 @@ static struct ieee80211_sta_ht_cap ath10k_get_ht_cap(struct ath10k *ar) ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_8; ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40; - ht_cap.cap |= WLAN_HT_CAP_SM_PS_STATIC << IEEE80211_HT_CAP_SM_PS_SHIFT; + ht_cap.cap |= + WLAN_HT_CAP_SM_PS_DISABLED << IEEE80211_HT_CAP_SM_PS_SHIFT; if (ar->ht_cap_info & WMI_HT_CAP_HT20_SGI) ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; @@ -3899,7 +4398,7 @@ static struct ieee80211_sta_ht_cap ath10k_get_ht_cap(struct ath10k *ar) ht_cap.cap |= smps; } - if (ar->ht_cap_info & WMI_HT_CAP_TX_STBC) + if (ar->ht_cap_info & WMI_HT_CAP_TX_STBC && (ar->cfg_tx_chainmask > 1)) ht_cap.cap |= IEEE80211_HT_CAP_TX_STBC; if (ar->ht_cap_info & WMI_HT_CAP_RX_STBC) { @@ -3946,9 +4445,6 @@ static void ath10k_mac_setup_ht_vht_cap(struct ath10k *ar) if (ar->phy_capability & WHAL_WLAN_11G_CAPABILITY) { band = &ar->mac.sbands[IEEE80211_BAND_2GHZ]; band->ht_cap = ht_cap; - - /* Enable the VHT support at 2.4 GHz */ - band->vht_cap = vht_cap; } if (ar->phy_capability & WHAL_WLAN_11A_CAPABILITY) { band = &ar->mac.sbands[IEEE80211_BAND_5GHZ]; @@ -4009,12 +4505,12 @@ static int ath10k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) static int ath10k_start(struct ieee80211_hw *hw) { struct ath10k *ar = hw->priv; - u32 burst_enable; + u32 param; int ret = 0; /* * This makes sense only when restarting hw. It is harmless to call - * uncoditionally. This is necessary to make sure no HTT/WMI tx + * unconditionally. This is necessary to make sure no HTT/WMI tx * commands will be submitted while restarting. */ ath10k_drain_tx(ar); @@ -4026,7 +4522,8 @@ static int ath10k_start(struct ieee80211_hw *hw) ar->state = ATH10K_STATE_ON; break; case ATH10K_STATE_RESTARTING: - ath10k_halt(ar); + if (!test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + ath10k_halt(ar); ar->state = ATH10K_STATE_RESTARTED; break; case ATH10K_STATE_ON: @@ -4046,24 +4543,34 @@ static int ath10k_start(struct ieee80211_hw *hw) goto err_off; } - ret = ath10k_core_start(ar, ATH10K_FIRMWARE_MODE_NORMAL); + ret = ath10k_core_start(ar, ATH10K_FIRMWARE_MODE_NORMAL, + &ar->normal_mode_fw); if (ret) { ath10k_err(ar, "Could not init core: %d\n", ret); goto err_power_down; } - ret = ath10k_wmi_pdev_set_param(ar, ar->wmi.pdev_param->pmf_qos, 1); + param = ar->wmi.pdev_param->pmf_qos; + ret = ath10k_wmi_pdev_set_param(ar, param, 1); if (ret) { ath10k_warn(ar, "failed to enable PMF QOS: %d\n", ret); goto err_core_stop; } - ret = ath10k_wmi_pdev_set_param(ar, ar->wmi.pdev_param->dynamic_bw, 1); + param = ar->wmi.pdev_param->dynamic_bw; + ret = ath10k_wmi_pdev_set_param(ar, param, 1); if (ret) { ath10k_warn(ar, "failed to enable dynamic BW: %d\n", ret); goto err_core_stop; } + param = ar->wmi.pdev_param->idle_ps_config; + ret = ath10k_wmi_pdev_set_param(ar, param, 1); + if (ret) { + ath10k_warn(ar, "failed to enable idle_ps_config: %d\n", ret); + goto err_core_stop; + } + if (test_bit(WMI_SERVICE_ADAPTIVE_OCS, ar->wmi.svc_map)) { ret = ath10k_wmi_adaptive_qcs(ar, true); if (ret) { @@ -4074,8 +4581,8 @@ static int ath10k_start(struct ieee80211_hw *hw) } if (test_bit(WMI_SERVICE_BURST, ar->wmi.svc_map)) { - burst_enable = ar->wmi.pdev_param->burst_enable; - ret = ath10k_wmi_pdev_set_param(ar, burst_enable, 0); + param = ar->wmi.pdev_param->burst_enable; + ret = ath10k_wmi_pdev_set_param(ar, param, 0); if (ret) { ath10k_warn(ar, "failed to disable burst: %d\n", ret); goto err_core_stop; @@ -4093,8 +4600,8 @@ static int ath10k_start(struct ieee80211_hw *hw) * this problem. */ - ret = ath10k_wmi_pdev_set_param(ar, - ar->wmi.pdev_param->arp_ac_override, 0); + param = ar->wmi.pdev_param->arp_ac_override; + ret = ath10k_wmi_pdev_set_param(ar, param, 0); if (ret) { ath10k_warn(ar, "failed to set arp ac override parameter: %d\n", ret); @@ -4102,7 +4609,7 @@ static int ath10k_start(struct ieee80211_hw *hw) } if (test_bit(ATH10K_FW_FEATURE_SUPPORTS_ADAPTIVE_CCA, - ar->fw_features)) { + ar->running_fw->fw_file.fw_features)) { ret = ath10k_wmi_pdev_enable_adaptive_cca(ar, 1, WMI_CCA_DETECT_LEVEL_AUTO, WMI_CCA_DETECT_MARGIN_AUTO); @@ -4111,10 +4618,11 @@ static int ath10k_start(struct ieee80211_hw *hw) ret); goto err_core_stop; } + ar->sifs_burst_enabled = false; } - ret = ath10k_wmi_pdev_set_param(ar, - ar->wmi.pdev_param->ani_enable, 1); + param = ar->wmi.pdev_param->ani_enable; + ret = ath10k_wmi_pdev_set_param(ar, param, 1); if (ret) { ath10k_warn(ar, "failed to enable ani by default: %d\n", ret); @@ -4123,6 +4631,31 @@ static int ath10k_start(struct ieee80211_hw *hw) ar->ani_enabled = true; + if (ath10k_peer_stats_enabled(ar)) { + param = ar->wmi.pdev_param->peer_stats_update_period; + ret = ath10k_wmi_pdev_set_param(ar, param, + PEER_DEFAULT_STATS_UPDATE_PERIOD); + if (ret) { + ath10k_warn(ar, + "failed to set peer stats period : %d\n", + ret); + goto err_core_stop; + } + } + + param = ar->wmi.pdev_param->enable_btcoex; + if (test_bit(WMI_SERVICE_COEX_GPIO, ar->wmi.svc_map) && + test_bit(ATH10K_FW_FEATURE_BTCOEX_PARAM, + ar->running_fw->fw_file.fw_features)) { + ret = ath10k_wmi_pdev_set_param(ar, param, 0); + if (ret) { + ath10k_warn(ar, + "failed to set btcoex param: %d\n", ret); + goto err_core_stop; + } + clear_bit(ATH10K_FLAG_BTCOEX, &ar->dev_flags); + } + ar->num_started_vdevs = 0; ath10k_regd_update(ar); @@ -4325,6 +4858,7 @@ static int ath10k_add_interface(struct ieee80211_hw *hw, { struct ath10k *ar = hw->priv; struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif); + struct ath10k_peer *peer; enum wmi_sta_powersave_param param; int ret = 0; u32 value; @@ -4337,6 +4871,7 @@ static int ath10k_add_interface(struct ieee80211_hw *hw, mutex_lock(&ar->conf_mutex); memset(arvif, 0, sizeof(*arvif)); + ath10k_mac_txq_init(vif->txq); arvif->ar = ar; arvif->vif = vif; @@ -4371,24 +4906,30 @@ static int ath10k_add_interface(struct ieee80211_hw *hw, bit, ar->free_vdev_map); arvif->vdev_id = bit; - arvif->vdev_subtype = WMI_VDEV_SUBTYPE_NONE; + arvif->vdev_subtype = + ath10k_wmi_get_vdev_subtype(ar, WMI_VDEV_SUBTYPE_NONE); switch (vif->type) { case NL80211_IFTYPE_P2P_DEVICE: arvif->vdev_type = WMI_VDEV_TYPE_STA; - arvif->vdev_subtype = WMI_VDEV_SUBTYPE_P2P_DEVICE; + arvif->vdev_subtype = ath10k_wmi_get_vdev_subtype + (ar, WMI_VDEV_SUBTYPE_P2P_DEVICE); break; case NL80211_IFTYPE_UNSPECIFIED: case NL80211_IFTYPE_STATION: arvif->vdev_type = WMI_VDEV_TYPE_STA; if (vif->p2p) - arvif->vdev_subtype = WMI_VDEV_SUBTYPE_P2P_CLIENT; + arvif->vdev_subtype = ath10k_wmi_get_vdev_subtype + (ar, WMI_VDEV_SUBTYPE_P2P_CLIENT); break; case NL80211_IFTYPE_ADHOC: arvif->vdev_type = WMI_VDEV_TYPE_IBSS; break; case NL80211_IFTYPE_MESH_POINT: - if (!test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags)) { + if (test_bit(WMI_SERVICE_MESH_11S, ar->wmi.svc_map)) { + arvif->vdev_subtype = ath10k_wmi_get_vdev_subtype + (ar, WMI_VDEV_SUBTYPE_MESH_11S); + } else if (!test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags)) { ret = -EINVAL; ath10k_warn(ar, "must load driver with rawmode=1 to add mesh interfaces\n"); goto err; @@ -4399,7 +4940,8 @@ static int ath10k_add_interface(struct ieee80211_hw *hw, arvif->vdev_type = WMI_VDEV_TYPE_AP; if (vif->p2p) - arvif->vdev_subtype = WMI_VDEV_SUBTYPE_P2P_GO; + arvif->vdev_subtype = ath10k_wmi_get_vdev_subtype + (ar, WMI_VDEV_SUBTYPE_P2P_GO); break; case NL80211_IFTYPE_MONITOR: arvif->vdev_type = WMI_VDEV_TYPE_MONITOR; @@ -4469,6 +5011,15 @@ static int ath10k_add_interface(struct ieee80211_hw *hw, goto err; } + if ((arvif->vdev_type == WMI_VDEV_TYPE_STA) && QCA_REV_WCN3990(ar)) { + ret = ath10k_wmi_csa_offload(ar, arvif->vdev_id, true); + if (ret) { + ath10k_err(ar, "CSA offload failed for vdev %i: %d\n", + arvif->vdev_id, ret); + goto err_vdev_delete; + } + } + ar->free_vdev_map &= ~(1LL << arvif->vdev_id); list_add(&arvif->list, &ar->arvifs); @@ -4513,13 +5064,31 @@ static int ath10k_add_interface(struct ieee80211_hw *hw, if (arvif->vdev_type == WMI_VDEV_TYPE_AP || arvif->vdev_type == WMI_VDEV_TYPE_IBSS) { - ret = ath10k_peer_create(ar, arvif->vdev_id, vif->addr, - WMI_PEER_TYPE_DEFAULT); + ret = ath10k_peer_create(ar, vif, NULL, arvif->vdev_id, + vif->addr, WMI_PEER_TYPE_DEFAULT); if (ret) { ath10k_warn(ar, "failed to create vdev %i peer for AP/IBSS: %d\n", arvif->vdev_id, ret); goto err_vdev_delete; } + + spin_lock_bh(&ar->data_lock); + + peer = ath10k_peer_find(ar, arvif->vdev_id, vif->addr); + if (!peer) { + ath10k_warn(ar, "failed to lookup peer %pM on vdev %i\n", + vif->addr, arvif->vdev_id); + spin_unlock_bh(&ar->data_lock); + ret = -ENOENT; + goto err_peer_delete; + } + + arvif->peer_id = find_first_bit(peer->peer_ids, + ATH10K_MAX_NUM_PEER_IDS); + + spin_unlock_bh(&ar->data_lock); + } else { + arvif->peer_id = HTT_INVALID_PEERID; } if (arvif->vdev_type == WMI_VDEV_TYPE_AP) { @@ -4598,7 +5167,7 @@ static int ath10k_add_interface(struct ieee80211_hw *hw, err_peer_delete: if (arvif->vdev_type == WMI_VDEV_TYPE_AP || arvif->vdev_type == WMI_VDEV_TYPE_IBSS) - ath10k_wmi_peer_delete(ar, arvif->vdev_id, vif->addr); + ath10k_peer_delete(ar, arvif->vdev_id, vif->addr); err_vdev_delete: ath10k_wmi_vdev_delete(ar, arvif->vdev_id); @@ -4630,7 +5199,10 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw, { struct ath10k *ar = hw->priv; struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif); + struct ath10k_peer *peer; + unsigned long time_left; int ret; + int i; cancel_work_sync(&arvif->ap_csa_work); cancel_delayed_work_sync(&arvif->connection_loss_work); @@ -4651,8 +5223,8 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw, if (arvif->vdev_type == WMI_VDEV_TYPE_AP || arvif->vdev_type == WMI_VDEV_TYPE_IBSS) { - ret = ath10k_wmi_peer_delete(arvif->ar, arvif->vdev_id, - vif->addr); + ret = ath10k_peer_delete(arvif->ar, arvif->vdev_id, + vif->addr); if (ret) ath10k_warn(ar, "failed to submit AP/IBSS self-peer removal on vdev %i: %d\n", arvif->vdev_id, ret); @@ -4660,6 +5232,9 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw, kfree(arvif->u.ap.noa_data); } + if ((arvif->vdev_type == WMI_VDEV_TYPE_STA) && QCA_REV_WCN3990(ar)) + ath10k_wmi_csa_offload(ar, arvif->vdev_id, false); + ath10k_dbg(ar, ATH10K_DBG_MAC, "mac vdev %i delete (remove interface)\n", arvif->vdev_id); @@ -4668,6 +5243,16 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw, ath10k_warn(ar, "failed to delete WMI vdev %i: %d\n", arvif->vdev_id, ret); + if (QCA_REV_WCN3990(ar)) { + time_left = wait_for_completion_timeout( + &ar->vdev_delete_done, + ATH10K_VDEV_DELETE_TIMEOUT_HZ); + if (time_left == 0) { + ath10k_warn(ar, "Timeout in receiving vdev delete resp\n"); + return; + } + } + /* Some firmware revisions don't notify host about self-peer removal * until after associated vdev is deleted. */ @@ -4684,7 +5269,22 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw, spin_unlock_bh(&ar->data_lock); } + spin_lock_bh(&ar->data_lock); + for (i = 0; i < ARRAY_SIZE(ar->peer_map); i++) { + peer = ar->peer_map[i]; + if (!peer) + continue; + + if (peer->vif == vif) { + ath10k_warn(ar, "found vif peer %pM entry on vdev %i after it was supposedly removed\n", + vif->addr, arvif->vdev_id); + peer->vif = NULL; + } + } + spin_unlock_bh(&ar->data_lock); + ath10k_peer_cleanup(ar, arvif->vdev_id); + ath10k_mac_txq_unref(ar, vif->txq); if (vif->type == NL80211_IFTYPE_MONITOR) { ar->monitor_arvif = NULL; @@ -4693,13 +5293,39 @@ static void ath10k_remove_interface(struct ieee80211_hw *hw, ath10k_warn(ar, "failed to recalc monitor: %d\n", ret); } + ret = ath10k_mac_txpower_recalc(ar); + if (ret) + ath10k_warn(ar, "failed to recalc tx power: %d\n", ret); + spin_lock_bh(&ar->htt.tx_lock); ath10k_mac_vif_tx_unlock_all(arvif); spin_unlock_bh(&ar->htt.tx_lock); + ath10k_mac_txq_unref(ar, vif->txq); + mutex_unlock(&ar->conf_mutex); } +static int ath10k_change_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum nl80211_iftype new_type, bool p2p) +{ + struct ath10k *ar = hw->priv; + int ret = 0; + + ath10k_dbg(ar, ATH10K_DBG_MAC, + "change_interface new: %d (%d), old: %d (%d)\n", new_type, + p2p, vif->type, vif->p2p); + + if (new_type != vif->type || vif->p2p != p2p) { + ath10k_remove_interface(hw, vif); + vif->type = new_type; + vif->p2p = p2p; + ret = ath10k_add_interface(hw, vif); + } + return ret; +} + /* * FIXME: Has to be verified. */ @@ -4728,7 +5354,7 @@ static void ath10k_configure_filter(struct ieee80211_hw *hw, ret = ath10k_monitor_recalc(ar); if (ret) - ath10k_warn(ar, "failed to recalc montior: %d\n", ret); + ath10k_warn(ar, "failed to recalc monitor: %d\n", ret); mutex_unlock(&ar->conf_mutex); } @@ -4920,7 +5546,8 @@ static int ath10k_hw_scan(struct ieee80211_hw *hw, struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif); struct cfg80211_scan_request *req = &hw_req->req; struct wmi_start_scan_arg arg; - int ret = 0; + const u8 *ptr; + int ret = 0, ie_skip_len = 0; int i; mutex_lock(&ar->conf_mutex); @@ -4952,8 +5579,16 @@ static int ath10k_hw_scan(struct ieee80211_hw *hw, arg.scan_id = ATH10K_SCAN_ID; if (req->ie_len) { - arg.ie_len = req->ie_len; - memcpy(arg.ie, req->ie, arg.ie_len); + if (QCA_REV_WCN3990(ar)) { + ptr = req->ie; + while (ptr[0] == WLAN_EID_SUPP_RATES || + ptr[0] == WLAN_EID_EXT_SUPP_RATES) { + ie_skip_len = ptr[1] + 2; + ptr += ie_skip_len; + } + } + arg.ie_len = req->ie_len - ie_skip_len; + memcpy(arg.ie, req->ie + ie_skip_len, arg.ie_len); } if (req->n_ssids) { @@ -4962,6 +5597,11 @@ static int ath10k_hw_scan(struct ieee80211_hw *hw, arg.ssids[i].len = req->ssids[i].ssid_len; arg.ssids[i].ssid = req->ssids[i].ssid; } + if (QCA_REV_WCN3990(ar)) { + arg.scan_ctrl_flags &= + ~(WMI_SCAN_ADD_BCAST_PROBE_REQ | + WMI_SCAN_CHAN_STAT_EVENT); + } } else { arg.scan_ctrl_flags |= WMI_SCAN_FLAG_PASSIVE; } @@ -5044,6 +5684,22 @@ static void ath10k_set_key_h_def_keyidx(struct ath10k *ar, arvif->vdev_id, ret); } +static void ath10k_set_rekey_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_gtk_rekey_data *data) +{ + struct ath10k *ar = hw->priv; + struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif); + + mutex_lock(&ar->conf_mutex); + memcpy(&arvif->gtk_rekey_data.kek, data->kek, NL80211_KEK_LEN); + memcpy(&arvif->gtk_rekey_data.kck, data->kck, NL80211_KCK_LEN); + arvif->gtk_rekey_data.replay_ctr = + be64_to_cpup((__be64 *)data->replay_ctr); + arvif->gtk_rekey_data.valid = true; + mutex_unlock(&ar->conf_mutex); +} + static int ath10k_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct ieee80211_key_conf *key) @@ -5398,13 +6054,18 @@ static int ath10k_sta_state(struct ieee80211_hw *hw, struct ath10k *ar = hw->priv; struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif); struct ath10k_sta *arsta = (struct ath10k_sta *)sta->drv_priv; + struct ath10k_peer *peer; int ret = 0; + int i; if (old_state == IEEE80211_STA_NOTEXIST && new_state == IEEE80211_STA_NONE) { memset(arsta, 0, sizeof(*arsta)); arsta->arvif = arvif; INIT_WORK(&arsta->update_wk, ath10k_sta_rc_update_wk); + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + ath10k_mac_txq_init(sta->txq[i]); } /* cancel must be done outside the mutex to avoid deadlock */ @@ -5412,6 +6073,9 @@ static int ath10k_sta_state(struct ieee80211_hw *hw, new_state == IEEE80211_STA_NOTEXIST)) cancel_work_sync(&arsta->update_wk); + if (vif->type == NL80211_IFTYPE_STATION && new_state > ar->sta_state) + ar->sta_state = new_state; + mutex_lock(&ar->conf_mutex); if (old_state == IEEE80211_STA_NOTEXIST && @@ -5439,8 +6103,8 @@ static int ath10k_sta_state(struct ieee80211_hw *hw, if (sta->tdls) peer_type = WMI_PEER_TYPE_TDLS; - ret = ath10k_peer_create(ar, arvif->vdev_id, sta->addr, - peer_type); + ret = ath10k_peer_create(ar, vif, sta, arvif->vdev_id, + sta->addr, peer_type); if (ret) { ath10k_warn(ar, "failed to add peer %pM for vdev %d when adding a new sta: %i\n", sta->addr, arvif->vdev_id, ret); @@ -5448,6 +6112,24 @@ static int ath10k_sta_state(struct ieee80211_hw *hw, goto exit; } + spin_lock_bh(&ar->data_lock); + + peer = ath10k_peer_find(ar, arvif->vdev_id, sta->addr); + if (!peer) { + ath10k_warn(ar, "failed to lookup peer %pM on vdev %i\n", + vif->addr, arvif->vdev_id); + spin_unlock_bh(&ar->data_lock); + ath10k_peer_delete(ar, arvif->vdev_id, sta->addr); + ath10k_mac_dec_num_stations(arvif, sta); + ret = -ENOENT; + goto exit; + } + + arsta->peer_id = find_first_bit(peer->peer_ids, + ATH10K_MAX_NUM_PEER_IDS); + + spin_unlock_bh(&ar->data_lock); + if (!sta->tdls) goto exit; @@ -5500,8 +6182,8 @@ static int ath10k_sta_state(struct ieee80211_hw *hw, * Existing station deletion. */ ath10k_dbg(ar, ATH10K_DBG_MAC, - "mac vdev %d peer delete %pM (sta gone)\n", - arvif->vdev_id, sta->addr); + "mac vdev %d peer delete %pM sta %pK (sta gone)\n", + arvif->vdev_id, sta->addr, sta); if (sta->tdls) { ret = ath10k_mac_tdls_peer_update(ar, arvif->vdev_id, @@ -5520,6 +6202,31 @@ static int ath10k_sta_state(struct ieee80211_hw *hw, ath10k_mac_dec_num_stations(arvif, sta); + spin_lock_bh(&ar->data_lock); + for (i = 0; i < ARRAY_SIZE(ar->peer_map); i++) { + peer = ar->peer_map[i]; + if (!peer) + continue; + + if (peer->sta == sta) { + ath10k_warn(ar, "found sta peer %pM (ptr %pK id %d) entry on vdev %i after it was supposedly removed\n", + sta->addr, peer, i, arvif->vdev_id); + peer->sta = NULL; + + /* Clean up the peer object as well since we + * must have failed to do this above. + */ + list_del(&peer->list); + ar->peer_map[i] = NULL; + kfree(peer); + ar->num_peers--; + } + } + spin_unlock_bh(&ar->data_lock); + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) + ath10k_mac_txq_unref(ar, sta->txq[i]); + if (!sta->tdls) goto exit; @@ -5766,7 +6473,7 @@ exit: return ret; } -#define ATH10K_ROC_TIMEOUT_HZ (2*HZ) +#define ATH10K_ROC_TIMEOUT_HZ (2 * HZ) static int ath10k_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif, @@ -5818,7 +6525,13 @@ static int ath10k_remain_on_channel(struct ieee80211_hw *hw, arg.dwell_time_passive = scan_time_msec; arg.max_scan_time = scan_time_msec; arg.scan_ctrl_flags |= WMI_SCAN_FLAG_PASSIVE; - arg.scan_ctrl_flags |= WMI_SCAN_FILTER_PROBE_REQ; + if (QCA_REV_WCN3990(ar)) { + arg.scan_ctrl_flags &= ~(WMI_SCAN_FILTER_PROBE_REQ | + WMI_SCAN_CHAN_STAT_EVENT | + WMI_SCAN_ADD_BCAST_PROBE_REQ); + } else { + arg.scan_ctrl_flags |= WMI_SCAN_FILTER_PROBE_REQ; + } arg.burst_duration_ms = duration; ret = ath10k_start_scan(ar, &arg); @@ -5830,7 +6543,7 @@ static int ath10k_remain_on_channel(struct ieee80211_hw *hw, goto exit; } - ret = wait_for_completion_timeout(&ar->scan.on_channel, 3*HZ); + ret = wait_for_completion_timeout(&ar->scan.on_channel, 3 * HZ); if (ret == 0) { ath10k_warn(ar, "failed to switch to channel for roc scan\n"); @@ -5982,6 +6695,39 @@ static void ath10k_reconfig_complete(struct ieee80211_hw *hw, mutex_unlock(&ar->conf_mutex); } +static void +ath10k_mac_update_bss_chan_survey(struct ath10k *ar, + struct ieee80211_channel *channel) +{ + int ret; + enum wmi_bss_survey_req_type type = WMI_BSS_SURVEY_REQ_TYPE_READ_CLEAR; + + lockdep_assert_held(&ar->conf_mutex); + + if (!test_bit(WMI_SERVICE_BSS_CHANNEL_INFO_64, ar->wmi.svc_map) || + (ar->rx_channel != channel)) + return; + + if (ar->scan.state != ATH10K_SCAN_IDLE) { + ath10k_dbg(ar, ATH10K_DBG_MAC, "ignoring bss chan info request while scanning..\n"); + return; + } + + reinit_completion(&ar->bss_survey_done); + + ret = ath10k_wmi_pdev_bss_chan_info_request(ar, type); + if (ret) { + ath10k_warn(ar, "failed to send pdev bss chan info request\n"); + return; + } + + ret = wait_for_completion_timeout(&ar->bss_survey_done, 3 * HZ); + if (!ret) { + ath10k_warn(ar, "bss channel survey timed out\n"); + return; + } +} + static int ath10k_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey) { @@ -6006,6 +6752,9 @@ static int ath10k_get_survey(struct ieee80211_hw *hw, int idx, goto exit; } + if (!QCA_REV_WCN3990(ar)) + ath10k_mac_update_bss_chan_survey(ar, &sband->channels[idx]); + spin_lock_bh(&ar->data_lock); memcpy(survey, ar_survey, sizeof(*survey)); spin_unlock_bh(&ar->data_lock); @@ -6404,6 +7153,32 @@ static u64 ath10k_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) return 0; } +static void ath10k_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u64 tsf) +{ + struct ath10k *ar = hw->priv; + struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif); + u32 tsf_offset, vdev_param = ar->wmi.vdev_param->set_tsf; + int ret; + + /* Workaround: + * + * Given tsf argument is entire TSF value, but firmware accepts + * only TSF offset to current TSF. + * + * get_tsf function is used to get offset value, however since + * ath10k_get_tsf is not implemented properly, it will return 0 always. + * Luckily all the caller functions to set_tsf, as of now, also rely on + * get_tsf function to get entire tsf value such get_tsf() + tsf_delta, + * final tsf offset value to firmware will be arithmetically correct. + */ + tsf_offset = tsf - ath10k_get_tsf(hw, vif); + ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, + vdev_param, tsf_offset); + if (ret && ret != -EOPNOTSUPP) + ath10k_warn(ar, "failed to set tsf offset: %d\n", ret); +} + static int ath10k_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_ampdu_params *params) @@ -6522,12 +7297,25 @@ ath10k_mac_update_vif_chan(struct ath10k *ar, if (WARN_ON(!arvif->is_up)) continue; + if (QCA_REV_WCN3990(ar)) { + /* In the case of wcn3990 WLAN module we send + * vdev restart only, no need to send vdev down. + */ - ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id); - if (ret) { - ath10k_warn(ar, "failed to down vdev %d: %d\n", - arvif->vdev_id, ret); - continue; + ret = ath10k_vdev_restart(arvif, &vifs[i].new_ctx->def); + if (ret) { + ath10k_warn(ar, + "failed to restart vdev %d: %d\n", + arvif->vdev_id, ret); + continue; + } + } else { + ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id); + if (ret) { + ath10k_warn(ar, "failed to down vdev %d: %d\n", + arvif->vdev_id, ret); + continue; + } } } @@ -6558,11 +7346,17 @@ ath10k_mac_update_vif_chan(struct ath10k *ar, ath10k_warn(ar, "failed to update prb tmpl during csa: %d\n", ret); - ret = ath10k_vdev_restart(arvif, &vifs[i].new_ctx->def); - if (ret) { - ath10k_warn(ar, "failed to restart vdev %d: %d\n", - arvif->vdev_id, ret); - continue; + if (!QCA_REV_WCN3990(ar)) { + /* In case of other than wcn3990 WLAN module we + * send vdev down and vdev restart to the firmware. + */ + + ret = ath10k_vdev_restart(arvif, &vifs[i].new_ctx->def); + if (ret) { + ath10k_warn(ar, "failed to restart vdev %d: %d\n", + arvif->vdev_id, ret); + continue; + } } ret = ath10k_wmi_vdev_up(arvif->ar, arvif->vdev_id, arvif->aid, @@ -6584,7 +7378,7 @@ ath10k_mac_op_add_chanctx(struct ieee80211_hw *hw, struct ath10k *ar = hw->priv; ath10k_dbg(ar, ATH10K_DBG_MAC, - "mac chanctx add freq %hu width %d ptr %p\n", + "mac chanctx add freq %hu width %d ptr %pK\n", ctx->def.chan->center_freq, ctx->def.width, ctx); mutex_lock(&ar->conf_mutex); @@ -6608,7 +7402,7 @@ ath10k_mac_op_remove_chanctx(struct ieee80211_hw *hw, struct ath10k *ar = hw->priv; ath10k_dbg(ar, ATH10K_DBG_MAC, - "mac chanctx remove freq %hu width %d ptr %p\n", + "mac chanctx remove freq %hu width %d ptr %pK\n", ctx->def.chan->center_freq, ctx->def.width, ctx); mutex_lock(&ar->conf_mutex); @@ -6673,7 +7467,7 @@ ath10k_mac_op_change_chanctx(struct ieee80211_hw *hw, mutex_lock(&ar->conf_mutex); ath10k_dbg(ar, ATH10K_DBG_MAC, - "mac chanctx change freq %hu width %d ptr %p changed %x\n", + "mac chanctx change freq %hu width %d ptr %pK changed %x\n", ctx->def.chan->center_freq, ctx->def.width, ctx, changed); /* This shouldn't really happen because channel switching should use @@ -6731,7 +7525,7 @@ ath10k_mac_op_assign_vif_chanctx(struct ieee80211_hw *hw, mutex_lock(&ar->conf_mutex); ath10k_dbg(ar, ATH10K_DBG_MAC, - "mac chanctx assign ptr %p vdev_id %i\n", + "mac chanctx assign ptr %pK vdev_id %i\n", ctx, arvif->vdev_id); if (WARN_ON(arvif->is_started)) { @@ -6799,12 +7593,13 @@ ath10k_mac_op_unassign_vif_chanctx(struct ieee80211_hw *hw, mutex_lock(&ar->conf_mutex); ath10k_dbg(ar, ATH10K_DBG_MAC, - "mac chanctx unassign ptr %p vdev_id %i\n", + "mac chanctx unassign ptr %pK vdev_id %i\n", ctx, arvif->vdev_id); WARN_ON(!arvif->is_started); - - if (vif->type == NL80211_IFTYPE_MONITOR) { + if (vif->type == NL80211_IFTYPE_MONITOR || + (vif->type == NL80211_IFTYPE_STATION && + ar->sta_state < IEEE80211_STA_ASSOC)) { WARN_ON(!arvif->is_up); ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id); @@ -6820,6 +7615,7 @@ ath10k_mac_op_unassign_vif_chanctx(struct ieee80211_hw *hw, ath10k_warn(ar, "failed to stop vdev %i: %d\n", arvif->vdev_id, ret); + ar->sta_state = IEEE80211_STA_NOTEXIST; arvif->is_started = false; mutex_unlock(&ar->conf_mutex); @@ -6845,16 +7641,19 @@ ath10k_mac_op_switch_vif_chanctx(struct ieee80211_hw *hw, } static const struct ieee80211_ops ath10k_ops = { - .tx = ath10k_tx, + .tx = ath10k_mac_op_tx, + .wake_tx_queue = ath10k_mac_op_wake_tx_queue, .start = ath10k_start, .stop = ath10k_stop, .config = ath10k_config, .add_interface = ath10k_add_interface, + .change_interface = ath10k_change_interface, .remove_interface = ath10k_remove_interface, .configure_filter = ath10k_configure_filter, .bss_info_changed = ath10k_bss_info_changed, .hw_scan = ath10k_hw_scan, .cancel_hw_scan = ath10k_cancel_hw_scan, + .set_rekey_data = ath10k_set_rekey_data, .set_key = ath10k_set_key, .set_default_unicast_key = ath10k_set_default_unicast_key, .sta_state = ath10k_sta_state, @@ -6872,6 +7671,7 @@ static const struct ieee80211_ops ath10k_ops = { .set_bitrate_mask = ath10k_mac_op_set_bitrate_mask, .sta_rc_update = ath10k_sta_rc_update, .get_tsf = ath10k_get_tsf, + .set_tsf = ath10k_set_tsf, .ampdu_action = ath10k_ampdu_action, .get_et_sset_count = ath10k_debug_get_et_sset_count, .get_et_stats = ath10k_debug_get_et_stats, @@ -6888,9 +7688,11 @@ static const struct ieee80211_ops ath10k_ops = { #ifdef CONFIG_PM .suspend = ath10k_wow_op_suspend, .resume = ath10k_wow_op_resume, + .set_wakeup = ath10k_wow_op_set_wakeup, #endif #ifdef CONFIG_MAC80211_DEBUGFS .sta_add_debugfs = ath10k_sta_add_debugfs, + .sta_statistics = ath10k_sta_statistics, #endif }; @@ -6960,54 +7762,69 @@ static const struct ieee80211_channel ath10k_5ghz_channels[] = { struct ath10k *ath10k_mac_create(size_t priv_size) { struct ieee80211_hw *hw; + struct ieee80211_ops *ops; struct ath10k *ar; - hw = ieee80211_alloc_hw(sizeof(struct ath10k) + priv_size, &ath10k_ops); - if (!hw) + ops = kmemdup(&ath10k_ops, sizeof(ath10k_ops), GFP_KERNEL); + if (!ops) return NULL; + hw = ieee80211_alloc_hw(sizeof(struct ath10k) + priv_size, ops); + if (!hw) { + kfree(ops); + return NULL; + } + ar = hw->priv; ar->hw = hw; + ar->ops = ops; return ar; } void ath10k_mac_destroy(struct ath10k *ar) { + struct ieee80211_ops *ops = ar->ops; + ieee80211_free_hw(ar->hw); + kfree(ops); } static const struct ieee80211_iface_limit ath10k_if_limits[] = { { - .max = 8, - .types = BIT(NL80211_IFTYPE_STATION) - | BIT(NL80211_IFTYPE_P2P_CLIENT) + .max = 8, + .types = BIT(NL80211_IFTYPE_STATION) + | BIT(NL80211_IFTYPE_P2P_CLIENT) }, { - .max = 3, - .types = BIT(NL80211_IFTYPE_P2P_GO) + .max = 3, + .types = BIT(NL80211_IFTYPE_P2P_GO) }, { - .max = 1, - .types = BIT(NL80211_IFTYPE_P2P_DEVICE) + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_DEVICE) }, { - .max = 7, - .types = BIT(NL80211_IFTYPE_AP) + .max = 7, + .types = BIT(NL80211_IFTYPE_AP) #ifdef CONFIG_MAC80211_MESH - | BIT(NL80211_IFTYPE_MESH_POINT) + | BIT(NL80211_IFTYPE_MESH_POINT) #endif }, }; static const struct ieee80211_iface_limit ath10k_10x_if_limits[] = { { - .max = 8, - .types = BIT(NL80211_IFTYPE_AP) + .max = 8, + .types = BIT(NL80211_IFTYPE_AP) #ifdef CONFIG_MAC80211_MESH - | BIT(NL80211_IFTYPE_MESH_POINT) + | BIT(NL80211_IFTYPE_MESH_POINT) #endif }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_STATION) + }, }; static const struct ieee80211_iface_combination ath10k_if_comb[] = { @@ -7129,6 +7946,85 @@ static struct ieee80211_iface_combination ath10k_tlv_qcs_if_comb[] = { }, }; +static const struct ieee80211_iface_limit ath10k_wcn3990_if_limit[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 2, + .types = BIT(NL80211_IFTYPE_AP) | +#ifdef CONFIG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO), + }, +}; + +static const struct ieee80211_iface_limit ath10k_wcn3990_qcs_if_limit[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 2, + .types = BIT(NL80211_IFTYPE_P2P_CLIENT), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP) | +#ifdef CONFIG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_P2P_GO), + }, +}; + +static const struct ieee80211_iface_limit ath10k_wcn3990_if_limit_ibss[] = { + { + .max = 1, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_ADHOC), + }, +}; + +static struct ieee80211_iface_combination ath10k_wcn3990_qcs_if_comb[] = { + { + .limits = ath10k_wcn3990_if_limit, + .num_different_channels = 1, + .max_interfaces = 4, + .n_limits = ARRAY_SIZE(ath10k_wcn3990_if_limit), +#ifdef CONFIG_ATH10K_DFS_CERTIFIED + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80), +#endif + }, + { + .limits = ath10k_wcn3990_qcs_if_limit, + .num_different_channels = 2, + .max_interfaces = 4, + .n_limits = ARRAY_SIZE(ath10k_wcn3990_qcs_if_limit), + }, + { + .limits = ath10k_wcn3990_if_limit_ibss, + .num_different_channels = 1, + .max_interfaces = 2, + .n_limits = ARRAY_SIZE(ath10k_wcn3990_if_limit_ibss), +#ifdef CONFIG_ATH10K_DFS_CERTIFIED + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80), +#endif + }, +}; + static const struct ieee80211_iface_limit ath10k_10_4_if_limits[] = { { .max = 1, @@ -7223,8 +8119,14 @@ int ath10k_mac_register(struct ath10k *ar) band = &ar->mac.sbands[IEEE80211_BAND_2GHZ]; band->n_channels = ARRAY_SIZE(ath10k_2ghz_channels); band->channels = channels; - band->n_bitrates = ath10k_g_rates_size; - band->bitrates = ath10k_g_rates; + + if (ar->hw_params.cck_rate_map_rev2) { + band->n_bitrates = ath10k_g_rates_rev2_size; + band->bitrates = ath10k_g_rates_rev2; + } else { + band->n_bitrates = ath10k_g_rates_size; + band->bitrates = ath10k_g_rates; + } ar->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = band; } @@ -7256,12 +8158,16 @@ int ath10k_mac_register(struct ath10k *ar) ar->hw->wiphy->available_antennas_rx = ar->cfg_rx_chainmask; ar->hw->wiphy->available_antennas_tx = ar->cfg_tx_chainmask; - if (!test_bit(ATH10K_FW_FEATURE_NO_P2P, ar->fw_features)) + if (!test_bit(ATH10K_FW_FEATURE_NO_P2P, ar->normal_mode_fw.fw_file.fw_features)) ar->hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_DEVICE) | BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO); + if (QCA_REV_WCN3990(ar)) + ar->hw->wiphy->interface_modes &= + ~BIT(NL80211_IFTYPE_P2P_DEVICE); + ieee80211_hw_set(ar->hw, SIGNAL_DBM); ieee80211_hw_set(ar->hw, SUPPORTS_PS); ieee80211_hw_set(ar->hw, SUPPORTS_DYNAMIC_PS); @@ -7296,6 +8202,7 @@ int ath10k_mac_register(struct ath10k *ar) ar->hw->vif_data_size = sizeof(struct ath10k_vif); ar->hw->sta_data_size = sizeof(struct ath10k_sta); + ar->hw->txq_data_size = sizeof(struct ath10k_txq); ar->hw->max_listen_interval = ATH10K_MAX_HW_LISTEN_INTERVAL; @@ -7320,7 +8227,8 @@ int ath10k_mac_register(struct ath10k *ar) ar->hw->wiphy->max_remain_on_channel_duration = 5000; ar->hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; - ar->hw->wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE; + ar->hw->wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | + NL80211_FEATURE_AP_SCAN; ar->hw->wiphy->max_ap_assoc_sta = ar->max_num_stations; @@ -7344,7 +8252,7 @@ int ath10k_mac_register(struct ath10k *ar) */ ar->hw->offchannel_tx_hw_queue = IEEE80211_MAX_QUEUES - 1; - switch (ar->wmi.op_version) { + switch (ar->running_fw->fw_file.wmi_op_version) { case ATH10K_FW_WMI_OP_VERSION_MAIN: ar->hw->wiphy->iface_combinations = ath10k_if_comb; ar->hw->wiphy->n_iface_combinations = @@ -7352,6 +8260,14 @@ int ath10k_mac_register(struct ath10k *ar) ar->hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC); break; case ATH10K_FW_WMI_OP_VERSION_TLV: + ar->hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC); + if (QCA_REV_WCN3990(ar)) { + ar->hw->wiphy->iface_combinations = + ath10k_wcn3990_qcs_if_comb; + ar->hw->wiphy->n_iface_combinations = + ARRAY_SIZE(ath10k_wcn3990_qcs_if_comb); + break; + } if (test_bit(WMI_SERVICE_ADAPTIVE_OCS, ar->wmi.svc_map)) { ar->hw->wiphy->iface_combinations = ath10k_tlv_qcs_if_comb; @@ -7362,7 +8278,6 @@ int ath10k_mac_register(struct ath10k *ar) ar->hw->wiphy->n_iface_combinations = ARRAY_SIZE(ath10k_tlv_if_comb); } - ar->hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC); break; case ATH10K_FW_WMI_OP_VERSION_10_1: case ATH10K_FW_WMI_OP_VERSION_10_2: @@ -7386,7 +8301,7 @@ int ath10k_mac_register(struct ath10k *ar) if (!test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags)) ar->hw->netdev_features = NETIF_F_HW_CSUM; - if (config_enabled(CONFIG_ATH10K_DFS_CERTIFIED)) { + if (IS_ENABLED(CONFIG_ATH10K_DFS_CERTIFIED)) { /* Init ath dfs pattern detector */ ar->ath_common.debug_mask = ATH_DBG_DFS; ar->dfs_detector = dfs_pattern_detector_init(&ar->ath_common, @@ -7396,6 +8311,15 @@ int ath10k_mac_register(struct ath10k *ar) ath10k_warn(ar, "failed to initialise DFS pattern detector\n"); } + /* Current wake_tx_queue implementation imposes a significant + * performance penalty in some setups. The tx scheduling code needs + * more work anyway so disable the wake_tx_queue unless firmware + * supports the pull-push mechanism. + */ + if (!test_bit(ATH10K_FW_FEATURE_PEER_FLOW_CONTROL, + ar->running_fw->fw_file.fw_features)) + ar->ops->wake_tx_queue = NULL; + ret = ath_regd_init(&ar->ath_common.regulatory, ar->hw->wiphy, ath10k_reg_notifier); if (ret) { @@ -7425,7 +8349,7 @@ err_unregister: ieee80211_unregister_hw(ar->hw); err_dfs_detector_exit: - if (config_enabled(CONFIG_ATH10K_DFS_CERTIFIED) && ar->dfs_detector) + if (IS_ENABLED(CONFIG_ATH10K_DFS_CERTIFIED) && ar->dfs_detector) ar->dfs_detector->exit(ar->dfs_detector); err_free: @@ -7438,9 +8362,10 @@ err_free: void ath10k_mac_unregister(struct ath10k *ar) { + ath10k_wow_deinit(ar); ieee80211_unregister_hw(ar->hw); - if (config_enabled(CONFIG_ATH10K_DFS_CERTIFIED) && ar->dfs_detector) + if (IS_ENABLED(CONFIG_ATH10K_DFS_CERTIFIED) && ar->dfs_detector) ar->dfs_detector->exit(ar->dfs_detector); kfree(ar->mac.sbands[IEEE80211_BAND_2GHZ].channels); diff --git a/drivers/net/wireless/ath/ath10k/mac.h b/drivers/net/wireless/ath/ath10k/mac.h index e3cefe4c7cfd..1bd29ecfcdcc 100644 --- a/drivers/net/wireless/ath/ath10k/mac.h +++ b/drivers/net/wireless/ath/ath10k/mac.h @@ -66,7 +66,7 @@ void ath10k_mac_handle_tx_pause_vdev(struct ath10k *ar, u32 vdev_id, enum wmi_tlv_tx_pause_action action); u8 ath10k_mac_hw_rate_to_idx(const struct ieee80211_supported_band *sband, - u8 hw_rate); + u8 hw_rate, bool cck); u8 ath10k_mac_bitrate_to_idx(const struct ieee80211_supported_band *sband, u32 bitrate); @@ -74,6 +74,14 @@ void ath10k_mac_tx_lock(struct ath10k *ar, int reason); void ath10k_mac_tx_unlock(struct ath10k *ar, int reason); void ath10k_mac_vif_tx_lock(struct ath10k_vif *arvif, int reason); void ath10k_mac_vif_tx_unlock(struct ath10k_vif *arvif, int reason); +bool ath10k_mac_tx_frm_has_freq(struct ath10k *ar); +void ath10k_mac_tx_push_pending(struct ath10k *ar); +int ath10k_mac_tx_push_txq(struct ieee80211_hw *hw, + struct ieee80211_txq *txq); +struct ieee80211_txq *ath10k_mac_txq_lookup(struct ath10k *ar, + u16 peer_id, + u8 tid); +int ath10k_mac_ext_resource_config(struct ath10k *ar, u32 val); static inline struct ath10k_vif *ath10k_vif_to_arvif(struct ieee80211_vif *vif) { diff --git a/drivers/net/wireless/ath/ath10k/pci.c b/drivers/net/wireless/ath/ath10k/pci.c index 907fd60c4241..ca77861c4320 100644 --- a/drivers/net/wireless/ath/ath10k/pci.c +++ b/drivers/net/wireless/ath/ath10k/pci.c @@ -33,12 +33,6 @@ #include "ce.h" #include "pci.h" -enum ath10k_pci_irq_mode { - ATH10K_PCI_IRQ_AUTO = 0, - ATH10K_PCI_IRQ_LEGACY = 1, - ATH10K_PCI_IRQ_MSI = 2, -}; - enum ath10k_pci_reset_mode { ATH10K_PCI_RESET_AUTO = 0, ATH10K_PCI_RESET_WARM_ONLY = 1, @@ -62,7 +56,10 @@ static const struct pci_device_id ath10k_pci_id_table[] = { { PCI_VDEVICE(ATHEROS, QCA6164_2_1_DEVICE_ID) }, /* PCI-E QCA6164 V2.1 */ { PCI_VDEVICE(ATHEROS, QCA6174_2_1_DEVICE_ID) }, /* PCI-E QCA6174 V2.1 */ { PCI_VDEVICE(ATHEROS, QCA99X0_2_0_DEVICE_ID) }, /* PCI-E QCA99X0 V2 */ + { PCI_VDEVICE(ATHEROS, QCA9888_2_0_DEVICE_ID) }, /* PCI-E QCA9888 V2 */ + { PCI_VDEVICE(ATHEROS, QCA9984_1_0_DEVICE_ID) }, /* PCI-E QCA9984 V1 */ { PCI_VDEVICE(ATHEROS, QCA9377_1_0_DEVICE_ID) }, /* PCI-E QCA9377 V1 */ + { PCI_VDEVICE(ATHEROS, QCA9887_1_0_DEVICE_ID) }, /* PCI-E QCA9887 */ {0} }; @@ -87,14 +84,19 @@ static const struct ath10k_pci_supp_chip ath10k_pci_supp_chips[] = { { QCA99X0_2_0_DEVICE_ID, QCA99X0_HW_2_0_CHIP_ID_REV }, + { QCA9984_1_0_DEVICE_ID, QCA9984_HW_1_0_CHIP_ID_REV }, + + { QCA9888_2_0_DEVICE_ID, QCA9888_HW_2_0_CHIP_ID_REV }, + { QCA9377_1_0_DEVICE_ID, QCA9377_HW_1_0_CHIP_ID_REV }, { QCA9377_1_0_DEVICE_ID, QCA9377_HW_1_1_CHIP_ID_REV }, + + { QCA9887_1_0_DEVICE_ID, QCA9887_HW_1_0_CHIP_ID_REV }, }; static void ath10k_pci_buffer_cleanup(struct ath10k *ar); static int ath10k_pci_cold_reset(struct ath10k *ar); static int ath10k_pci_safe_chip_reset(struct ath10k *ar); -static int ath10k_pci_wait_for_target_init(struct ath10k *ar); static int ath10k_pci_init_irq(struct ath10k *ar); static int ath10k_pci_deinit_irq(struct ath10k *ar); static int ath10k_pci_request_irq(struct ath10k *ar); @@ -107,6 +109,7 @@ static void ath10k_pci_htc_tx_cb(struct ath10k_ce_pipe *ce_state); static void ath10k_pci_htc_rx_cb(struct ath10k_ce_pipe *ce_state); static void ath10k_pci_htt_tx_cb(struct ath10k_ce_pipe *ce_state); static void ath10k_pci_htt_rx_cb(struct ath10k_ce_pipe *ce_state); +static void ath10k_pci_pktlog_rx_cb(struct ath10k_ce_pipe *ce_state); static void ath10k_pci_htt_htc_rx_cb(struct ath10k_ce_pipe *ce_state); static struct ce_attr host_ce_config_wlan[] = { @@ -186,6 +189,7 @@ static struct ce_attr host_ce_config_wlan[] = { .src_nentries = 0, .src_sz_max = 2048, .dest_nentries = 128, + .recv_cb = ath10k_pci_pktlog_rx_cb, }, /* CE9 target autonomous qcache memcpy */ @@ -485,6 +489,9 @@ static int ath10k_pci_force_wake(struct ath10k *ar) unsigned long flags; int ret = 0; + if (ar_pci->pci_ps) + return ret; + spin_lock_irqsave(&ar_pci->ps_lock, flags); if (!ar_pci->ps_awake) { @@ -615,7 +622,7 @@ static void ath10k_pci_sleep_sync(struct ath10k *ar) spin_unlock_irqrestore(&ar_pci->ps_lock, flags); } -void ath10k_pci_write32(struct ath10k *ar, u32 offset, u32 value) +static void ath10k_bus_pci_write32(struct ath10k *ar, u32 offset, u32 value) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); int ret; @@ -637,7 +644,7 @@ void ath10k_pci_write32(struct ath10k *ar, u32 offset, u32 value) ath10k_pci_sleep(ar); } -u32 ath10k_pci_read32(struct ath10k *ar, u32 offset) +static u32 ath10k_bus_pci_read32(struct ath10k *ar, u32 offset) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); u32 val; @@ -662,6 +669,20 @@ u32 ath10k_pci_read32(struct ath10k *ar, u32 offset) return val; } +inline void ath10k_pci_write32(struct ath10k *ar, u32 offset, u32 value) +{ + struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + + ar_pci->opaque_ctx.bus_ops->write32(ar, offset, value); +} + +inline u32 ath10k_pci_read32(struct ath10k *ar, u32 offset) +{ + struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + + return ar_pci->opaque_ctx.bus_ops->read32(ar, offset); +} + u32 ath10k_pci_soc_read32(struct ath10k *ar, u32 addr) { return ath10k_pci_read32(ar, RTC_SOC_BASE_ADDRESS + addr); @@ -682,7 +703,7 @@ void ath10k_pci_reg_write32(struct ath10k *ar, u32 addr, u32 val) ath10k_pci_write32(ar, PCIE_LOCAL_BASE_ADDRESS + addr, val); } -static bool ath10k_pci_irq_pending(struct ath10k *ar) +bool ath10k_pci_irq_pending(struct ath10k *ar) { u32 cause; @@ -695,7 +716,7 @@ static bool ath10k_pci_irq_pending(struct ath10k *ar) return false; } -static void ath10k_pci_disable_and_clear_legacy_irq(struct ath10k *ar) +void ath10k_pci_disable_and_clear_legacy_irq(struct ath10k *ar) { /* IMPORTANT: INTR_CLR register has to be set after * INTR_ENABLE is set to 0, otherwise interrupt can not be @@ -711,7 +732,7 @@ static void ath10k_pci_disable_and_clear_legacy_irq(struct ath10k *ar) PCIE_INTR_ENABLE_ADDRESS); } -static void ath10k_pci_enable_legacy_irq(struct ath10k *ar) +void ath10k_pci_enable_legacy_irq(struct ath10k *ar) { ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS + PCIE_INTR_ENABLE_ADDRESS, @@ -727,10 +748,7 @@ static inline const char *ath10k_pci_get_irq_method(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - if (ar_pci->num_msi_intrs > 1) - return "msi-x"; - - if (ar_pci->num_msi_intrs == 1) + if (ar_pci->oper_irq_mode == ATH10K_PCI_IRQ_MSI) return "msi"; return "legacy"; @@ -762,9 +780,9 @@ static int __ath10k_pci_rx_post_buf(struct ath10k_pci_pipe *pipe) ATH10K_SKB_RXCB(skb)->paddr = paddr; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_pci->opaque_ctx.ce_lock); ret = __ath10k_ce_rx_post_buf(ce_pipe, skb, paddr); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_pci->opaque_ctx.ce_lock); if (ret) { dma_unmap_single(ar->dev, paddr, skb->len + skb_tailroom(skb), DMA_FROM_DEVICE); @@ -788,10 +806,11 @@ static void ath10k_pci_rx_post_pipe(struct ath10k_pci_pipe *pipe) if (!ce_pipe->dest_ring) return; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_pci->opaque_ctx.ce_lock); num = __ath10k_ce_rx_num_free_bufs(ce_pipe); - spin_unlock_bh(&ar_pci->ce_lock); - while (num--) { + spin_unlock_bh(&ar_pci->opaque_ctx.ce_lock); + + while (num >= 0) { ret = __ath10k_pci_rx_post_buf(pipe); if (ret) { if (ret == -ENOSPC) @@ -801,10 +820,11 @@ static void ath10k_pci_rx_post_pipe(struct ath10k_pci_pipe *pipe) ATH10K_PCI_RX_POST_RETRY_MS); break; } + num--; } } -static void ath10k_pci_rx_post(struct ath10k *ar) +void ath10k_pci_rx_post(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); int i; @@ -813,7 +833,7 @@ static void ath10k_pci_rx_post(struct ath10k *ar) ath10k_pci_rx_post_pipe(&ar_pci->pipe_info[i]); } -static void ath10k_pci_rx_replenish_retry(unsigned long ptr) +void ath10k_pci_rx_replenish_retry(unsigned long ptr) { struct ath10k *ar = (void *)ptr; @@ -826,15 +846,21 @@ static u32 ath10k_pci_targ_cpu_to_ce_addr(struct ath10k *ar, u32 addr) switch (ar->hw_rev) { case ATH10K_HW_QCA988X: + case ATH10K_HW_QCA9887: case ATH10K_HW_QCA6174: case ATH10K_HW_QCA9377: val = (ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS + CORE_CTRL_ADDRESS) & 0x7ff) << 21; break; + case ATH10K_HW_QCA9888: case ATH10K_HW_QCA99X0: + case ATH10K_HW_QCA9984: + case ATH10K_HW_QCA4019: val = ath10k_pci_read32(ar, PCIE_BAR_REG_ADDRESS); break; + default: + break; } val |= 0x100000 | (addr & 0xfffff); @@ -851,10 +877,8 @@ static int ath10k_pci_diag_read_mem(struct ath10k *ar, u32 address, void *data, { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); int ret = 0; - u32 buf; - unsigned int completed_nbytes, orig_nbytes, remaining_bytes; - unsigned int id; - unsigned int flags; + u32 *buf; + unsigned int completed_nbytes, alloc_nbytes, remaining_bytes; struct ath10k_ce_pipe *ce_diag; /* Host buffer address in CE space */ u32 ce_data; @@ -862,7 +886,7 @@ static int ath10k_pci_diag_read_mem(struct ath10k *ar, u32 address, void *data, void *data_buf = NULL; int i; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_pci->opaque_ctx.ce_lock); ce_diag = ar_pci->ce_diag; @@ -872,9 +896,10 @@ static int ath10k_pci_diag_read_mem(struct ath10k *ar, u32 address, void *data, * 1) 4-byte alignment * 2) Buffer in DMA-able space */ - orig_nbytes = nbytes; + alloc_nbytes = min_t(unsigned int, nbytes, DIAG_TRANSFER_LIMIT); + data_buf = (unsigned char *)dma_alloc_coherent(ar->dev, - orig_nbytes, + alloc_nbytes, &ce_data_base, GFP_ATOMIC); @@ -882,15 +907,15 @@ static int ath10k_pci_diag_read_mem(struct ath10k *ar, u32 address, void *data, ret = -ENOMEM; goto done; } - memset(data_buf, 0, orig_nbytes); + memset(data_buf, 0, alloc_nbytes); - remaining_bytes = orig_nbytes; + remaining_bytes = nbytes; ce_data = ce_data_base; while (remaining_bytes) { nbytes = min_t(unsigned int, remaining_bytes, DIAG_TRANSFER_LIMIT); - ret = __ath10k_ce_rx_post_buf(ce_diag, NULL, ce_data); + ret = __ath10k_ce_rx_post_buf(ce_diag, &ce_data, ce_data); if (ret != 0) goto done; @@ -921,9 +946,10 @@ static int ath10k_pci_diag_read_mem(struct ath10k *ar, u32 address, void *data, } i = 0; - while (ath10k_ce_completed_recv_next_nolock(ce_diag, NULL, &buf, - &completed_nbytes, - &id, &flags) != 0) { + while (ath10k_ce_completed_recv_next_nolock(ce_diag, + (void **)&buf, + &completed_nbytes) + != 0) { mdelay(1); if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) { @@ -937,28 +963,31 @@ static int ath10k_pci_diag_read_mem(struct ath10k *ar, u32 address, void *data, goto done; } - if (buf != ce_data) { + if (*buf != ce_data) { ret = -EIO; goto done; } remaining_bytes -= nbytes; + + if (ret) { + ath10k_warn(ar, "failed to read diag value at 0x%x: %d\n", + address, ret); + break; + } + memcpy(data, data_buf, nbytes); + address += nbytes; - ce_data += nbytes; + data += nbytes; } done: - if (ret == 0) - memcpy(data, data_buf, orig_nbytes); - else - ath10k_warn(ar, "failed to read diag value at 0x%x: %d\n", - address, ret); if (data_buf) - dma_free_coherent(ar->dev, orig_nbytes, data_buf, + dma_free_coherent(ar->dev, alloc_nbytes, data_buf, ce_data_base); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_pci->opaque_ctx.ce_lock); return ret; } @@ -1002,22 +1031,20 @@ static int __ath10k_pci_diag_read_hi(struct ath10k *ar, void *dest, #define ath10k_pci_diag_read_hi(ar, dest, src, len) \ __ath10k_pci_diag_read_hi(ar, dest, HI_ITEM(src), len) -static int ath10k_pci_diag_write_mem(struct ath10k *ar, u32 address, - const void *data, int nbytes) +int ath10k_pci_diag_write_mem(struct ath10k *ar, u32 address, + const void *data, int nbytes) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); int ret = 0; - u32 buf; + u32 *buf; unsigned int completed_nbytes, orig_nbytes, remaining_bytes; - unsigned int id; - unsigned int flags; struct ath10k_ce_pipe *ce_diag; void *data_buf = NULL; u32 ce_data; /* Host buffer address in CE space */ dma_addr_t ce_data_base = 0; int i; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_pci->opaque_ctx.ce_lock); ce_diag = ar_pci->ce_diag; @@ -1059,7 +1086,7 @@ static int ath10k_pci_diag_write_mem(struct ath10k *ar, u32 address, nbytes = min_t(int, remaining_bytes, DIAG_TRANSFER_LIMIT); /* Set up to receive directly into Target(!) address */ - ret = __ath10k_ce_rx_post_buf(ce_diag, NULL, address); + ret = __ath10k_ce_rx_post_buf(ce_diag, &address, address); if (ret != 0) goto done; @@ -1084,9 +1111,10 @@ static int ath10k_pci_diag_write_mem(struct ath10k *ar, u32 address, } i = 0; - while (ath10k_ce_completed_recv_next_nolock(ce_diag, NULL, &buf, - &completed_nbytes, - &id, &flags) != 0) { + while (ath10k_ce_completed_recv_next_nolock(ce_diag, + (void **)&buf, + &completed_nbytes) + != 0) { mdelay(1); if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) { @@ -1100,7 +1128,7 @@ static int ath10k_pci_diag_write_mem(struct ath10k *ar, u32 address, goto done; } - if (buf != address) { + if (*buf != address) { ret = -EIO; goto done; } @@ -1120,7 +1148,7 @@ done: ath10k_warn(ar, "failed to write diag value at 0x%x: %d\n", address, ret); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_pci->opaque_ctx.ce_lock); return ret; } @@ -1162,15 +1190,11 @@ static void ath10k_pci_process_rx_cb(struct ath10k_ce_pipe *ce_state, struct sk_buff *skb; struct sk_buff_head list; void *transfer_context; - u32 ce_data; unsigned int nbytes, max_nbytes; - unsigned int transfer_id; - unsigned int flags; __skb_queue_head_init(&list); while (ath10k_ce_completed_recv_next(ce_state, &transfer_context, - &ce_data, &nbytes, &transfer_id, - &flags) == 0) { + &nbytes) == 0) { skb = transfer_context; max_nbytes = skb->len + skb_tailroom(skb); dma_unmap_single(ar->dev, ATH10K_SKB_RXCB(skb)->paddr, @@ -1199,6 +1223,63 @@ static void ath10k_pci_process_rx_cb(struct ath10k_ce_pipe *ce_state, ath10k_pci_rx_post_pipe(pipe_info); } +static void ath10k_pci_process_htt_rx_cb(struct ath10k_ce_pipe *ce_state, + void (*callback)(struct ath10k *ar, + struct sk_buff *skb)) +{ + struct ath10k *ar = ce_state->ar; + struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct ath10k_pci_pipe *pipe_info = &ar_pci->pipe_info[ce_state->id]; + struct ath10k_ce_pipe *ce_pipe = pipe_info->ce_hdl; + struct sk_buff *skb; + struct sk_buff_head list; + void *transfer_context; + unsigned int nbytes, max_nbytes, nentries; + int orig_len; + + /* No need to aquire ce_lock for CE5, since this is the only place CE5 + * is processed other than init and deinit. Before releasing CE5 + * buffers, interrupts are disabled. Thus CE5 access is serialized. + */ + __skb_queue_head_init(&list); + while (ath10k_ce_completed_recv_next_nolock(ce_state, &transfer_context, + &nbytes) == 0) { + skb = transfer_context; + max_nbytes = skb->len + skb_tailroom(skb); + + if (unlikely(max_nbytes < nbytes)) { + ath10k_warn(ar, "rxed more than expected (nbytes %d, max %d)", + nbytes, max_nbytes); + continue; + } + + dma_sync_single_for_cpu(ar->dev, ATH10K_SKB_RXCB(skb)->paddr, + max_nbytes, DMA_FROM_DEVICE); + skb_put(skb, nbytes); + __skb_queue_tail(&list, skb); + } + + nentries = skb_queue_len(&list); + while ((skb = __skb_dequeue(&list))) { + ath10k_dbg(ar, ATH10K_DBG_PCI, "pci rx ce pipe %d len %d\n", + ce_state->id, skb->len); + ath10k_dbg_dump(ar, ATH10K_DBG_PCI_DUMP, NULL, "pci rx: ", + skb->data, skb->len); + + orig_len = skb->len; + callback(ar, skb); + skb_push(skb, orig_len - skb->len); + skb_reset_tail_pointer(skb); + skb_trim(skb, 0); + + /*let device gain the buffer again*/ + dma_sync_single_for_device(ar->dev, ATH10K_SKB_RXCB(skb)->paddr, + skb->len + skb_tailroom(skb), + DMA_FROM_DEVICE); + } + ath10k_ce_rx_update_write_idx(ce_pipe, nentries); +} + /* Called by lower (CE) layer when data is received from the Target. */ static void ath10k_pci_htc_rx_cb(struct ath10k_ce_pipe *ce_state) { @@ -1215,6 +1296,15 @@ static void ath10k_pci_htt_htc_rx_cb(struct ath10k_ce_pipe *ce_state) ath10k_pci_process_rx_cb(ce_state, ath10k_htc_rx_completion_handler); } +/* Called by lower (CE) layer when data is received from the Target. + * Only 10.4 firmware uses separate CE to transfer pktlog data. + */ +static void ath10k_pci_pktlog_rx_cb(struct ath10k_ce_pipe *ce_state) +{ + ath10k_pci_process_rx_cb(ce_state, + ath10k_htt_rx_pktlog_completion_handler); +} + /* Called by lower (CE) layer when a send to HTT Target completes. */ static void ath10k_pci_htt_tx_cb(struct ath10k_ce_pipe *ce_state) { @@ -1246,11 +1336,11 @@ static void ath10k_pci_htt_rx_cb(struct ath10k_ce_pipe *ce_state) */ ath10k_ce_per_engine_service(ce_state->ar, 4); - ath10k_pci_process_rx_cb(ce_state, ath10k_pci_htt_rx_deliver); + ath10k_pci_process_htt_rx_cb(ce_state, ath10k_pci_htt_rx_deliver); } -static int ath10k_pci_hif_tx_sg(struct ath10k *ar, u8 pipe_id, - struct ath10k_hif_sg_item *items, int n_items) +int ath10k_pci_hif_tx_sg(struct ath10k *ar, u8 pipe_id, + struct ath10k_hif_sg_item *items, int n_items) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); struct ath10k_pci_pipe *pci_pipe = &ar_pci->pipe_info[pipe_id]; @@ -1261,7 +1351,7 @@ static int ath10k_pci_hif_tx_sg(struct ath10k *ar, u8 pipe_id, unsigned int write_index; int err, i = 0; - spin_lock_bh(&ar_pci->ce_lock); + spin_lock_bh(&ar_pci->opaque_ctx.ce_lock); nentries_mask = src_ring->nentries_mask; sw_index = src_ring->sw_index; @@ -1275,8 +1365,8 @@ static int ath10k_pci_hif_tx_sg(struct ath10k *ar, u8 pipe_id, for (i = 0; i < n_items - 1; i++) { ath10k_dbg(ar, ATH10K_DBG_PCI, - "pci tx item %d paddr 0x%08x len %d n_items %d\n", - i, items[i].paddr, items[i].len, n_items); + "pci tx item %d paddr %pad len %d n_items %d\n", + i, &items[i].paddr, items[i].len, n_items); ath10k_dbg_dump(ar, ATH10K_DBG_PCI_DUMP, NULL, "pci tx data: ", items[i].vaddr, items[i].len); @@ -1293,8 +1383,8 @@ static int ath10k_pci_hif_tx_sg(struct ath10k *ar, u8 pipe_id, /* `i` is equal to `n_items -1` after for() */ ath10k_dbg(ar, ATH10K_DBG_PCI, - "pci tx item %d paddr 0x%08x len %d n_items %d\n", - i, items[i].paddr, items[i].len, n_items); + "pci tx item %d paddr %pad len %d n_items %d\n", + i, &items[i].paddr, items[i].len, n_items); ath10k_dbg_dump(ar, ATH10K_DBG_PCI_DUMP, NULL, "pci tx data: ", items[i].vaddr, items[i].len); @@ -1307,24 +1397,24 @@ static int ath10k_pci_hif_tx_sg(struct ath10k *ar, u8 pipe_id, if (err) goto err; - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_pci->opaque_ctx.ce_lock); return 0; err: for (; i > 0; i--) __ath10k_ce_send_revert(ce_pipe); - spin_unlock_bh(&ar_pci->ce_lock); + spin_unlock_bh(&ar_pci->opaque_ctx.ce_lock); return err; } -static int ath10k_pci_hif_diag_read(struct ath10k *ar, u32 address, void *buf, - size_t buf_len) +int ath10k_pci_hif_diag_read(struct ath10k *ar, u32 address, void *buf, + size_t buf_len) { return ath10k_pci_diag_read_mem(ar, address, buf, buf_len); } -static u16 ath10k_pci_hif_get_free_queue_number(struct ath10k *ar, u8 pipe) +u16 ath10k_pci_hif_get_free_queue_number(struct ath10k *ar, u8 pipe) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); @@ -1392,8 +1482,8 @@ static void ath10k_pci_fw_crashed_dump(struct ath10k *ar) queue_work(ar->workqueue, &ar->restart_work); } -static void ath10k_pci_hif_send_complete_check(struct ath10k *ar, u8 pipe, - int force) +void ath10k_pci_hif_send_complete_check(struct ath10k *ar, u8 pipe, + int force) { ath10k_dbg(ar, ATH10K_DBG_PCI, "pci hif send complete check\n"); @@ -1418,22 +1508,15 @@ static void ath10k_pci_hif_send_complete_check(struct ath10k *ar, u8 pipe, ath10k_ce_per_engine_service(ar, pipe); } -static void ath10k_pci_kill_tasklet(struct ath10k *ar) +static void ath10k_pci_rx_retry_sync(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - int i; - - tasklet_kill(&ar_pci->intr_tq); - tasklet_kill(&ar_pci->msi_fw_err); - - for (i = 0; i < CE_COUNT; i++) - tasklet_kill(&ar_pci->pipe_info[i].intr); del_timer_sync(&ar_pci->rx_post_retry); } -static int ath10k_pci_hif_map_service_to_pipe(struct ath10k *ar, u16 service_id, - u8 *ul_pipe, u8 *dl_pipe) +int ath10k_pci_hif_map_service_to_pipe(struct ath10k *ar, u16 service_id, + u8 *ul_pipe, u8 *dl_pipe) { const struct service_to_pipe *entry; bool ul_set = false, dl_set = false; @@ -1477,8 +1560,8 @@ static int ath10k_pci_hif_map_service_to_pipe(struct ath10k *ar, u16 service_id, return 0; } -static void ath10k_pci_hif_get_default_pipe(struct ath10k *ar, - u8 *ul_pipe, u8 *dl_pipe) +void ath10k_pci_hif_get_default_pipe(struct ath10k *ar, + u8 *ul_pipe, u8 *dl_pipe) { ath10k_dbg(ar, ATH10K_DBG_PCI, "pci hif get default pipe\n"); @@ -1487,12 +1570,13 @@ static void ath10k_pci_hif_get_default_pipe(struct ath10k *ar, ul_pipe, dl_pipe); } -static void ath10k_pci_irq_msi_fw_mask(struct ath10k *ar) +void ath10k_pci_irq_msi_fw_mask(struct ath10k *ar) { u32 val; switch (ar->hw_rev) { case ATH10K_HW_QCA988X: + case ATH10K_HW_QCA9887: case ATH10K_HW_QCA6174: case ATH10K_HW_QCA9377: val = ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS + @@ -1502,10 +1586,15 @@ static void ath10k_pci_irq_msi_fw_mask(struct ath10k *ar) CORE_CTRL_ADDRESS, val); break; case ATH10K_HW_QCA99X0: + case ATH10K_HW_QCA9984: + case ATH10K_HW_QCA9888: + case ATH10K_HW_QCA4019: /* TODO: Find appropriate register configuration for QCA99X0 * to mask irq/MSI. */ break; + default: + break; } } @@ -1515,6 +1604,7 @@ static void ath10k_pci_irq_msi_fw_unmask(struct ath10k *ar) switch (ar->hw_rev) { case ATH10K_HW_QCA988X: + case ATH10K_HW_QCA9887: case ATH10K_HW_QCA6174: case ATH10K_HW_QCA9377: val = ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS + @@ -1524,10 +1614,15 @@ static void ath10k_pci_irq_msi_fw_unmask(struct ath10k *ar) CORE_CTRL_ADDRESS, val); break; case ATH10K_HW_QCA99X0: + case ATH10K_HW_QCA9984: + case ATH10K_HW_QCA9888: + case ATH10K_HW_QCA4019: /* TODO: Find appropriate register configuration for QCA99X0 * to unmask irq/MSI. */ break; + default: + break; } } @@ -1541,10 +1636,8 @@ static void ath10k_pci_irq_disable(struct ath10k *ar) static void ath10k_pci_irq_sync(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - int i; - for (i = 0; i < max(1, ar_pci->num_msi_intrs); i++) - synchronize_irq(ar_pci->pdev->irq + i); + synchronize_irq(ar_pci->pdev->irq); } static void ath10k_pci_irq_enable(struct ath10k *ar) @@ -1604,14 +1697,12 @@ static void ath10k_pci_rx_pipe_cleanup(struct ath10k_pci_pipe *pci_pipe) static void ath10k_pci_tx_pipe_cleanup(struct ath10k_pci_pipe *pci_pipe) { struct ath10k *ar; - struct ath10k_pci *ar_pci; struct ath10k_ce_pipe *ce_pipe; struct ath10k_ce_ring *ce_ring; struct sk_buff *skb; int i; ar = pci_pipe->hif_ce_state; - ar_pci = ath10k_pci_priv(ar); ce_pipe = pci_pipe->ce_hdl; ce_ring = ce_pipe->src_ring; @@ -1654,7 +1745,7 @@ static void ath10k_pci_buffer_cleanup(struct ath10k *ar) } } -static void ath10k_pci_ce_deinit(struct ath10k *ar) +void ath10k_pci_ce_deinit(struct ath10k *ar) { int i; @@ -1662,9 +1753,9 @@ static void ath10k_pci_ce_deinit(struct ath10k *ar) ath10k_ce_deinit_pipe(ar, i); } -static void ath10k_pci_flush(struct ath10k *ar) +void ath10k_pci_flush(struct ath10k *ar) { - ath10k_pci_kill_tasklet(ar); + ath10k_pci_rx_retry_sync(ar); ath10k_pci_buffer_cleanup(ar); } @@ -1691,15 +1782,17 @@ static void ath10k_pci_hif_stop(struct ath10k *ar) ath10k_pci_irq_disable(ar); ath10k_pci_irq_sync(ar); ath10k_pci_flush(ar); + napi_synchronize(&ar->napi); + napi_disable(&ar->napi); spin_lock_irqsave(&ar_pci->ps_lock, flags); WARN_ON(ar_pci->ps_wake_refcount > 0); spin_unlock_irqrestore(&ar_pci->ps_lock, flags); } -static int ath10k_pci_hif_exchange_bmi_msg(struct ath10k *ar, - void *req, u32 req_len, - void *resp, u32 *resp_len) +int ath10k_pci_hif_exchange_bmi_msg(struct ath10k *ar, + void *req, u32 req_len, + void *resp, u32 *resp_len) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); struct ath10k_pci_pipe *pci_tx = &ar_pci->pipe_info[BMI_CE_NUM_TO_TARG]; @@ -1742,7 +1835,7 @@ static int ath10k_pci_hif_exchange_bmi_msg(struct ath10k *ar, DMA_FROM_DEVICE); ret = dma_mapping_error(ar->dev, resp_paddr); if (ret) { - ret = EIO; + ret = -EIO; goto err_req; } @@ -1805,13 +1898,10 @@ static void ath10k_pci_bmi_recv_data(struct ath10k_ce_pipe *ce_state) { struct ath10k *ar = ce_state->ar; struct bmi_xfer *xfer; - u32 ce_data; unsigned int nbytes; - unsigned int transfer_id; - unsigned int flags; - if (ath10k_ce_completed_recv_next(ce_state, (void **)&xfer, &ce_data, - &nbytes, &transfer_id, &flags)) + if (ath10k_ce_completed_recv_next(ce_state, (void **)&xfer, + &nbytes)) return; if (WARN_ON_ONCE(!xfer)) @@ -1868,6 +1958,9 @@ static int ath10k_pci_get_num_banks(struct ath10k *ar) switch (ar_pci->pdev->device) { case QCA988X_2_0_DEVICE_ID: case QCA99X0_2_0_DEVICE_ID: + case QCA9888_2_0_DEVICE_ID: + case QCA9984_1_0_DEVICE_ID: + case QCA9887_1_0_DEVICE_ID: return 1; case QCA6164_2_1_DEVICE_ID: case QCA6174_2_1_DEVICE_ID: @@ -1893,7 +1986,14 @@ static int ath10k_pci_get_num_banks(struct ath10k *ar) return 1; } -static int ath10k_pci_init_config(struct ath10k *ar) +static int ath10k_bus_get_num_banks(struct ath10k *ar) +{ + struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + + return ar_pci->opaque_ctx.bus_ops->get_num_banks(ar); +} + +int ath10k_pci_init_config(struct ath10k *ar) { u32 interconnect_targ_addr; u32 pcie_state_targ_addr = 0; @@ -2004,7 +2104,7 @@ static int ath10k_pci_init_config(struct ath10k *ar) /* first bank is switched to IRAM */ ealloc_value |= ((HI_EARLY_ALLOC_MAGIC << HI_EARLY_ALLOC_MAGIC_SHIFT) & HI_EARLY_ALLOC_MAGIC_MASK); - ealloc_value |= ((ath10k_pci_get_num_banks(ar) << + ealloc_value |= ((ath10k_bus_get_num_banks(ar) << HI_EARLY_ALLOC_IRAM_BANKS_SHIFT) & HI_EARLY_ALLOC_IRAM_BANKS_MASK); @@ -2057,7 +2157,7 @@ static void ath10k_pci_override_ce_config(struct ath10k *ar) target_service_to_ce_map_wlan[15].pipenum = __cpu_to_le32(1); } -static int ath10k_pci_alloc_pipes(struct ath10k *ar) +int ath10k_pci_alloc_pipes(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); struct ath10k_pci_pipe *pipe; @@ -2065,7 +2165,7 @@ static int ath10k_pci_alloc_pipes(struct ath10k *ar) for (i = 0; i < CE_COUNT; i++) { pipe = &ar_pci->pipe_info[i]; - pipe->ce_hdl = &ar_pci->ce_states[i]; + pipe->ce_hdl = &ar_pci->opaque_ctx.ce_states[i]; pipe->pipe_num = i; pipe->hif_ce_state = ar; @@ -2088,7 +2188,7 @@ static int ath10k_pci_alloc_pipes(struct ath10k *ar) return 0; } -static void ath10k_pci_free_pipes(struct ath10k *ar) +void ath10k_pci_free_pipes(struct ath10k *ar) { int i; @@ -2096,7 +2196,7 @@ static void ath10k_pci_free_pipes(struct ath10k *ar) ath10k_ce_free_pipe(ar, i); } -static int ath10k_pci_init_pipes(struct ath10k *ar) +int ath10k_pci_init_pipes(struct ath10k *ar) { int i, ret; @@ -2127,6 +2227,14 @@ static void ath10k_pci_fw_crashed_clear(struct ath10k *ar) ath10k_pci_write32(ar, FW_INDICATOR_ADDRESS, val); } +static bool ath10k_pci_has_device_gone(struct ath10k *ar) +{ + u32 val; + + val = ath10k_pci_read32(ar, FW_INDICATOR_ADDRESS); + return (val == 0xffffffff); +} + /* this function effectively clears target memory controller assert line */ static void ath10k_pci_warm_reset_si0(struct ath10k *ar) { @@ -2222,16 +2330,20 @@ static int ath10k_pci_warm_reset(struct ath10k *ar) return 0; } +static int ath10k_pci_qca99x0_soft_chip_reset(struct ath10k *ar) +{ + ath10k_pci_irq_disable(ar); + return ath10k_pci_qca99x0_chip_reset(ar); +} + static int ath10k_pci_safe_chip_reset(struct ath10k *ar) { - if (QCA_REV_988X(ar) || QCA_REV_6174(ar)) { - return ath10k_pci_warm_reset(ar); - } else if (QCA_REV_99X0(ar)) { - ath10k_pci_irq_disable(ar); - return ath10k_pci_qca99x0_chip_reset(ar); - } else { + struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + + if (!ar_pci->pci_soft_reset) return -ENOTSUPP; - } + + return ar_pci->pci_soft_reset(ar); } static int ath10k_pci_qca988x_chip_reset(struct ath10k *ar) @@ -2366,16 +2478,12 @@ static int ath10k_pci_qca99x0_chip_reset(struct ath10k *ar) static int ath10k_pci_chip_reset(struct ath10k *ar) { - if (QCA_REV_988X(ar)) - return ath10k_pci_qca988x_chip_reset(ar); - else if (QCA_REV_6174(ar)) - return ath10k_pci_qca6174_chip_reset(ar); - else if (QCA_REV_9377(ar)) - return ath10k_pci_qca6174_chip_reset(ar); - else if (QCA_REV_99X0(ar)) - return ath10k_pci_qca99x0_chip_reset(ar); - else + struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + + if (WARN_ON(!ar_pci->pci_hard_reset)) return -ENOTSUPP; + + return ar_pci->pci_hard_reset(ar); } static int ath10k_pci_hif_power_up(struct ath10k *ar) @@ -2429,6 +2537,7 @@ static int ath10k_pci_hif_power_up(struct ath10k *ar) ath10k_err(ar, "could not wake up target CPU: %d\n", ret); goto err_ce; } + napi_enable(&ar->napi); return 0; @@ -2439,7 +2548,7 @@ err_sleep: return ret; } -static void ath10k_pci_hif_power_down(struct ath10k *ar) +void ath10k_pci_hif_power_down(struct ath10k *ar) { ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot hif power down\n"); @@ -2469,12 +2578,10 @@ static int ath10k_pci_hif_resume(struct ath10k *ar) u32 val; int ret = 0; - if (ar_pci->pci_ps == 0) { - ret = ath10k_pci_force_wake(ar); - if (ret) { - ath10k_err(ar, "failed to wake up target: %d\n", ret); - return ret; - } + ret = ath10k_pci_force_wake(ar); + if (ret) { + ath10k_err(ar, "failed to wake up target: %d\n", ret); + return ret; } /* Suspend/Resume resets the PCI configuration space, so we have to @@ -2490,6 +2597,144 @@ static int ath10k_pci_hif_resume(struct ath10k *ar) } #endif +static bool ath10k_pci_validate_cal(void *data, size_t size) +{ + __le16 *cal_words = data; + u16 checksum = 0; + size_t i; + + if (size % 2 != 0) + return false; + + for (i = 0; i < size / 2; i++) + checksum ^= le16_to_cpu(cal_words[i]); + + return checksum == 0xffff; +} + +static void ath10k_pci_enable_eeprom(struct ath10k *ar) +{ + /* Enable SI clock */ + ath10k_pci_soc_write32(ar, CLOCK_CONTROL_OFFSET, 0x0); + + /* Configure GPIOs for I2C operation */ + ath10k_pci_write32(ar, + GPIO_BASE_ADDRESS + GPIO_PIN0_OFFSET + + 4 * QCA9887_1_0_I2C_SDA_GPIO_PIN, + SM(QCA9887_1_0_I2C_SDA_PIN_CONFIG, + GPIO_PIN0_CONFIG) | + SM(1, GPIO_PIN0_PAD_PULL)); + + ath10k_pci_write32(ar, + GPIO_BASE_ADDRESS + GPIO_PIN0_OFFSET + + 4 * QCA9887_1_0_SI_CLK_GPIO_PIN, + SM(QCA9887_1_0_SI_CLK_PIN_CONFIG, GPIO_PIN0_CONFIG) | + SM(1, GPIO_PIN0_PAD_PULL)); + + ath10k_pci_write32(ar, + GPIO_BASE_ADDRESS + + QCA9887_1_0_GPIO_ENABLE_W1TS_LOW_ADDRESS, + 1u << QCA9887_1_0_SI_CLK_GPIO_PIN); + + /* In Swift ASIC - EEPROM clock will be (110MHz/512) = 214KHz */ + ath10k_pci_write32(ar, + SI_BASE_ADDRESS + SI_CONFIG_OFFSET, + SM(1, SI_CONFIG_ERR_INT) | + SM(1, SI_CONFIG_BIDIR_OD_DATA) | + SM(1, SI_CONFIG_I2C) | + SM(1, SI_CONFIG_POS_SAMPLE) | + SM(1, SI_CONFIG_INACTIVE_DATA) | + SM(1, SI_CONFIG_INACTIVE_CLK) | + SM(8, SI_CONFIG_DIVIDER)); +} + +static int ath10k_pci_read_eeprom(struct ath10k *ar, u16 addr, u8 *out) +{ + u32 reg; + int wait_limit; + + /* set device select byte and for the read operation */ + reg = QCA9887_EEPROM_SELECT_READ | + SM(addr, QCA9887_EEPROM_ADDR_LO) | + SM(addr >> 8, QCA9887_EEPROM_ADDR_HI); + ath10k_pci_write32(ar, SI_BASE_ADDRESS + SI_TX_DATA0_OFFSET, reg); + + /* write transmit data, transfer length, and START bit */ + ath10k_pci_write32(ar, SI_BASE_ADDRESS + SI_CS_OFFSET, + SM(1, SI_CS_START) | SM(1, SI_CS_RX_CNT) | + SM(4, SI_CS_TX_CNT)); + + /* wait max 1 sec */ + wait_limit = 100000; + + /* wait for SI_CS_DONE_INT */ + do { + reg = ath10k_pci_read32(ar, SI_BASE_ADDRESS + SI_CS_OFFSET); + if (MS(reg, SI_CS_DONE_INT)) + break; + + wait_limit--; + udelay(10); + } while (wait_limit > 0); + + if (!MS(reg, SI_CS_DONE_INT)) { + ath10k_err(ar, "timeout while reading device EEPROM at %04x\n", + addr); + return -ETIMEDOUT; + } + + /* clear SI_CS_DONE_INT */ + ath10k_pci_write32(ar, SI_BASE_ADDRESS + SI_CS_OFFSET, reg); + + if (MS(reg, SI_CS_DONE_ERR)) { + ath10k_err(ar, "failed to read device EEPROM at %04x\n", addr); + return -EIO; + } + + /* extract receive data */ + reg = ath10k_pci_read32(ar, SI_BASE_ADDRESS + SI_RX_DATA0_OFFSET); + *out = reg; + + return 0; +} + +static int ath10k_pci_hif_fetch_cal_eeprom(struct ath10k *ar, void **data, + size_t *data_len) +{ + u8 *caldata = NULL; + size_t calsize, i; + int ret; + + if (!QCA_REV_9887(ar)) + return -EOPNOTSUPP; + + calsize = ar->hw_params.cal_data_len; + caldata = kmalloc(calsize, GFP_KERNEL); + if (!caldata) + return -ENOMEM; + + ath10k_pci_enable_eeprom(ar); + + for (i = 0; i < calsize; i++) { + ret = ath10k_pci_read_eeprom(ar, i, &caldata[i]); + if (ret) + goto err_free; + } + + if (!ath10k_pci_validate_cal(caldata, calsize)) + goto err_free; + + *data = caldata; + *data_len = calsize; + + return 0; + +err_free: + kfree(caldata); + + return -EINVAL; +} + static const struct ath10k_hif_ops ath10k_pci_hif_ops = { .tx_sg = ath10k_pci_hif_tx_sg, .diag_read = ath10k_pci_hif_diag_read, @@ -2509,67 +2754,9 @@ static const struct ath10k_hif_ops ath10k_pci_hif_ops = { .suspend = ath10k_pci_hif_suspend, .resume = ath10k_pci_hif_resume, #endif + .fetch_cal_eeprom = ath10k_pci_hif_fetch_cal_eeprom, }; -static void ath10k_pci_ce_tasklet(unsigned long ptr) -{ - struct ath10k_pci_pipe *pipe = (struct ath10k_pci_pipe *)ptr; - struct ath10k_pci *ar_pci = pipe->ar_pci; - - ath10k_ce_per_engine_service(ar_pci->ar, pipe->pipe_num); -} - -static void ath10k_msi_err_tasklet(unsigned long data) -{ - struct ath10k *ar = (struct ath10k *)data; - - if (!ath10k_pci_has_fw_crashed(ar)) { - ath10k_warn(ar, "received unsolicited fw crash interrupt\n"); - return; - } - - ath10k_pci_irq_disable(ar); - ath10k_pci_fw_crashed_clear(ar); - ath10k_pci_fw_crashed_dump(ar); -} - -/* - * Handler for a per-engine interrupt on a PARTICULAR CE. - * This is used in cases where each CE has a private MSI interrupt. - */ -static irqreturn_t ath10k_pci_per_engine_handler(int irq, void *arg) -{ - struct ath10k *ar = arg; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - int ce_id = irq - ar_pci->pdev->irq - MSI_ASSIGN_CE_INITIAL; - - if (ce_id < 0 || ce_id >= ARRAY_SIZE(ar_pci->pipe_info)) { - ath10k_warn(ar, "unexpected/invalid irq %d ce_id %d\n", irq, - ce_id); - return IRQ_HANDLED; - } - - /* - * NOTE: We are able to derive ce_id from irq because we - * use a one-to-one mapping for CE's 0..5. - * CE's 6 & 7 do not use interrupts at all. - * - * This mapping must be kept in sync with the mapping - * used by firmware. - */ - tasklet_schedule(&ar_pci->pipe_info[ce_id].intr); - return IRQ_HANDLED; -} - -static irqreturn_t ath10k_pci_msi_fw_handler(int irq, void *arg) -{ - struct ath10k *ar = arg; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - - tasklet_schedule(&ar_pci->msi_fw_err); - return IRQ_HANDLED; -} - /* * Top-level interrupt handler for all PCI interrupts from a Target. * When a block of MSI interrupts is allocated, this top-level handler @@ -2581,77 +2768,63 @@ static irqreturn_t ath10k_pci_interrupt_handler(int irq, void *arg) struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); int ret; - if (ar_pci->pci_ps == 0) { - ret = ath10k_pci_force_wake(ar); - if (ret) { - ath10k_warn(ar, "failed to wake device up on irq: %d\n", - ret); - return IRQ_NONE; - } - } - - if (ar_pci->num_msi_intrs == 0) { - if (!ath10k_pci_irq_pending(ar)) - return IRQ_NONE; + if (ath10k_pci_has_device_gone(ar)) + return IRQ_NONE; - ath10k_pci_disable_and_clear_legacy_irq(ar); + ret = ath10k_pci_force_wake(ar); + if (ret) { + ath10k_warn(ar, "failed to wake device up on irq: %d\n", ret); + return IRQ_NONE; } - tasklet_schedule(&ar_pci->intr_tq); + if ((ar_pci->oper_irq_mode == ATH10K_PCI_IRQ_LEGACY) && + !ath10k_pci_irq_pending(ar)) + return IRQ_NONE; + + ath10k_pci_disable_and_clear_legacy_irq(ar); + ath10k_pci_irq_msi_fw_mask(ar); + napi_schedule(&ar->napi); return IRQ_HANDLED; } -static void ath10k_pci_tasklet(unsigned long data) +static int ath10k_pci_napi_poll(struct napi_struct *ctx, int budget) { - struct ath10k *ar = (struct ath10k *)data; - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + struct ath10k *ar = container_of(ctx, struct ath10k, napi); + int done = 0; + struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); if (ath10k_pci_has_fw_crashed(ar)) { - ath10k_pci_irq_disable(ar); ath10k_pci_fw_crashed_clear(ar); ath10k_pci_fw_crashed_dump(ar); - return; + napi_complete(ctx); + return done; } ath10k_ce_per_engine_service_any(ar); - /* Re-enable legacy irq that was disabled in the irq handler */ - if (ar_pci->num_msi_intrs == 0) - ath10k_pci_enable_legacy_irq(ar); -} - -static int ath10k_pci_request_irq_msix(struct ath10k *ar) -{ - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - int ret, i; - - ret = request_irq(ar_pci->pdev->irq + MSI_ASSIGN_FW, - ath10k_pci_msi_fw_handler, - IRQF_SHARED, "ath10k_pci", ar); - if (ret) { - ath10k_warn(ar, "failed to request MSI-X fw irq %d: %d\n", - ar_pci->pdev->irq + MSI_ASSIGN_FW, ret); - return ret; - } - - for (i = MSI_ASSIGN_CE_INITIAL; i <= MSI_ASSIGN_CE_MAX; i++) { - ret = request_irq(ar_pci->pdev->irq + i, - ath10k_pci_per_engine_handler, - IRQF_SHARED, "ath10k_pci", ar); - if (ret) { - ath10k_warn(ar, "failed to request MSI-X ce irq %d: %d\n", - ar_pci->pdev->irq + i, ret); - - for (i--; i >= MSI_ASSIGN_CE_INITIAL; i--) - free_irq(ar_pci->pdev->irq + i, ar); - - free_irq(ar_pci->pdev->irq + MSI_ASSIGN_FW, ar); - return ret; + done = ath10k_htt_txrx_compl_task(ar, budget); + + if (done < budget) { + napi_complete(ctx); + /* In case of MSI, it is possible that interrupts are received + * while NAPI poll is inprogress. So pending interrupts that are + * received after processing all copy engine pipes by NAPI poll + * will not be handled again. This is causing failure to + * complete boot sequence in x86 platform. So before enabling + * interrupts safer to check for pending interrupts for + * immediate servicing. + */ + if (CE_INTERRUPT_SUMMARY(ar, ar_opaque)) { + napi_reschedule(ctx); + goto out; } + ath10k_pci_enable_legacy_irq(ar); + ath10k_pci_irq_msi_fw_unmask(ar); } - return 0; +out: + return done; } static int ath10k_pci_request_irq_msi(struct ath10k *ar) @@ -2692,41 +2865,27 @@ static int ath10k_pci_request_irq(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - switch (ar_pci->num_msi_intrs) { - case 0: + switch (ar_pci->oper_irq_mode) { + case ATH10K_PCI_IRQ_LEGACY: return ath10k_pci_request_irq_legacy(ar); - case 1: + case ATH10K_PCI_IRQ_MSI: return ath10k_pci_request_irq_msi(ar); default: - return ath10k_pci_request_irq_msix(ar); + return -EINVAL; } } static void ath10k_pci_free_irq(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - int i; - /* There's at least one interrupt irregardless whether its legacy INTR - * or MSI or MSI-X */ - for (i = 0; i < max(1, ar_pci->num_msi_intrs); i++) - free_irq(ar_pci->pdev->irq + i, ar); + free_irq(ar_pci->pdev->irq, ar); } -static void ath10k_pci_init_irq_tasklets(struct ath10k *ar) +void ath10k_pci_init_napi(struct ath10k *ar) { - struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - int i; - - tasklet_init(&ar_pci->intr_tq, ath10k_pci_tasklet, (unsigned long)ar); - tasklet_init(&ar_pci->msi_fw_err, ath10k_msi_err_tasklet, - (unsigned long)ar); - - for (i = 0; i < CE_COUNT; i++) { - ar_pci->pipe_info[i].ar_pci = ar_pci; - tasklet_init(&ar_pci->pipe_info[i].intr, ath10k_pci_ce_tasklet, - (unsigned long)&ar_pci->pipe_info[i]); - } + netif_napi_add(&ar->napi_dev, &ar->napi, ath10k_pci_napi_poll, + ATH10K_NAPI_BUDGET); } static int ath10k_pci_init_irq(struct ath10k *ar) @@ -2734,26 +2893,15 @@ static int ath10k_pci_init_irq(struct ath10k *ar) struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); int ret; - ath10k_pci_init_irq_tasklets(ar); + ath10k_pci_init_napi(ar); if (ath10k_pci_irq_mode != ATH10K_PCI_IRQ_AUTO) ath10k_info(ar, "limiting irq mode to: %d\n", ath10k_pci_irq_mode); - /* Try MSI-X */ - if (ath10k_pci_irq_mode == ATH10K_PCI_IRQ_AUTO) { - ar_pci->num_msi_intrs = MSI_ASSIGN_CE_MAX + 1; - ret = pci_enable_msi_range(ar_pci->pdev, ar_pci->num_msi_intrs, - ar_pci->num_msi_intrs); - if (ret > 0) - return 0; - - /* fall-through */ - } - /* Try MSI */ if (ath10k_pci_irq_mode != ATH10K_PCI_IRQ_LEGACY) { - ar_pci->num_msi_intrs = 1; + ar_pci->oper_irq_mode = ATH10K_PCI_IRQ_MSI; ret = pci_enable_msi(ar_pci->pdev); if (ret == 0) return 0; @@ -2769,7 +2917,7 @@ static int ath10k_pci_init_irq(struct ath10k *ar) * This write might get lost if target has NOT written BAR. * For now, fix the race by repeating the write in below * synchronization checking. */ - ar_pci->num_msi_intrs = 0; + ar_pci->oper_irq_mode = ATH10K_PCI_IRQ_LEGACY; ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS + PCIE_INTR_ENABLE_ADDRESS, PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL); @@ -2787,8 +2935,8 @@ static int ath10k_pci_deinit_irq(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - switch (ar_pci->num_msi_intrs) { - case 0: + switch (ar_pci->oper_irq_mode) { + case ATH10K_PCI_IRQ_LEGACY: ath10k_pci_deinit_irq_legacy(ar); break; default: @@ -2799,7 +2947,7 @@ static int ath10k_pci_deinit_irq(struct ath10k *ar) return 0; } -static int ath10k_pci_wait_for_target_init(struct ath10k *ar) +int ath10k_pci_wait_for_target_init(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); unsigned long timeout; @@ -2826,7 +2974,7 @@ static int ath10k_pci_wait_for_target_init(struct ath10k *ar) if (val & FW_IND_INITIALIZED) break; - if (ar_pci->num_msi_intrs == 0) + if (ar_pci->oper_irq_mode == ATH10K_PCI_IRQ_LEGACY) /* Fix potential race by repeating CORE_BASE writes */ ath10k_pci_enable_legacy_irq(ar); @@ -2937,7 +3085,7 @@ static int ath10k_pci_claim(struct ath10k *ar) goto err_master; } - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot pci_mem 0x%p\n", ar_pci->mem); + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot pci_mem 0x%pK\n", ar_pci->mem); return 0; err_master: @@ -2980,6 +3128,44 @@ static bool ath10k_pci_chip_is_supported(u32 dev_id, u32 chip_id) return false; } +int ath10k_pci_setup_resource(struct ath10k *ar) +{ + struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + int ret; + + spin_lock_init(&ar_pci->opaque_ctx.ce_lock); + spin_lock_init(&ar_pci->ps_lock); + + setup_timer(&ar_pci->rx_post_retry, ath10k_pci_rx_replenish_retry, + (unsigned long)ar); + + if (QCA_REV_6174(ar) || QCA_REV_9377(ar)) + ath10k_pci_override_ce_config(ar); + + ret = ath10k_pci_alloc_pipes(ar); + if (ret) { + ath10k_err(ar, "failed to allocate copy engine pipes: %d\n", + ret); + return ret; + } + + return 0; +} + +void ath10k_pci_release_resource(struct ath10k *ar) +{ + ath10k_pci_rx_retry_sync(ar); + netif_napi_del(&ar->napi); + ath10k_pci_ce_deinit(ar); + ath10k_pci_free_pipes(ar); +} + +static const struct ath10k_bus_ops ath10k_pci_bus_ops = { + .read32 = ath10k_bus_pci_read32, + .write32 = ath10k_bus_pci_write32, + .get_num_banks = ath10k_pci_get_num_banks, +}; + static int ath10k_pci_probe(struct pci_dev *pdev, const struct pci_device_id *pci_dev) { @@ -2989,24 +3175,52 @@ static int ath10k_pci_probe(struct pci_dev *pdev, enum ath10k_hw_rev hw_rev; u32 chip_id; bool pci_ps; + int (*pci_soft_reset)(struct ath10k *ar); + int (*pci_hard_reset)(struct ath10k *ar); switch (pci_dev->device) { case QCA988X_2_0_DEVICE_ID: hw_rev = ATH10K_HW_QCA988X; pci_ps = false; + pci_soft_reset = ath10k_pci_warm_reset; + pci_hard_reset = ath10k_pci_qca988x_chip_reset; + break; + case QCA9887_1_0_DEVICE_ID: + hw_rev = ATH10K_HW_QCA9887; + pci_ps = false; + pci_soft_reset = ath10k_pci_warm_reset; + pci_hard_reset = ath10k_pci_qca988x_chip_reset; break; case QCA6164_2_1_DEVICE_ID: case QCA6174_2_1_DEVICE_ID: hw_rev = ATH10K_HW_QCA6174; pci_ps = true; + pci_soft_reset = ath10k_pci_warm_reset; + pci_hard_reset = ath10k_pci_qca6174_chip_reset; break; case QCA99X0_2_0_DEVICE_ID: hw_rev = ATH10K_HW_QCA99X0; pci_ps = false; + pci_soft_reset = ath10k_pci_qca99x0_soft_chip_reset; + pci_hard_reset = ath10k_pci_qca99x0_chip_reset; + break; + case QCA9984_1_0_DEVICE_ID: + hw_rev = ATH10K_HW_QCA9984; + pci_ps = false; + pci_soft_reset = ath10k_pci_qca99x0_soft_chip_reset; + pci_hard_reset = ath10k_pci_qca99x0_chip_reset; + break; + case QCA9888_2_0_DEVICE_ID: + hw_rev = ATH10K_HW_QCA9888; + pci_ps = false; + pci_soft_reset = ath10k_pci_qca99x0_soft_chip_reset; + pci_hard_reset = ath10k_pci_qca99x0_chip_reset; break; case QCA9377_1_0_DEVICE_ID: hw_rev = ATH10K_HW_QCA9377; pci_ps = true; + pci_soft_reset = NULL; + pci_hard_reset = ath10k_pci_qca6174_chip_reset; break; default: WARN_ON(1); @@ -3030,55 +3244,50 @@ static int ath10k_pci_probe(struct pci_dev *pdev, ar_pci->ar = ar; ar->dev_id = pci_dev->device; ar_pci->pci_ps = pci_ps; + ar_pci->opaque_ctx.bus_ops = &ath10k_pci_bus_ops; + ar_pci->pci_soft_reset = pci_soft_reset; + ar_pci->pci_hard_reset = pci_hard_reset; ar->id.vendor = pdev->vendor; ar->id.device = pdev->device; ar->id.subsystem_vendor = pdev->subsystem_vendor; ar->id.subsystem_device = pdev->subsystem_device; - spin_lock_init(&ar_pci->ce_lock); spin_lock_init(&ar_pci->ps_lock); - setup_timer(&ar_pci->rx_post_retry, ath10k_pci_rx_replenish_retry, (unsigned long)ar); setup_timer(&ar_pci->ps_timer, ath10k_pci_ps_timer, (unsigned long)ar); - ret = ath10k_pci_claim(ar); + ret = ath10k_pci_setup_resource(ar); if (ret) { - ath10k_err(ar, "failed to claim device: %d\n", ret); + ath10k_err(ar, "failed to setup resource: %d\n", ret); goto err_core_destroy; } - if (QCA_REV_6174(ar) || QCA_REV_9377(ar)) - ath10k_pci_override_ce_config(ar); + ret = ath10k_pci_claim(ar); + if (ret) { + ath10k_err(ar, "failed to claim device: %d\n", ret); + goto err_free_pipes; + } - ret = ath10k_pci_alloc_pipes(ar); + ret = ath10k_pci_force_wake(ar); if (ret) { - ath10k_err(ar, "failed to allocate copy engine pipes: %d\n", - ret); + ath10k_warn(ar, "failed to wake up device : %d\n", ret); goto err_sleep; } ath10k_pci_ce_deinit(ar); ath10k_pci_irq_disable(ar); - if (ar_pci->pci_ps == 0) { - ret = ath10k_pci_force_wake(ar); - if (ret) { - ath10k_warn(ar, "failed to wake up device : %d\n", ret); - goto err_free_pipes; - } - } - ret = ath10k_pci_init_irq(ar); if (ret) { ath10k_err(ar, "failed to init irqs: %d\n", ret); - goto err_free_pipes; + goto err_sleep; } - ath10k_info(ar, "pci irq %s interrupts %d irq_mode %d reset_mode %d\n", - ath10k_pci_get_irq_method(ar), ar_pci->num_msi_intrs, + ath10k_info(ar, "pci irq %s oper_irq_mode %d irq_mode %d reset_mode %d\n", + ath10k_pci_get_irq_method(ar), ar_pci->oper_irq_mode, ath10k_pci_irq_mode, ath10k_pci_reset_mode); ret = ath10k_pci_request_irq(ar); @@ -3115,18 +3324,18 @@ static int ath10k_pci_probe(struct pci_dev *pdev, err_free_irq: ath10k_pci_free_irq(ar); - ath10k_pci_kill_tasklet(ar); + ath10k_pci_rx_retry_sync(ar); err_deinit_irq: ath10k_pci_deinit_irq(ar); -err_free_pipes: - ath10k_pci_free_pipes(ar); - err_sleep: ath10k_pci_sleep_sync(ar); ath10k_pci_release(ar); +err_free_pipes: + ath10k_pci_free_pipes(ar); + err_core_destroy: ath10k_core_destroy(ar); @@ -3150,10 +3359,8 @@ static void ath10k_pci_remove(struct pci_dev *pdev) ath10k_core_unregister(ar); ath10k_pci_free_irq(ar); - ath10k_pci_kill_tasklet(ar); ath10k_pci_deinit_irq(ar); - ath10k_pci_ce_deinit(ar); - ath10k_pci_free_pipes(ar); + ath10k_pci_release_resource(ar); ath10k_pci_sleep_sync(ar); ath10k_pci_release(ar); ath10k_core_destroy(ar); @@ -3177,6 +3384,10 @@ static int __init ath10k_pci_init(void) printk(KERN_ERR "failed to register ath10k pci driver: %d\n", ret); + ret = ath10k_ahb_init(); + if (ret) + printk(KERN_ERR "ahb init failed: %d\n", ret); + return ret; } module_init(ath10k_pci_init); @@ -3184,16 +3395,16 @@ module_init(ath10k_pci_init); static void __exit ath10k_pci_exit(void) { pci_unregister_driver(&ath10k_pci_driver); + ath10k_ahb_exit(); } module_exit(ath10k_pci_exit); MODULE_AUTHOR("Qualcomm Atheros"); -MODULE_DESCRIPTION("Driver support for Atheros QCA988X PCIe devices"); +MODULE_DESCRIPTION("Driver support for Qualcomm Atheros 802.11ac WLAN PCIe/AHB devices"); MODULE_LICENSE("Dual BSD/GPL"); /* QCA988x 2.0 firmware files */ -MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" QCA988X_HW_2_0_FW_FILE); MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" ATH10K_FW_API2_FILE); MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" ATH10K_FW_API3_FILE); MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" ATH10K_FW_API4_FILE); @@ -3201,6 +3412,11 @@ MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" ATH10K_FW_API5_FILE); MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" QCA988X_HW_2_0_BOARD_DATA_FILE); MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" ATH10K_BOARD_API2_FILE); +/* QCA9887 1.0 firmware files */ +MODULE_FIRMWARE(QCA9887_HW_1_0_FW_DIR "/" ATH10K_FW_API5_FILE); +MODULE_FIRMWARE(QCA9887_HW_1_0_FW_DIR "/" QCA9887_HW_1_0_BOARD_DATA_FILE); +MODULE_FIRMWARE(QCA9887_HW_1_0_FW_DIR "/" ATH10K_BOARD_API2_FILE); + /* QCA6174 2.1 firmware files */ MODULE_FIRMWARE(QCA6174_HW_2_1_FW_DIR "/" ATH10K_FW_API4_FILE); MODULE_FIRMWARE(QCA6174_HW_2_1_FW_DIR "/" ATH10K_FW_API5_FILE); diff --git a/drivers/net/wireless/ath/ath10k/pci.h b/drivers/net/wireless/ath/ath10k/pci.h index f91bf333cb75..22730c700af3 100644 --- a/drivers/net/wireless/ath/ath10k/pci.h +++ b/drivers/net/wireless/ath/ath10k/pci.h @@ -22,6 +22,7 @@ #include "hw.h" #include "ce.h" +#include "ahb.h" /* * maximum number of bytes that can be handled atomically by DiagRead/DiagWrite @@ -147,9 +148,6 @@ struct ath10k_pci_pipe { /* protects compl_free and num_send_allowed */ spinlock_t pipe_lock; - - struct ath10k_pci *ar_pci; - struct tasklet_struct intr; }; struct ath10k_pci_supp_chip { @@ -157,32 +155,28 @@ struct ath10k_pci_supp_chip { u32 rev_id; }; +enum ath10k_pci_irq_mode { + ATH10K_PCI_IRQ_AUTO = 0, + ATH10K_PCI_IRQ_LEGACY = 1, + ATH10K_PCI_IRQ_MSI = 2, +}; + struct ath10k_pci { + struct bus_opaque opaque_ctx; struct pci_dev *pdev; struct device *dev; struct ath10k *ar; void __iomem *mem; size_t mem_len; - /* - * Number of MSI interrupts granted, 0 --> using legacy PCI line - * interrupts. - */ - int num_msi_intrs; - - struct tasklet_struct intr_tq; - struct tasklet_struct msi_fw_err; + /* Operating interrupt mode */ + enum ath10k_pci_irq_mode oper_irq_mode; struct ath10k_pci_pipe pipe_info[CE_COUNT_MAX]; /* Copy Engine used for Diagnostic Accesses */ struct ath10k_ce_pipe *ce_diag; - /* FIXME: document what this really protects */ - spinlock_t ce_lock; - - /* Map CE id to ce_state */ - struct ath10k_ce_pipe ce_states[CE_COUNT_MAX]; struct timer_list rx_post_retry; /* Due to HW quirks it is recommended to disable ASPM during device @@ -225,6 +219,18 @@ struct ath10k_pci { * on MMIO read/write. */ bool pci_ps; + + /* Chip specific pci reset routine used to do a safe reset */ + int (*pci_soft_reset)(struct ath10k *ar); + + /* Chip specific pci full reset function */ + int (*pci_hard_reset)(struct ath10k *ar); + + /* Keep this entry in the last, memory for struct ath10k_ahb is + * allocated (ahb support enabled case) in the continuation of + * this struct. + */ + struct ath10k_ahb ahb[0]; }; static inline struct ath10k_pci *ath10k_pci_priv(struct ath10k *ar) @@ -253,6 +259,40 @@ u32 ath10k_pci_read32(struct ath10k *ar, u32 offset); u32 ath10k_pci_soc_read32(struct ath10k *ar, u32 addr); u32 ath10k_pci_reg_read32(struct ath10k *ar, u32 addr); +int ath10k_pci_hif_tx_sg(struct ath10k *ar, u8 pipe_id, + struct ath10k_hif_sg_item *items, int n_items); +int ath10k_pci_hif_diag_read(struct ath10k *ar, u32 address, void *buf, + size_t buf_len); +int ath10k_pci_diag_write_mem(struct ath10k *ar, u32 address, + const void *data, int nbytes); +int ath10k_pci_hif_exchange_bmi_msg(struct ath10k *ar, void *req, u32 req_len, + void *resp, u32 *resp_len); +int ath10k_pci_hif_map_service_to_pipe(struct ath10k *ar, u16 service_id, + u8 *ul_pipe, u8 *dl_pipe); +void ath10k_pci_hif_get_default_pipe(struct ath10k *ar, u8 *ul_pipe, + u8 *dl_pipe); +void ath10k_pci_hif_send_complete_check(struct ath10k *ar, u8 pipe, + int force); +u16 ath10k_pci_hif_get_free_queue_number(struct ath10k *ar, u8 pipe); +void ath10k_pci_hif_power_down(struct ath10k *ar); +int ath10k_pci_alloc_pipes(struct ath10k *ar); +void ath10k_pci_free_pipes(struct ath10k *ar); +void ath10k_pci_free_pipes(struct ath10k *ar); +void ath10k_pci_rx_replenish_retry(unsigned long ptr); +void ath10k_pci_ce_deinit(struct ath10k *ar); +void ath10k_pci_init_napi(struct ath10k *ar); +int ath10k_pci_init_pipes(struct ath10k *ar); +int ath10k_pci_init_config(struct ath10k *ar); +void ath10k_pci_rx_post(struct ath10k *ar); +void ath10k_pci_flush(struct ath10k *ar); +void ath10k_pci_enable_legacy_irq(struct ath10k *ar); +bool ath10k_pci_irq_pending(struct ath10k *ar); +void ath10k_pci_disable_and_clear_legacy_irq(struct ath10k *ar); +void ath10k_pci_irq_msi_fw_mask(struct ath10k *ar); +int ath10k_pci_wait_for_target_init(struct ath10k *ar); +int ath10k_pci_setup_resource(struct ath10k *ar); +void ath10k_pci_release_resource(struct ath10k *ar); + /* QCA6174 is known to have Tx/Rx issues when SOC_WAKE register is poked too * frequently. To avoid this put SoC to sleep after a very conservative grace * period. Adjust with great care. diff --git a/drivers/net/wireless/ath/ath10k/qmi.c b/drivers/net/wireless/ath/ath10k/qmi.c new file mode 100644 index 000000000000..dd310d5a7028 --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/qmi.c @@ -0,0 +1,902 @@ +/* Copyright (c) 2017, 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 <soc/qcom/subsystem_notif.h> +#include <soc/qcom/subsystem_restart.h> +#include <soc/qcom/service-notifier.h> +#include <soc/qcom/msm_qmi_interface.h> +#include <soc/qcom/icnss.h> +#include <soc/qcom/service-locator.h> +#include "core.h" +#include "qmi.h" +#include "snoc.h" +#include "wcn3990_qmi_service_v01.h" + +static DECLARE_WAIT_QUEUE_HEAD(ath10k_fw_ready_wait_event); + +static int +ath10k_snoc_service_notifier_notify(struct notifier_block *nb, + unsigned long notification, void *data) +{ + struct ath10k_snoc *ar_snoc = container_of(nb, struct ath10k_snoc, + service_notifier_nb); + enum pd_subsys_state *state = data; + struct ath10k *ar = ar_snoc->ar; + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + int ret; + + switch (notification) { + case SERVREG_NOTIF_SERVICE_STATE_DOWN_V01: + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Service down, data: 0x%pK\n", + data); + + if (!state || *state != ROOT_PD_SHUTDOWN) { + atomic_set(&ar_snoc->fw_crashed, 1); + atomic_set(&qmi_cfg->fw_ready, 0); + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "PD went down %d\n", + atomic_read(&ar_snoc->fw_crashed)); + break; + case SERVREG_NOTIF_SERVICE_STATE_UP_V01: + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Service up\n"); + ret = wait_event_timeout(ath10k_fw_ready_wait_event, + (atomic_read(&qmi_cfg->fw_ready) && + atomic_read(&qmi_cfg->server_connected)), + msecs_to_jiffies(ATH10K_SNOC_WLAN_FW_READY_TIMEOUT)); + if (ret) { + queue_work(ar->workqueue, &ar->restart_work); + } else { + ath10k_err(ar, "restart failed, fw_ready timed out\n"); + return NOTIFY_OK; + } + break; + default: + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Service state Unknown, notification: 0x%lx\n", + notification); + return NOTIFY_DONE; + } + return NOTIFY_OK; +} + +static int ath10k_snoc_get_service_location_notify(struct notifier_block *nb, + unsigned long opcode, + void *data) +{ + struct ath10k_snoc *ar_snoc = container_of(nb, struct ath10k_snoc, + get_service_nb); + struct ath10k *ar = ar_snoc->ar; + struct pd_qmi_client_data *pd = data; + int curr_state; + int ret; + int i; + struct ath10k_service_notifier_context *notifier; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Get service notify opcode: %lu\n", + opcode); + + if (opcode != LOCATOR_UP) + return NOTIFY_DONE; + + if (!pd->total_domains) { + ath10k_err(ar, "Did not find any domains\n"); + ret = -ENOENT; + goto out; + } + + notifier = kcalloc(pd->total_domains, + sizeof(struct ath10k_service_notifier_context), + GFP_KERNEL); + if (!notifier) { + ret = -ENOMEM; + goto out; + } + + ar_snoc->service_notifier_nb.notifier_call = + ath10k_snoc_service_notifier_notify; + + for (i = 0; i < pd->total_domains; i++) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "%d: domain_name: %s, instance_id: %d\n", i, + pd->domain_list[i].name, + pd->domain_list[i].instance_id); + + notifier[i].handle = + service_notif_register_notifier( + pd->domain_list[i].name, + pd->domain_list[i].instance_id, + &ar_snoc->service_notifier_nb, + &curr_state); + notifier[i].instance_id = pd->domain_list[i].instance_id; + strlcpy(notifier[i].name, pd->domain_list[i].name, + QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1); + + if (IS_ERR(notifier[i].handle)) { + ath10k_err(ar, "%d: Unable to register notifier for %s(0x%x)\n", + i, pd->domain_list->name, + pd->domain_list->instance_id); + ret = PTR_ERR(notifier[i].handle); + goto free_handle; + } + } + + ar_snoc->service_notifier = notifier; + ar_snoc->total_domains = pd->total_domains; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "PD restart enabled\n"); + + return NOTIFY_OK; + +free_handle: + for (i = 0; i < pd->total_domains; i++) { + if (notifier[i].handle) { + service_notif_unregister_notifier( + notifier[i].handle, + &ar_snoc->service_notifier_nb); + } + } + kfree(notifier); + +out: + ath10k_err(ar, "PD restart not enabled: %d\n", ret); + + return NOTIFY_OK; +} + +int ath10k_snoc_pd_restart_enable(struct ath10k *ar) +{ + int ret; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Get service location\n"); + + ar_snoc->get_service_nb.notifier_call = + ath10k_snoc_get_service_location_notify; + ret = get_service_location(ATH10K_SERVICE_LOCATION_CLIENT_NAME, + ATH10K_WLAN_SERVICE_NAME, + &ar_snoc->get_service_nb); + if (ret) { + ath10k_err(ar, "Get service location failed: %d\n", ret); + goto out; + } + + return 0; +out: + ath10k_err(ar, "PD restart not enabled: %d\n", ret); + return ret; +} + +int ath10k_snoc_pdr_unregister_notifier(struct ath10k *ar) +{ + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < ar_snoc->total_domains; i++) { + if (ar_snoc->service_notifier[i].handle) + service_notif_unregister_notifier( + ar_snoc->service_notifier[i].handle, + &ar_snoc->service_notifier_nb); + } + + kfree(ar_snoc->service_notifier); + + ar_snoc->service_notifier = NULL; + + return 0; +} + +static int ath10k_snoc_modem_notifier_nb(struct notifier_block *nb, + unsigned long code, + void *data) +{ + struct notif_data *notif = data; + struct ath10k_snoc *ar_snoc = container_of(nb, struct ath10k_snoc, + modem_ssr_nb); + struct ath10k *ar = ar_snoc->ar; + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + if (code != SUBSYS_BEFORE_SHUTDOWN) + return NOTIFY_OK; + + if (notif->crashed) { + atomic_set(&ar_snoc->fw_crashed, 1); + atomic_set(&qmi_cfg->fw_ready, 0); + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Modem went down %d\n", + atomic_read(&ar_snoc->fw_crashed)); + + return NOTIFY_OK; +} + +int ath10k_snoc_modem_ssr_register_notifier(struct ath10k *ar) +{ + int ret = 0; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + ar_snoc->modem_ssr_nb.notifier_call = ath10k_snoc_modem_notifier_nb; + + ar_snoc->modem_notify_handler = + subsys_notif_register_notifier("modem", &ar_snoc->modem_ssr_nb); + + if (IS_ERR(ar_snoc->modem_notify_handler)) { + ret = PTR_ERR(ar_snoc->modem_notify_handler); + ath10k_err(ar, "Modem register notifier failed: %d\n", ret); + } + + return ret; +} + +int ath10k_snoc_modem_ssr_unregister_notifier(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + subsys_notif_unregister_notifier(ar_snoc->modem_notify_handler, + &ar_snoc->modem_ssr_nb); + ar_snoc->modem_notify_handler = NULL; + + return 0; +} + +static char * +ath10k_snoc_driver_event_to_str(enum ath10k_snoc_driver_event_type type) +{ + switch (type) { + case ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE: + return "SERVER_ARRIVE"; + case ATH10K_SNOC_DRIVER_EVENT_SERVER_EXIT: + return "SERVER_EXIT"; + case ATH10K_SNOC_DRIVER_EVENT_FW_READY_IND: + return "FW_READY"; + case ATH10K_SNOC_DRIVER_EVENT_MAX: + return "EVENT_MAX"; + } + + return "UNKNOWN"; +}; + +static int +ath10k_snoc_driver_event_post(enum ath10k_snoc_driver_event_type type, + u32 flags, void *data) +{ + int ret = 0; + int i = 0; + unsigned long irq_flags; + struct ath10k *ar = (struct ath10k *)data; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Posting event: %s type: %d\n", + ath10k_snoc_driver_event_to_str(type), type); + + if (type >= ATH10K_SNOC_DRIVER_EVENT_MAX) { + ath10k_err(ar, "Invalid Event type: %d, can't post", type); + return -EINVAL; + } + + spin_lock_irqsave(&qmi_cfg->event_lock, irq_flags); + + for (i = 0; i < ATH10K_SNOC_DRIVER_EVENT_MAX; i++) { + if (atomic_read(&qmi_cfg->qmi_ev_list[i].event_handled)) { + qmi_cfg->qmi_ev_list[i].type = type; + qmi_cfg->qmi_ev_list[i].data = data; + init_completion(&qmi_cfg->qmi_ev_list[i].complete); + qmi_cfg->qmi_ev_list[i].ret = + ATH10K_SNOC_EVENT_PENDING; + qmi_cfg->qmi_ev_list[i].sync = + !!(flags & ATH10K_SNOC_EVENT_SYNC); + atomic_set(&qmi_cfg->qmi_ev_list[i].event_handled, 0); + list_add_tail(&qmi_cfg->qmi_ev_list[i].list, + &qmi_cfg->event_list); + break; + } + } + + if (i >= ATH10K_SNOC_DRIVER_EVENT_MAX) + i = ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE; + + spin_unlock_irqrestore(&qmi_cfg->event_lock, irq_flags); + + queue_work(qmi_cfg->event_wq, &qmi_cfg->event_work); + + if (!(flags & ATH10K_SNOC_EVENT_SYNC)) + goto out; + + if (flags & ATH10K_SNOC_EVENT_UNINTERRUPTIBLE) + wait_for_completion(&qmi_cfg->qmi_ev_list[i].complete); + else + ret = wait_for_completion_interruptible( + &qmi_cfg->qmi_ev_list[i].complete); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Completed event: %s(%d)\n", + ath10k_snoc_driver_event_to_str(type), type); + + spin_lock_irqsave(&qmi_cfg->event_lock, irq_flags); + if (ret == -ERESTARTSYS && + qmi_cfg->qmi_ev_list[i].ret == ATH10K_SNOC_EVENT_PENDING) { + qmi_cfg->qmi_ev_list[i].sync = false; + atomic_set(&qmi_cfg->qmi_ev_list[i].event_handled, 1); + spin_unlock_irqrestore(&qmi_cfg->event_lock, irq_flags); + ret = -EINTR; + goto out; + } + spin_unlock_irqrestore(&qmi_cfg->event_lock, irq_flags); + +out: + return ret; +} + +static int +ath10k_snoc_wlan_mode_send_sync_msg(struct ath10k *ar, + enum wlfw_driver_mode_enum_v01 mode) +{ + int ret; + struct wlfw_wlan_mode_req_msg_v01 req; + struct wlfw_wlan_mode_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + if (!qmi_cfg || !qmi_cfg->wlfw_clnt) + return -ENODEV; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Sending Mode request, mode: %d\n", mode); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.mode = mode; + req.hw_debug_valid = 1; + req.hw_debug = 0; + + req_desc.max_msg_len = WLFW_WLAN_MODE_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_WLAN_MODE_REQ_V01; + req_desc.ei_array = wlfw_wlan_mode_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_WLAN_MODE_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_WLAN_MODE_RESP_V01; + resp_desc.ei_array = wlfw_wlan_mode_resp_msg_v01_ei; + + ret = qmi_send_req_wait(qmi_cfg->wlfw_clnt, + &req_desc, &req, sizeof(req), + &resp_desc, &resp, sizeof(resp), + WLFW_TIMEOUT_MS); + if (ret < 0) { + ath10k_err(ar, "Send mode req failed, mode: %d ret: %d\n", + mode, ret); + return ret; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + ath10k_err(ar, "QMI mode request rejected:"); + ath10k_err(ar, "mode:%d result:%d error:%d\n", + mode, resp.resp.result, resp.resp.error); + ret = resp.resp.result; + return ret; + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "wlan Mode request send success, mode: %d\n", mode); + return 0; +} + +static int +ath10k_snoc_wlan_cfg_send_sync_msg(struct ath10k *ar, + struct wlfw_wlan_cfg_req_msg_v01 *data) +{ + int ret; + struct wlfw_wlan_cfg_req_msg_v01 req; + struct wlfw_wlan_cfg_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + if (!qmi_cfg || !qmi_cfg->wlfw_clnt) + return -ENODEV; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Sending config request\n"); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + memcpy(&req, data, sizeof(req)); + + req_desc.max_msg_len = WLFW_WLAN_CFG_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_WLAN_CFG_REQ_V01; + req_desc.ei_array = wlfw_wlan_cfg_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_WLAN_CFG_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_WLAN_CFG_RESP_V01; + resp_desc.ei_array = wlfw_wlan_cfg_resp_msg_v01_ei; + + ret = qmi_send_req_wait(qmi_cfg->wlfw_clnt, + &req_desc, &req, sizeof(req), + &resp_desc, &resp, sizeof(resp), + WLFW_TIMEOUT_MS); + if (ret < 0) { + ath10k_err(ar, "Send config req failed %d\n", ret); + return ret; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + ath10k_err(ar, "QMI config request rejected:"); + ath10k_err(ar, "result:%d error:%d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + return ret; + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "wlan config request success..\n"); + return 0; +} + +int ath10k_snoc_qmi_wlan_enable(struct ath10k *ar, + struct ath10k_wlan_enable_cfg *config, + enum ath10k_driver_mode mode, + const char *host_version) +{ + struct wlfw_wlan_cfg_req_msg_v01 req; + u32 i; + int ret; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + unsigned long time_left; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Mode: %d, config: %p, host_version: %s\n", + mode, config, host_version); + + memset(&req, 0, sizeof(req)); + if (!config || !host_version) { + ath10k_err(ar, "WLAN_EN Config Invalid:%p: host_version:%p\n", + config, host_version); + ret = -EINVAL; + return ret; + } + + time_left = wait_event_timeout( + ath10k_fw_ready_wait_event, + (atomic_read(&qmi_cfg->fw_ready) && + atomic_read(&qmi_cfg->server_connected)), + msecs_to_jiffies(ATH10K_SNOC_WLAN_FW_READY_TIMEOUT)); + if (time_left == 0) { + ath10k_err(ar, "Wait for FW ready and server connect timed out\n"); + return -ETIMEDOUT; + } + + req.host_version_valid = 1; + strlcpy(req.host_version, host_version, + QMI_WLFW_MAX_STR_LEN_V01 + 1); + + req.tgt_cfg_valid = 1; + if (config->num_ce_tgt_cfg > QMI_WLFW_MAX_NUM_CE_V01) + req.tgt_cfg_len = QMI_WLFW_MAX_NUM_CE_V01; + else + req.tgt_cfg_len = config->num_ce_tgt_cfg; + for (i = 0; i < req.tgt_cfg_len; i++) { + req.tgt_cfg[i].pipe_num = config->ce_tgt_cfg[i].pipe_num; + req.tgt_cfg[i].pipe_dir = config->ce_tgt_cfg[i].pipe_dir; + req.tgt_cfg[i].nentries = config->ce_tgt_cfg[i].nentries; + req.tgt_cfg[i].nbytes_max = config->ce_tgt_cfg[i].nbytes_max; + req.tgt_cfg[i].flags = config->ce_tgt_cfg[i].flags; + } + + req.svc_cfg_valid = 1; + if (config->num_ce_svc_pipe_cfg > QMI_WLFW_MAX_NUM_SVC_V01) + req.svc_cfg_len = QMI_WLFW_MAX_NUM_SVC_V01; + else + req.svc_cfg_len = config->num_ce_svc_pipe_cfg; + for (i = 0; i < req.svc_cfg_len; i++) { + req.svc_cfg[i].service_id = config->ce_svc_cfg[i].service_id; + req.svc_cfg[i].pipe_dir = config->ce_svc_cfg[i].pipe_dir; + req.svc_cfg[i].pipe_num = config->ce_svc_cfg[i].pipe_num; + } + + req.shadow_reg_valid = 1; + if (config->num_shadow_reg_cfg > + QMI_WLFW_MAX_NUM_SHADOW_REG_V01) + req.shadow_reg_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V01; + else + req.shadow_reg_len = config->num_shadow_reg_cfg; + + memcpy(req.shadow_reg, config->shadow_reg_cfg, + sizeof(struct wlfw_shadow_reg_cfg_s_v01) * req.shadow_reg_len); + + ret = ath10k_snoc_wlan_cfg_send_sync_msg(ar, &req); + if (ret) { + ath10k_err(ar, "WLAN config send failed\n"); + return ret; + } + + ret = ath10k_snoc_wlan_mode_send_sync_msg(ar, mode); + if (ret) { + ath10k_err(ar, "WLAN mode send failed\n"); + return ret; + } + + return 0; +} + +int ath10k_snoc_qmi_wlan_disable(struct ath10k *ar) +{ + return ath10k_snoc_wlan_mode_send_sync_msg(ar, QMI_WLFW_OFF_V01); +} + +static int ath10k_snoc_ind_register_send_sync_msg(struct ath10k *ar) +{ + int ret; + struct wlfw_ind_register_req_msg_v01 req; + struct wlfw_ind_register_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Sending indication register message,\n"); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.client_id_valid = 1; + req.client_id = WLFW_CLIENT_ID; + req.fw_ready_enable_valid = 1; + req.fw_ready_enable = 1; + req.msa_ready_enable_valid = 1; + req.msa_ready_enable = 1; + + req_desc.max_msg_len = WLFW_IND_REGISTER_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_IND_REGISTER_REQ_V01; + req_desc.ei_array = wlfw_ind_register_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_IND_REGISTER_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_IND_REGISTER_RESP_V01; + resp_desc.ei_array = wlfw_ind_register_resp_msg_v01_ei; + + ret = qmi_send_req_wait(qmi_cfg->wlfw_clnt, + &req_desc, &req, sizeof(req), + &resp_desc, &resp, sizeof(resp), + WLFW_TIMEOUT_MS); + if (ret < 0) { + ath10k_err(ar, "Send indication register req failed %d\n", ret); + return ret; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + ath10k_err(ar, "QMI indication register request rejected:"); + ath10k_err(ar, "resut:%d error:%d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + return ret; + } + + return 0; +} + +static void ath10k_snoc_qmi_wlfw_clnt_notify_work(struct work_struct *work) +{ + int ret; + struct ath10k_snoc_qmi_config *qmi_cfg = + container_of(work, struct ath10k_snoc_qmi_config, + qmi_recv_msg_work); + struct ath10k_snoc *ar_snoc = + container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg); + struct ath10k *ar = ar_snoc->ar; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Receiving Event in work queue context\n"); + + do { + } while ((ret = qmi_recv_msg(qmi_cfg->wlfw_clnt)) == 0); + + if (ret != -ENOMSG) + ath10k_err(ar, "Error receiving message: %d\n", ret); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Receiving Event completed\n"); +} + +static void +ath10k_snoc_qmi_wlfw_clnt_notify(struct qmi_handle *handle, + enum qmi_event_type event, + void *notify_priv) +{ + struct ath10k_snoc_qmi_config *qmi_cfg = + (struct ath10k_snoc_qmi_config *)notify_priv; + struct ath10k_snoc *ar_snoc = + container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg); + struct ath10k *ar = ar_snoc->ar; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "QMI client notify: %d\n", event); + + if (!qmi_cfg || !qmi_cfg->wlfw_clnt) + return; + + switch (event) { + case QMI_RECV_MSG: + schedule_work(&qmi_cfg->qmi_recv_msg_work); + break; + default: + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Unknown Event: %d\n", event); + break; + } +} + +static void +ath10k_snoc_qmi_wlfw_clnt_ind(struct qmi_handle *handle, + unsigned int msg_id, void *msg, + unsigned int msg_len, void *ind_cb_priv) +{ + struct ath10k_snoc_qmi_config *qmi_cfg = + (struct ath10k_snoc_qmi_config *)ind_cb_priv; + struct ath10k_snoc *ar_snoc = + container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg); + struct ath10k *ar = ar_snoc->ar; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Received Ind 0x%x, msg_len: %d\n", msg_id, msg_len); + switch (msg_id) { + case QMI_WLFW_FW_READY_IND_V01: + ath10k_snoc_driver_event_post( + ATH10K_SNOC_DRIVER_EVENT_FW_READY_IND, 0, ar); + break; + case QMI_WLFW_MSA_READY_IND_V01: + qmi_cfg->msa_ready = true; + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Received MSA Ready, ind = 0x%x\n", msg_id); + break; + default: + ath10k_err(ar, "Invalid msg_id 0x%x\n", msg_id); + break; + } +} + +static int ath10k_snoc_driver_event_server_arrive(struct ath10k *ar) +{ + int ret = 0; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + if (!qmi_cfg) + return -ENODEV; + + qmi_cfg->wlfw_clnt = qmi_handle_create( + ath10k_snoc_qmi_wlfw_clnt_notify, qmi_cfg); + if (!qmi_cfg->wlfw_clnt) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "QMI client handle create failed\n"); + return -ENOMEM; + } + + ret = qmi_connect_to_service(qmi_cfg->wlfw_clnt, + WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_INS_ID_V01); + if (ret < 0) { + ath10k_err(ar, "QMI WLAN Service not found : %d\n", ret); + goto err_qmi_config; + } + + ret = qmi_register_ind_cb(qmi_cfg->wlfw_clnt, + ath10k_snoc_qmi_wlfw_clnt_ind, qmi_cfg); + if (ret < 0) { + ath10k_err(ar, "Failed to register indication callback: %d\n", + ret); + goto err_qmi_config; + } + + ret = ath10k_snoc_ind_register_send_sync_msg(ar); + if (ret) { + ath10k_err(ar, "Failed to config qmi ind register\n"); + goto err_qmi_config; + } + + atomic_set(&qmi_cfg->server_connected, 1); + wake_up_all(&ath10k_fw_ready_wait_event); + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "QMI Server Arrive Configuration Success\n"); + return 0; + +err_qmi_config: + qmi_handle_destroy(qmi_cfg->wlfw_clnt); + qmi_cfg->wlfw_clnt = NULL; + return ret; +} + +static int ath10k_snoc_driver_event_server_exit(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "QMI Server Exit event received\n"); + atomic_set(&qmi_cfg->fw_ready, 0); + qmi_cfg->msa_ready = false; + atomic_set(&qmi_cfg->server_connected, 0); + qmi_handle_destroy(qmi_cfg->wlfw_clnt); + return 0; +} + +static int ath10k_snoc_driver_event_fw_ready_ind(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "FW Ready event received.\n"); + atomic_set(&qmi_cfg->fw_ready, 1); + wake_up_all(&ath10k_fw_ready_wait_event); + + return 0; +} + +static void ath10k_snoc_driver_event_work(struct work_struct *work) +{ + int ret; + unsigned long irq_flags; + struct ath10k_snoc_qmi_driver_event *event; + struct ath10k_snoc_qmi_config *qmi_cfg = + container_of(work, struct ath10k_snoc_qmi_config, event_work); + struct ath10k_snoc *ar_snoc = + container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg); + struct ath10k *ar = ar_snoc->ar; + + spin_lock_irqsave(&qmi_cfg->event_lock, irq_flags); + + while (!list_empty(&qmi_cfg->event_list)) { + event = list_first_entry(&qmi_cfg->event_list, + struct ath10k_snoc_qmi_driver_event, + list); + list_del(&event->list); + spin_unlock_irqrestore(&qmi_cfg->event_lock, irq_flags); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Processing event: %s%s(%d)\n", + ath10k_snoc_driver_event_to_str(event->type), + event->sync ? "-sync" : "", event->type); + + switch (event->type) { + case ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE: + ret = ath10k_snoc_driver_event_server_arrive(ar); + break; + case ATH10K_SNOC_DRIVER_EVENT_SERVER_EXIT: + ret = ath10k_snoc_driver_event_server_exit(ar); + break; + case ATH10K_SNOC_DRIVER_EVENT_FW_READY_IND: + ret = ath10k_snoc_driver_event_fw_ready_ind(ar); + break; + default: + ath10k_err(ar, "Invalid Event type: %d", event->type); + kfree(event); + continue; + } + + atomic_set(&event->event_handled, 1); + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Event Processed: %s%s(%d), ret: %d\n", + ath10k_snoc_driver_event_to_str(event->type), + event->sync ? "-sync" : "", event->type, ret); + spin_lock_irqsave(&qmi_cfg->event_lock, irq_flags); + if (event->sync) { + event->ret = ret; + complete(&event->complete); + continue; + } + spin_unlock_irqrestore(&qmi_cfg->event_lock, irq_flags); + spin_lock_irqsave(&qmi_cfg->event_lock, irq_flags); + } + + spin_unlock_irqrestore(&qmi_cfg->event_lock, irq_flags); +} + +static int +ath10k_snoc_qmi_wlfw_clnt_svc_event_notify(struct notifier_block *this, + unsigned long code, + void *_cmd) +{ + int ret = 0; + struct ath10k_snoc_qmi_config *qmi_cfg = + container_of(this, struct ath10k_snoc_qmi_config, wlfw_clnt_nb); + struct ath10k_snoc *ar_snoc = + container_of(qmi_cfg, struct ath10k_snoc, qmi_cfg); + struct ath10k *ar = ar_snoc->ar; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Event Notify: code: %ld", code); + + switch (code) { + case QMI_SERVER_ARRIVE: + ret = ath10k_snoc_driver_event_post( + ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE, 0, ar); + break; + case QMI_SERVER_EXIT: + ret = ath10k_snoc_driver_event_post( + ATH10K_SNOC_DRIVER_EVENT_SERVER_EXIT, 0, ar); + break; + default: + ath10k_err(ar, "Invalid code: %ld", code); + break; + } + + return ret; +} + +int ath10k_snoc_start_qmi_service(struct ath10k *ar) +{ + int ret; + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + qmi_cfg->event_wq = alloc_workqueue("ath10k_snoc_driver_event", + WQ_UNBOUND, 1); + if (!qmi_cfg->event_wq) { + ath10k_err(ar, "Workqueue creation failed\n"); + return -EFAULT; + } + + spin_lock_init(&qmi_cfg->event_lock); + atomic_set(&qmi_cfg->fw_ready, 0); + atomic_set(&qmi_cfg->server_connected, 0); + + INIT_WORK(&qmi_cfg->event_work, ath10k_snoc_driver_event_work); + INIT_WORK(&qmi_cfg->qmi_recv_msg_work, + ath10k_snoc_qmi_wlfw_clnt_notify_work); + INIT_LIST_HEAD(&qmi_cfg->event_list); + + for (i = 0; i < ATH10K_SNOC_DRIVER_EVENT_MAX; i++) + atomic_set(&qmi_cfg->qmi_ev_list[i].event_handled, 1); + + qmi_cfg->wlfw_clnt_nb.notifier_call = + ath10k_snoc_qmi_wlfw_clnt_svc_event_notify; + ret = qmi_svc_event_notifier_register(WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_INS_ID_V01, + &qmi_cfg->wlfw_clnt_nb); + if (ret < 0) { + ath10k_err(ar, "Notifier register failed: %d\n", ret); + ret = -EFAULT; + goto out_destroy_wq; + } + + if (!icnss_is_fw_ready()) { + ath10k_err(ar, "failed to get fw ready indication\n"); + ret = -EFAULT; + goto err_fw_ready; + } + + atomic_set(&qmi_cfg->fw_ready, 1); + ath10k_dbg(ar, ATH10K_DBG_SNOC, "QMI service started successfully\n"); + return 0; + +err_fw_ready: + qmi_svc_event_notifier_unregister(WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_INS_ID_V01, + &qmi_cfg->wlfw_clnt_nb); +out_destroy_wq: + destroy_workqueue(qmi_cfg->event_wq); + return ret; +} + +void ath10k_snoc_stop_qmi_service(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_qmi_config *qmi_cfg = &ar_snoc->qmi_cfg; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Removing QMI service..\n"); + + wake_up_all(&ath10k_fw_ready_wait_event); + cancel_work_sync(&qmi_cfg->event_work); + cancel_work_sync(&qmi_cfg->qmi_recv_msg_work); + qmi_svc_event_notifier_unregister(WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_INS_ID_V01, + &qmi_cfg->wlfw_clnt_nb); + destroy_workqueue(qmi_cfg->event_wq); + qmi_handle_destroy(qmi_cfg->wlfw_clnt); + qmi_cfg = NULL; +} diff --git a/drivers/net/wireless/ath/ath10k/qmi.h b/drivers/net/wireless/ath/ath10k/qmi.h new file mode 100644 index 000000000000..29ad5acdf414 --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/qmi.h @@ -0,0 +1,156 @@ +/* Copyright (c) 2017, 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. + */ +#ifndef _QMI_H_ +#define _QMI_H_ + +#define ATH10K_SNOC_EVENT_PENDING 2989 +#define ATH10K_SNOC_EVENT_SYNC BIT(0) +#define ATH10K_SNOC_EVENT_UNINTERRUPTIBLE BIT(1) +#define ATH10K_SNOC_WLAN_FW_READY_TIMEOUT 8000 + +#define WLFW_SERVICE_INS_ID_V01 0 +#define WLFW_CLIENT_ID 0x41544851 +#define WLFW_TIMEOUT_MS 20000 + +enum ath10k_snoc_driver_event_type { + ATH10K_SNOC_DRIVER_EVENT_SERVER_ARRIVE, + ATH10K_SNOC_DRIVER_EVENT_SERVER_EXIT, + ATH10K_SNOC_DRIVER_EVENT_FW_READY_IND, + ATH10K_SNOC_DRIVER_EVENT_MAX, +}; + +/* enum ath10k_driver_mode: ath10k driver mode + * @ATH10K_MISSION: mission mode + * @ATH10K_FTM: ftm mode + * @ATH10K_EPPING: epping mode + * @ATH10K_OFF: off mode + */ +enum ath10k_driver_mode { + ATH10K_MISSION, + ATH10K_FTM, + ATH10K_EPPING, + ATH10K_OFF +}; + +/* struct ath10k_ce_tgt_pipe_cfg: target pipe configuration + * @pipe_num: pipe number + * @pipe_dir: pipe direction + * @nentries: entries in pipe + * @nbytes_max: pipe max size + * @flags: pipe flags + * @reserved: reserved + */ +struct ath10k_ce_tgt_pipe_cfg { + u32 pipe_num; + u32 pipe_dir; + u32 nentries; + u32 nbytes_max; + u32 flags; + u32 reserved; +}; + +/* struct ath10k_ce_svc_pipe_cfg: service pipe configuration + * @service_id: target version + * @pipe_dir: pipe direction + * @pipe_num: pipe number + */ +struct ath10k_ce_svc_pipe_cfg { + u32 service_id; + u32 pipe_dir; + u32 pipe_num; +}; + +/* struct ath10k_shadow_reg_cfg: shadow register configuration + * @ce_id: copy engine id + * @reg_offset: offset to copy engine + */ +struct ath10k_shadow_reg_cfg { + u16 ce_id; + u16 reg_offset; +}; + +/* struct ath10k_wlan_enable_cfg: wlan enable configuration + * @num_ce_tgt_cfg: no of ce target configuration + * @ce_tgt_cfg: target ce configuration + * @num_ce_svc_pipe_cfg: no of ce service configuration + * @ce_svc_cfg: ce service configuration + * @num_shadow_reg_cfg: no of shadow registers + * @shadow_reg_cfg: shadow register configuration + */ +struct ath10k_wlan_enable_cfg { + u32 num_ce_tgt_cfg; + struct ath10k_ce_tgt_pipe_cfg *ce_tgt_cfg; + u32 num_ce_svc_pipe_cfg; + struct ath10k_ce_svc_pipe_cfg *ce_svc_cfg; + u32 num_shadow_reg_cfg; + struct ath10k_shadow_reg_cfg *shadow_reg_cfg; +}; + +/* struct ath10k_snoc_qmi_driver_event: qmi driver event + * event_handled: event handled by event work handler + * sync: event synced + * ret: event received return value + * list: list to queue qmi event for process + * type: driver event type + * complete: completion for event handle complete + * data: encapsulate driver data for event handler callback + */ +struct ath10k_snoc_qmi_driver_event { + atomic_t event_handled; + bool sync; + int ret; + struct list_head list; + enum ath10k_snoc_driver_event_type type; + struct completion complete; + void *data; +}; + +/* struct ath10k_snoc_qmi_config: qmi service configuration + * fw_ready: wlan firmware ready for wlan operation + * msa_ready: wlan firmware msa memory ready for board data download + * server_connected: qmi server connected + * event_work: QMI event work + * event_list: QMI event list + * qmi_recv_msg_work: QMI message receive work + * event_wq: QMI event work queue + * wlfw_clnt_nb: WLAN firmware indication callback + * wlfw_clnt: QMI notifier handler for wlan firmware + * qmi_ev_list: QMI event list + * event_lock: spinlock for qmi event work queue + */ +struct ath10k_snoc_qmi_config { + atomic_t fw_ready; + bool msa_ready; + atomic_t server_connected; + struct work_struct event_work; + struct list_head event_list; + struct work_struct qmi_recv_msg_work; + struct workqueue_struct *event_wq; + struct notifier_block wlfw_clnt_nb; + struct qmi_handle *wlfw_clnt; + struct ath10k_snoc_qmi_driver_event + qmi_ev_list[ATH10K_SNOC_DRIVER_EVENT_MAX]; + spinlock_t event_lock; /* spinlock for qmi event work queue */ +}; + +int ath10k_snoc_pd_restart_enable(struct ath10k *ar); +int ath10k_snoc_modem_ssr_register_notifier(struct ath10k *ar); +int ath10k_snoc_modem_ssr_unregister_notifier(struct ath10k *ar); +int ath10k_snoc_pdr_unregister_notifier(struct ath10k *ar); +int ath10k_snoc_start_qmi_service(struct ath10k *ar); +void ath10k_snoc_stop_qmi_service(struct ath10k *ar); +int ath10k_snoc_qmi_wlan_enable(struct ath10k *ar, + struct ath10k_wlan_enable_cfg *config, + enum ath10k_driver_mode mode, + const char *host_version); +int ath10k_snoc_qmi_wlan_disable(struct ath10k *ar); +#endif diff --git a/drivers/net/wireless/ath/ath10k/rx_desc.h b/drivers/net/wireless/ath/ath10k/rx_desc.h index ca8d16884af1..5499bd2712e4 100644 --- a/drivers/net/wireless/ath/ath10k/rx_desc.h +++ b/drivers/net/wireless/ath/ath10k/rx_desc.h @@ -205,12 +205,25 @@ struct rx_attention { * descriptor. */ +#ifndef CONFIG_ATH10K_SNOC struct rx_frag_info { u8 ring0_more_count; u8 ring1_more_count; u8 ring2_more_count; u8 ring3_more_count; } __packed; +#else +struct rx_frag_info { + u8 ring0_more_count; + u8 ring1_more_count; + u8 ring2_more_count; + u8 ring3_more_count; + u8 ring4_more_count; + u8 ring5_more_count; + u8 ring6_more_count; + u8 ring7_more_count; +} __packed; +#endif /* * ring0_more_count @@ -239,6 +252,9 @@ enum htt_rx_mpdu_encrypt_type { HTT_RX_MPDU_ENCRYPT_WAPI = 5, HTT_RX_MPDU_ENCRYPT_AES_CCM_WPA2 = 6, HTT_RX_MPDU_ENCRYPT_NONE = 7, + HTT_RX_MPDU_ENCRYPT_AES_CCM256_WPA2 = 8, + HTT_RX_MPDU_ENCRYPT_AES_GCMP_WPA2 = 9, + HTT_RX_MPDU_ENCRYPT_AES_GCMP256_WPA2 = 10, }; #define RX_MPDU_START_INFO0_PEER_IDX_MASK 0x000007ff @@ -465,11 +481,23 @@ struct rx_msdu_start_qca99x0 { __le32 info2; /* %RX_MSDU_START_INFO2_ */ } __packed; +#ifdef CONFIG_ATH10K_SNOC +struct rx_msdu_start_wcn3990 { + __le32 info3; +} __packed; +#else +struct rx_msdu_start_wcn3990 { +} __packed; +#endif + struct rx_msdu_start { struct rx_msdu_start_common common; union { struct rx_msdu_start_qca99x0 qca99x0; } __packed; + union { + struct rx_msdu_start_wcn3990 wcn3990; + } __packed; } __packed; /* @@ -589,11 +617,21 @@ struct rx_msdu_end_qca99x0 { __le32 info2; } __packed; +struct rx_msdu_end_wcn3990 { + __le32 rule_indication_0; + __le32 rule_indication_1; + __le32 rule_indication_2; + __le32 rule_indication_3; +} __packed; + struct rx_msdu_end { struct rx_msdu_end_common common; union { struct rx_msdu_end_qca99x0 qca99x0; } __packed; +#ifdef CONFIG_ATH10K_SNOC + struct rx_msdu_end_wcn3990 wcn3990; +#endif } __packed; /* @@ -656,26 +694,6 @@ struct rx_msdu_end { * Reserved: HW should fill with zero. FW should ignore. */ -#define RX_PPDU_START_SIG_RATE_SELECT_OFDM 0 -#define RX_PPDU_START_SIG_RATE_SELECT_CCK 1 - -#define RX_PPDU_START_SIG_RATE_OFDM_48 0 -#define RX_PPDU_START_SIG_RATE_OFDM_24 1 -#define RX_PPDU_START_SIG_RATE_OFDM_12 2 -#define RX_PPDU_START_SIG_RATE_OFDM_6 3 -#define RX_PPDU_START_SIG_RATE_OFDM_54 4 -#define RX_PPDU_START_SIG_RATE_OFDM_36 5 -#define RX_PPDU_START_SIG_RATE_OFDM_18 6 -#define RX_PPDU_START_SIG_RATE_OFDM_9 7 - -#define RX_PPDU_START_SIG_RATE_CCK_LP_11 0 -#define RX_PPDU_START_SIG_RATE_CCK_LP_5_5 1 -#define RX_PPDU_START_SIG_RATE_CCK_LP_2 2 -#define RX_PPDU_START_SIG_RATE_CCK_LP_1 3 -#define RX_PPDU_START_SIG_RATE_CCK_SP_11 4 -#define RX_PPDU_START_SIG_RATE_CCK_SP_5_5 5 -#define RX_PPDU_START_SIG_RATE_CCK_SP_2 6 - #define HTT_RX_PPDU_START_PREAMBLE_LEGACY 0x04 #define HTT_RX_PPDU_START_PREAMBLE_HT 0x08 #define HTT_RX_PPDU_START_PREAMBLE_HT_WITH_TXBF 0x09 @@ -711,25 +729,6 @@ struct rx_msdu_end { /* No idea what this flag means. It seems to be always set in rate. */ #define RX_PPDU_START_RATE_FLAG BIT(3) -enum rx_ppdu_start_rate { - RX_PPDU_START_RATE_OFDM_48M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_OFDM_48M, - RX_PPDU_START_RATE_OFDM_24M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_OFDM_24M, - RX_PPDU_START_RATE_OFDM_12M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_OFDM_12M, - RX_PPDU_START_RATE_OFDM_6M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_OFDM_6M, - RX_PPDU_START_RATE_OFDM_54M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_OFDM_54M, - RX_PPDU_START_RATE_OFDM_36M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_OFDM_36M, - RX_PPDU_START_RATE_OFDM_18M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_OFDM_18M, - RX_PPDU_START_RATE_OFDM_9M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_OFDM_9M, - - RX_PPDU_START_RATE_CCK_LP_11M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_CCK_LP_11M, - RX_PPDU_START_RATE_CCK_LP_5_5M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_CCK_LP_5_5M, - RX_PPDU_START_RATE_CCK_LP_2M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_CCK_LP_2M, - RX_PPDU_START_RATE_CCK_LP_1M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_CCK_LP_1M, - RX_PPDU_START_RATE_CCK_SP_11M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_CCK_SP_11M, - RX_PPDU_START_RATE_CCK_SP_5_5M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_CCK_SP_5_5M, - RX_PPDU_START_RATE_CCK_SP_2M = RX_PPDU_START_RATE_FLAG | ATH10K_HW_RATE_CCK_SP_2M, -}; - struct rx_ppdu_start { struct { u8 pri20_mhz; @@ -992,9 +991,51 @@ struct rx_ppdu_end_qca6174 { struct rx_pkt_end { __le32 info0; /* %RX_PKT_END_INFO0_ */ +#ifndef CONFIG_ATH10K_SNOC __le32 phy_timestamp_1; __le32 phy_timestamp_2; - __le32 rx_location_info; /* %RX_LOCATION_INFO_ */ +#else + __le64 phy_timestamp_1; + __le64 phy_timestamp_2; +#endif +} __packed; + +#define RX_LOCATION_INFO0_RTT_FAC_LEGACY_MASK 0x00003fff +#define RX_LOCATION_INFO0_RTT_FAC_LEGACY_LSB 0 +#define RX_LOCATION_INFO0_RTT_FAC_VHT_MASK 0x1fff8000 +#define RX_LOCATION_INFO0_RTT_FAC_VHT_LSB 15 +#define RX_LOCATION_INFO0_RTT_STRONGEST_CHAIN_MASK 0xc0000000 +#define RX_LOCATION_INFO0_RTT_STRONGEST_CHAIN_LSB 30 +#define RX_LOCATION_INFO0_RTT_FAC_LEGACY_STATUS BIT(14) +#define RX_LOCATION_INFO0_RTT_FAC_VHT_STATUS BIT(29) + +#define RX_LOCATION_INFO1_RTT_PREAMBLE_TYPE_MASK 0x0000000c +#define RX_LOCATION_INFO1_RTT_PREAMBLE_TYPE_LSB 2 +#define RX_LOCATION_INFO1_PKT_BW_MASK 0x00000030 +#define RX_LOCATION_INFO1_PKT_BW_LSB 4 +#define RX_LOCATION_INFO1_SKIP_P_SKIP_BTCF_MASK 0x0000ff00 +#define RX_LOCATION_INFO1_SKIP_P_SKIP_BTCF_LSB 8 +#define RX_LOCATION_INFO1_RTT_MSC_RATE_MASK 0x000f0000 +#define RX_LOCATION_INFO1_RTT_MSC_RATE_LSB 16 +#define RX_LOCATION_INFO1_RTT_PBD_LEG_BW_MASK 0x00300000 +#define RX_LOCATION_INFO1_RTT_PBD_LEG_BW_LSB 20 +#define RX_LOCATION_INFO1_TIMING_BACKOFF_MASK 0x07c00000 +#define RX_LOCATION_INFO1_TIMING_BACKOFF_LSB 22 +#define RX_LOCATION_INFO1_RTT_TX_FRAME_PHASE_MASK 0x18000000 +#define RX_LOCATION_INFO1_RTT_TX_FRAME_PHASE_LSB 27 +#define RX_LOCATION_INFO1_RTT_CFR_STATUS BIT(0) +#define RX_LOCATION_INFO1_RTT_CIR_STATUS BIT(1) +#define RX_LOCATION_INFO1_RTT_GI_TYPE BIT(7) +#define RX_LOCATION_INFO1_RTT_MAC_PHY_PHASE BIT(29) +#define RX_LOCATION_INFO1_RTT_TX_DATA_START_X_PHASE BIT(30) +#define RX_LOCATION_INFO1_RX_LOCATION_VALID BIT(31) + +struct rx_location_info { + __le32 rx_location_info0; /* %RX_LOCATION_INFO0_ */ + __le32 rx_location_info1; /* %RX_LOCATION_INFO1_ */ +#ifdef CONFIG_ATH10K_SNOC + __le32 rx_location_info2; /* %RX_LOCATION_INFO2_ */ +#endif } __packed; enum rx_phy_ppdu_end_info0 { @@ -1067,6 +1108,7 @@ struct rx_phy_ppdu_end { struct rx_ppdu_end_qca99x0 { struct rx_pkt_end rx_pkt_end; + __le32 rx_location_info; /* %RX_LOCATION_INFO_ */ struct rx_phy_ppdu_end rx_phy_ppdu_end; __le32 rx_timing_offset; /* %RX_PPDU_END_RX_TIMING_OFFSET_ */ __le32 rx_info; /* %RX_PPDU_END_RX_INFO_ */ @@ -1074,12 +1116,44 @@ struct rx_ppdu_end_qca99x0 { __le16 info1; /* %RX_PPDU_END_INFO1_ */ } __packed; +struct rx_ppdu_end_qca9984 { + struct rx_pkt_end rx_pkt_end; + struct rx_location_info rx_location_info; + struct rx_phy_ppdu_end rx_phy_ppdu_end; + __le32 rx_timing_offset; /* %RX_PPDU_END_RX_TIMING_OFFSET_ */ + __le32 rx_info; /* %RX_PPDU_END_RX_INFO_ */ + __le16 bb_length; + __le16 info1; /* %RX_PPDU_END_INFO1_ */ +} __packed; + +struct rx_timing_offset { + __le32 timing_offset; +}; + +struct rx_ppdu_end_wcn3990 { + __le32 reserved_info_0; + __le32 reserved_info_1; + __le32 rx_antenna_info; + __le32 rx_coex_info; + __le32 rx_mpdu_cnt_info; + __le32 rx_bb_length; + __le64 phy_timestamp_tx; + struct rx_pkt_end rx_pkt_end; + struct rx_phy_ppdu_end rx_phy_ppdu_end; + struct rx_timing_offset rx_timing_offset; + struct rx_location_info rx_location_info; +} __packed; + struct rx_ppdu_end { struct rx_ppdu_end_common common; union { struct rx_ppdu_end_qca988x qca988x; struct rx_ppdu_end_qca6174 qca6174; struct rx_ppdu_end_qca99x0 qca99x0; + struct rx_ppdu_end_qca9984 qca9984; +#ifdef CONFIG_ATH10K_SNOC + struct rx_ppdu_end_wcn3990 wcn3990; +#endif } __packed; } __packed; diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c new file mode 100644 index 000000000000..638088ec45c9 --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -0,0 +1,1821 @@ +/* Copyright (c) 2005-2011 Atheros Communications Inc. + * Copyright (c) 2011-2013 Qualcomm Atheros, Inc. + * Copyright (c) 2017-2018 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/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/bitops.h> +#include <linux/suspend.h> +#include "core.h" +#include "debug.h" +#include "hif.h" +#include "htc.h" +#include "ce.h" +#include "snoc.h" +#include "qmi.h" +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> + +#define WCN3990_MAX_IRQ 12 +#define WCN3990_WAKE_IRQ_CE 2 + +const char *ce_name[WCN3990_MAX_IRQ] = { + "WLAN_CE_0", + "WLAN_CE_1", + "WLAN_CE_2", + "WLAN_CE_3", + "WLAN_CE_4", + "WLAN_CE_5", + "WLAN_CE_6", + "WLAN_CE_7", + "WLAN_CE_8", + "WLAN_CE_9", + "WLAN_CE_10", + "WLAN_CE_11", +}; + +#define ATH10K_SNOC_TARGET_WAIT 3000 +#define ATH10K_SNOC_NUM_WARM_RESET_ATTEMPTS 3 +#define SNOC_HIF_POWER_DOWN_DELAY 30 +#define ATH10K_MAX_PROP_SIZE 32 + +static void ath10k_snoc_buffer_cleanup(struct ath10k *ar); +static int ath10k_snoc_request_irq(struct ath10k *ar); +static void ath10k_snoc_free_irq(struct ath10k *ar); +static void ath10k_snoc_htc_tx_cb(struct ath10k_ce_pipe *ce_state); +static void ath10k_snoc_htc_rx_cb(struct ath10k_ce_pipe *ce_state); +static void ath10k_snoc_htt_tx_cb(struct ath10k_ce_pipe *ce_state); +static void ath10k_snoc_htt_rx_cb(struct ath10k_ce_pipe *ce_state); +static void ath10k_snoc_htt_htc_rx_cb(struct ath10k_ce_pipe *ce_state); + +static struct ce_attr host_ce_config_wlan[] = { + /* CE0: host->target HTC control streams */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 16, + .src_sz_max = 2048, + .dest_nentries = 0, + .send_cb = ath10k_snoc_htc_tx_cb, + }, + + /* CE1: target->host HTT + HTC control */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 0, + .src_sz_max = 2048, + .dest_nentries = 512, + .recv_cb = ath10k_snoc_htt_htc_rx_cb, + }, + + /* CE2: target->host WMI */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 0, + .src_sz_max = 2048, + .dest_nentries = 64, + .recv_cb = ath10k_snoc_htc_rx_cb, + }, + + /* CE3: host->target WMI */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 32, + .src_sz_max = 2048, + .dest_nentries = 0, + .send_cb = ath10k_snoc_htc_tx_cb, + }, + + /* CE4: host->target HTT */ + { + .flags = CE_ATTR_FLAGS | CE_ATTR_DIS_INTR, + .src_nentries = 2048, + .src_sz_max = 2048, + .dest_nentries = 0, + .send_cb = ath10k_snoc_htt_tx_cb, + }, + + /* CE5: target->host HTT (ipa_uc->target ) */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 0, + .src_sz_max = 0, + .dest_nentries = 0, + .recv_cb = ath10k_snoc_htt_rx_cb, + }, + + /* CE6: target autonomous hif_memcpy */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 0, + .src_sz_max = 0, + .dest_nentries = 0, + }, + + /* CE7: ce_diag, the Diagnostic Window */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 2, + .src_sz_max = 2048, + .dest_nentries = 2, + }, + + /* CE8: Target to uMC */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 0, + .src_sz_max = 2048, + .dest_nentries = 128, + }, + + /* CE9 target->host HTT */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 0, + .src_sz_max = 2048, + .dest_nentries = 512, + .recv_cb = ath10k_snoc_htt_htc_rx_cb, + }, + + /* CE10: target->host HTT */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 0, + .src_sz_max = 2048, + .dest_nentries = 512, + .recv_cb = ath10k_snoc_htt_htc_rx_cb, + }, + + /* CE11: target -> host PKTLOG */ + { + .flags = CE_ATTR_FLAGS, + .src_nentries = 0, + .src_sz_max = 2048, + .dest_nentries = 512, + .recv_cb = ath10k_snoc_htt_htc_rx_cb, + }, +}; + +static struct ce_pipe_config target_ce_config_wlan[] = { + /* CE0: host->target HTC control and raw streams */ + { + .pipenum = __cpu_to_le32(0), + .pipedir = __cpu_to_le32(PIPEDIR_OUT), + .nentries = __cpu_to_le32(32), + .nbytes_max = __cpu_to_le32(2048), + .flags = __cpu_to_le32(CE_ATTR_FLAGS), + .reserved = __cpu_to_le32(0), + }, + + /* CE1: target->host HTT + HTC control */ + { + .pipenum = __cpu_to_le32(1), + .pipedir = __cpu_to_le32(PIPEDIR_IN), + .nentries = __cpu_to_le32(32), + .nbytes_max = __cpu_to_le32(2048), + .flags = __cpu_to_le32(CE_ATTR_FLAGS), + .reserved = __cpu_to_le32(0), + }, + + /* CE2: target->host WMI */ + { + .pipenum = __cpu_to_le32(2), + .pipedir = __cpu_to_le32(PIPEDIR_IN), + .nentries = __cpu_to_le32(64), + .nbytes_max = __cpu_to_le32(2048), + .flags = __cpu_to_le32(CE_ATTR_FLAGS), + .reserved = __cpu_to_le32(0), + }, + + /* CE3: host->target WMI */ + { + .pipenum = __cpu_to_le32(3), + .pipedir = __cpu_to_le32(PIPEDIR_OUT), + .nentries = __cpu_to_le32(32), + .nbytes_max = __cpu_to_le32(2048), + .flags = __cpu_to_le32(CE_ATTR_FLAGS), + .reserved = __cpu_to_le32(0), + }, + + /* CE4: host->target HTT */ + { + .pipenum = __cpu_to_le32(4), + .pipedir = __cpu_to_le32(PIPEDIR_OUT), + .nentries = __cpu_to_le32(256), + .nbytes_max = __cpu_to_le32(256), + .flags = __cpu_to_le32(CE_ATTR_FLAGS | CE_ATTR_DIS_INTR), + .reserved = __cpu_to_le32(0), + }, + + /* CE5: target->host HTT (HIF->HTT) */ + { + .pipenum = __cpu_to_le32(5), + .pipedir = __cpu_to_le32(PIPEDIR_OUT), + .nentries = __cpu_to_le32(1024), + .nbytes_max = __cpu_to_le32(64), + .flags = __cpu_to_le32(CE_ATTR_FLAGS | CE_ATTR_DIS_INTR), + .reserved = __cpu_to_le32(0), + }, + + /* CE6: Reserved for target autonomous hif_memcpy */ + { + .pipenum = __cpu_to_le32(6), + .pipedir = __cpu_to_le32(PIPEDIR_INOUT), + .nentries = __cpu_to_le32(32), + .nbytes_max = __cpu_to_le32(16384), + .flags = __cpu_to_le32(CE_ATTR_FLAGS), + .reserved = __cpu_to_le32(0), + }, + + /* CE7 used only by Host */ + { + .pipenum = __cpu_to_le32(7), + .pipedir = __cpu_to_le32(4), + .nentries = __cpu_to_le32(0), + .nbytes_max = __cpu_to_le32(0), + .flags = __cpu_to_le32(CE_ATTR_FLAGS | CE_ATTR_DIS_INTR), + .reserved = __cpu_to_le32(0), + }, + + /* CE8 Target to uMC */ + { + .pipenum = __cpu_to_le32(8), + .pipedir = __cpu_to_le32(PIPEDIR_IN), + .nentries = __cpu_to_le32(32), + .nbytes_max = __cpu_to_le32(2048), + .flags = __cpu_to_le32(0), + .reserved = __cpu_to_le32(0), + }, + + /* CE9 target->host HTT */ + { + .pipenum = __cpu_to_le32(9), + .pipedir = __cpu_to_le32(PIPEDIR_IN), + .nentries = __cpu_to_le32(32), + .nbytes_max = __cpu_to_le32(2048), + .flags = __cpu_to_le32(CE_ATTR_FLAGS), + .reserved = __cpu_to_le32(0), + }, + + /* CE10 target->host HTT */ + { + .pipenum = __cpu_to_le32(10), + .pipedir = __cpu_to_le32(PIPEDIR_IN), + .nentries = __cpu_to_le32(32), + .nbytes_max = __cpu_to_le32(2048), + .flags = __cpu_to_le32(CE_ATTR_FLAGS), + .reserved = __cpu_to_le32(0), + }, + + /* CE11 target autonomous qcache memcpy */ + { + .pipenum = __cpu_to_le32(11), + .pipedir = __cpu_to_le32(PIPEDIR_IN), + .nentries = __cpu_to_le32(32), + .nbytes_max = __cpu_to_le32(2048), + .flags = __cpu_to_le32(CE_ATTR_FLAGS), + .reserved = __cpu_to_le32(0), + }, +}; + +static struct service_to_pipe target_service_to_ce_map_wlan[] = { + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_DATA_VO), + __cpu_to_le32(PIPEDIR_OUT), /* out = UL = host -> target */ + __cpu_to_le32(3), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_DATA_VO), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(2), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_DATA_BK), + __cpu_to_le32(PIPEDIR_OUT), /* out = UL = host -> target */ + __cpu_to_le32(3), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_DATA_BK), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(2), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_DATA_BE), + __cpu_to_le32(PIPEDIR_OUT), /* out = UL = host -> target */ + __cpu_to_le32(3), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_DATA_BE), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(2), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_DATA_VI), + __cpu_to_le32(PIPEDIR_OUT), /* out = UL = host -> target */ + __cpu_to_le32(3), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_DATA_VI), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(2), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_CONTROL), + __cpu_to_le32(PIPEDIR_OUT), /* out = UL = host -> target */ + __cpu_to_le32(3), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_WMI_CONTROL), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(2), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_RSVD_CTRL), + __cpu_to_le32(PIPEDIR_OUT), /* out = UL = host -> target */ + __cpu_to_le32(0), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_RSVD_CTRL), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(2), + }, + { /* not used */ + __cpu_to_le32(ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS), + __cpu_to_le32(PIPEDIR_OUT), /* out = UL = host -> target */ + __cpu_to_le32(0), + }, + { /* not used */ + __cpu_to_le32(ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(2), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_HTT_DATA_MSG), + __cpu_to_le32(PIPEDIR_OUT), /* out = UL = host -> target */ + __cpu_to_le32(4), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_HTT_DATA_MSG), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(1), + }, + { + __cpu_to_le32(ATH10K_HTC_SVC_ID_HTT_IPA_MSG), + __cpu_to_le32(PIPEDIR_OUT),/* IPA service */ + __cpu_to_le32(5), + }, + { /* in = DL = target -> host */ + __cpu_to_le32(ATH10K_HTC_SVC_ID_HTT_DATA2_MSG), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(9), + }, + { /* in = DL = target -> host */ + __cpu_to_le32(ATH10K_HTC_SVC_ID_HTT_DATA3_MSG), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(10), + }, + { /* in = DL = target -> host pktlog */ + __cpu_to_le32(ATH10K_HTC_SVC_ID_HTT_LOG_MSG), + __cpu_to_le32(PIPEDIR_IN), /* in = DL = target -> host */ + __cpu_to_le32(11), + }, + /* (Additions here) */ + + { /* must be last */ + __cpu_to_le32(0), + __cpu_to_le32(0), + __cpu_to_le32(0), + }, +}; + +#define WCN3990_SRC_WR_INDEX_OFFSET 0x3C +#define WCN3990_DST_WR_INDEX_OFFSET 0x40 + +static struct ath10k_shadow_reg_cfg target_shadow_reg_cfg_map[] = { + { 0, WCN3990_SRC_WR_INDEX_OFFSET}, + { 3, WCN3990_SRC_WR_INDEX_OFFSET}, + { 4, WCN3990_SRC_WR_INDEX_OFFSET}, + { 5, WCN3990_SRC_WR_INDEX_OFFSET}, + { 7, WCN3990_SRC_WR_INDEX_OFFSET}, + { 1, WCN3990_DST_WR_INDEX_OFFSET}, + { 2, WCN3990_DST_WR_INDEX_OFFSET}, + { 7, WCN3990_DST_WR_INDEX_OFFSET}, + { 8, WCN3990_DST_WR_INDEX_OFFSET}, + { 9, WCN3990_DST_WR_INDEX_OFFSET}, + { 10, WCN3990_DST_WR_INDEX_OFFSET}, + { 11, WCN3990_DST_WR_INDEX_OFFSET}, +}; + +static bool ath10k_snoc_has_fw_crashed(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + return atomic_read(&ar_snoc->fw_crashed); +} + +static void ath10k_snoc_fw_crashed_clear(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + atomic_set(&ar_snoc->fw_crashed, 0); +} + +void ath10k_snoc_write32(struct ath10k *ar, u32 offset, u32 value) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + if (!ar_snoc) + return; + + iowrite32(value, ar_snoc->mem + offset); +} + +u32 ath10k_snoc_read32(struct ath10k *ar, u32 offset) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + u32 val; + + if (!ar_snoc) + return -EINVAL; + + val = ioread32(ar_snoc->mem + offset); + + return val; +} + +static int __ath10k_snoc_rx_post_buf(struct ath10k_snoc_pipe *pipe) +{ + struct ath10k *ar = pipe->hif_ce_state; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_ce_pipe *ce_pipe = pipe->ce_hdl; + struct sk_buff *skb; + dma_addr_t paddr; + int ret; + + skb = dev_alloc_skb(pipe->buf_sz); + if (!skb) + return -ENOMEM; + + WARN_ONCE((unsigned long)skb->data & 3, "unaligned skb"); + + paddr = dma_map_single(ar->dev, skb->data, + skb->len + skb_tailroom(skb), + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(ar->dev, paddr))) { + ath10k_warn(ar, "failed to dma map snoc rx buf\n"); + dev_kfree_skb_any(skb); + return -EIO; + } + + ATH10K_SKB_RXCB(skb)->paddr = paddr; + + spin_lock_bh(&ar_snoc->opaque_ctx.ce_lock); + ret = __ath10k_ce_rx_post_buf(ce_pipe, skb, paddr); + spin_unlock_bh(&ar_snoc->opaque_ctx.ce_lock); + if (ret) { + dma_unmap_single(ar->dev, paddr, skb->len + skb_tailroom(skb), + DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); + return ret; + } + + return 0; +} + +static void ath10k_snoc_rx_post_pipe(struct ath10k_snoc_pipe *pipe) +{ + struct ath10k *ar = pipe->hif_ce_state; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_ce_pipe *ce_pipe = pipe->ce_hdl; + int ret, num; + + if (pipe->buf_sz == 0) + return; + + if (!ce_pipe->dest_ring) + return; + + spin_lock_bh(&ar_snoc->opaque_ctx.ce_lock); + num = __ath10k_ce_rx_num_free_bufs(ce_pipe); + spin_unlock_bh(&ar_snoc->opaque_ctx.ce_lock); + while (num--) { + ret = __ath10k_snoc_rx_post_buf(pipe); + if (ret) { + if (ret == -ENOSPC) + break; + ath10k_warn(ar, "failed to post rx buf: %d\n", ret); + mod_timer(&ar_snoc->rx_post_retry, jiffies + + ATH10K_SNOC_RX_POST_RETRY_MS); + break; + } + } +} + +static void ath10k_snoc_rx_post(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + int i; + + for (i = 0; i < CE_COUNT; i++) + ath10k_snoc_rx_post_pipe(&ar_snoc->pipe_info[i]); +} + +static void ath10k_snoc_rx_replenish_retry(unsigned long ptr) +{ + struct ath10k *ar = (void *)ptr; + ath10k_snoc_rx_post(ar); +} + +static void ath10k_snoc_htc_tx_cb(struct ath10k_ce_pipe *ce_state) +{ + struct ath10k *ar = ce_state->ar; + struct sk_buff_head list; + struct sk_buff *skb; + + __skb_queue_head_init(&list); + while (ath10k_ce_completed_send_next(ce_state, (void **)&skb) == 0) { + if (!skb) + continue; + + __skb_queue_tail(&list, skb); + } + + while ((skb = __skb_dequeue(&list))) + ath10k_htc_tx_completion_handler(ar, skb); +} + +static void ath10k_snoc_process_rx_cb(struct ath10k_ce_pipe *ce_state, + void (*callback)(struct ath10k *ar, + struct sk_buff *skb)) +{ + struct ath10k *ar = ce_state->ar; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_pipe *pipe_info = &ar_snoc->pipe_info[ce_state->id]; + struct sk_buff *skb; + struct sk_buff_head list; + void *transfer_context; + unsigned int nbytes, max_nbytes; + + __skb_queue_head_init(&list); + while (ath10k_ce_completed_recv_next(ce_state, &transfer_context, + &nbytes) == 0) { + skb = transfer_context; + max_nbytes = skb->len + skb_tailroom(skb); + dma_unmap_single(ar->dev, ATH10K_SKB_RXCB(skb)->paddr, + max_nbytes, DMA_FROM_DEVICE); + + if (unlikely(max_nbytes < nbytes)) { + ath10k_warn(ar, "rxed more than expected (nbytes %d, max %d)", + nbytes, max_nbytes); + dev_kfree_skb_any(skb); + continue; + } + + skb_put(skb, nbytes); + __skb_queue_tail(&list, skb); + } + + while ((skb = __skb_dequeue(&list))) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, "snoc rx ce pipe %d len %d\n", + ce_state->id, skb->len); + + callback(ar, skb); + } + + ath10k_snoc_rx_post_pipe(pipe_info); +} + +static void ath10k_snoc_htc_rx_cb(struct ath10k_ce_pipe *ce_state) +{ + ath10k_snoc_process_rx_cb(ce_state, ath10k_htc_rx_completion_handler); +} + +static void ath10k_snoc_htt_htc_rx_cb(struct ath10k_ce_pipe *ce_state) +{ + /* CE4 polling needs to be done whenever CE pipe which transports + * HTT Rx (target->host) is processed. + */ + ath10k_ce_per_engine_service(ce_state->ar, CE_POLL_PIPE); + + ath10k_snoc_process_rx_cb(ce_state, ath10k_htc_rx_completion_handler); +} + +static void ath10k_snoc_htt_tx_cb(struct ath10k_ce_pipe *ce_state) +{ + struct ath10k *ar = ce_state->ar; + struct sk_buff *skb; + + while (ath10k_ce_completed_send_next(ce_state, (void **)&skb) == 0) { + if (!skb) + continue; + + dma_unmap_single(ar->dev, ATH10K_SKB_CB(skb)->paddr, + skb->len, DMA_TO_DEVICE); + ath10k_htt_hif_tx_complete(ar, skb); + } +} + +static void ath10k_snoc_htt_rx_deliver(struct ath10k *ar, struct sk_buff *skb) +{ + skb_pull(skb, sizeof(struct ath10k_htc_hdr)); + ath10k_htt_t2h_msg_handler(ar, skb); +} + +static void ath10k_snoc_htt_rx_cb(struct ath10k_ce_pipe *ce_state) +{ + ath10k_ce_per_engine_service(ce_state->ar, CE_POLL_PIPE); + ath10k_snoc_process_rx_cb(ce_state, ath10k_snoc_htt_rx_deliver); +} + +static int ath10k_snoc_hif_tx_sg(struct ath10k *ar, u8 pipe_id, + struct ath10k_hif_sg_item *items, int n_items) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_pipe *snoc_pipe; + struct ath10k_ce_pipe *ce_pipe; + int err, i = 0; + + if (!ar_snoc) + return -EINVAL; + + if (atomic_read(&ar_snoc->fw_crashed)) + return -ESHUTDOWN; + + snoc_pipe = &ar_snoc->pipe_info[pipe_id]; + ce_pipe = snoc_pipe->ce_hdl; + spin_lock_bh(&ar_snoc->opaque_ctx.ce_lock); + + for (i = 0; i < n_items - 1; i++) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "snoc tx item %d paddr %pad len %d n_items %d\n", + i, &items[i].paddr, items[i].len, n_items); + + if (ath10k_snoc_has_fw_crashed(ar)) + return -EINVAL; + + err = ath10k_ce_send_nolock(ce_pipe, + items[i].transfer_context, + items[i].paddr, + items[i].len, + items[i].transfer_id, + CE_SEND_FLAG_GATHER); + if (err) + goto err; + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "snoc tx item %d paddr %pad len %d n_items %d\n", + i, &items[i].paddr, items[i].len, n_items); + + err = ath10k_ce_send_nolock(ce_pipe, + items[i].transfer_context, + items[i].paddr, + items[i].len, + items[i].transfer_id, + 0); + if (err) + goto err; + + spin_unlock_bh(&ar_snoc->opaque_ctx.ce_lock); + return 0; + +err: + for (; i > 0; i--) + __ath10k_ce_send_revert(ce_pipe); + + spin_unlock_bh(&ar_snoc->opaque_ctx.ce_lock); + return err; +} + +static u16 ath10k_snoc_hif_get_free_queue_number(struct ath10k *ar, u8 pipe) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "hif get free queue number\n"); + + return ath10k_ce_num_free_src_entries(ar_snoc->pipe_info[pipe].ce_hdl); +} + +static void ath10k_snoc_hif_send_complete_check(struct ath10k *ar, u8 pipe, + int force) +{ + int resources; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "snoc hif send complete check\n"); + + if (!force) { + resources = ath10k_snoc_hif_get_free_queue_number(ar, pipe); + + if (resources > (host_ce_config_wlan[pipe].src_nentries >> 1)) + return; + } + ath10k_ce_per_engine_service(ar, pipe); +} + +static int ath10k_snoc_hif_map_service_to_pipe(struct ath10k *ar, + u16 service_id, + u8 *ul_pipe, u8 *dl_pipe) +{ + const struct service_to_pipe *entry; + bool ul_set = false, dl_set = false; + int i; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "snoc hif map service\n"); + + for (i = 0; i < ARRAY_SIZE(target_service_to_ce_map_wlan); i++) { + entry = &target_service_to_ce_map_wlan[i]; + + if (__le32_to_cpu(entry->service_id) != service_id) + continue; + + switch (__le32_to_cpu(entry->pipedir)) { + case PIPEDIR_NONE: + break; + case PIPEDIR_IN: + WARN_ON(dl_set); + *dl_pipe = __le32_to_cpu(entry->pipenum); + dl_set = true; + break; + case PIPEDIR_OUT: + WARN_ON(ul_set); + *ul_pipe = __le32_to_cpu(entry->pipenum); + ul_set = true; + break; + case PIPEDIR_INOUT: + WARN_ON(dl_set); + WARN_ON(ul_set); + *dl_pipe = __le32_to_cpu(entry->pipenum); + *ul_pipe = __le32_to_cpu(entry->pipenum); + dl_set = true; + ul_set = true; + break; + } + } + + return 0; +} + +static void ath10k_snoc_hif_get_default_pipe(struct ath10k *ar, + u8 *ul_pipe, u8 *dl_pipe) +{ + ath10k_dbg(ar, ATH10K_DBG_SNOC, "snoc hif get default pipe\n"); + + (void)ath10k_snoc_hif_map_service_to_pipe(ar, + ATH10K_HTC_SVC_ID_RSVD_CTRL, + ul_pipe, dl_pipe); +} + +static void ath10k_snoc_irq_disable(struct ath10k *ar) +{ + ath10k_ce_disable_interrupts(ar); +} + +static void ath10k_snoc_irq_enable(struct ath10k *ar) +{ + ath10k_ce_enable_interrupts(ar); +} + +static void ath10k_snoc_rx_pipe_cleanup(struct ath10k_snoc_pipe *snoc_pipe) +{ + struct ath10k *ar; + struct ath10k_ce_pipe *ce_pipe; + struct ath10k_ce_ring *ce_ring; + struct sk_buff *skb; + int i; + + ar = snoc_pipe->hif_ce_state; + ce_pipe = snoc_pipe->ce_hdl; + ce_ring = ce_pipe->dest_ring; + + if (!ce_ring) + return; + + if (!snoc_pipe->buf_sz) + return; + + for (i = 0; i < ce_ring->nentries; i++) { + skb = ce_ring->per_transfer_context[i]; + if (!skb) + continue; + + ce_ring->per_transfer_context[i] = NULL; + + dma_unmap_single(ar->dev, ATH10K_SKB_RXCB(skb)->paddr, + skb->len + skb_tailroom(skb), + DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); + } +} + +static void ath10k_snoc_tx_pipe_cleanup(struct ath10k_snoc_pipe *snoc_pipe) +{ + struct ath10k *ar; + struct ath10k_snoc *ar_snoc; + struct ath10k_ce_pipe *ce_pipe; + struct ath10k_ce_ring *ce_ring; + struct sk_buff *skb; + int i; + + ar = snoc_pipe->hif_ce_state; + ar_snoc = ath10k_snoc_priv(ar); + ce_pipe = snoc_pipe->ce_hdl; + ce_ring = ce_pipe->src_ring; + + if (!ce_ring) + return; + + if (!snoc_pipe->buf_sz) + return; + + for (i = 0; i < ce_ring->nentries; i++) { + skb = ce_ring->per_transfer_context[i]; + if (!skb) + continue; + + ce_ring->per_transfer_context[i] = NULL; + + ath10k_htc_tx_completion_handler(ar, skb); + } +} + +static void ath10k_snoc_buffer_cleanup(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + int pipe_num; + + del_timer_sync(&ar_snoc->rx_post_retry); + for (pipe_num = 0; pipe_num < CE_COUNT; pipe_num++) { + struct ath10k_snoc_pipe *pipe_info; + + pipe_info = &ar_snoc->pipe_info[pipe_num]; + ath10k_snoc_rx_pipe_cleanup(pipe_info); + ath10k_snoc_tx_pipe_cleanup(pipe_info); + } +} + +static void ath10k_snoc_flush(struct ath10k *ar) +{ + ath10k_snoc_buffer_cleanup(ar); +} + +static void ath10k_snoc_hif_stop(struct ath10k *ar) +{ + if (!ar) + return; + if (ath10k_snoc_has_fw_crashed(ar) || + test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) { + ath10k_snoc_free_irq(ar); + } else { + ath10k_snoc_irq_disable(ar); + } + + ath10k_snoc_flush(ar); + napi_synchronize(&ar->napi); + napi_disable(&ar->napi); + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot hif stop\n"); +} + +static int ath10k_snoc_alloc_pipes(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct ath10k_snoc_pipe *pipe; + int i, ret; + + for (i = 0; i < CE_COUNT; i++) { + pipe = &ar_snoc->pipe_info[i]; + pipe->ce_hdl = &ar_snoc->opaque_ctx.ce_states[i]; + pipe->pipe_num = i; + pipe->hif_ce_state = ar; + + ret = ath10k_ce_alloc_pipe(ar, i, &host_ce_config_wlan[i]); + if (ret) { + ath10k_err(ar, "failed to allocate copy engine pipe %d: %d\n", + i, ret); + return ret; + } + + pipe->buf_sz = (size_t)(host_ce_config_wlan[i].src_sz_max); + } + + return 0; +} + +static void ath10k_snoc_free_pipes(struct ath10k *ar) +{ + int i; + + for (i = 0; i < CE_COUNT; i++) + ath10k_ce_free_pipe(ar, i); +} + +static void ath10k_snoc_release_resource(struct ath10k *ar) +{ + netif_napi_del(&ar->napi); + ath10k_snoc_free_pipes(ar); +} + +static int ath10k_snoc_init_pipes(struct ath10k *ar) +{ + int i, ret; + + for (i = 0; i < CE_COUNT; i++) { + ret = ath10k_ce_init_pipe(ar, i, &host_ce_config_wlan[i]); + if (ret) { + ath10k_err(ar, "failed to initialize copy engine pipe %d: %d\n", + i, ret); + return ret; + } + } + + return 0; +} + +static void ath10k_snoc_hif_power_down(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot hif power down\n"); + msleep(SNOC_HIF_POWER_DOWN_DELAY); + + if (!atomic_read(&ar_snoc->pm_ops_inprogress)) + ath10k_snoc_qmi_wlan_disable(ar); + + ce_remove_rri_on_ddr(ar); +} + +int ath10k_snoc_get_ce_id(struct ath10k *ar, int irq) +{ + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < CE_COUNT_MAX; i++) { + if (ar_snoc->ce_irqs[i].irq_line == irq) + return i; + } + ath10k_err(ar, "No matching CE id for irq %d\n", irq); + + return -EINVAL; +} + +static irqreturn_t ath10k_snoc_per_engine_handler(int irq, void *arg) +{ + struct ath10k *ar = arg; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + int ce_id = ath10k_snoc_get_ce_id(ar, irq); + + if (ce_id < 0 || ce_id >= ARRAY_SIZE(ar_snoc->pipe_info)) { + ath10k_warn(ar, "unexpected/invalid irq %d ce_id %d\n", irq, + ce_id); + return IRQ_HANDLED; + } + + ath10k_snoc_irq_disable(ar); + napi_schedule(&ar->napi); + + return IRQ_HANDLED; +} + +static int ath10k_snoc_request_irq(struct ath10k *ar) +{ + int ret, id; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + int irqflags = IRQF_TRIGGER_RISING; + + for (id = 0; id < CE_COUNT_MAX; id++) { + ret = request_irq(ar_snoc->ce_irqs[id].irq_line, + ath10k_snoc_per_engine_handler, + irqflags, ce_name[id], ar); + if (ret) { + ath10k_err(ar, + "%s: cannot register CE %d irq handler, ret = %d", + __func__, id, ret); + atomic_set(&ar_snoc->ce_irqs[id].irq_req_stat, 0); + return ret; + } else { + atomic_set(&ar_snoc->ce_irqs[id].irq_req_stat, 1); + } + } + + return 0; +} + +static void ath10k_snoc_free_irq(struct ath10k *ar) +{ + int id; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (id = 0; id < CE_COUNT_MAX; id++) { + if (atomic_read(&ar_snoc->ce_irqs[id].irq_req_stat)) { + free_irq(ar_snoc->ce_irqs[id].irq_line, ar); + atomic_set(&ar_snoc->ce_irqs[id].irq_req_stat, 0); + } + } +} + +static int ath10k_snoc_get_soc_info(struct ath10k *ar) +{ + struct resource *res; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + struct platform_device *pdev; + + pdev = ar_snoc->dev; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "membase"); + if (!res) { + ath10k_err(ar, "Memory base not found in DT\n"); + return -EINVAL; + } + + ar_snoc->mem_pa = res->start; + ar_snoc->mem = devm_ioremap(&pdev->dev, ar_snoc->mem_pa, + resource_size(res)); + if (!ar_snoc->mem) { + ath10k_err(ar, "Memory base ioremap failed: phy addr: %pa\n", + &ar_snoc->mem_pa); + return -EINVAL; + } + + ar_snoc->target_info.soc_version = ATH10K_HW_WCN3990; + ar_snoc->target_info.target_version = ATH10K_HW_WCN3990; + ar_snoc->target_info.target_revision = 0; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "%s: mem = %pS mem_pa = %pad soc ver=%x tgt ver=%x\n", + __func__, ar_snoc->mem, &ar_snoc->mem_pa, + ar_snoc->target_info.soc_version, + ar_snoc->target_info.target_version); + + return 0; +} + +static int ath10k_snoc_wlan_enable(struct ath10k *ar) +{ + struct ath10k_wlan_enable_cfg cfg; + enum ath10k_driver_mode mode; + int pipe_num; + struct ath10k_ce_tgt_pipe_cfg tgt_cfg[CE_COUNT_MAX]; + + for (pipe_num = 0; pipe_num < CE_COUNT_MAX; pipe_num++) { + tgt_cfg[pipe_num].pipe_num = + target_ce_config_wlan[pipe_num].pipenum; + tgt_cfg[pipe_num].pipe_dir = + target_ce_config_wlan[pipe_num].pipedir; + tgt_cfg[pipe_num].nentries = + target_ce_config_wlan[pipe_num].nentries; + tgt_cfg[pipe_num].nbytes_max = + target_ce_config_wlan[pipe_num].nbytes_max; + tgt_cfg[pipe_num].flags = + target_ce_config_wlan[pipe_num].flags; + tgt_cfg[pipe_num].reserved = 0; + } + + cfg.num_ce_tgt_cfg = sizeof(target_ce_config_wlan) / + sizeof(struct ath10k_ce_tgt_pipe_cfg); + cfg.ce_tgt_cfg = (struct ath10k_ce_tgt_pipe_cfg *) + &tgt_cfg; + cfg.num_ce_svc_pipe_cfg = sizeof(target_service_to_ce_map_wlan) / + sizeof(struct ath10k_ce_svc_pipe_cfg); + cfg.ce_svc_cfg = (struct ath10k_ce_svc_pipe_cfg *) + &target_service_to_ce_map_wlan; + cfg.num_shadow_reg_cfg = sizeof(target_shadow_reg_cfg_map) / + sizeof(struct ath10k_shadow_reg_cfg); + cfg.shadow_reg_cfg = (struct ath10k_shadow_reg_cfg *) + &target_shadow_reg_cfg_map; + + mode = ar->testmode.utf_monitor ? ATH10K_FTM : ATH10K_MISSION; + return ath10k_snoc_qmi_wlan_enable(ar, &cfg, mode, + "5.1.0.26N"); +} + +static int ath10k_snoc_bus_configure(struct ath10k *ar) +{ + int ret; + + ret = ath10k_snoc_wlan_enable(ar); + if (ret < 0) { + ath10k_err(ar, "%s: ath10k_snoc_bus_configure error = %d", + __func__, ret); + return ret; + } + + ce_config_rri_on_ddr(ar); + + return 0; +} + +static int ath10k_snoc_hif_start(struct ath10k *ar) +{ + if (ath10k_snoc_has_fw_crashed(ar)) { + ath10k_snoc_request_irq(ar); + ath10k_snoc_fw_crashed_clear(ar); + } + ath10k_snoc_irq_enable(ar); + ath10k_snoc_rx_post(ar); + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot hif start\n"); + return 0; +} + +static int ath10k_snoc_claim(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + ath10k_snoc_get_soc_info(ar); + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot snoc_mem 0x%p\n", ar_snoc->mem); + + return 0; +} + +static int ath10k_snoc_hif_power_up(struct ath10k *ar) +{ + int ret; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "%s:WCN3990 driver state = %d\n", + __func__, ar->state); + + if (atomic_read(&ar_snoc->pm_ops_inprogress)) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "%s: WLAN OFF CMD Reset on PM Resume\n", __func__); + ath10k_snoc_qmi_wlan_disable(ar); + atomic_set(&ar_snoc->pm_ops_inprogress, 0); + } + + if ((ar->state == ATH10K_STATE_ON) || + (ar->state == ATH10K_STATE_RESTARTING) || + test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) { + ret = ath10k_snoc_bus_configure(ar); + if (ret) { + ath10k_err(ar, "failed to configure bus: %d\n", ret); + return ret; + } + } + ret = ath10k_snoc_init_pipes(ar); + if (ret) { + ath10k_err(ar, "failed to initialize CE: %d\n", ret); + goto err_sleep; + } + + napi_enable(&ar->napi); + return 0; + +err_sleep: + return ret; +} + +static int ath10k_snoc_napi_poll(struct napi_struct *ctx, int budget) +{ + struct ath10k *ar = container_of(ctx, struct ath10k, napi); + int done = 0; + + if (ath10k_snoc_has_fw_crashed(ar)) { + napi_complete(ctx); + return done; + } + ath10k_ce_per_engine_service_any(ar); + + done = ath10k_htt_txrx_compl_task(ar, budget); + + if (done < budget) { + napi_complete(ctx); + ath10k_snoc_irq_enable(ar); + } + + return done; +} + +static int ath10k_snoc_resource_init(struct ath10k *ar) +{ + int i, ret = 0; + struct resource *res; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < CE_COUNT; i++) { + res = platform_get_resource(ar_snoc->dev, IORESOURCE_IRQ, i); + if (!res) { + ath10k_err(ar, "Fail to get IRQ-%d\n", i); + ret = -ENODEV; + goto out; + } else { + ar_snoc->ce_irqs[i].irq_line = res->start; + } + } + +out: + return ret; +} + +static +int ath10k_snoc_pm_notifier(struct notifier_block *nb, + unsigned long pm_event, void *data) +{ + struct ath10k_snoc *ar_snoc = + container_of(nb, struct ath10k_snoc, pm_notifier); + struct ath10k *ar = ar_snoc->ar; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "%s: PM Event: %lu\n", __func__, pm_event); + + switch (pm_event) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + case PM_RESTORE_PREPARE: + case PM_POST_RESTORE: + atomic_set(&ar_snoc->pm_ops_inprogress, 1); + break; + default: + break; + } + + return NOTIFY_DONE; +} + +static int ath10k_get_vreg_info(struct ath10k *ar, struct device *dev, + struct ath10k_wcn3990_vreg_info *vreg_info) +{ + int ret = 0; + char prop_name[ATH10K_MAX_PROP_SIZE]; + struct regulator *reg; + const __be32 *prop; + int len = 0; + int i; + + reg = devm_regulator_get_optional(dev, vreg_info->name); + if (PTR_ERR(reg) == -EPROBE_DEFER) { + ath10k_err(ar, "EPROBE_DEFER for regulator: %s\n", + vreg_info->name); + ret = PTR_ERR(reg); + goto out; + } + + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + + if (vreg_info->required) { + ath10k_err(ar, "Regulator %s doesn't exist: %d\n", + vreg_info->name, ret); + goto out; + } else { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Optional regulator %s doesn't exist: %d\n", + vreg_info->name, ret); + goto done; + } + } + + vreg_info->reg = reg; + + snprintf(prop_name, ATH10K_MAX_PROP_SIZE, + "qcom,%s-config", vreg_info->name); + + prop = of_get_property(dev->of_node, prop_name, &len); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Got regulator cfg,prop: %s, len: %d\n", + prop_name, len); + + if (!prop || len < (2 * sizeof(__be32))) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Property %s %s\n", prop_name, + prop ? "invalid format" : "doesn't exist"); + goto done; + } + + for (i = 0; (i * sizeof(__be32)) < len; i++) { + switch (i) { + case 0: + vreg_info->min_v = be32_to_cpup(&prop[0]); + break; + case 1: + vreg_info->max_v = be32_to_cpup(&prop[1]); + break; + case 2: + vreg_info->load_ua = be32_to_cpup(&prop[2]); + break; + case 3: + vreg_info->settle_delay = be32_to_cpup(&prop[3]); + break; + default: + ath10k_dbg(ar, ATH10K_DBG_SNOC, "%s, ignoring val %d\n", + prop_name, i); + break; + } + } + +done: + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "vreg: %s, min_v: %u, max_v: %u, load: %u, delay: %lu\n", + vreg_info->name, vreg_info->min_v, vreg_info->max_v, + vreg_info->load_ua, vreg_info->settle_delay); + + return 0; + +out: + return ret; +} + +static int ath10k_get_clk_info(struct ath10k *ar, struct device *dev, + struct ath10k_wcn3990_clk_info *clk_info) +{ + struct clk *handle; + int ret = 0; + + handle = devm_clk_get(dev, clk_info->name); + if (IS_ERR(handle)) { + ret = PTR_ERR(handle); + if (clk_info->required) { + ath10k_err(ar, "Clock %s isn't available: %d\n", + clk_info->name, ret); + goto out; + } else { + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Ignoring clk %s: %d\n", + clk_info->name, + ret); + ret = 0; + goto out; + } + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Clock: %s, freq: %u\n", + clk_info->name, clk_info->freq); + + clk_info->handle = handle; +out: + return ret; +} + +static int ath10k_wcn3990_vreg_on(struct ath10k *ar) +{ + int ret = 0; + struct ath10k_wcn3990_vreg_info *vreg_info; + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < ATH10K_WCN3990_VREG_INFO_SIZE; i++) { + vreg_info = &ar_snoc->vreg[i]; + + if (!vreg_info->reg) + continue; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Regulator %s being enabled\n", + vreg_info->name); + + ret = regulator_set_voltage(vreg_info->reg, vreg_info->min_v, + vreg_info->max_v); + if (ret) { + ath10k_err(ar, + "vreg %s, set failed:min:%u,max:%u,ret: %d\n", + vreg_info->name, vreg_info->min_v, + vreg_info->max_v, ret); + break; + } + + if (vreg_info->load_ua) { + ret = regulator_set_load(vreg_info->reg, + vreg_info->load_ua); + if (ret < 0) { + ath10k_err(ar, + "Reg %s, can't set load:%u,ret: %d\n", + vreg_info->name, + vreg_info->load_ua, ret); + break; + } + } + + ret = regulator_enable(vreg_info->reg); + if (ret) { + ath10k_err(ar, "Regulator %s, can't enable: %d\n", + vreg_info->name, ret); + break; + } + + if (vreg_info->settle_delay) + udelay(vreg_info->settle_delay); + } + + if (!ret) + return 0; + + for (; i >= 0; i--) { + vreg_info = &ar_snoc->vreg[i]; + + if (!vreg_info->reg) + continue; + + regulator_disable(vreg_info->reg); + regulator_set_load(vreg_info->reg, 0); + regulator_set_voltage(vreg_info->reg, 0, vreg_info->max_v); + } + + return ret; +} + +static int ath10k_wcn3990_vreg_off(struct ath10k *ar) +{ + int ret = 0; + struct ath10k_wcn3990_vreg_info *vreg_info; + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = ATH10K_WCN3990_VREG_INFO_SIZE - 1; i >= 0; i--) { + vreg_info = &ar_snoc->vreg[i]; + + if (!vreg_info->reg) + continue; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Regulator %s being disabled\n", + vreg_info->name); + + ret = regulator_disable(vreg_info->reg); + if (ret) + ath10k_err(ar, "Regulator %s, can't disable: %d\n", + vreg_info->name, ret); + + ret = regulator_set_load(vreg_info->reg, 0); + if (ret < 0) + ath10k_err(ar, "Regulator %s, can't set load: %d\n", + vreg_info->name, ret); + + ret = regulator_set_voltage(vreg_info->reg, 0, + vreg_info->max_v); + if (ret) + ath10k_err(ar, "Regulator %s, can't set voltage: %d\n", + vreg_info->name, ret); + } + + return ret; +} + +static int ath10k_wcn3990_clk_init(struct ath10k *ar) +{ + struct ath10k_wcn3990_clk_info *clk_info; + int i; + int ret = 0; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < ATH10K_WCN3990_CLK_INFO_SIZE; i++) { + clk_info = &ar_snoc->clk[i]; + + if (!clk_info->handle) + continue; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Clock %s being enabled\n", + clk_info->name); + + if (clk_info->freq) { + ret = clk_set_rate(clk_info->handle, clk_info->freq); + + if (ret) { + ath10k_err(ar, "Clk %s,set err: %u,ret: %d\n", + clk_info->name, clk_info->freq, + ret); + break; + } + } + + ret = clk_prepare_enable(clk_info->handle); + if (ret) { + ath10k_err(ar, "Clock %s, can't enable: %d\n", + clk_info->name, ret); + break; + } + } + + if (ret == 0) + return 0; + + for (; i >= 0; i--) { + clk_info = &ar_snoc->clk[i]; + + if (!clk_info->handle) + continue; + + clk_disable_unprepare(clk_info->handle); + } + + return ret; +} + +static int ath10k_wcn3990_clk_deinit(struct ath10k *ar) +{ + struct ath10k_wcn3990_clk_info *clk_info; + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < ATH10K_WCN3990_CLK_INFO_SIZE; i++) { + clk_info = &ar_snoc->clk[i]; + + if (!clk_info->handle) + continue; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Clock %s being disabled\n", + clk_info->name); + + clk_disable_unprepare(clk_info->handle); + } + + return 0; +} + +static int ath10k_hw_power_on(struct ath10k *ar) +{ + int ret = 0; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "HW Power on\n"); + + ret = ath10k_wcn3990_vreg_on(ar); + if (ret) + goto out; + + ret = ath10k_wcn3990_clk_init(ar); + if (ret) + goto vreg_off; + + return ret; + +vreg_off: + ath10k_wcn3990_vreg_off(ar); +out: + return ret; +} + +static int ath10k_hw_power_off(struct ath10k *ar) +{ + int ret = 0; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "HW Power off\n"); + + ath10k_wcn3990_clk_deinit(ar); + + ret = ath10k_wcn3990_vreg_off(ar); + + return ret; +} + +static int ath10k_snoc_hif_suspend(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + int ret = 0; + + if (!ar_snoc) + return -EINVAL; + + if (!device_may_wakeup(ar->dev)) + return -EINVAL; + + ret = enable_irq_wake(ar_snoc->ce_irqs[WCN3990_WAKE_IRQ_CE].irq_line); + if (ret) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "HIF Suspend: Failed to enable wakeup IRQ\n"); + return ret; + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "HIF Suspended\n"); + return ret; +} + +static int ath10k_snoc_hif_resume(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + int ret = 0; + + if (!ar_snoc) + return -EINVAL; + + if (!device_may_wakeup(ar->dev)) + return -EINVAL; + + ret = disable_irq_wake(ar_snoc->ce_irqs[WCN3990_WAKE_IRQ_CE].irq_line); + if (ret) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "HIF Resume: Failed to disable wakeup IRQ\n"); + return ret; + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "HIF Resumed\n"); + return ret; +} + +static const struct ath10k_hif_ops ath10k_snoc_hif_ops = { + .tx_sg = ath10k_snoc_hif_tx_sg, + .start = ath10k_snoc_hif_start, + .stop = ath10k_snoc_hif_stop, + .map_service_to_pipe = ath10k_snoc_hif_map_service_to_pipe, + .get_default_pipe = ath10k_snoc_hif_get_default_pipe, + .send_complete_check = ath10k_snoc_hif_send_complete_check, + .get_free_queue_number = ath10k_snoc_hif_get_free_queue_number, + .power_up = ath10k_snoc_hif_power_up, + .power_down = ath10k_snoc_hif_power_down, + .read32 = ath10k_snoc_read32, + .write32 = ath10k_snoc_write32, + .suspend = ath10k_snoc_hif_suspend, + .resume = ath10k_snoc_hif_resume, +}; + +static const struct ath10k_bus_ops ath10k_snoc_bus_ops = { + .read32 = ath10k_snoc_read32, + .write32 = ath10k_snoc_write32, +}; + +static int ath10k_snoc_probe(struct platform_device *pdev) +{ + int ret; + struct ath10k *ar; + struct ath10k_snoc *ar_snoc; + enum ath10k_hw_rev hw_rev; + struct device *dev; + u32 chip_id; + u32 i; + + dev = &pdev->dev; + hw_rev = ATH10K_HW_WCN3990; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(37)); + ar = ath10k_core_create(sizeof(*ar_snoc), dev, ATH10K_BUS_SNOC, + hw_rev, &ath10k_snoc_hif_ops); + if (!ar) { + dev_err(dev, "failed to allocate core\n"); + return -ENOMEM; + } + + ar_snoc = ath10k_snoc_priv(ar); + if (!ar_snoc) + return -EINVAL; + ar_snoc->dev = pdev; + platform_set_drvdata(pdev, ar); + ar_snoc->ar = ar; + + ret = ath10k_snoc_start_qmi_service(ar); + if (ret) { + ath10k_err(ar, "failed to start QMI service: %d\n", ret); + goto err_core_destroy; + } + + spin_lock_init(&ar_snoc->opaque_ctx.ce_lock); + ar_snoc->opaque_ctx.bus_ops = &ath10k_snoc_bus_ops; + ath10k_snoc_resource_init(ar); + + ar->target_version = ATH10K_HW_WCN3990; + ar->hw->wiphy->hw_version = ATH10K_HW_WCN3990; + setup_timer(&ar_snoc->rx_post_retry, ath10k_snoc_rx_replenish_retry, + (unsigned long)ar); + + memcpy(ar_snoc->vreg, vreg_cfg, sizeof(vreg_cfg)); + for (i = 0; i < ATH10K_WCN3990_VREG_INFO_SIZE; i++) { + ret = ath10k_get_vreg_info(ar, dev, &ar_snoc->vreg[i]); + if (ret) + goto err_core_destroy; + } + + memcpy(ar_snoc->clk, clk_cfg, sizeof(clk_cfg)); + for (i = 0; i < ATH10K_WCN3990_CLK_INFO_SIZE; i++) { + ret = ath10k_get_clk_info(ar, dev, &ar_snoc->clk[i]); + if (ret) + goto err_core_destroy; + } + + ret = ath10k_hw_power_on(ar); + if (ret) { + ath10k_err(ar, "failed to power on device: %d\n", ret); + goto err_stop_qmi_service; + } + + ret = ath10k_snoc_claim(ar); + if (ret) { + ath10k_err(ar, "failed to claim device: %d\n", ret); + goto err_hw_power_off; + } + + ret = ath10k_snoc_bus_configure(ar); + if (ret) { + ath10k_err(ar, "failed to configure bus: %d\n", ret); + goto err_hw_power_off; + } + + ret = ath10k_snoc_alloc_pipes(ar); + if (ret) { + ath10k_err(ar, "failed to allocate copy engine pipes: %d\n", + ret); + goto err_hw_power_off; + } + + netif_napi_add(&ar->napi_dev, &ar->napi, ath10k_snoc_napi_poll, + ATH10K_NAPI_BUDGET); + + ret = ath10k_snoc_request_irq(ar); + if (ret) { + ath10k_warn(ar, "failed to request irqs: %d\n", ret); + goto err_free_pipes; + } + + chip_id = ar_snoc->target_info.soc_version; + /* chip id needs to be retrieved from platform driver */ + ret = ath10k_core_register(ar, chip_id); + if (ret) { + ath10k_err(ar, "failed to register driver core: %d\n", ret); + goto err_free_irq; + } + + ath10k_snoc_modem_ssr_register_notifier(ar); + ath10k_snoc_pd_restart_enable(ar); + + ar_snoc->pm_notifier.notifier_call = ath10k_snoc_pm_notifier; + register_pm_notifier(&ar_snoc->pm_notifier); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "%s:WCN3990 probed\n", __func__); + + return 0; + +err_free_irq: + ath10k_snoc_free_irq(ar); + +err_free_pipes: + ath10k_snoc_free_pipes(ar); + +err_hw_power_off: + ath10k_hw_power_off(ar); + +err_stop_qmi_service: + ath10k_snoc_stop_qmi_service(ar); + +err_core_destroy: + ath10k_core_destroy(ar); + + return ret; +} + +static int ath10k_snoc_remove(struct platform_device *pdev) +{ + struct ath10k *ar = platform_get_drvdata(pdev); + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + if (!ar) + return -EINVAL; + + if (!ar_snoc) + return -EINVAL; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "%s:WCN3990 removed\n", __func__); + + unregister_pm_notifier(&ar_snoc->pm_notifier); + ath10k_core_unregister(ar); + ath10k_snoc_pdr_unregister_notifier(ar); + ath10k_snoc_modem_ssr_unregister_notifier(ar); + ath10k_snoc_free_irq(ar); + ath10k_snoc_release_resource(ar); + ath10k_snoc_free_pipes(ar); + ath10k_snoc_stop_qmi_service(ar); + ath10k_hw_power_off(ar); + ath10k_core_destroy(ar); + + return 0; +} + +static const struct of_device_id ath10k_snoc_dt_match[] = { + {.compatible = "qcom,wcn3990-wifi"}, + {} +}; +MODULE_DEVICE_TABLE(of, ath10k_snoc_dt_match); + +static struct platform_driver ath10k_snoc_driver = { + .probe = ath10k_snoc_probe, + .remove = ath10k_snoc_remove, + .driver = { + .name = "ath10k_snoc", + .owner = THIS_MODULE, + .of_match_table = ath10k_snoc_dt_match, + }, +}; + +static int __init ath10k_snoc_init(void) +{ + int ret; + + ret = platform_driver_register(&ath10k_snoc_driver); + if (ret) + pr_err("failed to register ath10k snoc driver: %d\n", + ret); + + return ret; +} +module_init(ath10k_snoc_init); + +static void __exit ath10k_snoc_exit(void) +{ + platform_driver_unregister(&ath10k_snoc_driver); +} +module_exit(ath10k_snoc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Driver support for Atheros WCN3990 SNOC devices"); diff --git a/drivers/net/wireless/ath/ath10k/snoc.h b/drivers/net/wireless/ath/ath10k/snoc.h new file mode 100644 index 000000000000..a02cb2ad928e --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/snoc.h @@ -0,0 +1,214 @@ +/* Copyright (c) 2017, 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. + */ + +#ifndef _SNOC_H_ +#define _SNOC_H_ + +#include "hw.h" +#include "ce.h" +#include "pci.h" +#include "qmi.h" +#include <linux/kernel.h> +#include <soc/qcom/service-locator.h> +#define ATH10K_SNOC_RX_POST_RETRY_MS 50 +#define CE_POLL_PIPE 4 +#define ATH10K_SERVICE_LOCATION_CLIENT_NAME "ATH10K-WLAN" +#define ATH10K_WLAN_SERVICE_NAME "wlan/fw" + +/* struct snoc_state: SNOC target state + * @pipe_cfg_addr: pipe configuration address + * @svc_to_pipe_map: pipe services + */ +struct snoc_state { + u32 pipe_cfg_addr; + u32 svc_to_pipe_map; +}; + +/* struct ath10k_snoc_pipe: SNOC pipe configuration + * @ath10k_ce_pipe: pipe handle + * @pipe_num: pipe number + * @hif_ce_state: pointer to ce state + * @buf_sz: buffer size + * @pipe_lock: pipe lock + * @ar_snoc: snoc private structure + */ + +struct ath10k_snoc_pipe { + struct ath10k_ce_pipe *ce_hdl; + u8 pipe_num; + struct ath10k *hif_ce_state; + size_t buf_sz; + /* protect ce info */ + spinlock_t pipe_lock; + struct ath10k_snoc *ar_snoc; +}; + +/* struct ath10k_snoc_supp_chip: supported chip set + * @dev_id: device id + * @rev_id: revison id + */ +struct ath10k_snoc_supp_chip { + u32 dev_id; + u32 rev_id; +}; + +/* struct ath10k_snoc_info: SNOC info struct + * @v_addr: base virtual address + * @p_addr: base physical address + * @chip_id: chip id + * @chip_family: chip family + * @board_id: board id + * @soc_id: soc id + * @fw_version: fw version + */ +struct ath10k_snoc_info { + void __iomem *v_addr; + phys_addr_t p_addr; + u32 chip_id; + u32 chip_family; + u32 board_id; + u32 soc_id; + u32 fw_version; +}; + +/* struct ath10k_target_info: SNOC target info + * @target_version: target version + * @target_type: target type + * @target_revision: target revision + * @soc_version: target soc version + */ +struct ath10k_target_info { + u32 target_version; + u32 target_type; + u32 target_revision; + u32 soc_version; +}; + +/* struct ath10k_service_notifier_context: service notification context + * @handle: notifier handle + * @instance_id: domain instance id + * @name: domain name + */ +struct ath10k_service_notifier_context { + void *handle; + u32 instance_id; + char name[QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1]; +}; + +/* struct ath10k_snoc_ce_irq: copy engine irq struct + * @irq_req_stat: irq request status + * @irq_line: irq line + */ +struct ath10k_snoc_ce_irq { + atomic_t irq_req_stat; + u32 irq_line; +}; + +struct ath10k_wcn3990_vreg_info { + struct regulator *reg; + const char *name; + u32 min_v; + u32 max_v; + u32 load_ua; + unsigned long settle_delay; + bool required; +}; + +struct ath10k_wcn3990_clk_info { + struct clk *handle; + const char *name; + u32 freq; + bool required; +}; + +static struct ath10k_wcn3990_vreg_info vreg_cfg[] = { + {NULL, "vdd-0.8-cx-mx", 800000, 800000, 0, 0, false}, + {NULL, "vdd-1.8-xo", 1800000, 1800000, 0, 0, false}, + {NULL, "vdd-1.3-rfa", 1304000, 1304000, 0, 0, false}, + {NULL, "vdd-3.3-ch0", 3312000, 3312000, 0, 0, false}, +}; + +#define ATH10K_WCN3990_VREG_INFO_SIZE ARRAY_SIZE(vreg_cfg) + +static struct ath10k_wcn3990_clk_info clk_cfg[] = { + {NULL, "cxo_ref_clk_pin", 0, false}, +}; + +#define ATH10K_WCN3990_CLK_INFO_SIZE ARRAY_SIZE(clk_cfg) + +/* struct ath10k_snoc: SNOC info struct + * @dev: device structure + * @ar:ath10k base structure + * @mem: mem base virtual address + * @mem_pa: mem base physical address + * @target_info: snoc target info + * @mem_len: mempry map length + * @pipe_info: pipe info struct + * @ce_irqs: copy engine irq list + * @ce_lock: protect ce structures + * @ce_states: maps ce id to ce state + * @rx_post_retry: rx buffer post processing timer + * @vaddr_rri_on_ddr: virtual address for RRI + * @is_driver_probed: flag to indicate driver state + * @modem_ssr_nb: notifier callback for modem notification + * @modem_notify_handler: modem notification handler + * @service_notifier: notifier context for service notification + * @service_notifier_nb: notifier callback for service notification + * @total_domains: no of service domains + * @get_service_nb: notifier callback for service discovery + * @fw_crashed: fw state flag + */ +struct ath10k_snoc { + struct bus_opaque opaque_ctx; + struct platform_device *dev; + struct ath10k *ar; + void __iomem *mem; + dma_addr_t mem_pa; + struct ath10k_target_info target_info; + size_t mem_len; + struct ath10k_snoc_pipe pipe_info[CE_COUNT_MAX]; + struct timer_list rx_post_retry; + struct ath10k_snoc_ce_irq ce_irqs[CE_COUNT_MAX]; + u32 *vaddr_rri_on_ddr; + bool is_driver_probed; + struct notifier_block modem_ssr_nb; + struct notifier_block pm_notifier; + void *modem_notify_handler; + struct ath10k_service_notifier_context *service_notifier; + struct notifier_block service_notifier_nb; + int total_domains; + struct notifier_block get_service_nb; + atomic_t fw_crashed; + atomic_t pm_ops_inprogress; + struct ath10k_snoc_qmi_config qmi_cfg; + struct ath10k_wcn3990_vreg_info vreg[ATH10K_WCN3990_VREG_INFO_SIZE]; + struct ath10k_wcn3990_clk_info clk[ATH10K_WCN3990_CLK_INFO_SIZE]; +}; + +struct ath10k_event_pd_down_data { + bool crashed; + bool fw_rejuvenate; +}; + +static inline struct ath10k_snoc *ath10k_snoc_priv(struct ath10k *ar) +{ + return (struct ath10k_snoc *)ar->drv_priv; +} + +void ath10k_snoc_write32(struct ath10k *ar, u32 offset, u32 value); +void ath10k_snoc_soc_write32(struct ath10k *ar, u32 addr, u32 val); +void ath10k_snoc_reg_write32(struct ath10k *ar, u32 addr, u32 val); +u32 ath10k_snoc_read32(struct ath10k *ar, u32 offset); +u32 ath10k_snoc_soc_read32(struct ath10k *ar, u32 addr); +u32 ath10k_snoc_reg_read32(struct ath10k *ar, u32 addr); + +#endif /* _SNOC_H_ */ diff --git a/drivers/net/wireless/ath/ath10k/spectral.c b/drivers/net/wireless/ath/ath10k/spectral.c index a0e7eebc206a..2ffc1fe4923b 100644 --- a/drivers/net/wireless/ath/ath10k/spectral.c +++ b/drivers/net/wireless/ath/ath10k/spectral.c @@ -101,9 +101,9 @@ int ath10k_spectral_process_fft(struct ath10k *ar, break; case 80: /* TODO: As experiments with an analogue sender and various - * configuaritions (fft-sizes of 64/128/256 and 20/40/80 Mhz) + * configurations (fft-sizes of 64/128/256 and 20/40/80 Mhz) * show, the particular configuration of 80 MHz/64 bins does - * not match with the other smaples at all. Until the reason + * not match with the other samples at all. Until the reason * for that is found, don't report these samples. */ if (bin_len == 64) diff --git a/drivers/net/wireless/ath/ath10k/swap.c b/drivers/net/wireless/ath/ath10k/swap.c index 3ca3fae408a7..adf4592374b4 100644 --- a/drivers/net/wireless/ath/ath10k/swap.c +++ b/drivers/net/wireless/ath/ath10k/swap.c @@ -135,26 +135,17 @@ ath10k_swap_code_seg_alloc(struct ath10k *ar, size_t swap_bin_len) } int ath10k_swap_code_seg_configure(struct ath10k *ar, - enum ath10k_swap_code_seg_bin_type type) + const struct ath10k_fw_file *fw_file) { int ret; struct ath10k_swap_code_seg_info *seg_info = NULL; - switch (type) { - case ATH10K_SWAP_CODE_SEG_BIN_TYPE_FW: - if (!ar->swap.firmware_swap_code_seg_info) - return 0; - - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot found firmware code swap binary\n"); - seg_info = ar->swap.firmware_swap_code_seg_info; - break; - default: - case ATH10K_SWAP_CODE_SEG_BIN_TYPE_OTP: - case ATH10K_SWAP_CODE_SEG_BIN_TYPE_UTF: - ath10k_warn(ar, "ignoring unknown code swap binary type %d\n", - type); + if (!fw_file->firmware_swap_code_seg_info) return 0; - } + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot found firmware code swap binary\n"); + + seg_info = fw_file->firmware_swap_code_seg_info; ret = ath10k_bmi_write_memory(ar, seg_info->target_addr, &seg_info->seg_hw_info, @@ -168,32 +159,41 @@ int ath10k_swap_code_seg_configure(struct ath10k *ar, return 0; } -void ath10k_swap_code_seg_release(struct ath10k *ar) +void ath10k_swap_code_seg_release(struct ath10k *ar, + struct ath10k_fw_file *fw_file) { - ath10k_swap_code_seg_free(ar, ar->swap.firmware_swap_code_seg_info); - ar->swap.firmware_codeswap_data = NULL; - ar->swap.firmware_codeswap_len = 0; - ar->swap.firmware_swap_code_seg_info = NULL; + ath10k_swap_code_seg_free(ar, fw_file->firmware_swap_code_seg_info); + + /* FIXME: these two assignments look to bein wrong place! Shouldn't + * they be in ath10k_core_free_firmware_files() like the rest? + */ + fw_file->codeswap_data = NULL; + fw_file->codeswap_len = 0; + + fw_file->firmware_swap_code_seg_info = NULL; } -int ath10k_swap_code_seg_init(struct ath10k *ar) +int ath10k_swap_code_seg_init(struct ath10k *ar, struct ath10k_fw_file *fw_file) { int ret; struct ath10k_swap_code_seg_info *seg_info; + const void *codeswap_data; + size_t codeswap_len; + + codeswap_data = fw_file->codeswap_data; + codeswap_len = fw_file->codeswap_len; - if (!ar->swap.firmware_codeswap_len || !ar->swap.firmware_codeswap_data) + if (!codeswap_len || !codeswap_data) return 0; - seg_info = ath10k_swap_code_seg_alloc(ar, - ar->swap.firmware_codeswap_len); + seg_info = ath10k_swap_code_seg_alloc(ar, codeswap_len); if (!seg_info) { ath10k_err(ar, "failed to allocate fw code swap segment\n"); return -ENOMEM; } ret = ath10k_swap_code_seg_fill(ar, seg_info, - ar->swap.firmware_codeswap_data, - ar->swap.firmware_codeswap_len); + codeswap_data, codeswap_len); if (ret) { ath10k_warn(ar, "failed to initialize fw code swap segment: %d\n", @@ -202,7 +202,7 @@ int ath10k_swap_code_seg_init(struct ath10k *ar) return ret; } - ar->swap.firmware_swap_code_seg_info = seg_info; + fw_file->firmware_swap_code_seg_info = seg_info; return 0; } diff --git a/drivers/net/wireless/ath/ath10k/swap.h b/drivers/net/wireless/ath/ath10k/swap.h index 5c89952dd20f..f5dc0476493e 100644 --- a/drivers/net/wireless/ath/ath10k/swap.h +++ b/drivers/net/wireless/ath/ath10k/swap.h @@ -23,6 +23,8 @@ /* Currently only one swap segment is supported */ #define ATH10K_SWAP_CODE_SEG_NUM_SUPPORTED 1 +struct ath10k_fw_file; + struct ath10k_swap_code_seg_tlv { __le32 address; __le32 length; @@ -39,12 +41,6 @@ union ath10k_swap_code_seg_item { struct ath10k_swap_code_seg_tail tail; } __packed; -enum ath10k_swap_code_seg_bin_type { - ATH10K_SWAP_CODE_SEG_BIN_TYPE_OTP, - ATH10K_SWAP_CODE_SEG_BIN_TYPE_FW, - ATH10K_SWAP_CODE_SEG_BIN_TYPE_UTF, -}; - struct ath10k_swap_code_seg_hw_info { /* Swap binary image size */ __le32 swap_size; @@ -65,8 +61,10 @@ struct ath10k_swap_code_seg_info { }; int ath10k_swap_code_seg_configure(struct ath10k *ar, - enum ath10k_swap_code_seg_bin_type type); -void ath10k_swap_code_seg_release(struct ath10k *ar); -int ath10k_swap_code_seg_init(struct ath10k *ar); + const struct ath10k_fw_file *fw_file); +void ath10k_swap_code_seg_release(struct ath10k *ar, + struct ath10k_fw_file *fw_file); +int ath10k_swap_code_seg_init(struct ath10k *ar, + struct ath10k_fw_file *fw_file); #endif diff --git a/drivers/net/wireless/ath/ath10k/targaddrs.h b/drivers/net/wireless/ath/ath10k/targaddrs.h index 05a421bc322a..a47cab44d9c8 100644 --- a/drivers/net/wireless/ath/ath10k/targaddrs.h +++ b/drivers/net/wireless/ath/ath10k/targaddrs.h @@ -405,7 +405,7 @@ Fw Mode/SubMode Mask * 1. target firmware would check magic number and if it's a match, firmware * would consider the bits[0:15] are valid and base on that to calculate * the end of DRAM. Early allocation would be located at that area and - * may be reclaimed when necesary + * may be reclaimed when necessary * 2. if no magic number is found, early allocation would happen at "_end" * symbol of ROM which is located before the app-data and might NOT be * re-claimable. If this is adopted, link script should keep this in @@ -438,7 +438,7 @@ Fw Mode/SubMode Mask ((HOST_INTEREST->hi_pwr_save_flags & HI_PWR_SAVE_LPL_ENABLED)) #define HI_DEV_LPL_TYPE_GET(_devix) \ (HOST_INTEREST->hi_pwr_save_flags & ((HI_PWR_SAVE_LPL_DEV_MASK) << \ - (HI_PWR_SAVE_LPL_DEV0_LSB + (_devix)*2))) + (HI_PWR_SAVE_LPL_DEV0_LSB + (_devix) * 2))) #define HOST_INTEREST_SMPS_IS_ALLOWED() \ ((HOST_INTEREST->hi_smps_options & HI_SMPS_ALLOW_MASK)) @@ -447,6 +447,9 @@ Fw Mode/SubMode Mask #define QCA988X_BOARD_DATA_SZ 7168 #define QCA988X_BOARD_EXT_DATA_SZ 0 +#define QCA9887_BOARD_DATA_SZ 7168 +#define QCA9887_BOARD_EXT_DATA_SZ 0 + #define QCA6174_BOARD_DATA_SZ 8192 #define QCA6174_BOARD_EXT_DATA_SZ 0 @@ -456,4 +459,7 @@ Fw Mode/SubMode Mask #define QCA99X0_BOARD_DATA_SZ 12288 #define QCA99X0_BOARD_EXT_DATA_SZ 0 +#define QCA4019_BOARD_DATA_SZ 12064 +#define QCA4019_BOARD_EXT_DATA_SZ 0 + #endif /* __TARGADDRS_H__ */ diff --git a/drivers/net/wireless/ath/ath10k/testmode.c b/drivers/net/wireless/ath/ath10k/testmode.c index 1d5a2fdcbf56..1a067a4ece4d 100644 --- a/drivers/net/wireless/ath/ath10k/testmode.c +++ b/drivers/net/wireless/ath/ath10k/testmode.c @@ -23,6 +23,7 @@ #include "wmi.h" #include "hif.h" #include "hw.h" +#include "core.h" #include "testmode_i.h" @@ -45,7 +46,7 @@ bool ath10k_tm_event_wmi(struct ath10k *ar, u32 cmd_id, struct sk_buff *skb) int ret; ath10k_dbg(ar, ATH10K_DBG_TESTMODE, - "testmode event wmi cmd_id %d skb %p skb->len %d\n", + "testmode event wmi cmd_id %d skb %pK skb->len %d\n", cmd_id, skb, skb->len); ath10k_dbg_dump(ar, ATH10K_DBG_TESTMODE, NULL, "", skb->data, skb->len); @@ -136,130 +137,18 @@ static int ath10k_tm_cmd_get_version(struct ath10k *ar, struct nlattr *tb[]) return ret; } - return cfg80211_testmode_reply(skb); -} - -static int ath10k_tm_fetch_utf_firmware_api_2(struct ath10k *ar) -{ - size_t len, magic_len, ie_len; - struct ath10k_fw_ie *hdr; - char filename[100]; - __le32 *version; - const u8 *data; - int ie_id, ret; - - snprintf(filename, sizeof(filename), "%s/%s", - ar->hw_params.fw.dir, ATH10K_FW_UTF_API2_FILE); - - /* load utf firmware image */ - ret = request_firmware(&ar->testmode.utf, filename, ar->dev); + ret = nla_put_u32(skb, ATH10K_TM_ATTR_WMI_OP_VERSION, + ar->normal_mode_fw.fw_file.wmi_op_version); if (ret) { - ath10k_warn(ar, "failed to retrieve utf firmware '%s': %d\n", - filename, ret); + kfree_skb(skb); return ret; } - data = ar->testmode.utf->data; - len = ar->testmode.utf->size; - - /* FIXME: call release_firmware() in error cases */ - - /* magic also includes the null byte, check that as well */ - magic_len = strlen(ATH10K_FIRMWARE_MAGIC) + 1; - - if (len < magic_len) { - ath10k_err(ar, "utf firmware file is too small to contain magic\n"); - ret = -EINVAL; - goto err; - } - - if (memcmp(data, ATH10K_FIRMWARE_MAGIC, magic_len) != 0) { - ath10k_err(ar, "invalid firmware magic\n"); - ret = -EINVAL; - goto err; - } - - /* jump over the padding */ - magic_len = ALIGN(magic_len, 4); - - len -= magic_len; - data += magic_len; - - /* loop elements */ - while (len > sizeof(struct ath10k_fw_ie)) { - hdr = (struct ath10k_fw_ie *)data; - - ie_id = le32_to_cpu(hdr->id); - ie_len = le32_to_cpu(hdr->len); - - len -= sizeof(*hdr); - data += sizeof(*hdr); - - if (len < ie_len) { - ath10k_err(ar, "invalid length for FW IE %d (%zu < %zu)\n", - ie_id, len, ie_len); - ret = -EINVAL; - goto err; - } - - switch (ie_id) { - case ATH10K_FW_IE_FW_VERSION: - if (ie_len > sizeof(ar->testmode.utf_version) - 1) - break; - - memcpy(ar->testmode.utf_version, data, ie_len); - ar->testmode.utf_version[ie_len] = '\0'; - - ath10k_dbg(ar, ATH10K_DBG_TESTMODE, - "testmode found fw utf version %s\n", - ar->testmode.utf_version); - break; - case ATH10K_FW_IE_TIMESTAMP: - /* ignore timestamp, but don't warn about it either */ - break; - case ATH10K_FW_IE_FW_IMAGE: - ath10k_dbg(ar, ATH10K_DBG_TESTMODE, - "testmode found fw image ie (%zd B)\n", - ie_len); - - ar->testmode.utf_firmware_data = data; - ar->testmode.utf_firmware_len = ie_len; - break; - case ATH10K_FW_IE_WMI_OP_VERSION: - if (ie_len != sizeof(u32)) - break; - version = (__le32 *)data; - ar->testmode.op_version = le32_to_cpup(version); - ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode found fw ie wmi op version %d\n", - ar->testmode.op_version); - break; - default: - ath10k_warn(ar, "Unknown testmode FW IE: %u\n", - le32_to_cpu(hdr->id)); - break; - } - /* jump over the padding */ - ie_len = ALIGN(ie_len, 4); - - len -= ie_len; - data += ie_len; - } - - if (!ar->testmode.utf_firmware_data || !ar->testmode.utf_firmware_len) { - ath10k_err(ar, "No ATH10K_FW_IE_FW_IMAGE found\n"); - ret = -EINVAL; - goto err; - } - - return 0; - -err: - release_firmware(ar->testmode.utf); - - return ret; + return cfg80211_testmode_reply(skb); } -static int ath10k_tm_fetch_utf_firmware_api_1(struct ath10k *ar) +static int ath10k_tm_fetch_utf_firmware_api_1(struct ath10k *ar, + struct ath10k_fw_file *fw_file) { char filename[100]; int ret; @@ -268,7 +157,7 @@ static int ath10k_tm_fetch_utf_firmware_api_1(struct ath10k *ar) ar->hw_params.fw.dir, ATH10K_FW_UTF_FILE); /* load utf firmware image */ - ret = request_firmware(&ar->testmode.utf, filename, ar->dev); + ret = request_firmware(&fw_file->firmware, filename, ar->dev); if (ret) { ath10k_warn(ar, "failed to retrieve utf firmware '%s': %d\n", filename, ret); @@ -281,24 +170,34 @@ static int ath10k_tm_fetch_utf_firmware_api_1(struct ath10k *ar) * correct WMI interface. */ - ar->testmode.op_version = ATH10K_FW_WMI_OP_VERSION_10_1; - ar->testmode.utf_firmware_data = ar->testmode.utf->data; - ar->testmode.utf_firmware_len = ar->testmode.utf->size; + fw_file->wmi_op_version = ATH10K_FW_WMI_OP_VERSION_10_1; + fw_file->htt_op_version = ATH10K_FW_HTT_OP_VERSION_10_1; + fw_file->firmware_data = fw_file->firmware->data; + fw_file->firmware_len = fw_file->firmware->size; return 0; } static int ath10k_tm_fetch_firmware(struct ath10k *ar) { + struct ath10k_fw_components *utf_mode_fw; + struct ath10k_fw_file *fw_file; int ret; - ret = ath10k_tm_fetch_utf_firmware_api_2(ar); + if (!ar->is_bmi) { + fw_file = &ar->testmode.utf_mode_fw.fw_file; + fw_file->wmi_op_version = ATH10K_FW_WMI_OP_VERSION_TLV; + fw_file->htt_op_version = ATH10K_FW_HTT_OP_VERSION_TLV; + return 0; + } + ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_UTF_API2_FILE, + &ar->testmode.utf_mode_fw.fw_file); if (ret == 0) { ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode using fw utf api 2"); - return 0; + goto out; } - ret = ath10k_tm_fetch_utf_firmware_api_1(ar); + ret = ath10k_tm_fetch_utf_firmware_api_1(ar, &ar->testmode.utf_mode_fw.fw_file); if (ret) { ath10k_err(ar, "failed to fetch utf firmware binary: %d", ret); return ret; @@ -306,6 +205,21 @@ static int ath10k_tm_fetch_firmware(struct ath10k *ar) ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode using utf api 1"); +out: + utf_mode_fw = &ar->testmode.utf_mode_fw; + + /* Use the same board data file as the normal firmware uses (but + * it's still "owned" by normal_mode_fw so we shouldn't free it. + */ + utf_mode_fw->board_data = ar->normal_mode_fw.board_data; + utf_mode_fw->board_len = ar->normal_mode_fw.board_len; + + if (!utf_mode_fw->fw_file.otp_data) { + ath10k_info(ar, "utf.bin didn't contain otp binary, taking it from the normal mode firmware"); + utf_mode_fw->fw_file.otp_data = ar->normal_mode_fw.fw_file.otp_data; + utf_mode_fw->fw_file.otp_len = ar->normal_mode_fw.fw_file.otp_len; + } + return 0; } @@ -329,7 +243,7 @@ static int ath10k_tm_cmd_utf_start(struct ath10k *ar, struct nlattr *tb[]) goto err; } - if (WARN_ON(ar->testmode.utf != NULL)) { + if (WARN_ON(ar->testmode.utf_mode_fw.fw_file.firmware != NULL)) { /* utf image is already downloaded, it shouldn't be */ ret = -EEXIST; goto err; @@ -341,30 +255,34 @@ static int ath10k_tm_cmd_utf_start(struct ath10k *ar, struct nlattr *tb[]) goto err; } + if (ar->testmode.utf_mode_fw.fw_file.codeswap_data && + ar->testmode.utf_mode_fw.fw_file.codeswap_len) { + ret = ath10k_swap_code_seg_init(ar, + &ar->testmode.utf_mode_fw.fw_file); + if (ret) { + ath10k_warn(ar, + "failed to init utf code swap segment: %d\n", + ret); + goto err_release_utf_mode_fw; + } + } + spin_lock_bh(&ar->data_lock); ar->testmode.utf_monitor = true; spin_unlock_bh(&ar->data_lock); - BUILD_BUG_ON(sizeof(ar->fw_features) != - sizeof(ar->testmode.orig_fw_features)); - - memcpy(ar->testmode.orig_fw_features, ar->fw_features, - sizeof(ar->fw_features)); - ar->testmode.orig_wmi_op_version = ar->wmi.op_version; - memset(ar->fw_features, 0, sizeof(ar->fw_features)); - - ar->wmi.op_version = ar->testmode.op_version; ath10k_dbg(ar, ATH10K_DBG_TESTMODE, "testmode wmi version %d\n", - ar->wmi.op_version); + ar->testmode.utf_mode_fw.fw_file.wmi_op_version); ret = ath10k_hif_power_up(ar); if (ret) { ath10k_err(ar, "failed to power up hif (testmode): %d\n", ret); ar->state = ATH10K_STATE_OFF; - goto err_fw_features; + goto err_release_utf_mode_fw; } - ret = ath10k_core_start(ar, ATH10K_FIRMWARE_MODE_UTF); + ret = ath10k_core_start(ar, ATH10K_FIRMWARE_MODE_UTF, + &ar->testmode.utf_mode_fw); if (ret) { ath10k_err(ar, "failed to start core (testmode): %d\n", ret); ar->state = ATH10K_STATE_OFF; @@ -373,8 +291,8 @@ static int ath10k_tm_cmd_utf_start(struct ath10k *ar, struct nlattr *tb[]) ar->state = ATH10K_STATE_UTF; - if (strlen(ar->testmode.utf_version) > 0) - ver = ar->testmode.utf_version; + if (strlen(ar->testmode.utf_mode_fw.fw_file.fw_version) > 0) + ver = ar->testmode.utf_mode_fw.fw_file.fw_version; else ver = "API 1"; @@ -387,14 +305,14 @@ static int ath10k_tm_cmd_utf_start(struct ath10k *ar, struct nlattr *tb[]) err_power_down: ath10k_hif_power_down(ar); -err_fw_features: - /* return the original firmware features */ - memcpy(ar->fw_features, ar->testmode.orig_fw_features, - sizeof(ar->fw_features)); - ar->wmi.op_version = ar->testmode.orig_wmi_op_version; +err_release_utf_mode_fw: + if (ar->testmode.utf_mode_fw.fw_file.codeswap_data && + ar->testmode.utf_mode_fw.fw_file.codeswap_len) + ath10k_swap_code_seg_release(ar, + &ar->testmode.utf_mode_fw.fw_file); - release_firmware(ar->testmode.utf); - ar->testmode.utf = NULL; + release_firmware(ar->testmode.utf_mode_fw.fw_file.firmware); + ar->testmode.utf_mode_fw.fw_file.firmware = NULL; err: mutex_unlock(&ar->conf_mutex); @@ -415,13 +333,13 @@ static void __ath10k_tm_cmd_utf_stop(struct ath10k *ar) spin_unlock_bh(&ar->data_lock); - /* return the original firmware features */ - memcpy(ar->fw_features, ar->testmode.orig_fw_features, - sizeof(ar->fw_features)); - ar->wmi.op_version = ar->testmode.orig_wmi_op_version; + if (ar->testmode.utf_mode_fw.fw_file.codeswap_data && + ar->testmode.utf_mode_fw.fw_file.codeswap_len) + ath10k_swap_code_seg_release(ar, + &ar->testmode.utf_mode_fw.fw_file); - release_firmware(ar->testmode.utf); - ar->testmode.utf = NULL; + release_firmware(ar->testmode.utf_mode_fw.fw_file.firmware); + ar->testmode.utf_mode_fw.fw_file.firmware = NULL; ar->state = ATH10K_STATE_OFF; } @@ -479,7 +397,7 @@ static int ath10k_tm_cmd_wmi(struct ath10k *ar, struct nlattr *tb[]) cmd_id = nla_get_u32(tb[ATH10K_TM_ATTR_WMI_CMDID]); ath10k_dbg(ar, ATH10K_DBG_TESTMODE, - "testmode cmd wmi cmd_id %d buf %p buf_len %d\n", + "testmode cmd wmi cmd_id %d buf %pK buf_len %d\n", cmd_id, buf, buf_len); ath10k_dbg_dump(ar, ATH10K_DBG_TESTMODE, NULL, "", buf, buf_len); diff --git a/drivers/net/wireless/ath/ath10k/testmode_i.h b/drivers/net/wireless/ath/ath10k/testmode_i.h index ba81bf66ce85..191a8f34c8ea 100644 --- a/drivers/net/wireless/ath/ath10k/testmode_i.h +++ b/drivers/net/wireless/ath/ath10k/testmode_i.h @@ -33,6 +33,7 @@ enum ath10k_tm_attr { ATH10K_TM_ATTR_WMI_CMDID = 3, ATH10K_TM_ATTR_VERSION_MAJOR = 4, ATH10K_TM_ATTR_VERSION_MINOR = 5, + ATH10K_TM_ATTR_WMI_OP_VERSION = 6, /* keep last */ __ATH10K_TM_ATTR_AFTER_LAST, diff --git a/drivers/net/wireless/ath/ath10k/thermal.c b/drivers/net/wireless/ath/ath10k/thermal.c index 60fe562e3041..0a47269be289 100644 --- a/drivers/net/wireless/ath/ath10k/thermal.c +++ b/drivers/net/wireless/ath/ath10k/thermal.c @@ -187,12 +187,12 @@ int ath10k_thermal_register(struct ath10k *ar) /* Do not register hwmon device when temperature reading is not * supported by firmware */ - if (ar->wmi.op_version != ATH10K_FW_WMI_OP_VERSION_10_2_4) + if (!(ar->wmi.ops->gen_pdev_get_temperature)) return 0; /* Avoid linking error on devm_hwmon_device_register_with_groups, I * guess linux/hwmon.h is missing proper stubs. */ - if (!config_enabled(CONFIG_HWMON)) + if (!IS_REACHABLE(CONFIG_HWMON)) return 0; hwmon_dev = devm_hwmon_device_register_with_groups(ar->dev, diff --git a/drivers/net/wireless/ath/ath10k/thermal.h b/drivers/net/wireless/ath/ath10k/thermal.h index b610ea5caae8..3abb97f63b1e 100644 --- a/drivers/net/wireless/ath/ath10k/thermal.h +++ b/drivers/net/wireless/ath/ath10k/thermal.h @@ -20,7 +20,7 @@ #define ATH10K_QUIET_PERIOD_MIN 25 #define ATH10K_QUIET_START_OFFSET 10 #define ATH10K_HWMON_NAME_LEN 15 -#define ATH10K_THERMAL_SYNC_TIMEOUT_HZ (5*HZ) +#define ATH10K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ) #define ATH10K_THERMAL_THROTTLE_MAX 100 struct ath10k_thermal { @@ -36,7 +36,7 @@ struct ath10k_thermal { int temperature; }; -#ifdef CONFIG_THERMAL +#if IS_REACHABLE(CONFIG_THERMAL) int ath10k_thermal_register(struct ath10k *ar); void ath10k_thermal_unregister(struct ath10k *ar); void ath10k_thermal_event_temperature(struct ath10k *ar, int temperature); diff --git a/drivers/net/wireless/ath/ath10k/trace.h b/drivers/net/wireless/ath/ath10k/trace.h index 71bdb368813d..e0d00cef0bd8 100644 --- a/drivers/net/wireless/ath/ath10k/trace.h +++ b/drivers/net/wireless/ath/ath10k/trace.h @@ -250,6 +250,7 @@ TRACE_EVENT(ath10k_wmi_dbglog, TP_STRUCT__entry( __string(device, dev_name(ar->dev)) __string(driver, dev_driver_string(ar->dev)) + __field(u8, hw_type); __field(size_t, buf_len) __dynamic_array(u8, buf, buf_len) ), @@ -257,14 +258,16 @@ TRACE_EVENT(ath10k_wmi_dbglog, TP_fast_assign( __assign_str(device, dev_name(ar->dev)); __assign_str(driver, dev_driver_string(ar->dev)); + __entry->hw_type = ar->hw_rev; __entry->buf_len = buf_len; memcpy(__get_dynamic_array(buf), buf, buf_len); ), TP_printk( - "%s %s len %zu", + "%s %s %d len %zu", __get_str(driver), __get_str(device), + __entry->hw_type, __entry->buf_len ) ); @@ -277,6 +280,7 @@ TRACE_EVENT(ath10k_htt_pktlog, TP_STRUCT__entry( __string(device, dev_name(ar->dev)) __string(driver, dev_driver_string(ar->dev)) + __field(u8, hw_type); __field(u16, buf_len) __dynamic_array(u8, pktlog, buf_len) ), @@ -284,14 +288,16 @@ TRACE_EVENT(ath10k_htt_pktlog, TP_fast_assign( __assign_str(device, dev_name(ar->dev)); __assign_str(driver, dev_driver_string(ar->dev)); + __entry->hw_type = ar->hw_rev; __entry->buf_len = buf_len; memcpy(__get_dynamic_array(pktlog), buf, buf_len); ), TP_printk( - "%s %s size %hu", + "%s %s %d size %hu", __get_str(driver), __get_str(device), + __entry->hw_type, __entry->buf_len ) ); @@ -440,6 +446,7 @@ TRACE_EVENT(ath10k_htt_rx_desc, TP_STRUCT__entry( __string(device, dev_name(ar->dev)) __string(driver, dev_driver_string(ar->dev)) + __field(u8, hw_type); __field(u16, len) __dynamic_array(u8, rxdesc, len) ), @@ -447,14 +454,16 @@ TRACE_EVENT(ath10k_htt_rx_desc, TP_fast_assign( __assign_str(device, dev_name(ar->dev)); __assign_str(driver, dev_driver_string(ar->dev)); + __entry->hw_type = ar->hw_rev; __entry->len = len; memcpy(__get_dynamic_array(rxdesc), data, len); ), TP_printk( - "%s %s rxdesc len %d", + "%s %s %d rxdesc len %d", __get_str(driver), __get_str(device), + __entry->hw_type, __entry->len ) ); diff --git a/drivers/net/wireless/ath/ath10k/txrx.c b/drivers/net/wireless/ath/ath10k/txrx.c index 6d1105ab4592..9852c5d51139 100644 --- a/drivers/net/wireless/ath/ath10k/txrx.c +++ b/drivers/net/wireless/ath/ath10k/txrx.c @@ -23,7 +23,12 @@ static void ath10k_report_offchan_tx(struct ath10k *ar, struct sk_buff *skb) { - if (!ATH10K_SKB_CB(skb)->htt.is_offchan) + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (likely(!(info->flags & IEEE80211_TX_CTL_TX_OFFCHAN))) + return; + + if (ath10k_mac_tx_frm_has_freq(ar)) return; /* If the original wait_for_completion() timed out before @@ -39,32 +44,30 @@ static void ath10k_report_offchan_tx(struct ath10k *ar, struct sk_buff *skb) complete(&ar->offchan_tx_completed); ar->offchan_tx_skb = NULL; /* just for sanity */ - ath10k_dbg(ar, ATH10K_DBG_HTT, "completed offchannel skb %p\n", skb); + ath10k_dbg(ar, ATH10K_DBG_HTT, "completed offchannel skb %pK\n", skb); out: spin_unlock_bh(&ar->data_lock); } -void ath10k_txrx_tx_unref(struct ath10k_htt *htt, - const struct htt_tx_done *tx_done) +int ath10k_txrx_tx_unref(struct ath10k_htt *htt, + const struct htt_tx_done *tx_done) { struct ath10k *ar = htt->ar; struct device *dev = ar->dev; struct ieee80211_tx_info *info; + struct ieee80211_txq *txq; struct ath10k_skb_cb *skb_cb; + struct ath10k_txq *artxq; struct sk_buff *msdu; - struct ieee80211_hdr *hdr; - __le16 fc; - bool limit_mgmt_desc = false; ath10k_dbg(ar, ATH10K_DBG_HTT, - "htt tx completion msdu_id %u discard %d no_ack %d success %d\n", - tx_done->msdu_id, !!tx_done->discard, - !!tx_done->no_ack, !!tx_done->success); + "htt tx completion msdu_id %u status %d\n", + tx_done->msdu_id, tx_done->status); if (tx_done->msdu_id >= htt->max_num_pending_tx) { ath10k_warn(ar, "warning: msdu_id %d too big, ignoring\n", tx_done->msdu_id); - return; + return -EINVAL; } spin_lock_bh(&htt->tx_lock); @@ -73,23 +76,23 @@ void ath10k_txrx_tx_unref(struct ath10k_htt *htt, ath10k_warn(ar, "received tx completion for invalid msdu_id: %d\n", tx_done->msdu_id); spin_unlock_bh(&htt->tx_lock); - return; + return -ENOENT; } - hdr = (struct ieee80211_hdr *)msdu->data; - fc = hdr->frame_control; + skb_cb = ATH10K_SKB_CB(msdu); + txq = skb_cb->txq; - if (unlikely(ieee80211_is_mgmt(fc)) && - ar->hw_params.max_probe_resp_desc_thres) - limit_mgmt_desc = true; + if (txq) { + artxq = (void *)txq->drv_priv; + artxq->num_fw_queued--; + } ath10k_htt_tx_free_msdu_id(htt, tx_done->msdu_id); - __ath10k_htt_tx_dec_pending(htt, limit_mgmt_desc); + ath10k_htt_tx_dec_pending(htt); if (htt->num_pending_tx == 0) wake_up(&htt->empty_tx_wq); spin_unlock_bh(&htt->tx_lock); - skb_cb = ATH10K_SKB_CB(msdu); dma_unmap_single(dev, skb_cb->paddr, msdu->len, DMA_TO_DEVICE); ath10k_report_offchan_tx(htt->ar, msdu); @@ -98,22 +101,25 @@ void ath10k_txrx_tx_unref(struct ath10k_htt *htt, memset(&info->status, 0, sizeof(info->status)); trace_ath10k_txrx_tx_unref(ar, tx_done->msdu_id); - if (tx_done->discard) { + if (tx_done->status == HTT_TX_COMPL_STATE_DISCARD) { ieee80211_free_txskb(htt->ar->hw, msdu); - return; + return 0; } if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) info->flags |= IEEE80211_TX_STAT_ACK; - if (tx_done->no_ack) + if (tx_done->status == HTT_TX_COMPL_STATE_NOACK) info->flags &= ~IEEE80211_TX_STAT_ACK; - if (tx_done->success && (info->flags & IEEE80211_TX_CTL_NO_ACK)) + if ((tx_done->status == HTT_TX_COMPL_STATE_ACK) && + (info->flags & IEEE80211_TX_CTL_NO_ACK)) info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED; ieee80211_tx_status(htt->ar->hw, msdu); /* we do not own the msdu anymore */ + + return 0; } struct ath10k_peer *ath10k_peer_find(struct ath10k *ar, int vdev_id, @@ -126,7 +132,7 @@ struct ath10k_peer *ath10k_peer_find(struct ath10k *ar, int vdev_id, list_for_each_entry(peer, &ar->peers, list) { if (peer->vdev_id != vdev_id) continue; - if (memcmp(peer->addr, addr, ETH_ALEN)) + if (!ether_addr_equal(peer->addr, addr)) continue; return peer; @@ -162,7 +168,7 @@ static int ath10k_wait_for_peer_common(struct ath10k *ar, int vdev_id, (mapped == expect_mapped || test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)); - }), 3*HZ); + }), 3 * HZ); if (time_left == 0) return -ETIMEDOUT; @@ -186,6 +192,13 @@ void ath10k_peer_map_event(struct ath10k_htt *htt, struct ath10k *ar = htt->ar; struct ath10k_peer *peer; + if (ev->peer_id >= ATH10K_MAX_NUM_PEER_IDS) { + ath10k_warn(ar, + "received htt peer map event with idx out of bounds: %hu\n", + ev->peer_id); + return; + } + spin_lock_bh(&ar->data_lock); peer = ath10k_peer_find(ar, ev->vdev_id, ev->addr); if (!peer) { @@ -202,6 +215,8 @@ void ath10k_peer_map_event(struct ath10k_htt *htt, ath10k_dbg(ar, ATH10K_DBG_HTT, "htt peer map vdev %d peer %pM id %d\n", ev->vdev_id, ev->addr, ev->peer_id); + WARN_ON(ar->peer_map[ev->peer_id] && (ar->peer_map[ev->peer_id] != peer)); + ar->peer_map[ev->peer_id] = peer; set_bit(ev->peer_id, peer->peer_ids); exit: spin_unlock_bh(&ar->data_lock); @@ -213,6 +228,13 @@ void ath10k_peer_unmap_event(struct ath10k_htt *htt, struct ath10k *ar = htt->ar; struct ath10k_peer *peer; + if (ev->peer_id >= ATH10K_MAX_NUM_PEER_IDS) { + ath10k_warn(ar, + "received htt peer unmap event with idx out of bounds: %hu\n", + ev->peer_id); + return; + } + spin_lock_bh(&ar->data_lock); peer = ath10k_peer_find_by_id(ar, ev->peer_id); if (!peer) { @@ -224,6 +246,7 @@ void ath10k_peer_unmap_event(struct ath10k_htt *htt, ath10k_dbg(ar, ATH10K_DBG_HTT, "htt peer unmap vdev %d peer %pM id %d\n", peer->vdev_id, peer->addr, ev->peer_id); + ar->peer_map[ev->peer_id] = NULL; clear_bit(ev->peer_id, peer->peer_ids); if (bitmap_empty(peer->peer_ids, ATH10K_MAX_NUM_PEER_IDS)) { diff --git a/drivers/net/wireless/ath/ath10k/txrx.h b/drivers/net/wireless/ath/ath10k/txrx.h index a90e09f5c7f2..e7ea1ae1c438 100644 --- a/drivers/net/wireless/ath/ath10k/txrx.h +++ b/drivers/net/wireless/ath/ath10k/txrx.h @@ -19,8 +19,8 @@ #include "htt.h" -void ath10k_txrx_tx_unref(struct ath10k_htt *htt, - const struct htt_tx_done *tx_done); +int ath10k_txrx_tx_unref(struct ath10k_htt *htt, + const struct htt_tx_done *tx_done); struct ath10k_peer *ath10k_peer_find(struct ath10k *ar, int vdev_id, const u8 *addr); diff --git a/drivers/net/wireless/ath/ath10k/wcn3990_qmi_service_v01.c b/drivers/net/wireless/ath/ath10k/wcn3990_qmi_service_v01.c new file mode 100644 index 000000000000..7d6cf8b23814 --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/wcn3990_qmi_service_v01.c @@ -0,0 +1,2091 @@ + /* Copyright (c) 2017, 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/qmi_encdec.h> + +#include <soc/qcom/msm_qmi_interface.h> + +#include "wcn3990_qmi_service_v01.h" + +static struct elem_info wlfw_ce_tgt_pipe_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + pipe_num), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_pipedir_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + pipe_dir), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + nentries), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + nbytes_max), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + flags), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_ce_svc_pipe_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_svc_pipe_cfg_s_v01, + service_id), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_pipedir_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_svc_pipe_cfg_s_v01, + pipe_dir), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_svc_pipe_cfg_s_v01, + pipe_num), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_shadow_reg_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_shadow_reg_cfg_s_v01, + id), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_shadow_reg_cfg_s_v01, + offset), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_shadow_reg_v2_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_shadow_reg_v2_cfg_s_v01, + addr), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_memory_region_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_memory_region_info_s_v01, + region_addr), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_memory_region_info_s_v01, + size), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_memory_region_info_s_v01, + secure_flag), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_rf_chip_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_rf_chip_info_s_v01, + chip_id), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_rf_chip_info_s_v01, + chip_family), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_rf_board_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_rf_board_info_s_v01, + board_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_soc_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_soc_info_s_v01, + soc_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_fw_version_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_fw_version_info_s_v01, + fw_version), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_MAX_TIMESTAMP_LEN_V01 + 1, + .elem_size = sizeof(char), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_fw_version_info_s_v01, + fw_build_timestamp), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_ind_register_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_ready_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_ready_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + initiate_cal_download_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + initiate_cal_download_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + initiate_cal_update_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + initiate_cal_update_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + msa_ready_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + msa_ready_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + pin_connect_result_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + pin_connect_result_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + client_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + client_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + request_mem_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + request_mem_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_mem_ready_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_mem_ready_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + cold_boot_cal_done_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + cold_boot_cal_done_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + rejuvenate_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + rejuvenate_enable), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_ind_register_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_ind_register_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_ind_register_resp_msg_v01, + fw_status_valid), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_ind_register_resp_msg_v01, + fw_status), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_fw_ready_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_ready_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_pin_connect_result_ind_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_pin_connect_result_ind_msg_v01, + pwr_pin_result_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_pin_connect_result_ind_msg_v01, + pwr_pin_result), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct wlfw_pin_connect_result_ind_msg_v01, + phy_io_pin_result_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct wlfw_pin_connect_result_ind_msg_v01, + phy_io_pin_result), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof( + struct wlfw_pin_connect_result_ind_msg_v01, + rf_pin_result_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof( + struct wlfw_pin_connect_result_ind_msg_v01, + rf_pin_result), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_wlan_mode_req_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_driver_mode_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_wlan_mode_req_msg_v01, + mode), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_wlan_mode_req_msg_v01, + hw_debug_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_wlan_mode_req_msg_v01, + hw_debug), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_wlan_mode_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_wlan_mode_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_wlan_cfg_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + host_version_valid), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_MAX_STR_LEN_V01 + 1, + .elem_size = sizeof(char), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + host_version), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + tgt_cfg_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + tgt_cfg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_CE_V01, + .elem_size = sizeof(struct wlfw_ce_tgt_pipe_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + tgt_cfg), + .ei_array = wlfw_ce_tgt_pipe_cfg_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + svc_cfg_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + svc_cfg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_SVC_V01, + .elem_size = sizeof(struct wlfw_ce_svc_pipe_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + svc_cfg), + .ei_array = wlfw_ce_svc_pipe_cfg_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V01, + .elem_size = sizeof(struct wlfw_shadow_reg_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg), + .ei_array = wlfw_shadow_reg_cfg_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_v2_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_v2_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01, + .elem_size = sizeof(struct wlfw_shadow_reg_v2_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_v2), + .ei_array = wlfw_shadow_reg_v2_cfg_s_v01_ei, + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_wlan_cfg_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_wlan_cfg_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cap_req_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cap_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + chip_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct wlfw_rf_chip_info_s_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + chip_info), + .ei_array = wlfw_rf_chip_info_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + board_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct wlfw_rf_board_info_s_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + board_info), + .ei_array = wlfw_rf_board_info_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + soc_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct wlfw_soc_info_s_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + soc_info), + .ei_array = wlfw_soc_info_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + fw_version_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct wlfw_fw_version_info_s_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + fw_version_info), + .ei_array = wlfw_fw_version_info_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + fw_build_id_valid), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_MAX_BUILD_ID_LEN_V01 + 1, + .elem_size = sizeof(char), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + fw_build_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_bdf_download_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + valid), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + file_id_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + file_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + total_size_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + total_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + seg_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + seg_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_DATA_SIZE_V01, + .elem_size = sizeof(uint8_t), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + end_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + end), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_bdf_download_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_bdf_download_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_report_req_msg_v01_ei[] = { + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_cal_report_req_msg_v01, + meta_data_len), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = QMI_WLFW_MAX_NUM_CAL_V01, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_cal_report_req_msg_v01, + meta_data), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_report_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_cal_report_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_initiate_cal_download_ind_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof( + struct wlfw_initiate_cal_download_ind_msg_v01, + cal_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_download_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + valid), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + file_id_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + file_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + total_size_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + total_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + seg_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + seg_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_DATA_SIZE_V01, + .elem_size = sizeof(uint8_t), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + end_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + end), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_download_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_cal_download_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_initiate_cal_update_ind_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof( + struct wlfw_initiate_cal_update_ind_msg_v01, + cal_id), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_initiate_cal_update_ind_msg_v01, + total_size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_update_req_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_cal_update_req_msg_v01, + cal_id), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_cal_update_req_msg_v01, + seg_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_update_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + file_id_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + file_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + total_size_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + total_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + seg_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + seg_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_DATA_SIZE_V01, + .elem_size = sizeof(uint8_t), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + end_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + end), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_info_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_msa_info_req_msg_v01, + msa_addr), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_msa_info_req_msg_v01, + size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_info_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_msa_info_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct wlfw_msa_info_resp_msg_v01, + mem_region_info_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01, + .elem_size = sizeof(struct wlfw_memory_region_info_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct wlfw_msa_info_resp_msg_v01, + mem_region_info), + .ei_array = wlfw_memory_region_info_s_v01_ei, + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_ready_req_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_ready_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_msa_ready_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_ini_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_ini_req_msg_v01, + enablefwlog_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_ini_req_msg_v01, + enablefwlog), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_ini_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_ini_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_athdiag_read_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_athdiag_read_req_msg_v01, + offset), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_athdiag_read_req_msg_v01, + mem_type), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct wlfw_athdiag_read_req_msg_v01, + data_len), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_athdiag_read_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_athdiag_read_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_athdiag_read_resp_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_athdiag_read_resp_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_DATA_SIZE_V01, + .elem_size = sizeof(uint8_t), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_athdiag_read_resp_msg_v01, + data), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_athdiag_write_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof( + struct wlfw_athdiag_write_req_msg_v01, + offset), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_athdiag_write_req_msg_v01, + mem_type), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof( + struct wlfw_athdiag_write_req_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_DATA_SIZE_V01, + .elem_size = sizeof(uint8_t), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x03, + .offset = offsetof( + struct wlfw_athdiag_write_req_msg_v01, + data), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_athdiag_write_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_athdiag_write_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_vbatt_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_vbatt_req_msg_v01, + voltage_uv), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_vbatt_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_vbatt_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_mac_addr_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_mac_addr_req_msg_v01, + mac_addr_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAC_ADDR_SIZE_V01, + .elem_size = sizeof(uint8_t), + .is_array = STATIC_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_mac_addr_req_msg_v01, + mac_addr), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_mac_addr_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_mac_addr_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_host_cap_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + daemon_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + daemon_support), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_host_cap_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_host_cap_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_request_mem_ind_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_request_mem_ind_msg_v01, + size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_respond_mem_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_respond_mem_req_msg_v01, + addr), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_respond_mem_req_msg_v01, + size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_respond_mem_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_respond_mem_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_fw_mem_ready_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cold_boot_cal_done_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_rejuvenate_ind_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + cause_for_rejuvenation_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + cause_for_rejuvenation), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + requesting_sub_system_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + requesting_sub_system), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + line_number_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint16_t), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + line_number), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + function_name_valid), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_FUNCTION_NAME_LEN_V01 + 1, + .elem_size = sizeof(char), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + function_name), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_rejuvenate_ack_req_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_rejuvenate_ack_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_rejuvenate_ack_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_dynamic_feature_mask_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_dynamic_feature_mask_req_msg_v01, + mask_valid), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_req_msg_v01, + mask), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_dynamic_feature_mask_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + prev_mask_valid), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + prev_mask), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + curr_mask_valid), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint64_t), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + curr_mask), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .is_array = QMI_COMMON_TLV_TYPE, + }, +}; + diff --git a/drivers/net/wireless/ath/ath10k/wcn3990_qmi_service_v01.h b/drivers/net/wireless/ath/ath10k/wcn3990_qmi_service_v01.h new file mode 100644 index 000000000000..21513b8f6200 --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/wcn3990_qmi_service_v01.h @@ -0,0 +1,619 @@ + /* Copyright (c) 2017, 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. + * + */ +#ifndef WLAN_FIRMWARE_SERVICE_V01_H +#define WLAN_FIRMWARE_SERVICE_V01_H + +#define WLFW_SERVICE_ID_V01 0x45 +#define WLFW_SERVICE_VERS_V01 0x01 + +#define QMI_WLFW_BDF_DOWNLOAD_REQ_V01 0x0025 +#define QMI_WLFW_FW_MEM_READY_IND_V01 0x0037 +#define QMI_WLFW_INITIATE_CAL_UPDATE_IND_V01 0x002A +#define QMI_WLFW_HOST_CAP_REQ_V01 0x0034 +#define QMI_WLFW_DYNAMIC_FEATURE_MASK_RESP_V01 0x003B +#define QMI_WLFW_CAP_REQ_V01 0x0024 +#define QMI_WLFW_CAL_REPORT_REQ_V01 0x0026 +#define QMI_WLFW_CAL_UPDATE_RESP_V01 0x0029 +#define QMI_WLFW_CAL_DOWNLOAD_RESP_V01 0x0027 +#define QMI_WLFW_INI_RESP_V01 0x002F +#define QMI_WLFW_CAL_REPORT_RESP_V01 0x0026 +#define QMI_WLFW_MAC_ADDR_RESP_V01 0x0033 +#define QMI_WLFW_INITIATE_CAL_DOWNLOAD_IND_V01 0x0028 +#define QMI_WLFW_HOST_CAP_RESP_V01 0x0034 +#define QMI_WLFW_MSA_READY_IND_V01 0x002B +#define QMI_WLFW_ATHDIAG_WRITE_RESP_V01 0x0031 +#define QMI_WLFW_WLAN_MODE_REQ_V01 0x0022 +#define QMI_WLFW_IND_REGISTER_REQ_V01 0x0020 +#define QMI_WLFW_WLAN_CFG_RESP_V01 0x0023 +#define QMI_WLFW_COLD_BOOT_CAL_DONE_IND_V01 0x0038 +#define QMI_WLFW_REQUEST_MEM_IND_V01 0x0035 +#define QMI_WLFW_REJUVENATE_IND_V01 0x0039 +#define QMI_WLFW_DYNAMIC_FEATURE_MASK_REQ_V01 0x003B +#define QMI_WLFW_ATHDIAG_WRITE_REQ_V01 0x0031 +#define QMI_WLFW_WLAN_MODE_RESP_V01 0x0022 +#define QMI_WLFW_RESPOND_MEM_REQ_V01 0x0036 +#define QMI_WLFW_PIN_CONNECT_RESULT_IND_V01 0x002C +#define QMI_WLFW_FW_READY_IND_V01 0x0021 +#define QMI_WLFW_MSA_READY_RESP_V01 0x002E +#define QMI_WLFW_CAL_UPDATE_REQ_V01 0x0029 +#define QMI_WLFW_INI_REQ_V01 0x002F +#define QMI_WLFW_BDF_DOWNLOAD_RESP_V01 0x0025 +#define QMI_WLFW_REJUVENATE_ACK_RESP_V01 0x003A +#define QMI_WLFW_MSA_INFO_RESP_V01 0x002D +#define QMI_WLFW_MSA_READY_REQ_V01 0x002E +#define QMI_WLFW_CAP_RESP_V01 0x0024 +#define QMI_WLFW_REJUVENATE_ACK_REQ_V01 0x003A +#define QMI_WLFW_ATHDIAG_READ_RESP_V01 0x0030 +#define QMI_WLFW_VBATT_REQ_V01 0x0032 +#define QMI_WLFW_MAC_ADDR_REQ_V01 0x0033 +#define QMI_WLFW_RESPOND_MEM_RESP_V01 0x0036 +#define QMI_WLFW_VBATT_RESP_V01 0x0032 +#define QMI_WLFW_MSA_INFO_REQ_V01 0x002D +#define QMI_WLFW_CAL_DOWNLOAD_REQ_V01 0x0027 +#define QMI_WLFW_ATHDIAG_READ_REQ_V01 0x0030 +#define QMI_WLFW_WLAN_CFG_REQ_V01 0x0023 +#define QMI_WLFW_IND_REGISTER_RESP_V01 0x0020 + +#define QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01 2 +#define QMI_WLFW_MAX_NUM_CAL_V01 5 +#define QMI_WLFW_MAX_DATA_SIZE_V01 6144 +#define QMI_WLFW_FUNCTION_NAME_LEN_V01 128 +#define QMI_WLFW_MAX_NUM_CE_V01 12 +#define QMI_WLFW_MAX_TIMESTAMP_LEN_V01 32 +#define QMI_WLFW_MAX_BUILD_ID_LEN_V01 128 +#define QMI_WLFW_MAX_STR_LEN_V01 16 +#define QMI_WLFW_MAX_NUM_SHADOW_REG_V01 24 +#define QMI_WLFW_MAC_ADDR_SIZE_V01 6 +#define QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01 36 +#define QMI_WLFW_MAX_NUM_SVC_V01 24 + +enum wlfw_driver_mode_enum_v01 { + WLFW_DRIVER_MODE_ENUM_MIN_VAL_V01 = INT_MIN, + QMI_WLFW_MISSION_V01 = 0, + QMI_WLFW_FTM_V01 = 1, + QMI_WLFW_EPPING_V01 = 2, + QMI_WLFW_WALTEST_V01 = 3, + QMI_WLFW_OFF_V01 = 4, + QMI_WLFW_CCPM_V01 = 5, + QMI_WLFW_QVIT_V01 = 6, + QMI_WLFW_CALIBRATION_V01 = 7, + WLFW_DRIVER_MODE_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum wlfw_cal_temp_id_enum_v01 { + WLFW_CAL_TEMP_ID_ENUM_MIN_VAL_V01 = INT_MIN, + QMI_WLFW_CAL_TEMP_IDX_0_V01 = 0, + QMI_WLFW_CAL_TEMP_IDX_1_V01 = 1, + QMI_WLFW_CAL_TEMP_IDX_2_V01 = 2, + QMI_WLFW_CAL_TEMP_IDX_3_V01 = 3, + QMI_WLFW_CAL_TEMP_IDX_4_V01 = 4, + WLFW_CAL_TEMP_ID_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum wlfw_pipedir_enum_v01 { + WLFW_PIPEDIR_ENUM_MIN_VAL_V01 = INT_MIN, + QMI_WLFW_PIPEDIR_NONE_V01 = 0, + QMI_WLFW_PIPEDIR_IN_V01 = 1, + QMI_WLFW_PIPEDIR_OUT_V01 = 2, + QMI_WLFW_PIPEDIR_INOUT_V01 = 3, + WLFW_PIPEDIR_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +#define QMI_WLFW_CE_ATTR_FLAGS_V01 ((uint32_t)0x00) +#define QMI_WLFW_CE_ATTR_NO_SNOOP_V01 ((uint32_t)0x01) +#define QMI_WLFW_CE_ATTR_BYTE_SWAP_DATA_V01 ((uint32_t)0x02) +#define QMI_WLFW_CE_ATTR_SWIZZLE_DESCRIPTORS_V01 ((uint32_t)0x04) +#define QMI_WLFW_CE_ATTR_DISABLE_INTR_V01 ((uint32_t)0x08) +#define QMI_WLFW_CE_ATTR_ENABLE_POLL_V01 ((uint32_t)0x10) + +#define QMI_WLFW_ALREADY_REGISTERED_V01 ((uint64_t)0x01ULL) +#define QMI_WLFW_FW_READY_V01 ((uint64_t)0x02ULL) +#define QMI_WLFW_MSA_READY_V01 ((uint64_t)0x04ULL) +#define QMI_WLFW_FW_MEM_READY_V01 ((uint64_t)0x08ULL) + +#define QMI_WLFW_FW_REJUVENATE_V01 ((uint64_t)0x01ULL) + +struct wlfw_ce_tgt_pipe_cfg_s_v01 { + u32 pipe_num; + enum wlfw_pipedir_enum_v01 pipe_dir; + u32 nentries; + u32 nbytes_max; + u32 flags; +}; + +struct wlfw_ce_svc_pipe_cfg_s_v01 { + u32 service_id; + enum wlfw_pipedir_enum_v01 pipe_dir; + u32 pipe_num; +}; + +struct wlfw_shadow_reg_cfg_s_v01 { + u16 id; + u16 offset; +}; + +struct wlfw_shadow_reg_v2_cfg_s_v01 { + u32 addr; +}; + +struct wlfw_memory_region_info_s_v01 { + u64 region_addr; + u32 size; + u8 secure_flag; +}; + +struct wlfw_rf_chip_info_s_v01 { + u32 chip_id; + u32 chip_family; +}; + +struct wlfw_rf_board_info_s_v01 { + u32 board_id; +}; + +struct wlfw_soc_info_s_v01 { + u32 soc_id; +}; + +struct wlfw_fw_version_info_s_v01 { + u32 fw_version; + char fw_build_timestamp[QMI_WLFW_MAX_TIMESTAMP_LEN_V01 + 1]; +}; + +struct wlfw_ind_register_req_msg_v01 { + u8 fw_ready_enable_valid; + u8 fw_ready_enable; + u8 initiate_cal_download_enable_valid; + u8 initiate_cal_download_enable; + u8 initiate_cal_update_enable_valid; + u8 initiate_cal_update_enable; + u8 msa_ready_enable_valid; + u8 msa_ready_enable; + u8 pin_connect_result_enable_valid; + u8 pin_connect_result_enable; + u8 client_id_valid; + u32 client_id; + u8 request_mem_enable_valid; + u8 request_mem_enable; + u8 fw_mem_ready_enable_valid; + u8 fw_mem_ready_enable; + u8 cold_boot_cal_done_enable_valid; + u8 cold_boot_cal_done_enable; + u8 rejuvenate_enable_valid; + u32 rejuvenate_enable; +}; + +#define WLFW_IND_REGISTER_REQ_MSG_V01_MAX_MSG_LEN 46 +extern struct elem_info wlfw_ind_register_req_msg_v01_ei[]; + +struct wlfw_ind_register_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 fw_status_valid; + u64 fw_status; +}; + +#define WLFW_IND_REGISTER_RESP_MSG_V01_MAX_MSG_LEN 18 +extern struct elem_info wlfw_ind_register_resp_msg_v01_ei[]; + +struct wlfw_fw_ready_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_FW_READY_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_fw_ready_ind_msg_v01_ei[]; + +struct wlfw_msa_ready_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_MSA_READY_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_msa_ready_ind_msg_v01_ei[]; + +struct wlfw_pin_connect_result_ind_msg_v01 { + u8 pwr_pin_result_valid; + u32 pwr_pin_result; + u8 phy_io_pin_result_valid; + u32 phy_io_pin_result; + u8 rf_pin_result_valid; + u32 rf_pin_result; +}; + +#define WLFW_PIN_CONNECT_RESULT_IND_MSG_V01_MAX_MSG_LEN 21 +extern struct elem_info wlfw_pin_connect_result_ind_msg_v01_ei[]; + +struct wlfw_wlan_mode_req_msg_v01 { + enum wlfw_driver_mode_enum_v01 mode; + u8 hw_debug_valid; + u8 hw_debug; +}; + +#define WLFW_WLAN_MODE_REQ_MSG_V01_MAX_MSG_LEN 11 +extern struct elem_info wlfw_wlan_mode_req_msg_v01_ei[]; + +struct wlfw_wlan_mode_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_WLAN_MODE_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_wlan_mode_resp_msg_v01_ei[]; + +struct wlfw_wlan_cfg_req_msg_v01 { + u8 host_version_valid; + char host_version[QMI_WLFW_MAX_STR_LEN_V01 + 1]; + u8 tgt_cfg_valid; + u32 tgt_cfg_len; + struct wlfw_ce_tgt_pipe_cfg_s_v01 tgt_cfg[QMI_WLFW_MAX_NUM_CE_V01]; + u8 svc_cfg_valid; + u32 svc_cfg_len; + struct wlfw_ce_svc_pipe_cfg_s_v01 svc_cfg[QMI_WLFW_MAX_NUM_SVC_V01]; + u8 shadow_reg_valid; + u32 shadow_reg_len; + struct wlfw_shadow_reg_cfg_s_v01 + shadow_reg[QMI_WLFW_MAX_NUM_SHADOW_REG_V01]; + u8 shadow_reg_v2_valid; + u32 shadow_reg_v2_len; + struct wlfw_shadow_reg_v2_cfg_s_v01 + shadow_reg_v2[QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01]; +}; + +#define WLFW_WLAN_CFG_REQ_MSG_V01_MAX_MSG_LEN 803 +extern struct elem_info wlfw_wlan_cfg_req_msg_v01_ei[]; + +struct wlfw_wlan_cfg_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_WLAN_CFG_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_wlan_cfg_resp_msg_v01_ei[]; + +struct wlfw_cap_req_msg_v01 { + char placeholder; +}; + +#define WLFW_CAP_REQ_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_cap_req_msg_v01_ei[]; + +struct wlfw_cap_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 chip_info_valid; + struct wlfw_rf_chip_info_s_v01 chip_info; + u8 board_info_valid; + struct wlfw_rf_board_info_s_v01 board_info; + u8 soc_info_valid; + struct wlfw_soc_info_s_v01 soc_info; + u8 fw_version_info_valid; + struct wlfw_fw_version_info_s_v01 fw_version_info; + u8 fw_build_id_valid; + char fw_build_id[QMI_WLFW_MAX_BUILD_ID_LEN_V01 + 1]; +}; + +#define WLFW_CAP_RESP_MSG_V01_MAX_MSG_LEN 203 +extern struct elem_info wlfw_cap_resp_msg_v01_ei[]; + +struct wlfw_bdf_download_req_msg_v01 { + u8 valid; + u8 file_id_valid; + enum wlfw_cal_temp_id_enum_v01 file_id; + u8 total_size_valid; + u32 total_size; + u8 seg_id_valid; + u32 seg_id; + u8 data_valid; + u32 data_len; + u8 data[QMI_WLFW_MAX_DATA_SIZE_V01]; + u8 end_valid; + u8 end; +}; + +#define WLFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_MSG_LEN 6178 +extern struct elem_info wlfw_bdf_download_req_msg_v01_ei[]; + +struct wlfw_bdf_download_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_BDF_DOWNLOAD_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_bdf_download_resp_msg_v01_ei[]; + +struct wlfw_cal_report_req_msg_v01 { + u32 meta_data_len; + enum wlfw_cal_temp_id_enum_v01 meta_data[QMI_WLFW_MAX_NUM_CAL_V01]; +}; + +#define WLFW_CAL_REPORT_REQ_MSG_V01_MAX_MSG_LEN 24 +extern struct elem_info wlfw_cal_report_req_msg_v01_ei[]; + +struct wlfw_cal_report_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_CAL_REPORT_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_cal_report_resp_msg_v01_ei[]; + +struct wlfw_initiate_cal_download_ind_msg_v01 { + enum wlfw_cal_temp_id_enum_v01 cal_id; +}; + +#define WLFW_INITIATE_CAL_DOWNLOAD_IND_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_initiate_cal_download_ind_msg_v01_ei[]; + +struct wlfw_cal_download_req_msg_v01 { + u8 valid; + u8 file_id_valid; + enum wlfw_cal_temp_id_enum_v01 file_id; + u8 total_size_valid; + u32 total_size; + u8 seg_id_valid; + u32 seg_id; + u8 data_valid; + u32 data_len; + u8 data[QMI_WLFW_MAX_DATA_SIZE_V01]; + u8 end_valid; + u8 end; +}; + +#define WLFW_CAL_DOWNLOAD_REQ_MSG_V01_MAX_MSG_LEN 6178 +extern struct elem_info wlfw_cal_download_req_msg_v01_ei[]; + +struct wlfw_cal_download_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_CAL_DOWNLOAD_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_cal_download_resp_msg_v01_ei[]; + +struct wlfw_initiate_cal_update_ind_msg_v01 { + enum wlfw_cal_temp_id_enum_v01 cal_id; + u32 total_size; +}; + +#define WLFW_INITIATE_CAL_UPDATE_IND_MSG_V01_MAX_MSG_LEN 14 +extern struct elem_info wlfw_initiate_cal_update_ind_msg_v01_ei[]; + +struct wlfw_cal_update_req_msg_v01 { + enum wlfw_cal_temp_id_enum_v01 cal_id; + u32 seg_id; +}; + +#define WLFW_CAL_UPDATE_REQ_MSG_V01_MAX_MSG_LEN 14 +extern struct elem_info wlfw_cal_update_req_msg_v01_ei[]; + +struct wlfw_cal_update_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 file_id_valid; + enum wlfw_cal_temp_id_enum_v01 file_id; + u8 total_size_valid; + u32 total_size; + u8 seg_id_valid; + u32 seg_id; + u8 data_valid; + u32 data_len; + u8 data[QMI_WLFW_MAX_DATA_SIZE_V01]; + u8 end_valid; + u8 end; +}; + +#define WLFW_CAL_UPDATE_RESP_MSG_V01_MAX_MSG_LEN 6181 +extern struct elem_info wlfw_cal_update_resp_msg_v01_ei[]; + +struct wlfw_msa_info_req_msg_v01 { + u64 msa_addr; + u32 size; +}; + +#define WLFW_MSA_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 +extern struct elem_info wlfw_msa_info_req_msg_v01_ei[]; + +struct wlfw_msa_info_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u32 mem_region_info_len; + struct wlfw_memory_region_info_s_v01 + mem_region_info[QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01]; +}; + +#define WLFW_MSA_INFO_RESP_MSG_V01_MAX_MSG_LEN 37 +extern struct elem_info wlfw_msa_info_resp_msg_v01_ei[]; + +struct wlfw_msa_ready_req_msg_v01 { + char placeholder; +}; + +#define WLFW_MSA_READY_REQ_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_msa_ready_req_msg_v01_ei[]; + +struct wlfw_msa_ready_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_MSA_READY_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_msa_ready_resp_msg_v01_ei[]; + +struct wlfw_ini_req_msg_v01 { + u8 enablefwlog_valid; + u8 enablefwlog; +}; + +#define WLFW_INI_REQ_MSG_V01_MAX_MSG_LEN 4 +extern struct elem_info wlfw_ini_req_msg_v01_ei[]; + +struct wlfw_ini_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_INI_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_ini_resp_msg_v01_ei[]; + +struct wlfw_athdiag_read_req_msg_v01 { + u32 offset; + u32 mem_type; + u32 data_len; +}; + +#define WLFW_ATHDIAG_READ_REQ_MSG_V01_MAX_MSG_LEN 21 +extern struct elem_info wlfw_athdiag_read_req_msg_v01_ei[]; + +struct wlfw_athdiag_read_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 data_valid; + u32 data_len; + u8 data[QMI_WLFW_MAX_DATA_SIZE_V01]; +}; + +#define WLFW_ATHDIAG_READ_RESP_MSG_V01_MAX_MSG_LEN 6156 +extern struct elem_info wlfw_athdiag_read_resp_msg_v01_ei[]; + +struct wlfw_athdiag_write_req_msg_v01 { + u32 offset; + u32 mem_type; + u32 data_len; + u8 data[QMI_WLFW_MAX_DATA_SIZE_V01]; +}; + +#define WLFW_ATHDIAG_WRITE_REQ_MSG_V01_MAX_MSG_LEN 6163 +extern struct elem_info wlfw_athdiag_write_req_msg_v01_ei[]; + +struct wlfw_athdiag_write_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_ATHDIAG_WRITE_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_athdiag_write_resp_msg_v01_ei[]; + +struct wlfw_vbatt_req_msg_v01 { + u64 voltage_uv; +}; + +#define WLFW_VBATT_REQ_MSG_V01_MAX_MSG_LEN 11 +extern struct elem_info wlfw_vbatt_req_msg_v01_ei[]; + +struct wlfw_vbatt_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_VBATT_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_vbatt_resp_msg_v01_ei[]; + +struct wlfw_mac_addr_req_msg_v01 { + u8 mac_addr_valid; + u8 mac_addr[QMI_WLFW_MAC_ADDR_SIZE_V01]; +}; + +#define WLFW_MAC_ADDR_REQ_MSG_V01_MAX_MSG_LEN 9 +extern struct elem_info wlfw_mac_addr_req_msg_v01_ei[]; + +struct wlfw_mac_addr_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_MAC_ADDR_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_mac_addr_resp_msg_v01_ei[]; + +struct wlfw_host_cap_req_msg_v01 { + u8 daemon_support_valid; + u8 daemon_support; +}; + +#define WLFW_HOST_CAP_REQ_MSG_V01_MAX_MSG_LEN 4 +extern struct elem_info wlfw_host_cap_req_msg_v01_ei[]; + +struct wlfw_host_cap_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_HOST_CAP_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_host_cap_resp_msg_v01_ei[]; + +struct wlfw_request_mem_ind_msg_v01 { + u32 size; +}; + +#define WLFW_REQUEST_MEM_IND_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_request_mem_ind_msg_v01_ei[]; + +struct wlfw_respond_mem_req_msg_v01 { + u64 addr; + u32 size; +}; + +#define WLFW_RESPOND_MEM_REQ_MSG_V01_MAX_MSG_LEN 18 +extern struct elem_info wlfw_respond_mem_req_msg_v01_ei[]; + +struct wlfw_respond_mem_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_RESPOND_MEM_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_respond_mem_resp_msg_v01_ei[]; + +struct wlfw_fw_mem_ready_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_FW_MEM_READY_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_fw_mem_ready_ind_msg_v01_ei[]; + +struct wlfw_cold_boot_cal_done_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_COLD_BOOT_CAL_DONE_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_cold_boot_cal_done_ind_msg_v01_ei[]; + +struct wlfw_rejuvenate_ind_msg_v01 { + u8 cause_for_rejuvenation_valid; + u8 cause_for_rejuvenation; + u8 requesting_sub_system_valid; + u8 requesting_sub_system; + u8 line_number_valid; + u16 line_number; + u8 function_name_valid; + char function_name[QMI_WLFW_FUNCTION_NAME_LEN_V01 + 1]; +}; + +#define WLFW_REJUVENATE_IND_MSG_V01_MAX_MSG_LEN 144 +extern struct elem_info wlfw_rejuvenate_ind_msg_v01_ei[]; + +struct wlfw_rejuvenate_ack_req_msg_v01 { + char placeholder; +}; + +#define WLFW_REJUVENATE_ACK_REQ_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_rejuvenate_ack_req_msg_v01_ei[]; + +struct wlfw_rejuvenate_ack_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_REJUVENATE_ACK_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_rejuvenate_ack_resp_msg_v01_ei[]; + +struct wlfw_dynamic_feature_mask_req_msg_v01 { + u8 mask_valid; + u64 mask; +}; + +#define WLFW_DYNAMIC_FEATURE_MASK_REQ_MSG_V01_MAX_MSG_LEN 11 +extern struct elem_info wlfw_dynamic_feature_mask_req_msg_v01_ei[]; + +struct wlfw_dynamic_feature_mask_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 prev_mask_valid; + u64 prev_mask; + u8 curr_mask_valid; + u64 curr_mask; +}; + +#define WLFW_DYNAMIC_FEATURE_MASK_RESP_MSG_V01_MAX_MSG_LEN 29 +extern struct elem_info wlfw_dynamic_feature_mask_resp_msg_v01_ei[]; + +#endif diff --git a/drivers/net/wireless/ath/ath10k/wmi-ops.h b/drivers/net/wireless/ath/ath10k/wmi-ops.h index cfed5808bc4e..468ad47f0298 100644 --- a/drivers/net/wireless/ath/ath10k/wmi-ops.h +++ b/drivers/net/wireless/ath/ath10k/wmi-ops.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2005-2011 Atheros Communications Inc. - * Copyright (c) 2011-2014 Qualcomm Atheros, Inc. + * Copyright (c) 2011-2014, 2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,8 +29,12 @@ struct wmi_ops { struct wmi_scan_ev_arg *arg); int (*pull_mgmt_rx)(struct ath10k *ar, struct sk_buff *skb, struct wmi_mgmt_rx_ev_arg *arg); + int (*pull_mgmt_tx_compl)(struct ath10k *ar, struct sk_buff *skb, + struct wmi_tlv_mgmt_tx_compl_ev_arg *arg); int (*pull_ch_info)(struct ath10k *ar, struct sk_buff *skb, struct wmi_ch_info_ev_arg *arg); + int (*pull_peer_delete_resp)(struct ath10k *ar, struct sk_buff *skb, + struct wmi_peer_delete_resp_ev_arg *arg); int (*pull_vdev_start)(struct ath10k *ar, struct sk_buff *skb, struct wmi_vdev_start_ev_arg *arg); int (*pull_peer_kick)(struct ath10k *ar, struct sk_buff *skb, @@ -51,6 +55,8 @@ struct wmi_ops { struct wmi_roam_ev_arg *arg); int (*pull_wow_event)(struct ath10k *ar, struct sk_buff *skb, struct wmi_wow_ev_arg *arg); + int (*pull_echo_ev)(struct ath10k *ar, struct sk_buff *skb, + struct wmi_echo_ev_arg *arg); enum wmi_txbf_conf (*get_txbf_conf_scheme)(struct ath10k *ar); struct sk_buff *(*gen_pdev_suspend)(struct ath10k *ar, u32 suspend_opt); @@ -123,7 +129,10 @@ struct wmi_ops { enum wmi_force_fw_hang_type type, u32 delay_ms); struct sk_buff *(*gen_mgmt_tx)(struct ath10k *ar, struct sk_buff *skb); - struct sk_buff *(*gen_dbglog_cfg)(struct ath10k *ar, u32 module_enable, + struct sk_buff *(*gen_mgmt_tx_send)(struct ath10k *ar, + struct sk_buff *skb, + dma_addr_t paddr); + struct sk_buff *(*gen_dbglog_cfg)(struct ath10k *ar, u64 module_enable, u32 log_level); struct sk_buff *(*gen_pktlog_enable)(struct ath10k *ar, u32 filter); struct sk_buff *(*gen_pktlog_disable)(struct ath10k *ar); @@ -156,6 +165,10 @@ struct wmi_ops { u32 num_ac); struct sk_buff *(*gen_sta_keepalive)(struct ath10k *ar, const struct wmi_sta_keepalive_arg *arg); + struct sk_buff *(*gen_set_arp_ns_offload)(struct ath10k *ar, + struct ath10k_vif *arvif); + struct sk_buff *(*gen_gtk_offload)(struct ath10k *ar, + struct ath10k_vif *arvif); struct sk_buff *(*gen_wow_enable)(struct ath10k *ar); struct sk_buff *(*gen_wow_add_wakeup_event)(struct ath10k *ar, u32 vdev_id, enum wmi_wow_wakeup_event event, @@ -186,6 +199,20 @@ struct wmi_ops { u8 enable, u32 detect_level, u32 detect_margin); + struct sk_buff *(*gen_set_pdev_mac_addr)(struct ath10k *ar, u32 pdev_id, + u8 *mac_addr); + + struct sk_buff *(*ext_resource_config)(struct ath10k *ar, + enum wmi_host_platform_type type, + u32 fw_feature_bitmap); + int (*get_vdev_subtype)(struct ath10k *ar, + enum wmi_vdev_subtype subtype); + struct sk_buff *(*gen_pdev_bss_chan_info_req) + (struct ath10k *ar, + enum wmi_bss_survey_req_type type); + struct sk_buff *(*gen_echo)(struct ath10k *ar, u32 value); + struct sk_buff *(*gen_csa_offload)(struct ath10k *ar, + u32 vdev_id, bool enable); }; int ath10k_wmi_cmd_send(struct ath10k *ar, struct sk_buff *skb, u32 cmd_id); @@ -222,6 +249,16 @@ ath10k_wmi_pull_scan(struct ath10k *ar, struct sk_buff *skb, } static inline int +ath10k_wmi_pull_mgmt_tx_compl(struct ath10k *ar, struct sk_buff *skb, + struct wmi_tlv_mgmt_tx_compl_ev_arg *arg) +{ + if (!ar->wmi.ops->pull_mgmt_tx_compl) + return -EOPNOTSUPP; + + return ar->wmi.ops->pull_mgmt_tx_compl(ar, skb, arg); +} + +static inline int ath10k_wmi_pull_mgmt_rx(struct ath10k *ar, struct sk_buff *skb, struct wmi_mgmt_rx_ev_arg *arg) { @@ -232,6 +269,16 @@ ath10k_wmi_pull_mgmt_rx(struct ath10k *ar, struct sk_buff *skb, } static inline int +ath10k_wmi_pull_peer_delete_resp(struct ath10k *ar, struct sk_buff *skb, + struct wmi_peer_delete_resp_ev_arg *arg) +{ + if (!ar->wmi.ops->pull_peer_delete_resp) + return -EOPNOTSUPP; + + return ar->wmi.ops->pull_peer_delete_resp(ar, skb, arg); +} + +static inline int ath10k_wmi_pull_ch_info(struct ath10k *ar, struct sk_buff *skb, struct wmi_ch_info_ev_arg *arg) { @@ -341,6 +388,16 @@ ath10k_wmi_pull_wow_event(struct ath10k *ar, struct sk_buff *skb, return ar->wmi.ops->pull_wow_event(ar, skb, arg); } +static inline int +ath10k_wmi_pull_echo_ev(struct ath10k *ar, struct sk_buff *skb, + struct wmi_echo_ev_arg *arg) +{ + if (!ar->wmi.ops->pull_echo_ev) + return -EOPNOTSUPP; + + return ar->wmi.ops->pull_echo_ev(ar, skb, arg); +} + static inline enum wmi_txbf_conf ath10k_wmi_get_txbf_conf_scheme(struct ath10k *ar) { @@ -351,6 +408,28 @@ ath10k_wmi_get_txbf_conf_scheme(struct ath10k *ar) } static inline int +ath10k_wmi_mgmt_tx_send(struct ath10k *ar, struct sk_buff *msdu, + dma_addr_t paddr) +{ + struct sk_buff *skb; + int ret; + + if (!ar->wmi.ops->gen_mgmt_tx_send) + return -EOPNOTSUPP; + + skb = ar->wmi.ops->gen_mgmt_tx_send(ar, msdu, paddr); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + ret = ath10k_wmi_cmd_send(ar, skb, + ar->wmi.cmd->mgmt_tx_send_cmdid); + if (ret) + return ret; + + return 0; +} + +static inline int ath10k_wmi_mgmt_tx(struct ath10k *ar, struct sk_buff *msdu) { struct ieee80211_tx_info *info = IEEE80211_SKB_CB(msdu); @@ -364,7 +443,8 @@ ath10k_wmi_mgmt_tx(struct ath10k *ar, struct sk_buff *msdu) if (IS_ERR(skb)) return PTR_ERR(skb); - ret = ath10k_wmi_cmd_send(ar, skb, ar->wmi.cmd->mgmt_tx_cmdid); + ret = ath10k_wmi_cmd_send(ar, skb, + ar->wmi.cmd->mgmt_tx_cmdid); if (ret) return ret; @@ -930,7 +1010,7 @@ ath10k_wmi_force_fw_hang(struct ath10k *ar, } static inline int -ath10k_wmi_dbglog_cfg(struct ath10k *ar, u32 module_enable, u32 log_level) +ath10k_wmi_dbglog_cfg(struct ath10k *ar, u64 module_enable, u32 log_level) { struct sk_buff *skb; @@ -1145,6 +1225,40 @@ ath10k_wmi_sta_keepalive(struct ath10k *ar, } static inline int +ath10k_wmi_set_arp_ns_offload(struct ath10k *ar, struct ath10k_vif *arvif) +{ + struct sk_buff *skb; + u32 cmd_id; + + if (!ar->wmi.ops->gen_set_arp_ns_offload) + return -EOPNOTSUPP; + + skb = ar->wmi.ops->gen_set_arp_ns_offload(ar, arvif); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + cmd_id = ar->wmi.cmd->set_arp_ns_offload_cmdid; + return ath10k_wmi_cmd_send(ar, skb, cmd_id); +} + +static inline int +ath10k_wmi_gtk_offload(struct ath10k *ar, struct ath10k_vif *arvif) +{ + struct sk_buff *skb; + u32 cmd_id; + + if (!ar->wmi.ops->gen_gtk_offload) + return -EOPNOTSUPP; + + skb = ar->wmi.ops->gen_gtk_offload(ar, arvif); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + cmd_id = ar->wmi.cmd->gtk_offload_cmdid; + return ath10k_wmi_cmd_send(ar, skb, cmd_id); +} + +static inline int ath10k_wmi_wow_enable(struct ath10k *ar) { struct sk_buff *skb; @@ -1333,4 +1447,105 @@ ath10k_wmi_pdev_enable_adaptive_cca(struct ath10k *ar, u8 enable, ar->wmi.cmd->pdev_enable_adaptive_cca_cmdid); } +static inline int +ath10k_wmi_ext_resource_config(struct ath10k *ar, + enum wmi_host_platform_type type, + u32 fw_feature_bitmap) +{ + struct sk_buff *skb; + + if (!ar->wmi.ops->ext_resource_config) + return -EOPNOTSUPP; + + skb = ar->wmi.ops->ext_resource_config(ar, type, + fw_feature_bitmap); + + if (IS_ERR(skb)) + return PTR_ERR(skb); + + return ath10k_wmi_cmd_send(ar, skb, + ar->wmi.cmd->ext_resource_cfg_cmdid); +} + +static inline int +ath10k_wmi_get_vdev_subtype(struct ath10k *ar, enum wmi_vdev_subtype subtype) +{ + if (!ar->wmi.ops->get_vdev_subtype) + return -EOPNOTSUPP; + + return ar->wmi.ops->get_vdev_subtype(ar, subtype); +} + +static inline int +ath10k_wmi_pdev_bss_chan_info_request(struct ath10k *ar, + enum wmi_bss_survey_req_type type) +{ + struct ath10k_wmi *wmi = &ar->wmi; + struct sk_buff *skb; + + if (!wmi->ops->gen_pdev_bss_chan_info_req) + return -EOPNOTSUPP; + + skb = wmi->ops->gen_pdev_bss_chan_info_req(ar, type); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + return ath10k_wmi_cmd_send(ar, skb, + wmi->cmd->pdev_bss_chan_info_request_cmdid); +} + +static inline int +ath10k_wmi_csa_offload(struct ath10k *ar, u32 vdev_id, bool enable) +{ + struct sk_buff *skb; + u32 cmd_id; + + if (!ar->wmi.ops->gen_csa_offload) + return -EOPNOTSUPP; + + skb = ar->wmi.ops->gen_csa_offload(ar, vdev_id, enable); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + cmd_id = ar->wmi.cmd->csa_offload_enable_cmdid; + return ath10k_wmi_cmd_send(ar, skb, cmd_id); +} + +static inline int +ath10k_wmi_echo(struct ath10k *ar, u32 value) +{ + struct ath10k_wmi *wmi = &ar->wmi; + struct sk_buff *skb; + + if (!wmi->ops->gen_echo) + return -EOPNOTSUPP; + + skb = wmi->ops->gen_echo(ar, value); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + return ath10k_wmi_cmd_send(ar, skb, wmi->cmd->echo_cmdid); +} + +static inline int +ath10k_gen_set_base_mac_addr(struct ath10k *ar, u8 *mac) +{ + struct sk_buff *skb; + int ret; + + if (!ar->wmi.ops->gen_set_pdev_mac_addr) + return -EOPNOTSUPP; + + skb = ar->wmi.ops->gen_set_pdev_mac_addr(ar, 0, mac); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + ret = ath10k_wmi_cmd_send(ar, skb, + ar->wmi.cmd->pdev_set_base_macaddr_cmdid); + if (ret) + return ret; + + return 0; +} + #endif diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.c b/drivers/net/wireless/ath/ath10k/wmi-tlv.c index c72eb4464de9..3d323f3e73af 100644 --- a/drivers/net/wireless/ath/ath10k/wmi-tlv.c +++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2005-2011 Atheros Communications Inc. - * Copyright (c) 2011-2014 Qualcomm Atheros, Inc. + * Copyright (c) 2011-2014, 2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -468,6 +468,9 @@ static void ath10k_wmi_tlv_op_rx(struct ath10k *ar, struct sk_buff *skb) case WMI_TLV_VDEV_STOPPED_EVENTID: ath10k_wmi_event_vdev_stopped(ar, skb); break; + case WMI_TLV_VDEV_DELETE_RESP_EVENTID: + ath10k_wmi_event_vdev_delete_resp(ar, skb); + break; case WMI_TLV_PEER_STA_KICKOUT_EVENTID: ath10k_wmi_event_peer_sta_kickout(ar, skb); break; @@ -552,8 +555,14 @@ static void ath10k_wmi_tlv_op_rx(struct ath10k *ar, struct sk_buff *skb) case WMI_TLV_TX_PAUSE_EVENTID: ath10k_wmi_tlv_event_tx_pause(ar, skb); break; + case WMI_TLV_PEER_DELETE_RESP_EVENTID: + ath10k_wmi_tlv_event_peer_delete_resp(ar, skb); + break; + case WMI_TLV_MGMT_TX_COMPLETION_EVENTID: + ath10k_wmi_tlv_event_mgmt_tx_compl(ar, skb); + break; default: - ath10k_warn(ar, "Unknown eventid: %d\n", id); + ath10k_dbg(ar, ATH10K_DBG_WMI, "Unknown eventid: %d\n", id); break; } @@ -593,6 +602,31 @@ static int ath10k_wmi_tlv_op_pull_scan_ev(struct ath10k *ar, return 0; } +static int ath10k_wmi_tlv_op_pull_mgmt_tx_compl_ev( + struct ath10k *ar, struct sk_buff *skb, + struct wmi_tlv_mgmt_tx_compl_ev_arg *arg) +{ + const void **tb; + const struct wmi_tlv_mgmt_tx_compl_ev *ev; + int ret; + + tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC); + if (IS_ERR(tb)) { + ret = PTR_ERR(tb); + ath10k_warn(ar, "failed to parse tlv: %d\n", ret); + return ret; + } + + ev = tb[WMI_TLV_TAG_STRUCT_MGMT_TX_COMPL]; + + arg->desc_id = ev->desc_id; + arg->status = ev->status; + arg->pdev_id = ev->pdev_id; + + kfree(tb); + return 0; +} + static int ath10k_wmi_tlv_op_pull_mgmt_rx_ev(struct ath10k *ar, struct sk_buff *skb, struct wmi_mgmt_rx_ev_arg *arg) @@ -642,6 +676,34 @@ static int ath10k_wmi_tlv_op_pull_mgmt_rx_ev(struct ath10k *ar, return 0; } +static int ath10k_wmi_tlv_op_pull_peer_delete_ev( + struct ath10k *ar, struct sk_buff *skb, + struct wmi_peer_delete_resp_ev_arg *arg) +{ + const void **tb; + const struct wmi_peer_delete_resp_ev_arg *ev; + int ret; + + tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC); + if (IS_ERR(tb)) { + ret = PTR_ERR(tb); + ath10k_warn(ar, "failed to parse tlv: %d\n", ret); + return ret; + } + + ev = tb[WMI_TLV_TAG_STRUCT_PEER_DELETE_RESP_EVENT]; + if (!ev) { + kfree(tb); + return -EPROTO; + } + + arg->vdev_id = ev->vdev_id; + arg->peer_addr = ev->peer_addr; + + kfree(tb); + return 0; +} + static int ath10k_wmi_tlv_op_pull_ch_info_ev(struct ath10k *ar, struct sk_buff *skb, struct wmi_ch_info_ev_arg *arg) @@ -669,6 +731,13 @@ static int ath10k_wmi_tlv_op_pull_ch_info_ev(struct ath10k *ar, arg->noise_floor = ev->noise_floor; arg->rx_clear_count = ev->rx_clear_count; arg->cycle_count = ev->cycle_count; + arg->chan_tx_pwr_range = ev->chan_tx_pwr_range; + arg->chan_tx_pwr_tp = ev->chan_tx_pwr_tp; + arg->rx_frame_count = ev->rx_frame_count; + arg->my_bss_rx_cycle_count = ev->my_bss_rx_cycle_count; + arg->rx_11b_mode_data_duration = ev->rx_11b_mode_data_duration; + arg->tx_frame_cnt = ev->tx_frame_cnt; + arg->mac_clk_mhz = ev->mac_clk_mhz; kfree(tb); return 0; @@ -882,6 +951,11 @@ static int ath10k_wmi_tlv_op_pull_phyerr_ev_hdr(struct ath10k *ar, arg->buf_len = __le32_to_cpu(ev->buf_len); arg->phyerrs = phyerrs; + if (QCA_REV_WCN3990(ar)) { + arg->phy_err_mask0 = __le32_to_cpu(ev->rs_phy_err_mask0); + arg->phy_err_mask1 = __le32_to_cpu(ev->rs_phy_err_mask1); + } + kfree(tb); return 0; } @@ -937,7 +1011,12 @@ static int ath10k_wmi_tlv_op_pull_svc_rdy_ev(struct ath10k *ar, ev = tb[WMI_TLV_TAG_STRUCT_SERVICE_READY_EVENT]; reg = tb[WMI_TLV_TAG_STRUCT_HAL_REG_CAPABILITIES]; - svc_bmap = tb[WMI_TLV_TAG_ARRAY_UINT32]; + if (QCA_REV_WCN3990(ar)) { + svc_bmap = (__le32 *)(skb->data + + WMI_TLV_TAG_STRUCT_HL_1_0_SVC_OFFSET); + } else { + svc_bmap = tb[WMI_TLV_TAG_ARRAY_UINT32]; + } mem_reqs = tb[WMI_TLV_TAG_ARRAY_STRUCT]; if (!ev || !reg || !svc_bmap || !mem_reqs) { @@ -1229,6 +1308,33 @@ ath10k_wmi_tlv_op_pull_wow_ev(struct ath10k *ar, struct sk_buff *skb, return 0; } +static int ath10k_wmi_tlv_op_pull_echo_ev(struct ath10k *ar, + struct sk_buff *skb, + struct wmi_echo_ev_arg *arg) +{ + const void **tb; + const struct wmi_echo_event *ev; + int ret; + + tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC); + if (IS_ERR(tb)) { + ret = PTR_ERR(tb); + ath10k_warn(ar, "failed to parse tlv: %d\n", ret); + return ret; + } + + ev = tb[WMI_TLV_TAG_STRUCT_ECHO_EVENT]; + if (!ev) { + kfree(tb); + return -EPROTO; + } + + arg->value = ev->value; + + kfree(tb); + return 0; +} + static struct sk_buff * ath10k_wmi_tlv_op_gen_pdev_suspend(struct ath10k *ar, u32 opt) { @@ -1379,7 +1485,18 @@ static struct sk_buff *ath10k_wmi_tlv_op_gen_init(struct ath10k *ar) cmd->num_host_mem_chunks = __cpu_to_le32(ar->wmi.num_mem_chunks); cfg->num_vdevs = __cpu_to_le32(TARGET_TLV_NUM_VDEVS); - cfg->num_peers = __cpu_to_le32(TARGET_TLV_NUM_PEERS); + if (QCA_REV_WCN3990(ar)) { + cfg->num_peers = __cpu_to_le32(TARGET_HL_10_TLV_NUM_PEERS); + cfg->ast_skid_limit = + __cpu_to_le32(TARGET_HL_10_TLV_AST_SKID_LIMIT); + cfg->num_wds_entries = + __cpu_to_le32(TARGET_HL_10_TLV_NUM_WDS_ENTRIES); + } else { + cfg->num_peers = __cpu_to_le32(TARGET_TLV_NUM_PEERS); + cfg->ast_skid_limit = __cpu_to_le32(0x10); + cfg->num_wds_entries = __cpu_to_le32(0x20); + } + if (test_bit(WMI_SERVICE_RX_FULL_REORDER, ar->wmi.svc_map)) { cfg->num_offload_peers = __cpu_to_le32(TARGET_TLV_NUM_VDEVS); @@ -1391,7 +1508,6 @@ static struct sk_buff *ath10k_wmi_tlv_op_gen_init(struct ath10k *ar) cfg->num_peer_keys = __cpu_to_le32(2); cfg->num_tids = __cpu_to_le32(TARGET_TLV_NUM_TIDS); - cfg->ast_skid_limit = __cpu_to_le32(0x10); cfg->tx_chain_mask = __cpu_to_le32(0x7); cfg->rx_chain_mask = __cpu_to_le32(0x7); cfg->rx_timeout_pri[0] = __cpu_to_le32(0x64); @@ -1407,7 +1523,6 @@ static struct sk_buff *ath10k_wmi_tlv_op_gen_init(struct ath10k *ar) cfg->num_mcast_table_elems = __cpu_to_le32(0); cfg->mcast2ucast_mode = __cpu_to_le32(0); cfg->tx_dbg_log_size = __cpu_to_le32(0x400); - cfg->num_wds_entries = __cpu_to_le32(0x20); cfg->dma_burst_size = __cpu_to_le32(0); cfg->mac_aggr_delim = __cpu_to_le32(0); cfg->rx_skip_defrag_timeout_dup_detection_check = __cpu_to_le32(0); @@ -1428,7 +1543,8 @@ static struct sk_buff *ath10k_wmi_tlv_op_gen_init(struct ath10k *ar) cfg->num_ocb_vdevs = __cpu_to_le32(0); cfg->num_ocb_channels = __cpu_to_le32(0); cfg->num_ocb_schedules = __cpu_to_le32(0); - cfg->host_capab = __cpu_to_le32(0); + cfg->host_capab = + __cpu_to_le32(WMI_TLV_TX_MSDU_ID_NEW_PARTITION_SUPPORT); ath10k_wmi_put_host_mem_chunks(ar, chunks); @@ -1482,10 +1598,10 @@ ath10k_wmi_tlv_op_gen_start_scan(struct ath10k *ar, cmd->ie_len = __cpu_to_le32(arg->ie_len); cmd->num_probes = __cpu_to_le32(3); - /* FIXME: There are some scan flag inconsistencies across firmwares, - * e.g. WMI-TLV inverts the logic behind the following flag. - */ - cmd->common.scan_ctrl_flags ^= __cpu_to_le32(WMI_SCAN_FILTER_PROBE_REQ); + if (!QCA_REV_WCN3990(ar)) { + cmd->common.scan_ctrl_flags ^= + __cpu_to_le32(WMI_SCAN_FILTER_PROBE_REQ); + } ptr += sizeof(*tlv); ptr += sizeof(*cmd); @@ -2406,6 +2522,30 @@ ath10k_wmi_tlv_op_gen_pdev_set_wmm(struct ath10k *ar, return skb; } +static int +ath10k_wmi_mgmt_tx_alloc_msdu_id(struct ath10k *ar, struct sk_buff *skb, + dma_addr_t paddr) +{ + struct ath10k_wmi *wmi = &ar->wmi; + struct ath10k_mgmt_tx_pkt_addr *pkt_addr; + int ret; + + pkt_addr = kmalloc(sizeof(*pkt_addr), GFP_ATOMIC); + if (!pkt_addr) + return -ENOMEM; + + pkt_addr->vaddr = skb; + pkt_addr->paddr = paddr; + + spin_lock_bh(&wmi->mgmt_tx_lock); + ret = idr_alloc(&wmi->mgmt_pending_tx, pkt_addr, 0, + wmi->mgmt_max_num_pending_tx, GFP_ATOMIC); + spin_unlock_bh(&wmi->mgmt_tx_lock); + + ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi mgmt tx alloc msdu_id %d\n", ret); + return ret; +} + static struct sk_buff * ath10k_wmi_tlv_op_gen_request_stats(struct ath10k *ar, u32 stats_mask) { @@ -2428,6 +2568,84 @@ ath10k_wmi_tlv_op_gen_request_stats(struct ath10k *ar, u32 stats_mask) } static struct sk_buff * +ath10k_wmi_tlv_op_gen_mgmt_tx_send(struct ath10k *ar, struct sk_buff *msdu, + dma_addr_t paddr) +{ + struct ath10k_skb_cb *cb = ATH10K_SKB_CB(msdu); + struct wmi_tlv_mgmt_tx_cmd *cmd; + struct ieee80211_hdr *hdr; + struct ath10k_vif *arvif; + u32 buf_len = msdu->len; + struct wmi_tlv *tlv; + struct sk_buff *skb; + int desc_id, len; + u32 vdev_id; + void *ptr; + u16 fc; + + hdr = (struct ieee80211_hdr *)msdu->data; + fc = le16_to_cpu(hdr->frame_control); + + if (cb->vif) { + arvif = (void *)cb->vif->drv_priv; + vdev_id = arvif->vdev_id; + } else { + return ERR_PTR(-EINVAL); + } + + if (WARN_ON_ONCE(!ieee80211_is_mgmt(hdr->frame_control))) + return ERR_PTR(-EINVAL); + + len = sizeof(*cmd) + buf_len; + + if ((ieee80211_is_action(hdr->frame_control) || + ieee80211_is_deauth(hdr->frame_control) || + ieee80211_is_disassoc(hdr->frame_control)) && + ieee80211_has_protected(hdr->frame_control)) { + len += IEEE80211_CCMP_MIC_LEN; + buf_len += IEEE80211_CCMP_MIC_LEN; + } + + len += sizeof(*tlv); + len = round_up(len, 4); + skb = ath10k_wmi_alloc_skb(ar, len); + if (!skb) + return ERR_PTR(-ENOMEM); + + desc_id = ath10k_wmi_mgmt_tx_alloc_msdu_id(ar, msdu, paddr); + if (desc_id < 0) + goto msdu_id_alloc_fail; + + ptr = (void *)skb->data; + tlv = ptr; + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_STRUCT_MGMT_TX_CMD); + tlv->len = __cpu_to_le16(sizeof(cmd->hdr)); + cmd = (void *)tlv->value; + cmd->hdr.vdev_id = vdev_id; + cmd->hdr.desc_id = desc_id; + cmd->hdr.chanfreq = 0; + cmd->hdr.buf_len = __cpu_to_le32(buf_len); + cmd->hdr.frame_len = __cpu_to_le32(msdu->len); + cmd->hdr.paddr = __cpu_to_le64(paddr); + cmd->data_len = buf_len; + cmd->data_tag = 0x11; + + ptr += sizeof(*tlv); + ptr += sizeof(*cmd); + + tlv = ptr; + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_ARRAY_UINT32); + tlv->len = __cpu_to_le16(sizeof(msdu->len)); + memcpy(cmd->buf, msdu->data, buf_len); + + return skb; + +msdu_id_alloc_fail: + dev_kfree_skb(skb); + return ERR_PTR(desc_id); +} + +static struct sk_buff * ath10k_wmi_tlv_op_gen_force_fw_hang(struct ath10k *ar, enum wmi_force_fw_hang_type type, u32 delay_ms) @@ -2452,7 +2670,7 @@ ath10k_wmi_tlv_op_gen_force_fw_hang(struct ath10k *ar, } static struct sk_buff * -ath10k_wmi_tlv_op_gen_dbglog_cfg(struct ath10k *ar, u32 module_enable, +ath10k_wmi_tlv_op_gen_dbglog_cfg(struct ath10k *ar, u64 module_enable, u32 log_level) { struct wmi_tlv_dbglog_cmd *cmd; struct wmi_tlv *tlv; @@ -2861,6 +3079,165 @@ ath10k_wmi_tlv_op_gen_tdls_peer_update(struct ath10k *ar, } static struct sk_buff * +ath10k_wmi_tlv_op_gen_csa_offload(struct ath10k *ar, u32 vdev_id, bool enable) +{ + struct wmi_csa_offload_enable_cmd *cmd; + struct wmi_tlv *tlv; + struct sk_buff *skb; + int len; + + len = sizeof(*cmd) + sizeof(*tlv); + skb = ath10k_wmi_alloc_skb(ar, len); + if (!skb) + return ERR_PTR(-ENOMEM); + + tlv = (void *)skb->data; + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_STRUCT_CSA_OFFLOAD_ENABLE_CMD); + tlv->len = __cpu_to_le16(sizeof(*cmd)); + cmd = (void *)tlv->value; + + cmd->vdev_id = __cpu_to_le32(vdev_id); + if (enable) + cmd->csa_offload_enable |= + __cpu_to_le32(WMI_CSA_OFFLOAD_ENABLE); + else + cmd->csa_offload_enable |= + __cpu_to_le32(WMI_CSA_OFFLOAD_DISABLE); + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi CSA offload for vdev: %d\n", vdev_id); + return skb; +} + +static struct sk_buff * +ath10k_wmi_op_gen_gtk_offload(struct ath10k *ar, struct ath10k_vif *arvif) +{ + struct wmi_tlv_gtk_offload_cmd *cmd; + struct wmi_tlv *tlv; + struct sk_buff *skb; + struct wmi_gtk_rekey_data *rekey_data = &arvif->gtk_rekey_data; + int len; + + len = sizeof(*cmd) + sizeof(*tlv); + skb = ath10k_wmi_alloc_skb(ar, len); + if (!skb) + return ERR_PTR(-ENOMEM); + + tlv = (void *)skb->data; + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_STRUCT_GTK_OFFLOAD_CMD); + tlv->len = __cpu_to_le16(sizeof(*cmd)); + cmd = (void *)tlv->value; + if (rekey_data->enable_offload) { + cmd->vdev_id = __cpu_to_le32(arvif->vdev_id); + cmd->flags |= __cpu_to_le32(WMI_GTK_OFFLOAD_ENABLE_OPCODE); + memcpy(cmd->kek, rekey_data->kek, NL80211_KEK_LEN); + memcpy(cmd->kck, rekey_data->kck, NL80211_KCK_LEN); + cmd->replay_ctr = __cpu_to_le64(rekey_data->replay_ctr); + } else { + cmd->vdev_id = __cpu_to_le32(arvif->vdev_id); + cmd->flags |= __cpu_to_le32(WMI_GTK_OFFLOAD_DISABLE_OPCODE); + } + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi GTK offload for vdev: %d\n", arvif->vdev_id); + return skb; +} + +static struct sk_buff * +ath10k_wmi_tlv_op_gen_set_arp_ns_offload(struct ath10k *ar, + struct ath10k_vif *arvif) +{ + struct wmi_tlv_arp_ns_offload_cmd *cmd; + struct wmi_tlv *tlv; + struct sk_buff *skb; + size_t len; + void *ptr; + int i; + struct wmi_ns_arp_offload_req *arp = &arvif->arp_offload; + struct wmi_ns_arp_offload_req *ns = &arvif->ns_offload; + struct wmi_ns_offload *ns_tuple; + struct wmi_arp_offload *arp_tuple; + + len = sizeof(*cmd) + sizeof(*tlv) + + sizeof(*tlv) + WMI_NS_ARP_OFFLOAD * + (sizeof(struct wmi_ns_offload) + sizeof(*tlv)) + + sizeof(*tlv) + WMI_NS_ARP_OFFLOAD * + (sizeof(struct wmi_arp_offload) + sizeof(*tlv)); + + skb = ath10k_wmi_alloc_skb(ar, len); + if (!skb) + return ERR_PTR(-ENOMEM); + + ptr = (void *)skb->data; + tlv = ptr; + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_STRUCT_SET_ARP_NS_OFFLOAD_CMD); + tlv->len = __cpu_to_le16(sizeof(*cmd)); + cmd = (struct wmi_tlv_arp_ns_offload_cmd *)tlv->value; + cmd->flags = __cpu_to_le32(0); + cmd->vdev_id = __cpu_to_le32(arvif->vdev_id); + + ptr += (sizeof(*tlv) + sizeof(*cmd)); + tlv = ptr; + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_ARRAY_STRUCT); + tlv->len = __cpu_to_le16(WMI_NS_ARP_OFFLOAD * + (sizeof(struct wmi_ns_offload) + sizeof(*tlv))); + ptr += sizeof(*tlv); + tlv = ptr; + + for (i = 0; i < WMI_NS_ARP_OFFLOAD; i++) { + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_STRUCT_NS_OFFLOAD_TUPLE); + tlv->len = __cpu_to_le16(sizeof(struct wmi_ns_offload)); + ns_tuple = (struct wmi_ns_offload *)tlv->value; + if (ns->enable_offload) { + ns_tuple->flags |= + __cpu_to_le32(WMI_ARP_NS_OFF_FLAGS_VALID); + if (ns->info.target_addr_valid.s6_addr[i]) { + memcpy(&ns_tuple->target_ipaddr[0], + &ns->info.target_addr[i], + sizeof(struct in6_addr)); + } + memcpy(&ns_tuple->solicitation_ipaddr, + &ns->info.self_addr[i], sizeof(struct in6_addr)); + if (ns->info.target_ipv6_ac.s6_addr[i] == IPV6_ADDR_ANY) + ns_tuple->flags |= + __cpu_to_le32(WMI_NSOFF_IPV6_ANYCAST); + } else { + ns_tuple->flags |= + __cpu_to_le32(WMI_ARP_NS_OFFLOAD_DISABLE); + } + ptr += (sizeof(*tlv) + sizeof(struct wmi_ns_offload)); + tlv = ptr; + } + + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_ARRAY_STRUCT); + tlv->len = __cpu_to_le16(WMI_NS_ARP_OFFLOAD * + (sizeof(struct wmi_arp_offload) + sizeof(*tlv))); + ptr += sizeof(*tlv); + tlv = ptr; + + for (i = 0; i < WMI_NS_ARP_OFFLOAD; i++) { + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_STRUCT_ARP_OFFLOAD_TUPLE); + tlv->len = __cpu_to_le16(sizeof(struct wmi_arp_offload)); + arp_tuple = (struct wmi_arp_offload *)tlv->value; + if (arp->enable_offload && (i == 0)) { + arp_tuple->flags |= + __cpu_to_le32(WMI_ARP_NS_OFF_FLAGS_VALID); + memcpy(&arp_tuple->target_ipaddr, + &arp->params.ipv4_addr, + sizeof(arp_tuple->target_ipaddr)); + } else { + arp_tuple->flags |= + __cpu_to_le32(WMI_ARP_NS_OFFLOAD_DISABLE); + } + ptr += (sizeof(*tlv) + sizeof(struct wmi_arp_offload)); + tlv = ptr; + } + + ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi tlv set arp ns offload\n"); + return skb; +} + +static struct sk_buff * ath10k_wmi_tlv_op_gen_wow_enable(struct ath10k *ar) { struct wmi_tlv_wow_enable_cmd *cmd; @@ -2879,6 +3256,8 @@ ath10k_wmi_tlv_op_gen_wow_enable(struct ath10k *ar) cmd = (void *)tlv->value; cmd->enable = __cpu_to_le32(1); + if (QCA_REV_WCN3990(ar)) + cmd->pause_iface_config = __cpu_to_le32(1); ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi tlv wow enable\n"); return skb; @@ -3065,6 +3444,33 @@ ath10k_wmi_tlv_op_gen_wow_del_pattern(struct ath10k *ar, u32 vdev_id, } static struct sk_buff * +ath10k_wmi_tlv_op_gen_set_base_mac_addr(struct ath10k *ar, u32 pdev_id, + u8 *mac_addr) +{ + struct wmi_tlv_mac_addr_cmd *cmd; + struct wmi_tlv *tlv; + struct sk_buff *skb; + size_t len; + + len = sizeof(*tlv) + sizeof(*cmd); + skb = ath10k_wmi_alloc_skb(ar, len); + if (!skb) + return ERR_PTR(-ENOMEM); + + tlv = (struct wmi_tlv *)skb->data; + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_STRUCT_PDEV_SET_BASE_MACADDR_CMD); + tlv->len = __cpu_to_le16(sizeof(*cmd)); + cmd = (void *)tlv->value; + + cmd->pdev_id = __cpu_to_le32(pdev_id); + ether_addr_copy(cmd->mac_addr.addr, mac_addr); + + ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi tlv set_base_mac addr pdev_id %d mac addr %pM\n", + cmd->pdev_id, cmd->mac_addr.addr); + return skb; +} + +static struct sk_buff * ath10k_wmi_tlv_op_gen_adaptive_qcs(struct ath10k *ar, bool enable) { struct wmi_tlv_adaptive_qcs *cmd; @@ -3092,6 +3498,34 @@ ath10k_wmi_tlv_op_gen_adaptive_qcs(struct ath10k *ar, bool enable) return skb; } +static struct sk_buff * +ath10k_wmi_tlv_op_gen_echo(struct ath10k *ar, u32 value) +{ + struct wmi_echo_cmd *cmd; + struct wmi_tlv *tlv; + struct sk_buff *skb; + void *ptr; + size_t len; + + len = sizeof(*tlv) + sizeof(*cmd); + skb = ath10k_wmi_alloc_skb(ar, len); + if (!skb) + return ERR_PTR(-ENOMEM); + + ptr = (void *)skb->data; + tlv = ptr; + tlv->tag = __cpu_to_le16(WMI_TLV_TAG_STRUCT_ECHO_CMD); + tlv->len = __cpu_to_le16(sizeof(*cmd)); + cmd = (void *)tlv->value; + cmd->value = cpu_to_le32(value); + + ptr += sizeof(*tlv); + ptr += sizeof(*cmd); + + ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi tlv echo value 0x%08x\n", value); + return skb; +} + /****************/ /* TLV mappings */ /****************/ @@ -3138,6 +3572,7 @@ static struct wmi_cmd_map wmi_tlv_cmd_map = { .bcn_filter_rx_cmdid = WMI_TLV_BCN_FILTER_RX_CMDID, .prb_req_filter_rx_cmdid = WMI_TLV_PRB_REQ_FILTER_RX_CMDID, .mgmt_tx_cmdid = WMI_TLV_MGMT_TX_CMDID, + .mgmt_tx_send_cmdid = WMI_TLV_MGMT_TX_SEND_CMD, .prb_tmpl_cmdid = WMI_TLV_PRB_TMPL_CMDID, .addba_clear_resp_cmdid = WMI_TLV_ADDBA_CLEAR_RESP_CMDID, .addba_send_cmdid = WMI_TLV_ADDBA_SEND_CMDID, @@ -3420,6 +3855,7 @@ static struct wmi_vdev_param_map wmi_tlv_vdev_param_map = { .meru_vc = WMI_VDEV_PARAM_UNSUPPORTED, .rx_decap_type = WMI_VDEV_PARAM_UNSUPPORTED, .bw_nss_ratemask = WMI_VDEV_PARAM_UNSUPPORTED, + .set_tsf = WMI_VDEV_PARAM_UNSUPPORTED, }; static const struct wmi_ops wmi_tlv_ops = { @@ -3428,7 +3864,9 @@ static const struct wmi_ops wmi_tlv_ops = { .pull_scan = ath10k_wmi_tlv_op_pull_scan_ev, .pull_mgmt_rx = ath10k_wmi_tlv_op_pull_mgmt_rx_ev, + .pull_mgmt_tx_compl = ath10k_wmi_tlv_op_pull_mgmt_tx_compl_ev, .pull_ch_info = ath10k_wmi_tlv_op_pull_ch_info_ev, + .pull_peer_delete_resp = ath10k_wmi_tlv_op_pull_peer_delete_ev, .pull_vdev_start = ath10k_wmi_tlv_op_pull_vdev_start_ev, .pull_peer_kick = ath10k_wmi_tlv_op_pull_peer_kick_ev, .pull_swba = ath10k_wmi_tlv_op_pull_swba_ev, @@ -3439,6 +3877,7 @@ static const struct wmi_ops wmi_tlv_ops = { .pull_fw_stats = ath10k_wmi_tlv_op_pull_fw_stats, .pull_roam_ev = ath10k_wmi_tlv_op_pull_roam_ev, .pull_wow_event = ath10k_wmi_tlv_op_pull_wow_ev, + .pull_echo_ev = ath10k_wmi_tlv_op_pull_echo_ev, .get_txbf_conf_scheme = ath10k_wmi_tlv_txbf_conf_scheme, .gen_pdev_suspend = ath10k_wmi_tlv_op_gen_pdev_suspend, @@ -3470,7 +3909,7 @@ static const struct wmi_ops wmi_tlv_ops = { .gen_pdev_set_wmm = ath10k_wmi_tlv_op_gen_pdev_set_wmm, .gen_request_stats = ath10k_wmi_tlv_op_gen_request_stats, .gen_force_fw_hang = ath10k_wmi_tlv_op_gen_force_fw_hang, - /* .gen_mgmt_tx = not implemented; HTT is used */ + .gen_mgmt_tx_send = ath10k_wmi_tlv_op_gen_mgmt_tx_send, .gen_dbglog_cfg = ath10k_wmi_tlv_op_gen_dbglog_cfg, .gen_pktlog_enable = ath10k_wmi_tlv_op_gen_pktlog_enable, .gen_pktlog_disable = ath10k_wmi_tlv_op_gen_pktlog_disable, @@ -3485,6 +3924,9 @@ static const struct wmi_ops wmi_tlv_ops = { .gen_p2p_go_bcn_ie = ath10k_wmi_tlv_op_gen_p2p_go_bcn_ie, .gen_vdev_sta_uapsd = ath10k_wmi_tlv_op_gen_vdev_sta_uapsd, .gen_sta_keepalive = ath10k_wmi_tlv_op_gen_sta_keepalive, + .gen_set_arp_ns_offload = ath10k_wmi_tlv_op_gen_set_arp_ns_offload, + .gen_gtk_offload = ath10k_wmi_op_gen_gtk_offload, + .gen_csa_offload = ath10k_wmi_tlv_op_gen_csa_offload, .gen_wow_enable = ath10k_wmi_tlv_op_gen_wow_enable, .gen_wow_add_wakeup_event = ath10k_wmi_tlv_op_gen_wow_add_wakeup_event, .gen_wow_host_wakeup_ind = ath10k_wmi_tlv_gen_wow_host_wakeup_ind, @@ -3494,9 +3936,29 @@ static const struct wmi_ops wmi_tlv_ops = { .gen_tdls_peer_update = ath10k_wmi_tlv_op_gen_tdls_peer_update, .gen_adaptive_qcs = ath10k_wmi_tlv_op_gen_adaptive_qcs, .fw_stats_fill = ath10k_wmi_main_op_fw_stats_fill, + .get_vdev_subtype = ath10k_wmi_op_get_vdev_subtype, + .gen_echo = ath10k_wmi_tlv_op_gen_echo, + .gen_set_pdev_mac_addr = ath10k_wmi_tlv_op_gen_set_base_mac_addr, +}; + +static const struct wmi_peer_flags_map wmi_tlv_peer_flags_map = { + .auth = WMI_TLV_PEER_AUTH, + .qos = WMI_TLV_PEER_QOS, + .need_ptk_4_way = WMI_TLV_PEER_NEED_PTK_4_WAY, + .need_gtk_2_way = WMI_TLV_PEER_NEED_GTK_2_WAY, + .apsd = WMI_TLV_PEER_APSD, + .ht = WMI_TLV_PEER_HT, + .bw40 = WMI_TLV_PEER_40MHZ, + .stbc = WMI_TLV_PEER_STBC, + .ldbc = WMI_TLV_PEER_LDPC, + .dyn_mimops = WMI_TLV_PEER_DYN_MIMOPS, + .static_mimops = WMI_TLV_PEER_STATIC_MIMOPS, + .spatial_mux = WMI_TLV_PEER_SPATIAL_MUX, + .vht = WMI_TLV_PEER_VHT, + .bw80 = WMI_TLV_PEER_80MHZ, + .pmf = WMI_TLV_PEER_PMF, }; -/************/ /* TLV init */ /************/ @@ -3506,4 +3968,5 @@ void ath10k_wmi_tlv_attach(struct ath10k *ar) ar->wmi.vdev_param = &wmi_tlv_vdev_param_map; ar->wmi.pdev_param = &wmi_tlv_pdev_param_map; ar->wmi.ops = &wmi_tlv_ops; + ar->wmi.peer_flags = &wmi_tlv_peer_flags_map; } diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.h b/drivers/net/wireless/ath/ath10k/wmi-tlv.h index f5031f3965c5..0d8543d20968 100644 --- a/drivers/net/wireless/ath/ath10k/wmi-tlv.h +++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2005-2011 Atheros Communications Inc. - * Copyright (c) 2011-2014 Qualcomm Atheros, Inc. + * Copyright (c) 2011-2014, 2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -22,6 +22,7 @@ #define WMI_TLV_CMD_UNSUPPORTED 0 #define WMI_TLV_PDEV_PARAM_UNSUPPORTED 0 #define WMI_TLV_VDEV_PARAM_UNSUPPORTED 0 +#define WMI_TX_DL_FRM_LEN 64 enum wmi_tlv_grp_id { WMI_TLV_GRP_START = 0x3, @@ -132,6 +133,7 @@ enum wmi_tlv_cmd_id { WMI_TLV_PRB_REQ_FILTER_RX_CMDID, WMI_TLV_MGMT_TX_CMDID, WMI_TLV_PRB_TMPL_CMDID, + WMI_TLV_MGMT_TX_SEND_CMD, WMI_TLV_ADDBA_CLEAR_RESP_CMDID = WMI_TLV_CMD(WMI_TLV_GRP_BA_NEG), WMI_TLV_ADDBA_SEND_CMDID, WMI_TLV_ADDBA_STATUS_CMDID, @@ -306,16 +308,21 @@ enum wmi_tlv_event_id { WMI_TLV_VDEV_STOPPED_EVENTID, WMI_TLV_VDEV_INSTALL_KEY_COMPLETE_EVENTID, WMI_TLV_VDEV_MCC_BCN_INTERVAL_CHANGE_REQ_EVENTID, + WMI_TLV_VDEV_TSF_REPORT_EVENTID, + WMI_TLV_VDEV_DELETE_RESP_EVENTID, WMI_TLV_PEER_STA_KICKOUT_EVENTID = WMI_TLV_EV(WMI_TLV_GRP_PEER), WMI_TLV_PEER_INFO_EVENTID, WMI_TLV_PEER_TX_FAIL_CNT_THR_EVENTID, WMI_TLV_PEER_ESTIMATED_LINKSPEED_EVENTID, WMI_TLV_PEER_STATE_EVENTID, + WMI_TLV_PEER_ASSOC_CONF_EVENTID, + WMI_TLV_PEER_DELETE_RESP_EVENTID, WMI_TLV_MGMT_RX_EVENTID = WMI_TLV_EV(WMI_TLV_GRP_MGMT), WMI_TLV_HOST_SWBA_EVENTID, WMI_TLV_TBTTOFFSET_UPDATE_EVENTID, WMI_TLV_OFFLOAD_BCN_TX_STATUS_EVENTID, WMI_TLV_OFFLOAD_PROB_RESP_TX_STATUS_EVENTID, + WMI_TLV_MGMT_TX_COMPLETION_EVENTID, WMI_TLV_TX_DELBA_COMPLETE_EVENTID = WMI_TLV_EV(WMI_TLV_GRP_BA_NEG), WMI_TLV_TX_ADDBA_COMPLETE_EVENTID, WMI_TLV_BA_RSP_SSN_EVENTID, @@ -527,6 +534,24 @@ enum wmi_tlv_vdev_param { WMI_TLV_VDEV_PARAM_IBSS_PS_1RX_CHAIN_IN_ATIM_WINDOW_ENABLE, }; +enum wmi_tlv_peer_flags { + WMI_TLV_PEER_AUTH = 0x00000001, + WMI_TLV_PEER_QOS = 0x00000002, + WMI_TLV_PEER_NEED_PTK_4_WAY = 0x00000004, + WMI_TLV_PEER_NEED_GTK_2_WAY = 0x00000010, + WMI_TLV_PEER_APSD = 0x00000800, + WMI_TLV_PEER_HT = 0x00001000, + WMI_TLV_PEER_40MHZ = 0x00002000, + WMI_TLV_PEER_STBC = 0x00008000, + WMI_TLV_PEER_LDPC = 0x00010000, + WMI_TLV_PEER_DYN_MIMOPS = 0x00020000, + WMI_TLV_PEER_STATIC_MIMOPS = 0x00040000, + WMI_TLV_PEER_SPATIAL_MUX = 0x00200000, + WMI_TLV_PEER_VHT = 0x02000000, + WMI_TLV_PEER_80MHZ = 0x04000000, + WMI_TLV_PEER_PMF = 0x08000000, +}; + enum wmi_tlv_tag { WMI_TLV_TAG_LAST_RESERVED = 15, @@ -871,6 +896,11 @@ enum wmi_tlv_tag { WMI_TLV_TAG_STRUCT_SAP_OFL_DEL_STA_EVENT, WMI_TLV_TAG_STRUCT_APFIND_CMD_PARAM, WMI_TLV_TAG_STRUCT_APFIND_EVENT_HDR, + WMI_TLV_TAG_STRUCT_HL_1_0_SVC_OFFSET = 176, + + WMI_TLV_TAG_STRUCT_MGMT_TX_CMD = 0x1A6, + WMI_TLV_TAG_STRUCT_MGMT_TX_COMPL, + WMI_TLV_TAG_STRUCT_PEER_DELETE_RESP_EVENT = 0x1C3, WMI_TLV_TAG_MAX }; @@ -946,12 +976,56 @@ enum wmi_tlv_service { WMI_TLV_SERVICE_STA_RX_IPA_OFFLOAD_SUPPORT, WMI_TLV_SERVICE_MDNS_OFFLOAD, WMI_TLV_SERVICE_SAP_AUTH_OFFLOAD, + WMI_TLV_SERVICE_DUAL_BAND_SIMULTANEOUS_SUPPORT, + WMI_TLV_SERVICE_OCB, + WMI_TLV_SERVICE_AP_ARPNS_OFFLOAD, + WMI_TLV_SERVICE_PER_BAND_CHAINMASK_SUPPORT, + WMI_TLV_SERVICE_PACKET_FILTER_OFFLOAD, + WMI_TLV_SERVICE_MGMT_TX_HTT, + WMI_TLV_SERVICE_MGMT_TX_WMI, + WMI_TLV_SERVICE_EXT_MSG, + WMI_TLV_SERVICE_MAWC, + WMI_TLV_SERVICE_PEER_ASSOC_CONF, + WMI_TLV_SERVICE_EGAP, + WMI_TLV_SERVICE_STA_PMF_OFFLOAD, + WMI_TLV_SERVICE_UNIFIED_WOW_CAPABILITY, + WMI_TLV_SERVICE_ENHANCED_PROXY_STA, + WMI_TLV_SERVICE_ATF, + WMI_TLV_SERVICE_COEX_GPIO, + WMI_TLV_SERVICE_AUX_SPECTRAL_INTF, + WMI_TLV_SERVICE_AUX_CHAN_LOAD_INTF, + WMI_TLV_SERVICE_BSS_CHANNEL_INFO_64, + WMI_TLV_SERVICE_ENTERPRISE_MESH, + WMI_TLV_SERVICE_RESTRT_CHNL_SUPPORT, + WMI_TLV_SERVICE_BPF_OFFLOAD, + WMI_TLV_SERVICE_SYNC_DELETE_CMDS, + WMI_TLV_SERVICE_SMART_ANTENNA_SW_SUPPORT, + WMI_TLV_SERVICE_SMART_ANTENNA_HW_SUPPORT, + WMI_TLV_SERVICE_RATECTRL_LIMIT_MAX_MIN_RATES, + WMI_TLV_SERVICE_NAN_DATA, + WMI_TLV_SERVICE_NAN_RTT, + WMI_TLV_SERVICE_11AX, + WMI_TLV_SERVICE_DEPRECATED_REPLACE, + WMI_TLV_SERVICE_TDLS_CONN_TRACKER_IN_HOST_MODE, + WMI_TLV_SERVICE_ENHANCED_MCAST_FILTER, + WMI_TLV_SERVICE_PERIODIC_CHAN_STAT_SUPPORT, + WMI_TLV_SERVICE_MESH_11S, + WMI_TLV_SERVICE_HALF_RATE_QUARTER_RATE_SUPPORT, + WMI_TLV_SERVICE_VDEV_RX_FILTER, + WMI_TLV_SERVICE_P2P_LISTEN_OFFLOAD_SUPPORT, + WMI_TLV_SERVICE_MARK_FIRST_WAKEUP_PACKET, + WMI_TLV_SERVICE_MULTIPLE_MCAST_FILTER_SET, + WMI_TLV_SERVICE_HOST_MANAGED_RX_REORDER, + WMI_TLV_SERVICE_FLASH_RDWR_SUPPORT, + WMI_TLV_SERVICE_WLAN_STATS_REPORT, + WMI_TLV_SERVICE_TX_MSDU_ID_NEW_PARTITION_SUPPORT, + WMI_TLV_SERVICE_DFS_PHYERR_OFFLOAD, }; #define WMI_SERVICE_IS_ENABLED(wmi_svc_bmap, svc_id, len) \ ((svc_id) < (len) && \ - __le32_to_cpu((wmi_svc_bmap)[(svc_id)/(sizeof(u32))]) & \ - BIT((svc_id)%(sizeof(u32)))) + __le32_to_cpu((wmi_svc_bmap)[(svc_id) / (sizeof(u32))]) & \ + BIT((svc_id) % (sizeof(u32)))) #define SVCMAP(x, y, len) \ do { \ @@ -1102,6 +1176,8 @@ wmi_tlv_svc_map(const __le32 *in, unsigned long *out, size_t len) WMI_SERVICE_MDNS_OFFLOAD, len); SVCMAP(WMI_TLV_SERVICE_SAP_AUTH_OFFLOAD, WMI_SERVICE_SAP_AUTH_OFFLOAD, len); + SVCMAP(WMI_TLV_SERVICE_MGMT_TX_WMI, + WMI_SERVICE_MGMT_TX_WMI, len); } #undef SVCMAP @@ -1112,6 +1188,17 @@ struct wmi_tlv { u8 value[0]; } __packed; +struct ath10k_mgmt_tx_pkt_addr { + void *vaddr; + dma_addr_t paddr; +}; + +struct wmi_tlv_mgmt_tx_compl_ev { + __le32 desc_id; + __le32 status; + __le32 pdev_id; +}; + #define WMI_TLV_MGMT_RX_NUM_RSSI 4 struct wmi_tlv_mgmt_rx_ev { @@ -1164,6 +1251,14 @@ struct wmi_tlv_svc_rdy_ev { __le32 max_num_scan_chans; __le32 hw_bd_id; /* 0 means hw_bd_info is invalid */ struct wmi_tlv_hw_bd_info hw_bd_info[5]; +#ifdef CONFIG_ATH10K_SNOC + __le32 max_supported_macs; + __le32 wmi_fw_sub_feat_caps; + __le32 num_dbs_hw_modes; + __le32 txrx_chainmask; + __le32 default_dbs_hw_mode_index; + __le32 num_msdu_desc; +#endif } __packed; struct wmi_tlv_rdy_ev { @@ -1172,6 +1267,8 @@ struct wmi_tlv_rdy_ev { __le32 status; } __packed; +#define WMI_TLV_TX_MSDU_ID_NEW_PARTITION_SUPPORT BIT(10) + struct wmi_tlv_resource_config { __le32 num_vdevs; __le32 num_peers; @@ -1322,6 +1419,10 @@ struct wmi_tlv_phyerr_ev { __le32 tsf_l32; __le32 tsf_u32; __le32 buf_len; + __le32 pdev_id; + __le32 rs_phy_err_mask0; + __le32 rs_phy_err_mask1; + __le32 rs_phy_err_mask2; } __packed; enum wmi_tlv_dbglog_param { @@ -1477,6 +1578,21 @@ struct wmi_tlv_wow_add_del_event_cmd { struct wmi_tlv_wow_enable_cmd { __le32 enable; + __le32 pause_iface_config; +} __packed; + +struct wmi_tlv_arp_ns_offload_cmd { + __le32 flags; + __le32 vdev_id; + __le32 num_ns_ext_tuples; +} __packed; + +struct wmi_tlv_gtk_offload_cmd { + __le32 vdev_id; + __le32 flags; + u8 kek[NL80211_KEK_LEN]; + u8 kck[NL80211_KCK_LEN]; + __le64 replay_ctr; } __packed; struct wmi_tlv_wow_host_wakeup_ind { @@ -1629,4 +1745,24 @@ struct wmi_tlv_tx_pause_ev { void ath10k_wmi_tlv_attach(struct ath10k *ar); +struct wmi_tlv_mgmt_tx_hdr { + __le32 vdev_id; + __le32 desc_id; + __le32 chanfreq; + __le64 paddr; + __le32 frame_len; + __le32 buf_len; +} __packed; + +struct wmi_tlv_mgmt_tx_cmd { + struct wmi_tlv_mgmt_tx_hdr hdr; + __le16 data_len; + __le16 data_tag; + u8 buf[0]; +} __packed; + +struct wmi_tlv_mac_addr_cmd { + __le32 pdev_id; + struct wmi_mac_addr mac_addr; +} __packed; #endif diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c index 7569db0f69b5..938fc845d845 100644 --- a/drivers/net/wireless/ath/ath10k/wmi.c +++ b/drivers/net/wireless/ath/ath10k/wmi.c @@ -29,6 +29,9 @@ #include "p2p.h" #include "hw.h" +#define ATH10K_WMI_BARRIER_ECHO_ID 0xBA991E9 +#define ATH10K_WMI_BARRIER_TIMEOUT_HZ (3 * HZ) + /* MAIN WMI cmd track */ static struct wmi_cmd_map wmi_cmd_map = { .init_cmdid = WMI_INIT_CMDID, @@ -521,7 +524,8 @@ static struct wmi_cmd_map wmi_10_2_4_cmd_map = { .vdev_filter_neighbor_rx_packets_cmdid = WMI_CMD_UNSUPPORTED, .mu_cal_start_cmdid = WMI_CMD_UNSUPPORTED, .set_cca_params_cmdid = WMI_CMD_UNSUPPORTED, - .pdev_bss_chan_info_request_cmdid = WMI_CMD_UNSUPPORTED, + .pdev_bss_chan_info_request_cmdid = + WMI_10_2_PDEV_BSS_CHAN_INFO_REQUEST_CMDID, }; /* 10.4 WMI cmd track */ @@ -705,6 +709,7 @@ static struct wmi_cmd_map wmi_10_4_cmd_map = { .set_cca_params_cmdid = WMI_10_4_SET_CCA_PARAMS_CMDID, .pdev_bss_chan_info_request_cmdid = WMI_10_4_PDEV_BSS_CHAN_INFO_REQUEST_CMDID, + .ext_resource_cfg_cmdid = WMI_10_4_EXT_RESOURCE_CFG_CMDID, }; /* MAIN WMI VDEV param map */ @@ -780,6 +785,7 @@ static struct wmi_vdev_param_map wmi_vdev_param_map = { .meru_vc = WMI_VDEV_PARAM_UNSUPPORTED, .rx_decap_type = WMI_VDEV_PARAM_UNSUPPORTED, .bw_nss_ratemask = WMI_VDEV_PARAM_UNSUPPORTED, + .set_tsf = WMI_VDEV_PARAM_UNSUPPORTED, }; /* 10.X WMI VDEV param map */ @@ -855,6 +861,7 @@ static struct wmi_vdev_param_map wmi_10x_vdev_param_map = { .meru_vc = WMI_VDEV_PARAM_UNSUPPORTED, .rx_decap_type = WMI_VDEV_PARAM_UNSUPPORTED, .bw_nss_ratemask = WMI_VDEV_PARAM_UNSUPPORTED, + .set_tsf = WMI_VDEV_PARAM_UNSUPPORTED, }; static struct wmi_vdev_param_map wmi_10_2_4_vdev_param_map = { @@ -929,6 +936,7 @@ static struct wmi_vdev_param_map wmi_10_2_4_vdev_param_map = { .meru_vc = WMI_VDEV_PARAM_UNSUPPORTED, .rx_decap_type = WMI_VDEV_PARAM_UNSUPPORTED, .bw_nss_ratemask = WMI_VDEV_PARAM_UNSUPPORTED, + .set_tsf = WMI_10X_VDEV_PARAM_TSF_INCREMENT, }; static struct wmi_vdev_param_map wmi_10_4_vdev_param_map = { @@ -1004,6 +1012,7 @@ static struct wmi_vdev_param_map wmi_10_4_vdev_param_map = { .meru_vc = WMI_10_4_VDEV_PARAM_MERU_VC, .rx_decap_type = WMI_10_4_VDEV_PARAM_RX_DECAP_TYPE, .bw_nss_ratemask = WMI_10_4_VDEV_PARAM_BW_NSS_RATEMASK, + .set_tsf = WMI_10_4_VDEV_PARAM_TSF_INCREMENT, }; static struct wmi_pdev_param_map wmi_pdev_param_map = { @@ -1098,6 +1107,7 @@ static struct wmi_pdev_param_map wmi_pdev_param_map = { .wapi_mbssid_offset = WMI_PDEV_PARAM_UNSUPPORTED, .arp_srcaddr = WMI_PDEV_PARAM_UNSUPPORTED, .arp_dstaddr = WMI_PDEV_PARAM_UNSUPPORTED, + .enable_btcoex = WMI_PDEV_PARAM_UNSUPPORTED, }; static struct wmi_pdev_param_map wmi_10x_pdev_param_map = { @@ -1193,6 +1203,7 @@ static struct wmi_pdev_param_map wmi_10x_pdev_param_map = { .wapi_mbssid_offset = WMI_PDEV_PARAM_UNSUPPORTED, .arp_srcaddr = WMI_PDEV_PARAM_UNSUPPORTED, .arp_dstaddr = WMI_PDEV_PARAM_UNSUPPORTED, + .enable_btcoex = WMI_PDEV_PARAM_UNSUPPORTED, }; static struct wmi_pdev_param_map wmi_10_2_4_pdev_param_map = { @@ -1288,6 +1299,7 @@ static struct wmi_pdev_param_map wmi_10_2_4_pdev_param_map = { .wapi_mbssid_offset = WMI_PDEV_PARAM_UNSUPPORTED, .arp_srcaddr = WMI_PDEV_PARAM_UNSUPPORTED, .arp_dstaddr = WMI_PDEV_PARAM_UNSUPPORTED, + .enable_btcoex = WMI_PDEV_PARAM_UNSUPPORTED, }; /* firmware 10.2 specific mappings */ @@ -1544,6 +1556,62 @@ static struct wmi_pdev_param_map wmi_10_4_pdev_param_map = { .wapi_mbssid_offset = WMI_10_4_PDEV_PARAM_WAPI_MBSSID_OFFSET, .arp_srcaddr = WMI_10_4_PDEV_PARAM_ARP_SRCADDR, .arp_dstaddr = WMI_10_4_PDEV_PARAM_ARP_DSTADDR, + .enable_btcoex = WMI_10_4_PDEV_PARAM_ENABLE_BTCOEX, +}; + +static const struct wmi_peer_flags_map wmi_peer_flags_map = { + .auth = WMI_PEER_AUTH, + .qos = WMI_PEER_QOS, + .need_ptk_4_way = WMI_PEER_NEED_PTK_4_WAY, + .need_gtk_2_way = WMI_PEER_NEED_GTK_2_WAY, + .apsd = WMI_PEER_APSD, + .ht = WMI_PEER_HT, + .bw40 = WMI_PEER_40MHZ, + .stbc = WMI_PEER_STBC, + .ldbc = WMI_PEER_LDPC, + .dyn_mimops = WMI_PEER_DYN_MIMOPS, + .static_mimops = WMI_PEER_STATIC_MIMOPS, + .spatial_mux = WMI_PEER_SPATIAL_MUX, + .vht = WMI_PEER_VHT, + .bw80 = WMI_PEER_80MHZ, + .vht_2g = WMI_PEER_VHT_2G, + .pmf = WMI_PEER_PMF, +}; + +static const struct wmi_peer_flags_map wmi_10x_peer_flags_map = { + .auth = WMI_10X_PEER_AUTH, + .qos = WMI_10X_PEER_QOS, + .need_ptk_4_way = WMI_10X_PEER_NEED_PTK_4_WAY, + .need_gtk_2_way = WMI_10X_PEER_NEED_GTK_2_WAY, + .apsd = WMI_10X_PEER_APSD, + .ht = WMI_10X_PEER_HT, + .bw40 = WMI_10X_PEER_40MHZ, + .stbc = WMI_10X_PEER_STBC, + .ldbc = WMI_10X_PEER_LDPC, + .dyn_mimops = WMI_10X_PEER_DYN_MIMOPS, + .static_mimops = WMI_10X_PEER_STATIC_MIMOPS, + .spatial_mux = WMI_10X_PEER_SPATIAL_MUX, + .vht = WMI_10X_PEER_VHT, + .bw80 = WMI_10X_PEER_80MHZ, +}; + +static const struct wmi_peer_flags_map wmi_10_2_peer_flags_map = { + .auth = WMI_10_2_PEER_AUTH, + .qos = WMI_10_2_PEER_QOS, + .need_ptk_4_way = WMI_10_2_PEER_NEED_PTK_4_WAY, + .need_gtk_2_way = WMI_10_2_PEER_NEED_GTK_2_WAY, + .apsd = WMI_10_2_PEER_APSD, + .ht = WMI_10_2_PEER_HT, + .bw40 = WMI_10_2_PEER_40MHZ, + .stbc = WMI_10_2_PEER_STBC, + .ldbc = WMI_10_2_PEER_LDPC, + .dyn_mimops = WMI_10_2_PEER_DYN_MIMOPS, + .static_mimops = WMI_10_2_PEER_STATIC_MIMOPS, + .spatial_mux = WMI_10_2_PEER_SPATIAL_MUX, + .vht = WMI_10_2_PEER_VHT, + .bw80 = WMI_10_2_PEER_80MHZ, + .vht_2g = WMI_10_2_PEER_VHT_2G, + .pmf = WMI_10_2_PEER_PMF, }; void ath10k_wmi_put_wmi_channel(struct wmi_channel *ch, @@ -1573,6 +1641,7 @@ void ath10k_wmi_put_wmi_channel(struct wmi_channel *ch, ch->max_power = arg->max_power; ch->reg_power = arg->max_reg_power; ch->antenna_max = arg->max_antenna_gain; + ch->max_tx_power = arg->max_power; /* mode & flags share storage */ ch->mode = arg->mode; @@ -1660,6 +1729,8 @@ static void ath10k_wmi_tx_beacon_nowait(struct ath10k_vif *arvif) struct ath10k *ar = arvif->ar; struct ath10k_skb_cb *cb; struct sk_buff *bcn; + bool dtim_zero; + bool deliver_cab; int ret; spin_lock_bh(&ar->data_lock); @@ -1679,12 +1750,14 @@ static void ath10k_wmi_tx_beacon_nowait(struct ath10k_vif *arvif) arvif->beacon_state = ATH10K_BEACON_SENDING; spin_unlock_bh(&ar->data_lock); + dtim_zero = !!(cb->flags & ATH10K_SKB_F_DTIM_ZERO); + deliver_cab = !!(cb->flags & ATH10K_SKB_F_DELIVER_CAB); ret = ath10k_wmi_beacon_send_ref_nowait(arvif->ar, arvif->vdev_id, bcn->data, bcn->len, cb->paddr, - cb->bcn.dtim_zero, - cb->bcn.deliver_cab); + dtim_zero, + deliver_cab); spin_lock_bh(&ar->data_lock); @@ -1726,6 +1799,9 @@ int ath10k_wmi_cmd_send(struct ath10k *ar, struct sk_buff *skb, u32 cmd_id) { int ret = -EOPNOTSUPP; + if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + return -ESHUTDOWN; + might_sleep(); if (cmd_id == WMI_CMD_UNSUPPORTED) { @@ -1744,7 +1820,7 @@ int ath10k_wmi_cmd_send(struct ath10k *ar, struct sk_buff *skb, u32 cmd_id) ret = -ESHUTDOWN; (ret != -EAGAIN); - }), 3*HZ); + }), 3 * HZ); if (ret) dev_kfree_skb_any(skb); @@ -1755,16 +1831,26 @@ int ath10k_wmi_cmd_send(struct ath10k *ar, struct sk_buff *skb, u32 cmd_id) static struct sk_buff * ath10k_wmi_op_gen_mgmt_tx(struct ath10k *ar, struct sk_buff *msdu) { + struct ath10k_skb_cb *cb = ATH10K_SKB_CB(msdu); + struct ath10k_vif *arvif; struct wmi_mgmt_tx_cmd *cmd; struct ieee80211_hdr *hdr; struct sk_buff *skb; int len; + u32 vdev_id; u32 buf_len = msdu->len; u16 fc; hdr = (struct ieee80211_hdr *)msdu->data; fc = le16_to_cpu(hdr->frame_control); + if (cb->vif) { + arvif = (void *)cb->vif->drv_priv; + vdev_id = arvif->vdev_id; + } else { + vdev_id = 0; + } + if (WARN_ON_ONCE(!ieee80211_is_mgmt(hdr->frame_control))) return ERR_PTR(-EINVAL); @@ -1786,7 +1872,7 @@ ath10k_wmi_op_gen_mgmt_tx(struct ath10k *ar, struct sk_buff *msdu) cmd = (struct wmi_mgmt_tx_cmd *)skb->data; - cmd->hdr.vdev_id = __cpu_to_le32(ATH10K_SKB_CB(msdu)->vdev_id); + cmd->hdr.vdev_id = __cpu_to_le32(vdev_id); cmd->hdr.tx_rate = 0; cmd->hdr.tx_power = 0; cmd->hdr.buf_len = __cpu_to_le32(buf_len); @@ -1794,7 +1880,7 @@ ath10k_wmi_op_gen_mgmt_tx(struct ath10k *ar, struct sk_buff *msdu) ether_addr_copy(cmd->hdr.peer_macaddr.addr, ieee80211_get_DA(hdr)); memcpy(cmd->buf, msdu->data, msdu->len); - ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi mgmt tx skb %p len %d ftype %02x stype %02x\n", + ath10k_dbg(ar, ATH10K_DBG_WMI, "wmi mgmt tx skb %pK len %d ftype %02x stype %02x\n", msdu, skb->len, fc & IEEE80211_FCTL_FTYPE, fc & IEEE80211_FCTL_STYPE); trace_ath10k_tx_hdr(ar, skb->data, skb->len); @@ -2032,34 +2118,6 @@ int ath10k_wmi_event_scan(struct ath10k *ar, struct sk_buff *skb) return 0; } -static inline enum ieee80211_band phy_mode_to_band(u32 phy_mode) -{ - enum ieee80211_band band; - - switch (phy_mode) { - case MODE_11A: - case MODE_11NA_HT20: - case MODE_11NA_HT40: - case MODE_11AC_VHT20: - case MODE_11AC_VHT40: - case MODE_11AC_VHT80: - band = IEEE80211_BAND_5GHZ; - break; - case MODE_11G: - case MODE_11B: - case MODE_11GONLY: - case MODE_11NG_HT20: - case MODE_11NG_HT40: - case MODE_11AC_VHT20_2G: - case MODE_11AC_VHT40_2G: - case MODE_11AC_VHT80_2G: - default: - band = IEEE80211_BAND_2GHZ; - } - - return band; -} - /* If keys are configured, HW decrypts all frames * with protected bit set. Mark such frames as decrypted. */ @@ -2100,10 +2158,13 @@ static int ath10k_wmi_op_pull_mgmt_rx_ev(struct ath10k *ar, struct sk_buff *skb, struct wmi_mgmt_rx_event_v1 *ev_v1; struct wmi_mgmt_rx_event_v2 *ev_v2; struct wmi_mgmt_rx_hdr_v1 *ev_hdr; + struct wmi_mgmt_rx_ext_info *ext_info; size_t pull_len; u32 msdu_len; + u32 len; - if (test_bit(ATH10K_FW_FEATURE_EXT_WMI_MGMT_RX, ar->fw_features)) { + if (test_bit(ATH10K_FW_FEATURE_EXT_WMI_MGMT_RX, + ar->running_fw->fw_file.fw_features)) { ev_v2 = (struct wmi_mgmt_rx_event_v2 *)skb->data; ev_hdr = &ev_v2->hdr.v1; pull_len = sizeof(*ev_v2); @@ -2128,6 +2189,12 @@ static int ath10k_wmi_op_pull_mgmt_rx_ev(struct ath10k *ar, struct sk_buff *skb, if (skb->len < msdu_len) return -EPROTO; + if (le32_to_cpu(arg->status) & WMI_RX_STATUS_EXT_INFO) { + len = ALIGN(le32_to_cpu(arg->buf_len), 4); + ext_info = (struct wmi_mgmt_rx_ext_info *)(skb->data + len); + memcpy(&arg->ext_info, ext_info, + sizeof(struct wmi_mgmt_rx_ext_info)); + } /* the WMI buffer might've ended up being padded to 4 bytes due to HTC * trailer with credit update. Trim the excess garbage. */ @@ -2144,6 +2211,8 @@ static int ath10k_wmi_10_4_op_pull_mgmt_rx_ev(struct ath10k *ar, struct wmi_10_4_mgmt_rx_hdr *ev_hdr; size_t pull_len; u32 msdu_len; + struct wmi_mgmt_rx_ext_info *ext_info; + u32 len; ev = (struct wmi_10_4_mgmt_rx_event *)skb->data; ev_hdr = &ev->hdr; @@ -2164,12 +2233,113 @@ static int ath10k_wmi_10_4_op_pull_mgmt_rx_ev(struct ath10k *ar, if (skb->len < msdu_len) return -EPROTO; + if (le32_to_cpu(arg->status) & WMI_RX_STATUS_EXT_INFO) { + len = ALIGN(le32_to_cpu(arg->buf_len), 4); + ext_info = (struct wmi_mgmt_rx_ext_info *)(skb->data + len); + memcpy(&arg->ext_info, ext_info, + sizeof(struct wmi_mgmt_rx_ext_info)); + } + /* Make sure bytes added for padding are removed. */ skb_trim(skb, msdu_len); return 0; } +static bool ath10k_wmi_rx_is_decrypted(struct ath10k *ar, + struct ieee80211_hdr *hdr) +{ + if (!ieee80211_has_protected(hdr->frame_control)) + return false; + + /* FW delivers WEP Shared Auth frame with Protected Bit set and + * encrypted payload. However in case of PMF it delivers decrypted + * frames with Protected Bit set. + */ + if (ieee80211_is_auth(hdr->frame_control)) + return false; + + /* qca99x0 based FW delivers broadcast or multicast management frames + * (ex: group privacy action frames in mesh) as encrypted payload. + */ + if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) && + ar->hw_params.sw_decrypt_mcast_mgmt) + return false; + + return true; +} + +int ath10k_wmi_tlv_event_peer_delete_resp(struct ath10k *ar, + struct sk_buff *skb) +{ + int ret; + struct wmi_peer_delete_resp_ev_arg arg = {}; + + ret = ath10k_wmi_pull_peer_delete_resp(ar, skb, &arg); + if (ret) { + ath10k_warn(ar, "failed to parse peer delete resp: %d\n", ret); + dev_kfree_skb(skb); + return ret; + } + ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_TLV_PEER_DELETE_RESP_EVENTID\n"); + complete(&ar->peer_delete_done); + + return 0; +} + +static int wmi_tlv_process_mgmt_tx_comp(struct ath10k *ar, u32 desc_id, + u32 status) +{ + struct ath10k_mgmt_tx_pkt_addr *pkt_addr; + struct ath10k_wmi *wmi = &ar->wmi; + struct ieee80211_tx_info *info; + struct sk_buff *msdu; + int ret = 0; + + spin_lock_bh(&wmi->mgmt_tx_lock); + pkt_addr = idr_find(&wmi->mgmt_pending_tx, desc_id); + if (!pkt_addr) { + ath10k_warn(ar, "received mgmt tx completion for invalid msdu_id: %d\n", + desc_id); + ret = -ENOENT; + goto tx_comp_process_done; + } + + msdu = pkt_addr->vaddr; + dma_unmap_single(ar->dev, pkt_addr->paddr, + msdu->len, DMA_FROM_DEVICE); + info = IEEE80211_SKB_CB(msdu); + if (!status) + info->flags |= IEEE80211_TX_STAT_ACK; + else + info->flags |= status; + ieee80211_tx_status_irqsafe(ar->hw, msdu); + ret = 0; + +tx_comp_process_done: + idr_remove(&wmi->mgmt_pending_tx, desc_id); + spin_unlock_bh(&wmi->mgmt_tx_lock); + + return ret; +} + +int ath10k_wmi_tlv_event_mgmt_tx_compl(struct ath10k *ar, struct sk_buff *skb) +{ + int ret; + struct wmi_tlv_mgmt_tx_compl_ev_arg arg; + + ret = ath10k_wmi_pull_mgmt_tx_compl(ar, skb, &arg); + if (ret) { + ath10k_warn(ar, "failed to parse mgmt comp event: %d\n", ret); + return ret; + } + + wmi_tlv_process_mgmt_tx_comp(ar, arg.desc_id, arg.status); + ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_TLV_MGMT_TX_COMPLETION_EVENTID\n"); + + return 0; +} + int ath10k_wmi_event_mgmt_rx(struct ath10k *ar, struct sk_buff *skb) { struct wmi_mgmt_rx_ev_arg arg = {}; @@ -2204,22 +2374,9 @@ int ath10k_wmi_event_mgmt_rx(struct ath10k *ar, struct sk_buff *skb) ath10k_dbg(ar, ATH10K_DBG_MGMT, "event mgmt rx status %08x\n", rx_status); - if (test_bit(ATH10K_CAC_RUNNING, &ar->dev_flags)) { - dev_kfree_skb(skb); - return 0; - } - - if (rx_status & WMI_RX_STATUS_ERR_DECRYPT) { - dev_kfree_skb(skb); - return 0; - } - - if (rx_status & WMI_RX_STATUS_ERR_KEY_CACHE_MISS) { - dev_kfree_skb(skb); - return 0; - } - - if (rx_status & WMI_RX_STATUS_ERR_CRC) { + if ((test_bit(ATH10K_CAC_RUNNING, &ar->dev_flags)) || + (rx_status & (WMI_RX_STATUS_ERR_DECRYPT | + WMI_RX_STATUS_ERR_KEY_CACHE_MISS | WMI_RX_STATUS_ERR_CRC))) { dev_kfree_skb(skb); return 0; } @@ -2227,6 +2384,11 @@ int ath10k_wmi_event_mgmt_rx(struct ath10k *ar, struct sk_buff *skb) if (rx_status & WMI_RX_STATUS_ERR_MIC) status->flag |= RX_FLAG_MMIC_ERROR; + if (rx_status & WMI_RX_STATUS_EXT_INFO) { + status->mactime = + __le64_to_cpu(arg.ext_info.rx_mac_timestamp); + status->flag |= RX_FLAG_MACTIME_END; + } /* Hardware can Rx CCK rates on 5GHz. In that case phy_mode is set to * MODE_11B. This means phy_mode is not a reliable source for the band * of mgmt rx. @@ -2256,13 +2418,15 @@ int ath10k_wmi_event_mgmt_rx(struct ath10k *ar, struct sk_buff *skb) hdr = (struct ieee80211_hdr *)skb->data; fc = le16_to_cpu(hdr->frame_control); + /* Firmware is guaranteed to report all essential management frames via + * WMI while it can deliver some extra via HTT. Since there can be + * duplicates split the reporting wrt monitor/sniffing. + */ + status->flag |= RX_FLAG_SKIP_MONITOR; + ath10k_wmi_handle_wep_reauth(ar, skb, status); - /* FW delivers WEP Shared Auth frame with Protected Bit set and - * encrypted payload. However in case of PMF it delivers decrypted - * frames with Protected Bit set. */ - if (ieee80211_has_protected(hdr->frame_control) && - !ieee80211_is_auth(hdr->frame_control)) { + if (ath10k_wmi_rx_is_decrypted(ar, hdr)) { status->flag |= RX_FLAG_DECRYPTED; if (!ieee80211_is_action(hdr->frame_control) && @@ -2279,7 +2443,7 @@ int ath10k_wmi_event_mgmt_rx(struct ath10k *ar, struct sk_buff *skb) ath10k_mac_handle_beacon(ar, skb); ath10k_dbg(ar, ATH10K_DBG_MGMT, - "event mgmt rx skb %p len %d ftype %02x stype %02x\n", + "event mgmt rx skb %pK len %d ftype %02x stype %02x\n", skb, skb->len, fc & IEEE80211_FCTL_FTYPE, fc & IEEE80211_FCTL_STYPE); @@ -2353,6 +2517,31 @@ static int ath10k_wmi_10_4_op_pull_ch_info_ev(struct ath10k *ar, return 0; } +static void wlan_fill_survey_result(struct ath10k *ar, + struct survey_info *survey, + struct wmi_ch_info_ev_arg arg) +{ + u64 clock_freq; + + if (!arg.mac_clk_mhz || !survey) + return; + + clock_freq = arg.mac_clk_mhz * 1000; + + memset(survey, 0, sizeof(*survey)); + + survey->noise = __le32_to_cpu(arg.noise_floor); + survey->time = __le32_to_cpu(arg.cycle_count) / clock_freq; + survey->time_busy = __le32_to_cpu(arg.rx_clear_count) / clock_freq; + survey->time_tx = __le32_to_cpu(arg.rx_clear_count) / clock_freq; + + survey->filled = SURVEY_INFO_NOISE_DBM; + ar->ch_info_can_report_survey = true; + + survey->filled |= (SURVEY_INFO_TIME | SURVEY_INFO_TIME_BUSY | + SURVEY_INFO_TIME_TX); +} + void ath10k_wmi_event_chan_info(struct ath10k *ar, struct sk_buff *skb) { struct wmi_ch_info_ev_arg arg = {}; @@ -2397,6 +2586,12 @@ void ath10k_wmi_event_chan_info(struct ath10k *ar, struct sk_buff *skb) goto exit; } + if (QCA_REV_WCN3990(ar)) { + survey = &ar->survey[idx]; + wlan_fill_survey_result(ar, survey, arg); + goto exit; + } + if (cmd_flags & WMI_CHAN_INFO_FLAG_COMPLETE) { if (ar->ch_info_can_report_survey) { survey = &ar->survey[idx]; @@ -2427,7 +2622,21 @@ exit: void ath10k_wmi_event_echo(struct ath10k *ar, struct sk_buff *skb) { - ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_ECHO_EVENTID\n"); + struct wmi_echo_ev_arg arg = {}; + int ret; + + ret = ath10k_wmi_pull_echo_ev(ar, skb, &arg); + if (ret) { + ath10k_warn(ar, "failed to parse echo: %d\n", ret); + return; + } + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi event echo value 0x%08x\n", + le32_to_cpu(arg.value)); + + if (le32_to_cpu(arg.value) == ATH10K_WMI_BARRIER_ECHO_ID) + complete(&ar->wmi.barrier); } int ath10k_wmi_event_debug_mesg(struct ath10k *ar, struct sk_buff *skb) @@ -2558,6 +2767,16 @@ void ath10k_wmi_pull_peer_stats(const struct wmi_peer_stats *src, dst->peer_tx_rate = __le32_to_cpu(src->peer_tx_rate); } +static void +ath10k_wmi_10_4_pull_peer_stats(const struct wmi_10_4_peer_stats *src, + struct ath10k_fw_stats_peer *dst) +{ + ether_addr_copy(dst->peer_macaddr, src->peer_macaddr.addr); + dst->peer_rssi = __le32_to_cpu(src->peer_rssi); + dst->peer_tx_rate = __le32_to_cpu(src->peer_tx_rate); + dst->peer_rx_rate = __le32_to_cpu(src->peer_rx_rate); +} + static int ath10k_wmi_main_op_pull_fw_stats(struct ath10k *ar, struct sk_buff *skb, struct ath10k_fw_stats *stats) @@ -2808,11 +3027,17 @@ static int ath10k_wmi_10_2_4_op_pull_fw_stats(struct ath10k *ar, /* fw doesn't implement vdev stats */ for (i = 0; i < num_peer_stats; i++) { - const struct wmi_10_2_4_peer_stats *src; + const struct wmi_10_2_4_ext_peer_stats *src; struct ath10k_fw_stats_peer *dst; + int stats_len; + + if (test_bit(WMI_SERVICE_PEER_STATS, ar->wmi.svc_map)) + stats_len = sizeof(struct wmi_10_2_4_ext_peer_stats); + else + stats_len = sizeof(struct wmi_10_2_4_peer_stats); src = (void *)skb->data; - if (!skb_pull(skb, sizeof(*src))) + if (!skb_pull(skb, stats_len)) return -EPROTO; dst = kzalloc(sizeof(*dst), GFP_ATOMIC); @@ -2822,6 +3047,9 @@ static int ath10k_wmi_10_2_4_op_pull_fw_stats(struct ath10k *ar, ath10k_wmi_pull_peer_stats(&src->common.old, dst); dst->peer_rx_rate = __le32_to_cpu(src->common.peer_rx_rate); + + if (ath10k_peer_stats_enabled(ar)) + dst->rx_duration = __le32_to_cpu(src->rx_duration); /* FIXME: expose 10.2 specific values */ list_add_tail(&dst->list, &stats->peers); @@ -2839,6 +3067,8 @@ static int ath10k_wmi_10_4_op_pull_fw_stats(struct ath10k *ar, u32 num_pdev_ext_stats; u32 num_vdev_stats; u32 num_peer_stats; + u32 num_bcnflt_stats; + u32 stats_id; int i; if (!skb_pull(skb, sizeof(*ev))) @@ -2848,6 +3078,8 @@ static int ath10k_wmi_10_4_op_pull_fw_stats(struct ath10k *ar, num_pdev_ext_stats = __le32_to_cpu(ev->num_pdev_ext_stats); num_vdev_stats = __le32_to_cpu(ev->num_vdev_stats); num_peer_stats = __le32_to_cpu(ev->num_peer_stats); + num_bcnflt_stats = __le32_to_cpu(ev->num_bcnflt_stats); + stats_id = __le32_to_cpu(ev->stats_id); for (i = 0; i < num_pdev_stats; i++) { const struct wmi_10_4_pdev_stats *src; @@ -2898,15 +3130,46 @@ static int ath10k_wmi_10_4_op_pull_fw_stats(struct ath10k *ar, if (!dst) continue; - ether_addr_copy(dst->peer_macaddr, src->peer_macaddr.addr); - dst->peer_rssi = __le32_to_cpu(src->peer_rssi); - dst->peer_tx_rate = __le32_to_cpu(src->peer_tx_rate); - dst->peer_rx_rate = __le32_to_cpu(src->peer_rx_rate); - /* FIXME: expose 10.4 specific values */ - + ath10k_wmi_10_4_pull_peer_stats(src, dst); list_add_tail(&dst->list, &stats->peers); } + for (i = 0; i < num_bcnflt_stats; i++) { + const struct wmi_10_4_bss_bcn_filter_stats *src; + + src = (void *)skb->data; + if (!skb_pull(skb, sizeof(*src))) + return -EPROTO; + + /* FIXME: expose values to userspace + * + * Note: Even though this loop seems to do nothing it is + * required to parse following sub-structures properly. + */ + } + + if ((stats_id & WMI_10_4_STAT_PEER_EXTD) == 0) + return 0; + + stats->extended = true; + + for (i = 0; i < num_peer_stats; i++) { + const struct wmi_10_4_peer_extd_stats *src; + struct ath10k_fw_extd_stats_peer *dst; + + src = (void *)skb->data; + if (!skb_pull(skb, sizeof(*src))) + return -EPROTO; + + dst = kzalloc(sizeof(*dst), GFP_ATOMIC); + if (!dst) + continue; + + ether_addr_copy(dst->peer_macaddr, src->peer_macaddr.addr); + dst->rx_duration = __le32_to_cpu(src->rx_duration); + list_add_tail(&dst->list, &stats->peers_extd); + } + return 0; } @@ -2959,6 +3222,12 @@ void ath10k_wmi_event_vdev_stopped(struct ath10k *ar, struct sk_buff *skb) complete(&ar->vdev_setup_done); } +void ath10k_wmi_event_vdev_delete_resp(struct ath10k *ar, struct sk_buff *skb) +{ + ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_VDEV_DELETE_RESP_EVENTID\n"); + complete(&ar->vdev_delete_done); +} + static int ath10k_wmi_op_pull_peer_kick_ev(struct ath10k *ar, struct sk_buff *skb, struct wmi_peer_kick_ev_arg *arg) @@ -3115,10 +3384,10 @@ static void ath10k_wmi_update_tim(struct ath10k *ar, memcpy(tim->virtual_map, arvif->u.ap.tim_bitmap, pvm_len); if (tim->dtim_count == 0) { - ATH10K_SKB_CB(bcn)->bcn.dtim_zero = true; + ATH10K_SKB_CB(bcn)->flags |= ATH10K_SKB_F_DTIM_ZERO; if (__le32_to_cpu(tim_info->tim_mcast) == 1) - ATH10K_SKB_CB(bcn)->bcn.deliver_cab = true; + ATH10K_SKB_CB(bcn)->flags |= ATH10K_SKB_F_DELIVER_CAB; } ath10k_dbg(ar, ATH10K_DBG_MGMT, "dtim %d/%d mcast %d pvmlen %d\n", @@ -3130,7 +3399,7 @@ static void ath10k_wmi_update_noa(struct ath10k *ar, struct ath10k_vif *arvif, struct sk_buff *bcn, const struct wmi_p2p_noa_info *noa) { - if (arvif->vdev_subtype != WMI_VDEV_SUBTYPE_P2P_GO) + if (!arvif->vif->p2p) return; ath10k_dbg(ar, ATH10K_DBG_MGMT, "noa changed: %d\n", noa->changed); @@ -3190,6 +3459,50 @@ static int ath10k_wmi_op_pull_swba_ev(struct ath10k *ar, struct sk_buff *skb, return 0; } +static int ath10k_wmi_10_2_4_op_pull_swba_ev(struct ath10k *ar, + struct sk_buff *skb, + struct wmi_swba_ev_arg *arg) +{ + struct wmi_10_2_4_host_swba_event *ev = (void *)skb->data; + u32 map; + size_t i; + + if (skb->len < sizeof(*ev)) + return -EPROTO; + + skb_pull(skb, sizeof(*ev)); + arg->vdev_map = ev->vdev_map; + + for (i = 0, map = __le32_to_cpu(ev->vdev_map); map; map >>= 1) { + if (!(map & BIT(0))) + continue; + + /* If this happens there were some changes in firmware and + * ath10k should update the max size of tim_info array. + */ + if (WARN_ON_ONCE(i == ARRAY_SIZE(arg->tim_info))) + break; + + if (__le32_to_cpu(ev->bcn_info[i].tim_info.tim_len) > + sizeof(ev->bcn_info[i].tim_info.tim_bitmap)) { + ath10k_warn(ar, "refusing to parse invalid swba structure\n"); + return -EPROTO; + } + + arg->tim_info[i].tim_len = ev->bcn_info[i].tim_info.tim_len; + arg->tim_info[i].tim_mcast = ev->bcn_info[i].tim_info.tim_mcast; + arg->tim_info[i].tim_bitmap = + ev->bcn_info[i].tim_info.tim_bitmap; + arg->tim_info[i].tim_changed = + ev->bcn_info[i].tim_info.tim_changed; + arg->tim_info[i].tim_num_ps_pending = + ev->bcn_info[i].tim_info.tim_num_ps_pending; + i++; + } + + return 0; +} + static int ath10k_wmi_10_4_op_pull_swba_ev(struct ath10k *ar, struct sk_buff *skb, struct wmi_swba_ev_arg *arg) @@ -3312,6 +3625,12 @@ void ath10k_wmi_event_host_swba(struct ath10k *ar, struct sk_buff *skb) continue; } + /* mac80211 would have already asked us to stop beaconing and + * bring the vdev down, so continue in that case + */ + if (!arvif->is_up) + continue; + /* There are no completions for beacons so wait for next SWBA * before telling mac80211 to decrement CSA counter * @@ -3361,7 +3680,6 @@ void ath10k_wmi_event_host_swba(struct ath10k *ar, struct sk_buff *skb) ath10k_warn(ar, "failed to map beacon: %d\n", ret); dev_kfree_skb_any(bcn); - ret = -EIO; goto skip; } @@ -3538,7 +3856,7 @@ void ath10k_wmi_event_dfs(struct ath10k *ar, phyerr->tsf_timestamp, tsf, buf_len); /* Skip event if DFS disabled */ - if (!config_enabled(CONFIG_ATH10K_DFS_CERTIFIED)) + if (!IS_ENABLED(CONFIG_ATH10K_DFS_CERTIFIED)) return; ATH10K_DFS_STAT_INC(ar, pulses_total); @@ -3811,19 +4129,20 @@ void ath10k_wmi_event_phyerr(struct ath10k *ar, struct sk_buff *skb) left_len -= buf_len; - switch (phy_err_code) { - case PHY_ERROR_RADAR: + if ((phy_err_code == PHY_ERROR_RADAR) || + (hdr_arg.phy_err_mask0 & + WMI_PHY_ERROR_MASK0_RADAR)) { ath10k_wmi_event_dfs(ar, &phyerr_arg, tsf); - break; - case PHY_ERROR_SPECTRAL_SCAN: + } else if ((phy_err_code == + WMI_PHY_ERROR_MASK0_SPECTRAL_SCAN) || + (hdr_arg.phy_err_mask0 & + WMI_PHY_ERROR_MASK0_SPECTRAL_SCAN)) { ath10k_wmi_event_spectral_scan(ar, &phyerr_arg, tsf); - break; - case PHY_ERROR_FALSE_RADAR_EXT: + } else if ((phy_err_code == PHY_ERROR_FALSE_RADAR_EXT) || + (hdr_arg.phy_err_mask0 & + WMI_PHY_ERROR_MASK0_FALSE_RADAR_EXT)) { ath10k_wmi_event_dfs(ar, &phyerr_arg, tsf); ath10k_wmi_event_spectral_scan(ar, &phyerr_arg, tsf); - break; - default: - break; } phyerr = phyerr + phyerr_arg.hdr_len + buf_len; @@ -3949,6 +4268,7 @@ void ath10k_wmi_event_wow_wakeup_host(struct ath10k *ar, struct sk_buff *skb) return; } + ar->wow.wakeup_reason = ev.wake_reason; ath10k_dbg(ar, ATH10K_DBG_WMI, "wow wakeup host reason %s\n", wow_reason(ev.wake_reason)); } @@ -4258,34 +4578,58 @@ void ath10k_wmi_event_vdev_resume_req(struct ath10k *ar, struct sk_buff *skb) ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_VDEV_RESUME_REQ_EVENTID\n"); } -static int ath10k_wmi_alloc_host_mem(struct ath10k *ar, u32 req_id, - u32 num_units, u32 unit_len) +static int ath10k_wmi_alloc_chunk(struct ath10k *ar, u32 req_id, + u32 num_units, u32 unit_len) { dma_addr_t paddr; - u32 pool_size; + u32 pool_size = 0; int idx = ar->wmi.num_mem_chunks; + void *vaddr = NULL; - pool_size = num_units * round_up(unit_len, 4); + if (ar->wmi.num_mem_chunks == ARRAY_SIZE(ar->wmi.mem_chunks)) + return -ENOMEM; - if (!pool_size) - return -EINVAL; + while (!vaddr && num_units) { + pool_size = num_units * round_up(unit_len, 4); + if (!pool_size) + return -EINVAL; - ar->wmi.mem_chunks[idx].vaddr = dma_alloc_coherent(ar->dev, - pool_size, - &paddr, - GFP_KERNEL); - if (!ar->wmi.mem_chunks[idx].vaddr) { - ath10k_warn(ar, "failed to allocate memory chunk\n"); - return -ENOMEM; + vaddr = kzalloc(pool_size, GFP_KERNEL | __GFP_NOWARN); + if (!vaddr) + num_units /= 2; } - memset(ar->wmi.mem_chunks[idx].vaddr, 0, pool_size); + if (!num_units) + return -ENOMEM; + paddr = dma_map_single(ar->dev, vaddr, pool_size, DMA_TO_DEVICE); + if (dma_mapping_error(ar->dev, paddr)) { + kfree(vaddr); + return -ENOMEM; + } + + ar->wmi.mem_chunks[idx].vaddr = vaddr; ar->wmi.mem_chunks[idx].paddr = paddr; ar->wmi.mem_chunks[idx].len = pool_size; ar->wmi.mem_chunks[idx].req_id = req_id; ar->wmi.num_mem_chunks++; + return num_units; +} + +static int ath10k_wmi_alloc_host_mem(struct ath10k *ar, u32 req_id, + u32 num_units, u32 unit_len) +{ + int ret; + + while (num_units) { + ret = ath10k_wmi_alloc_chunk(ar, req_id, num_units, unit_len); + if (ret < 0) + return ret; + + num_units -= ret; + } + return 0; } @@ -4450,10 +4794,6 @@ static void ath10k_wmi_event_service_ready_work(struct work_struct *work) ath10k_dbg_dump(ar, ATH10K_DBG_WMI, NULL, "wmi svc: ", arg.service_map, arg.service_map_len); - /* only manually set fw features when not using FW IE format */ - if (ar->fw_api == 1 && ar->fw_version_build > 636) - set_bit(ATH10K_FW_FEATURE_EXT_WMI_MGMT_RX, ar->fw_features); - if (ar->num_rf_chains > ar->max_spatial_stream) { ath10k_warn(ar, "hardware advertises support for more spatial streams than it should (%d > %d)\n", ar->num_rf_chains, ar->max_spatial_stream); @@ -4483,10 +4823,16 @@ static void ath10k_wmi_event_service_ready_work(struct work_struct *work) } if (test_bit(WMI_SERVICE_PEER_CACHING, ar->wmi.svc_map)) { + if (test_bit(ATH10K_FW_FEATURE_PEER_FLOW_CONTROL, + ar->running_fw->fw_file.fw_features)) + ar->num_active_peers = TARGET_10_4_QCACHE_ACTIVE_PEERS_PFC + + ar->max_num_vdevs; + else + ar->num_active_peers = TARGET_10_4_QCACHE_ACTIVE_PEERS + + ar->max_num_vdevs; + ar->max_num_peers = TARGET_10_4_NUM_QCACHE_PEERS_MAX + - TARGET_10_4_NUM_VDEVS; - ar->num_active_peers = TARGET_10_4_QCACHE_ACTIVE_PEERS + - TARGET_10_4_NUM_VDEVS; + ar->max_num_vdevs; ar->num_tids = ar->num_active_peers * 2; ar->max_num_stations = TARGET_10_4_NUM_QCACHE_PEERS_MAX; } @@ -4600,6 +4946,50 @@ static int ath10k_wmi_op_pull_roam_ev(struct ath10k *ar, struct sk_buff *skb, return 0; } +static int ath10k_wmi_op_pull_echo_ev(struct ath10k *ar, + struct sk_buff *skb, + struct wmi_echo_ev_arg *arg) +{ + struct wmi_echo_event *ev = (void *)skb->data; + + arg->value = ev->value; + + return 0; +} + +void +ath10k_generate_mac_addr_auto(struct ath10k *ar, struct wmi_rdy_ev_arg *arg) +{ + unsigned int soc_serial_num; + u8 bdata_mac_addr[ETH_ALEN]; + u8 udef_mac_addr[] = {0x00, 0x0A, 0xF5, 0x00, 0x00, 0x00}; + + soc_serial_num = socinfo_get_serial_number(); + if (!soc_serial_num) + return; + + if (arg->mac_addr) { + ether_addr_copy(ar->base_mac_addr, arg->mac_addr); + ether_addr_copy(bdata_mac_addr, arg->mac_addr); + soc_serial_num &= 0x00ffffff; + bdata_mac_addr[3] = (soc_serial_num >> 16) & 0xff; + bdata_mac_addr[4] = (soc_serial_num >> 8) & 0xff; + bdata_mac_addr[5] = soc_serial_num & 0xff; + ether_addr_copy(ar->mac_addr, bdata_mac_addr); + } else { + /* If mac address not encoded in wlan board data, + * Auto-generate mac address using device serial + * number and user defined mac address 'udef_mac_addr'. + */ + udef_mac_addr[3] = (soc_serial_num >> 16) & 0xff; + udef_mac_addr[4] = (soc_serial_num >> 8) & 0xff; + udef_mac_addr[5] = soc_serial_num & 0xff; + ether_addr_copy(ar->base_mac_addr, udef_mac_addr); + udef_mac_addr[2] = (soc_serial_num >> 24) & 0xff; + ether_addr_copy(ar->mac_addr, udef_mac_addr); + } +} + int ath10k_wmi_event_ready(struct ath10k *ar, struct sk_buff *skb) { struct wmi_rdy_ev_arg arg = {}; @@ -4618,7 +5008,11 @@ int ath10k_wmi_event_ready(struct ath10k *ar, struct sk_buff *skb) arg.mac_addr, __le32_to_cpu(arg.status)); - ether_addr_copy(ar->mac_addr, arg.mac_addr); + if (QCA_REV_WCN3990(ar)) + ath10k_generate_mac_addr_auto(ar, &arg); + else + ether_addr_copy(ar->mac_addr, arg.mac_addr); + complete(&ar->wmi.unified_ready); return 0; } @@ -4635,6 +5029,58 @@ static int ath10k_wmi_event_temperature(struct ath10k *ar, struct sk_buff *skb) return 0; } +static int ath10k_wmi_event_pdev_bss_chan_info(struct ath10k *ar, + struct sk_buff *skb) +{ + struct wmi_pdev_bss_chan_info_event *ev; + struct survey_info *survey; + u64 busy, total, tx, rx, rx_bss; + u32 freq, noise_floor; + u32 cc_freq_hz = ar->hw_params.channel_counters_freq_hz; + int idx; + + ev = (struct wmi_pdev_bss_chan_info_event *)skb->data; + if (WARN_ON(skb->len < sizeof(*ev))) + return -EPROTO; + + freq = __le32_to_cpu(ev->freq); + noise_floor = __le32_to_cpu(ev->noise_floor); + busy = __le64_to_cpu(ev->cycle_busy); + total = __le64_to_cpu(ev->cycle_total); + tx = __le64_to_cpu(ev->cycle_tx); + rx = __le64_to_cpu(ev->cycle_rx); + rx_bss = __le64_to_cpu(ev->cycle_rx_bss); + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi event pdev bss chan info:\n freq: %d noise: %d cycle: busy %llu total %llu tx %llu rx %llu rx_bss %llu\n", + freq, noise_floor, busy, total, tx, rx, rx_bss); + + spin_lock_bh(&ar->data_lock); + idx = freq_to_idx(ar, freq); + if (idx >= ARRAY_SIZE(ar->survey)) { + ath10k_warn(ar, "bss chan info: invalid frequency %d (idx %d out of bounds)\n", + freq, idx); + goto exit; + } + + survey = &ar->survey[idx]; + + survey->noise = noise_floor; + survey->time = div_u64(total, cc_freq_hz); + survey->time_busy = div_u64(busy, cc_freq_hz); + survey->time_rx = div_u64(rx_bss, cc_freq_hz); + survey->time_tx = div_u64(tx, cc_freq_hz); + survey->filled |= (SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_TIME | + SURVEY_INFO_TIME_BUSY | + SURVEY_INFO_TIME_RX | + SURVEY_INFO_TIME_TX); +exit: + spin_unlock_bh(&ar->data_lock); + complete(&ar->bss_survey_done); + return 0; +} + static void ath10k_wmi_op_rx(struct ath10k *ar, struct sk_buff *skb) { struct wmi_cmd_hdr *cmd_hdr; @@ -4880,6 +5326,7 @@ static void ath10k_wmi_10_2_op_rx(struct ath10k *ar, struct sk_buff *skb) { struct wmi_cmd_hdr *cmd_hdr; enum wmi_10_2_event_id id; + bool consumed; cmd_hdr = (struct wmi_cmd_hdr *)skb->data; id = MS(__le32_to_cpu(cmd_hdr->cmd_id), WMI_CMD_HDR_CMD_ID); @@ -4889,6 +5336,18 @@ static void ath10k_wmi_10_2_op_rx(struct ath10k *ar, struct sk_buff *skb) trace_ath10k_wmi_event(ar, id, skb->data, skb->len); + consumed = ath10k_tm_event_wmi(ar, id, skb); + + /* Ready event must be handled normally also in UTF mode so that we + * know the UTF firmware has booted, others we are just bypass WMI + * events to testmode. + */ + if (consumed && id != WMI_10_2_READY_EVENTID) { + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi testmode consumed 0x%x\n", id); + goto out; + } + switch (id) { case WMI_10_2_MGMT_RX_EVENTID: ath10k_wmi_event_mgmt_rx(ar, skb); @@ -4978,6 +5437,9 @@ static void ath10k_wmi_10_2_op_rx(struct ath10k *ar, struct sk_buff *skb) case WMI_10_2_PDEV_TEMPERATURE_EVENTID: ath10k_wmi_event_temperature(ar, skb); break; + case WMI_10_2_PDEV_BSS_CHAN_INFO_EVENTID: + ath10k_wmi_event_pdev_bss_chan_info(ar, skb); + break; case WMI_10_2_RTT_KEEPALIVE_EVENTID: case WMI_10_2_GPIO_INPUT_EVENTID: case WMI_10_2_PEER_RATECODE_LIST_EVENTID: @@ -5001,6 +5463,7 @@ static void ath10k_wmi_10_4_op_rx(struct ath10k *ar, struct sk_buff *skb) { struct wmi_cmd_hdr *cmd_hdr; enum wmi_10_4_event_id id; + bool consumed; cmd_hdr = (struct wmi_cmd_hdr *)skb->data; id = MS(__le32_to_cpu(cmd_hdr->cmd_id), WMI_CMD_HDR_CMD_ID); @@ -5010,6 +5473,18 @@ static void ath10k_wmi_10_4_op_rx(struct ath10k *ar, struct sk_buff *skb) trace_ath10k_wmi_event(ar, id, skb->data, skb->len); + consumed = ath10k_tm_event_wmi(ar, id, skb); + + /* Ready event must be handled normally also in UTF mode so that we + * know the UTF firmware has booted, others we are just bypass WMI + * events to testmode. + */ + if (consumed && id != WMI_10_4_READY_EVENTID) { + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi testmode consumed 0x%x\n", id); + goto out; + } + switch (id) { case WMI_10_4_MGMT_RX_EVENTID: ath10k_wmi_event_mgmt_rx(ar, skb); @@ -5039,6 +5514,9 @@ static void ath10k_wmi_10_4_op_rx(struct ath10k *ar, struct sk_buff *skb) case WMI_10_4_PEER_STA_KICKOUT_EVENTID: ath10k_wmi_event_peer_sta_kickout(ar, skb); break; + case WMI_10_4_ROAM_EVENTID: + ath10k_wmi_event_roam(ar, skb); + break; case WMI_10_4_HOST_SWBA_EVENTID: ath10k_wmi_event_host_swba(ar, skb); break; @@ -5055,12 +5533,20 @@ static void ath10k_wmi_10_4_op_rx(struct ath10k *ar, struct sk_buff *skb) ath10k_wmi_event_vdev_stopped(ar, skb); break; case WMI_10_4_WOW_WAKEUP_HOST_EVENTID: + case WMI_10_4_PEER_RATECODE_LIST_EVENTID: + case WMI_10_4_WDS_PEER_EVENTID: ath10k_dbg(ar, ATH10K_DBG_WMI, "received event id %d not implemented\n", id); break; case WMI_10_4_UPDATE_STATS_EVENTID: ath10k_wmi_event_update_stats(ar, skb); break; + case WMI_10_4_PDEV_TEMPERATURE_EVENTID: + ath10k_wmi_event_temperature(ar, skb); + break; + case WMI_10_4_PDEV_BSS_CHAN_INFO_EVENTID: + ath10k_wmi_event_pdev_bss_chan_info(ar, skb); + break; default: ath10k_warn(ar, "Unknown eventid: %d\n", id); break; @@ -5379,9 +5865,16 @@ static struct sk_buff *ath10k_wmi_10_2_op_gen_init(struct ath10k *ar) u32 len, val, features; config.num_vdevs = __cpu_to_le32(TARGET_10X_NUM_VDEVS); - config.num_peers = __cpu_to_le32(TARGET_10X_NUM_PEERS); config.num_peer_keys = __cpu_to_le32(TARGET_10X_NUM_PEER_KEYS); - config.num_tids = __cpu_to_le32(TARGET_10X_NUM_TIDS); + + if (ath10k_peer_stats_enabled(ar)) { + config.num_peers = __cpu_to_le32(TARGET_10X_TX_STATS_NUM_PEERS); + config.num_tids = __cpu_to_le32(TARGET_10X_TX_STATS_NUM_TIDS); + } else { + config.num_peers = __cpu_to_le32(TARGET_10X_NUM_PEERS); + config.num_tids = __cpu_to_le32(TARGET_10X_NUM_TIDS); + } + config.ast_skid_limit = __cpu_to_le32(TARGET_10X_AST_SKID_LIMIT); config.tx_chain_mask = __cpu_to_le32(TARGET_10X_TX_CHAIN_MASK); config.rx_chain_mask = __cpu_to_le32(TARGET_10X_RX_CHAIN_MASK); @@ -5431,8 +5924,17 @@ static struct sk_buff *ath10k_wmi_10_2_op_gen_init(struct ath10k *ar) cmd = (struct wmi_init_cmd_10_2 *)buf->data; features = WMI_10_2_RX_BATCH_MODE; - if (test_bit(WMI_SERVICE_COEX_GPIO, ar->wmi.svc_map)) + + if (test_bit(ATH10K_FLAG_BTCOEX, &ar->dev_flags) && + test_bit(WMI_SERVICE_COEX_GPIO, ar->wmi.svc_map)) features |= WMI_10_2_COEX_GPIO; + + if (ath10k_peer_stats_enabled(ar)) + features |= WMI_10_2_PEER_STATS; + + if (test_bit(WMI_SERVICE_BSS_CHANNEL_INFO_64, ar->wmi.svc_map)) + features |= WMI_10_2_BSS_CHAN_INFO; + cmd->resource_config.feature_mask = __cpu_to_le32(features); memcpy(&cmd->resource_config.common, &config, sizeof(config)); @@ -5459,8 +5961,8 @@ static struct sk_buff *ath10k_wmi_10_4_op_gen_init(struct ath10k *ar) __cpu_to_le32(TARGET_10_4_NUM_OFFLOAD_REORDER_BUFFS); config.num_peer_keys = __cpu_to_le32(TARGET_10_4_NUM_PEER_KEYS); config.ast_skid_limit = __cpu_to_le32(TARGET_10_4_AST_SKID_LIMIT); - config.tx_chain_mask = __cpu_to_le32(TARGET_10_4_TX_CHAIN_MASK); - config.rx_chain_mask = __cpu_to_le32(TARGET_10_4_RX_CHAIN_MASK); + config.tx_chain_mask = __cpu_to_le32(ar->hw_params.tx_chain_mask); + config.rx_chain_mask = __cpu_to_le32(ar->hw_params.rx_chain_mask); config.rx_timeout_pri[0] = __cpu_to_le32(TARGET_10_4_RX_TIMEOUT_LO_PRI); config.rx_timeout_pri[1] = __cpu_to_le32(TARGET_10_4_RX_TIMEOUT_LO_PRI); @@ -5491,7 +5993,7 @@ static struct sk_buff *ath10k_wmi_10_4_op_gen_init(struct ath10k *ar) config.vow_config = __cpu_to_le32(TARGET_10_4_VOW_CONFIG); config.gtk_offload_max_vdev = __cpu_to_le32(TARGET_10_4_GTK_OFFLOAD_MAX_VDEV); - config.num_msdu_desc = __cpu_to_le32(TARGET_10_4_NUM_MSDU_DESC); + config.num_msdu_desc = __cpu_to_le32(ar->htt.max_num_pending_tx); config.max_frag_entries = __cpu_to_le32(TARGET_10_4_11AC_TX_MAX_FRAGS); config.max_peer_ext_stats = __cpu_to_le32(TARGET_10_4_MAX_PEER_EXT_STATS); @@ -5651,9 +6153,8 @@ ath10k_wmi_put_start_scan_tlvs(struct wmi_start_scan_tlvs *tlvs, bssids->num_bssid = __cpu_to_le32(arg->n_bssids); for (i = 0; i < arg->n_bssids; i++) - memcpy(&bssids->bssid_list[i], - arg->bssids[i].bssid, - ETH_ALEN); + ether_addr_copy(bssids->bssid_list[i].addr, + arg->bssids[i].bssid); ptr += sizeof(*bssids); ptr += sizeof(struct wmi_mac_addr) * arg->n_bssids; @@ -5746,6 +6247,8 @@ void ath10k_wmi_start_scan_init(struct ath10k *ar, | WMI_SCAN_EVENT_BSS_CHANNEL | WMI_SCAN_EVENT_FOREIGN_CHANNEL | WMI_SCAN_EVENT_DEQUEUED; + if (QCA_REV_WCN3990(ar)) + arg->scan_ctrl_flags = ar->fw_flags->flags; arg->scan_ctrl_flags |= WMI_SCAN_CHAN_STAT_EVENT; arg->n_bssids = 1; arg->bssids[0].bssid = "\xFF\xFF\xFF\xFF\xFF\xFF"; @@ -6328,6 +6831,16 @@ ath10k_wmi_peer_assoc_fill_10_2(struct ath10k *ar, void *buf, cmd->info0 = __cpu_to_le32(info0); } +static void +ath10k_wmi_peer_assoc_fill_10_4(struct ath10k *ar, void *buf, + const struct wmi_peer_assoc_complete_arg *arg) +{ + struct wmi_10_4_peer_assoc_complete_cmd *cmd = buf; + + ath10k_wmi_peer_assoc_fill_10_2(ar, buf, arg); + cmd->peer_bw_rxnss_override = 0; +} + static int ath10k_wmi_peer_assoc_check_arg(const struct wmi_peer_assoc_complete_arg *arg) { @@ -6417,6 +6930,31 @@ ath10k_wmi_10_2_op_gen_peer_assoc(struct ath10k *ar, } static struct sk_buff * +ath10k_wmi_10_4_op_gen_peer_assoc(struct ath10k *ar, + const struct wmi_peer_assoc_complete_arg *arg) +{ + size_t len = sizeof(struct wmi_10_4_peer_assoc_complete_cmd); + struct sk_buff *skb; + int ret; + + ret = ath10k_wmi_peer_assoc_check_arg(arg); + if (ret) + return ERR_PTR(ret); + + skb = ath10k_wmi_alloc_skb(ar, len); + if (!skb) + return ERR_PTR(-ENOMEM); + + ath10k_wmi_peer_assoc_fill_10_4(ar, skb->data, arg); + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi peer assoc vdev %d addr %pM (%s)\n", + arg->vdev_id, arg->addr, + arg->peer_reassoc ? "reassociate" : "new"); + return skb; +} + +static struct sk_buff * ath10k_wmi_10_2_op_gen_pdev_get_temperature(struct ath10k *ar) { struct sk_buff *skb; @@ -6429,6 +6967,26 @@ ath10k_wmi_10_2_op_gen_pdev_get_temperature(struct ath10k *ar) return skb; } +static struct sk_buff * +ath10k_wmi_10_2_op_gen_pdev_bss_chan_info(struct ath10k *ar, + enum wmi_bss_survey_req_type type) +{ + struct wmi_pdev_chan_info_req_cmd *cmd; + struct sk_buff *skb; + + skb = ath10k_wmi_alloc_skb(ar, sizeof(*cmd)); + if (!skb) + return ERR_PTR(-ENOMEM); + + cmd = (struct wmi_pdev_chan_info_req_cmd *)skb->data; + cmd->type = __cpu_to_le32(type); + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi pdev bss info request type %d\n", type); + + return skb; +} + /* This function assumes the beacon is already DMA mapped */ static struct sk_buff * ath10k_wmi_op_gen_beacon_dma(struct ath10k *ar, u32 vdev_id, const void *bcn, @@ -6536,7 +7094,7 @@ ath10k_wmi_op_gen_force_fw_hang(struct ath10k *ar, } static struct sk_buff * -ath10k_wmi_op_gen_dbglog_cfg(struct ath10k *ar, u32 module_enable, +ath10k_wmi_op_gen_dbglog_cfg(struct ath10k *ar, u64 module_enable, u32 log_level) { struct wmi_dbglog_cfg_cmd *cmd; @@ -6574,6 +7132,44 @@ ath10k_wmi_op_gen_dbglog_cfg(struct ath10k *ar, u32 module_enable, } static struct sk_buff * +ath10k_wmi_10_4_op_gen_dbglog_cfg(struct ath10k *ar, u64 module_enable, + u32 log_level) +{ + struct wmi_10_4_dbglog_cfg_cmd *cmd; + struct sk_buff *skb; + u32 cfg; + + skb = ath10k_wmi_alloc_skb(ar, sizeof(*cmd)); + if (!skb) + return ERR_PTR(-ENOMEM); + + cmd = (struct wmi_10_4_dbglog_cfg_cmd *)skb->data; + + if (module_enable) { + cfg = SM(log_level, + ATH10K_DBGLOG_CFG_LOG_LVL); + } else { + /* set back defaults, all modules with WARN level */ + cfg = SM(ATH10K_DBGLOG_LEVEL_WARN, + ATH10K_DBGLOG_CFG_LOG_LVL); + module_enable = ~0; + } + + cmd->module_enable = __cpu_to_le64(module_enable); + cmd->module_valid = __cpu_to_le64(~0); + cmd->config_enable = __cpu_to_le32(cfg); + cmd->config_valid = __cpu_to_le32(ATH10K_DBGLOG_CFG_LOG_LVL_MASK); + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi dbglog cfg modules 0x%016llx 0x%016llx config %08x %08x\n", + __le64_to_cpu(cmd->module_enable), + __le64_to_cpu(cmd->module_valid), + __le32_to_cpu(cmd->config_enable), + __le32_to_cpu(cmd->config_valid)); + return skb; +} + +static struct sk_buff * ath10k_wmi_op_gen_pktlog_enable(struct ath10k *ar, u32 ev_bitmap) { struct wmi_pdev_pktlog_enable_cmd *cmd; @@ -7007,6 +7603,9 @@ ath10k_wmi_fw_peer_stats_fill(const struct ath10k_fw_stats_peer *peer, "Peer TX rate", peer->peer_tx_rate); len += scnprintf(buf + len, buf_len - len, "%30s %u\n", "Peer RX rate", peer->peer_rx_rate); + len += scnprintf(buf + len, buf_len - len, "%30s %u\n", + "Peer RX duration", peer->rx_duration); + len += scnprintf(buf + len, buf_len - len, "\n"); *length = len; } @@ -7232,6 +7831,135 @@ unlock: buf[len] = 0; } +int ath10k_wmi_op_get_vdev_subtype(struct ath10k *ar, + enum wmi_vdev_subtype subtype) +{ + switch (subtype) { + case WMI_VDEV_SUBTYPE_NONE: + return WMI_VDEV_SUBTYPE_LEGACY_NONE; + case WMI_VDEV_SUBTYPE_P2P_DEVICE: + return WMI_VDEV_SUBTYPE_LEGACY_P2P_DEV; + case WMI_VDEV_SUBTYPE_P2P_CLIENT: + return WMI_VDEV_SUBTYPE_LEGACY_P2P_CLI; + case WMI_VDEV_SUBTYPE_P2P_GO: + return WMI_VDEV_SUBTYPE_LEGACY_P2P_GO; + case WMI_VDEV_SUBTYPE_PROXY_STA: + return WMI_VDEV_SUBTYPE_LEGACY_PROXY_STA; + case WMI_VDEV_SUBTYPE_MESH_11S: + case WMI_VDEV_SUBTYPE_MESH_NON_11S: + return -ENOTSUPP; + } + return -ENOTSUPP; +} + +static int ath10k_wmi_10_2_4_op_get_vdev_subtype(struct ath10k *ar, + enum wmi_vdev_subtype subtype) +{ + switch (subtype) { + case WMI_VDEV_SUBTYPE_NONE: + return WMI_VDEV_SUBTYPE_10_2_4_NONE; + case WMI_VDEV_SUBTYPE_P2P_DEVICE: + return WMI_VDEV_SUBTYPE_10_2_4_P2P_DEV; + case WMI_VDEV_SUBTYPE_P2P_CLIENT: + return WMI_VDEV_SUBTYPE_10_2_4_P2P_CLI; + case WMI_VDEV_SUBTYPE_P2P_GO: + return WMI_VDEV_SUBTYPE_10_2_4_P2P_GO; + case WMI_VDEV_SUBTYPE_PROXY_STA: + return WMI_VDEV_SUBTYPE_10_2_4_PROXY_STA; + case WMI_VDEV_SUBTYPE_MESH_11S: + return WMI_VDEV_SUBTYPE_10_2_4_MESH_11S; + case WMI_VDEV_SUBTYPE_MESH_NON_11S: + return -ENOTSUPP; + } + return -ENOTSUPP; +} + +static int ath10k_wmi_10_4_op_get_vdev_subtype(struct ath10k *ar, + enum wmi_vdev_subtype subtype) +{ + switch (subtype) { + case WMI_VDEV_SUBTYPE_NONE: + return WMI_VDEV_SUBTYPE_10_4_NONE; + case WMI_VDEV_SUBTYPE_P2P_DEVICE: + return WMI_VDEV_SUBTYPE_10_4_P2P_DEV; + case WMI_VDEV_SUBTYPE_P2P_CLIENT: + return WMI_VDEV_SUBTYPE_10_4_P2P_CLI; + case WMI_VDEV_SUBTYPE_P2P_GO: + return WMI_VDEV_SUBTYPE_10_4_P2P_GO; + case WMI_VDEV_SUBTYPE_PROXY_STA: + return WMI_VDEV_SUBTYPE_10_4_PROXY_STA; + case WMI_VDEV_SUBTYPE_MESH_11S: + return WMI_VDEV_SUBTYPE_10_4_MESH_11S; + case WMI_VDEV_SUBTYPE_MESH_NON_11S: + return WMI_VDEV_SUBTYPE_10_4_MESH_NON_11S; + } + return -ENOTSUPP; +} + +static struct sk_buff * +ath10k_wmi_10_4_ext_resource_config(struct ath10k *ar, + enum wmi_host_platform_type type, + u32 fw_feature_bitmap) +{ + struct wmi_ext_resource_config_10_4_cmd *cmd; + struct sk_buff *skb; + + skb = ath10k_wmi_alloc_skb(ar, sizeof(*cmd)); + if (!skb) + return ERR_PTR(-ENOMEM); + + cmd = (struct wmi_ext_resource_config_10_4_cmd *)skb->data; + cmd->host_platform_config = __cpu_to_le32(type); + cmd->fw_feature_bitmap = __cpu_to_le32(fw_feature_bitmap); + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi ext resource config host type %d firmware feature bitmap %08x\n", + type, fw_feature_bitmap); + return skb; +} + +static struct sk_buff * +ath10k_wmi_op_gen_echo(struct ath10k *ar, u32 value) +{ + struct wmi_echo_cmd *cmd; + struct sk_buff *skb; + + skb = ath10k_wmi_alloc_skb(ar, sizeof(*cmd)); + if (!skb) + return ERR_PTR(-ENOMEM); + + cmd = (struct wmi_echo_cmd *)skb->data; + cmd->value = cpu_to_le32(value); + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "wmi echo value 0x%08x\n", value); + return skb; +} + +int +ath10k_wmi_barrier(struct ath10k *ar) +{ + int ret; + int time_left; + + spin_lock_bh(&ar->data_lock); + reinit_completion(&ar->wmi.barrier); + spin_unlock_bh(&ar->data_lock); + + ret = ath10k_wmi_echo(ar, ATH10K_WMI_BARRIER_ECHO_ID); + if (ret) { + ath10k_warn(ar, "failed to submit wmi echo: %d\n", ret); + return ret; + } + + time_left = wait_for_completion_timeout(&ar->wmi.barrier, + ATH10K_WMI_BARRIER_TIMEOUT_HZ); + if (!time_left) + return -ETIMEDOUT; + + return 0; +} + static const struct wmi_ops wmi_ops = { .rx = ath10k_wmi_op_rx, .map_svc = wmi_main_svc_map, @@ -7248,6 +7976,7 @@ static const struct wmi_ops wmi_ops = { .pull_rdy = ath10k_wmi_op_pull_rdy_ev, .pull_fw_stats = ath10k_wmi_main_op_pull_fw_stats, .pull_roam_ev = ath10k_wmi_op_pull_roam_ev, + .pull_echo_ev = ath10k_wmi_op_pull_echo_ev, .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend, .gen_pdev_resume = ath10k_wmi_op_gen_pdev_resume, @@ -7291,6 +8020,8 @@ static const struct wmi_ops wmi_ops = { .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp, .gen_delba_send = ath10k_wmi_op_gen_delba_send, .fw_stats_fill = ath10k_wmi_main_op_fw_stats_fill, + .get_vdev_subtype = ath10k_wmi_op_get_vdev_subtype, + .gen_echo = ath10k_wmi_op_gen_echo, /* .gen_bcn_tmpl not implemented */ /* .gen_prb_tmpl not implemented */ /* .gen_p2p_go_bcn_ie not implemented */ @@ -7320,6 +8051,7 @@ static const struct wmi_ops wmi_10_1_ops = { .pull_phyerr = ath10k_wmi_op_pull_phyerr_ev, .pull_rdy = ath10k_wmi_op_pull_rdy_ev, .pull_roam_ev = ath10k_wmi_op_pull_roam_ev, + .pull_echo_ev = ath10k_wmi_op_pull_echo_ev, .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend, .gen_pdev_resume = ath10k_wmi_op_gen_pdev_resume, @@ -7358,6 +8090,8 @@ static const struct wmi_ops wmi_10_1_ops = { .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp, .gen_delba_send = ath10k_wmi_op_gen_delba_send, .fw_stats_fill = ath10k_wmi_10x_op_fw_stats_fill, + .get_vdev_subtype = ath10k_wmi_op_get_vdev_subtype, + .gen_echo = ath10k_wmi_op_gen_echo, /* .gen_bcn_tmpl not implemented */ /* .gen_prb_tmpl not implemented */ /* .gen_p2p_go_bcn_ie not implemented */ @@ -7377,6 +8111,7 @@ static const struct wmi_ops wmi_10_2_ops = { .pull_svc_rdy = ath10k_wmi_10x_op_pull_svc_rdy_ev, .gen_pdev_set_rd = ath10k_wmi_10x_op_gen_pdev_set_rd, .gen_start_scan = ath10k_wmi_10x_op_gen_start_scan, + .gen_echo = ath10k_wmi_op_gen_echo, .pull_scan = ath10k_wmi_op_pull_scan_ev, .pull_mgmt_rx = ath10k_wmi_op_pull_mgmt_rx_ev, @@ -7388,6 +8123,7 @@ static const struct wmi_ops wmi_10_2_ops = { .pull_phyerr = ath10k_wmi_op_pull_phyerr_ev, .pull_rdy = ath10k_wmi_op_pull_rdy_ev, .pull_roam_ev = ath10k_wmi_op_pull_roam_ev, + .pull_echo_ev = ath10k_wmi_op_pull_echo_ev, .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend, .gen_pdev_resume = ath10k_wmi_op_gen_pdev_resume, @@ -7426,6 +8162,7 @@ static const struct wmi_ops wmi_10_2_ops = { .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp, .gen_delba_send = ath10k_wmi_op_gen_delba_send, .fw_stats_fill = ath10k_wmi_10x_op_fw_stats_fill, + .get_vdev_subtype = ath10k_wmi_op_get_vdev_subtype, /* .gen_pdev_enable_adaptive_cca not implemented */ }; @@ -7435,23 +8172,26 @@ static const struct wmi_ops wmi_10_2_4_ops = { .gen_init = ath10k_wmi_10_2_op_gen_init, .gen_peer_assoc = ath10k_wmi_10_2_op_gen_peer_assoc, .gen_pdev_get_temperature = ath10k_wmi_10_2_op_gen_pdev_get_temperature, + .gen_pdev_bss_chan_info_req = ath10k_wmi_10_2_op_gen_pdev_bss_chan_info, /* shared with 10.1 */ .map_svc = wmi_10x_svc_map, .pull_svc_rdy = ath10k_wmi_10x_op_pull_svc_rdy_ev, .gen_pdev_set_rd = ath10k_wmi_10x_op_gen_pdev_set_rd, .gen_start_scan = ath10k_wmi_10x_op_gen_start_scan, + .gen_echo = ath10k_wmi_op_gen_echo, .pull_scan = ath10k_wmi_op_pull_scan_ev, .pull_mgmt_rx = ath10k_wmi_op_pull_mgmt_rx_ev, .pull_ch_info = ath10k_wmi_op_pull_ch_info_ev, .pull_vdev_start = ath10k_wmi_op_pull_vdev_start_ev, .pull_peer_kick = ath10k_wmi_op_pull_peer_kick_ev, - .pull_swba = ath10k_wmi_op_pull_swba_ev, + .pull_swba = ath10k_wmi_10_2_4_op_pull_swba_ev, .pull_phyerr_hdr = ath10k_wmi_op_pull_phyerr_ev_hdr, .pull_phyerr = ath10k_wmi_op_pull_phyerr_ev, .pull_rdy = ath10k_wmi_op_pull_rdy_ev, .pull_roam_ev = ath10k_wmi_op_pull_roam_ev, + .pull_echo_ev = ath10k_wmi_op_pull_echo_ev, .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend, .gen_pdev_resume = ath10k_wmi_op_gen_pdev_resume, @@ -7492,6 +8232,7 @@ static const struct wmi_ops wmi_10_2_4_ops = { .fw_stats_fill = ath10k_wmi_10x_op_fw_stats_fill, .gen_pdev_enable_adaptive_cca = ath10k_wmi_op_gen_pdev_enable_adaptive_cca, + .get_vdev_subtype = ath10k_wmi_10_2_4_op_get_vdev_subtype, /* .gen_bcn_tmpl not implemented */ /* .gen_prb_tmpl not implemented */ /* .gen_p2p_go_bcn_ie not implemented */ @@ -7513,6 +8254,7 @@ static const struct wmi_ops wmi_10_4_ops = { .pull_phyerr = ath10k_wmi_10_4_op_pull_phyerr_ev, .pull_svc_rdy = ath10k_wmi_main_op_pull_svc_rdy_ev, .pull_rdy = ath10k_wmi_op_pull_rdy_ev, + .pull_roam_ev = ath10k_wmi_op_pull_roam_ev, .get_txbf_conf_scheme = ath10k_wmi_10_4_txbf_conf_scheme, .gen_pdev_suspend = ath10k_wmi_op_gen_pdev_suspend, @@ -7536,6 +8278,7 @@ static const struct wmi_ops wmi_10_4_ops = { .gen_peer_delete = ath10k_wmi_op_gen_peer_delete, .gen_peer_flush = ath10k_wmi_op_gen_peer_flush, .gen_peer_set_param = ath10k_wmi_op_gen_peer_set_param, + .gen_peer_assoc = ath10k_wmi_10_4_op_gen_peer_assoc, .gen_set_psmode = ath10k_wmi_op_gen_set_psmode, .gen_set_sta_ps = ath10k_wmi_op_gen_set_sta_ps, .gen_set_ap_ps = ath10k_wmi_op_gen_set_ap_ps, @@ -7544,7 +8287,7 @@ static const struct wmi_ops wmi_10_4_ops = { .gen_pdev_set_wmm = ath10k_wmi_op_gen_pdev_set_wmm, .gen_force_fw_hang = ath10k_wmi_op_gen_force_fw_hang, .gen_mgmt_tx = ath10k_wmi_op_gen_mgmt_tx, - .gen_dbglog_cfg = ath10k_wmi_op_gen_dbglog_cfg, + .gen_dbglog_cfg = ath10k_wmi_10_4_op_gen_dbglog_cfg, .gen_pktlog_enable = ath10k_wmi_op_gen_pktlog_enable, .gen_pktlog_disable = ath10k_wmi_op_gen_pktlog_disable, .gen_pdev_set_quiet_mode = ath10k_wmi_op_gen_pdev_set_quiet_mode, @@ -7553,44 +8296,54 @@ static const struct wmi_ops wmi_10_4_ops = { .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp, .gen_delba_send = ath10k_wmi_op_gen_delba_send, .fw_stats_fill = ath10k_wmi_10_4_op_fw_stats_fill, + .ext_resource_config = ath10k_wmi_10_4_ext_resource_config, /* shared with 10.2 */ - .gen_peer_assoc = ath10k_wmi_10_2_op_gen_peer_assoc, + .pull_echo_ev = ath10k_wmi_op_pull_echo_ev, .gen_request_stats = ath10k_wmi_op_gen_request_stats, + .gen_pdev_get_temperature = ath10k_wmi_10_2_op_gen_pdev_get_temperature, + .get_vdev_subtype = ath10k_wmi_10_4_op_get_vdev_subtype, + .gen_pdev_bss_chan_info_req = ath10k_wmi_10_2_op_gen_pdev_bss_chan_info, + .gen_echo = ath10k_wmi_op_gen_echo, }; int ath10k_wmi_attach(struct ath10k *ar) { - switch (ar->wmi.op_version) { + switch (ar->running_fw->fw_file.wmi_op_version) { case ATH10K_FW_WMI_OP_VERSION_10_4: ar->wmi.ops = &wmi_10_4_ops; ar->wmi.cmd = &wmi_10_4_cmd_map; ar->wmi.vdev_param = &wmi_10_4_vdev_param_map; ar->wmi.pdev_param = &wmi_10_4_pdev_param_map; + ar->wmi.peer_flags = &wmi_10_2_peer_flags_map; break; case ATH10K_FW_WMI_OP_VERSION_10_2_4: ar->wmi.cmd = &wmi_10_2_4_cmd_map; ar->wmi.ops = &wmi_10_2_4_ops; ar->wmi.vdev_param = &wmi_10_2_4_vdev_param_map; ar->wmi.pdev_param = &wmi_10_2_4_pdev_param_map; + ar->wmi.peer_flags = &wmi_10_2_peer_flags_map; break; case ATH10K_FW_WMI_OP_VERSION_10_2: ar->wmi.cmd = &wmi_10_2_cmd_map; ar->wmi.ops = &wmi_10_2_ops; ar->wmi.vdev_param = &wmi_10x_vdev_param_map; ar->wmi.pdev_param = &wmi_10x_pdev_param_map; + ar->wmi.peer_flags = &wmi_10_2_peer_flags_map; break; case ATH10K_FW_WMI_OP_VERSION_10_1: ar->wmi.cmd = &wmi_10x_cmd_map; ar->wmi.ops = &wmi_10_1_ops; ar->wmi.vdev_param = &wmi_10x_vdev_param_map; ar->wmi.pdev_param = &wmi_10x_pdev_param_map; + ar->wmi.peer_flags = &wmi_10x_peer_flags_map; break; case ATH10K_FW_WMI_OP_VERSION_MAIN: ar->wmi.cmd = &wmi_cmd_map; ar->wmi.ops = &wmi_ops; ar->wmi.vdev_param = &wmi_vdev_param_map; ar->wmi.pdev_param = &wmi_pdev_param_map; + ar->wmi.peer_flags = &wmi_peer_flags_map; break; case ATH10K_FW_WMI_OP_VERSION_TLV: ath10k_wmi_tlv_attach(ar); @@ -7598,15 +8351,21 @@ int ath10k_wmi_attach(struct ath10k *ar) case ATH10K_FW_WMI_OP_VERSION_UNSET: case ATH10K_FW_WMI_OP_VERSION_MAX: ath10k_err(ar, "unsupported WMI op version: %d\n", - ar->wmi.op_version); + ar->running_fw->fw_file.wmi_op_version); return -EINVAL; } init_completion(&ar->wmi.service_ready); init_completion(&ar->wmi.unified_ready); + init_completion(&ar->wmi.barrier); INIT_WORK(&ar->svc_rdy_work, ath10k_wmi_event_service_ready_work); + if (QCA_REV_WCN3990(ar)) { + spin_lock_init(&ar->wmi.mgmt_tx_lock); + idr_init(&ar->wmi.mgmt_pending_tx); + } + return 0; } @@ -7616,17 +8375,42 @@ void ath10k_wmi_free_host_mem(struct ath10k *ar) /* free the host memory chunks requested by firmware */ for (i = 0; i < ar->wmi.num_mem_chunks; i++) { - dma_free_coherent(ar->dev, - ar->wmi.mem_chunks[i].len, - ar->wmi.mem_chunks[i].vaddr, - ar->wmi.mem_chunks[i].paddr); + dma_unmap_single(ar->dev, + ar->wmi.mem_chunks[i].paddr, + ar->wmi.mem_chunks[i].len, + DMA_TO_DEVICE); + kfree(ar->wmi.mem_chunks[i].vaddr); } ar->wmi.num_mem_chunks = 0; } +static int ath10k_wmi_mgmt_tx_clean_up_pending(int msdu_id, void *ptr, + void *ctx) +{ + struct ath10k_mgmt_tx_pkt_addr *pkt_addr = ptr; + struct ath10k *ar = ctx; + struct sk_buff *msdu; + + ath10k_dbg(ar, ATH10K_DBG_WMI, + "force cleanup mgmt msdu_id %hu\n", msdu_id); + + msdu = pkt_addr->vaddr; + dma_unmap_single(ar->dev, pkt_addr->paddr, + msdu->len, DMA_FROM_DEVICE); + ieee80211_free_txskb(ar->hw, msdu); + + return 0; +} + void ath10k_wmi_detach(struct ath10k *ar) { + if (QCA_REV_WCN3990(ar)) { + idr_for_each(&ar->wmi.mgmt_pending_tx, + ath10k_wmi_mgmt_tx_clean_up_pending, ar); + idr_destroy(&ar->wmi.mgmt_pending_tx); + } + cancel_work_sync(&ar->svc_rdy_work); if (ar->svc_rdy_skb) diff --git a/drivers/net/wireless/ath/ath10k/wmi.h b/drivers/net/wireless/ath/ath10k/wmi.h index a8b2553e8988..4892c7d3cce3 100644 --- a/drivers/net/wireless/ath/ath10k/wmi.h +++ b/drivers/net/wireless/ath/ath10k/wmi.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2005-2011 Atheros Communications Inc. - * Copyright (c) 2011-2013 Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, 2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,6 +20,9 @@ #include <linux/types.h> #include <net/mac80211.h> +#include <linux/ipv6.h> +#include <net/ipv6.h> +#include <linux/in.h> /* * This file specifies the WMI interface for the Unified Software @@ -55,7 +58,7 @@ * type. * * 6. Comment each parameter part of the WMI command/event structure by - * using the 2 stars at the begining of C comment instead of one star to + * using the 2 stars at the beginning of C comment instead of one star to * enable HTML document generation using Doxygen. * */ @@ -175,6 +178,16 @@ enum wmi_service { WMI_SERVICE_AUX_SPECTRAL_INTF, WMI_SERVICE_AUX_CHAN_LOAD_INTF, WMI_SERVICE_BSS_CHANNEL_INFO_64, + WMI_SERVICE_EXT_RES_CFG_SUPPORT, + WMI_SERVICE_MESH_11S, + WMI_SERVICE_MESH_NON_11S, + WMI_SERVICE_PEER_STATS, + WMI_SERVICE_RESTRT_CHNL_SUPPORT, + WMI_SERVICE_PERIODIC_CHAN_STAT_SUPPORT, + WMI_SERVICE_TX_MODE_PUSH_ONLY, + WMI_SERVICE_TX_MODE_PUSH_PULL, + WMI_SERVICE_TX_MODE_DYNAMIC, + WMI_SERVICE_MGMT_TX_WMI, /* keep last */ WMI_SERVICE_MAX, @@ -206,6 +219,12 @@ enum wmi_10x_service { WMI_10X_SERVICE_SMART_ANTENNA_HW_SUPPORT, WMI_10X_SERVICE_ATF, WMI_10X_SERVICE_COEX_GPIO, + WMI_10X_SERVICE_AUX_SPECTRAL_INTF, + WMI_10X_SERVICE_AUX_CHAN_LOAD_INTF, + WMI_10X_SERVICE_BSS_CHANNEL_INFO_64, + WMI_10X_SERVICE_MESH, + WMI_10X_SERVICE_EXT_RES_CFG_SUPPORT, + WMI_10X_SERVICE_PEER_STATS, }; enum wmi_main_service { @@ -286,6 +305,15 @@ enum wmi_10_4_service { WMI_10_4_SERVICE_AUX_SPECTRAL_INTF, WMI_10_4_SERVICE_AUX_CHAN_LOAD_INTF, WMI_10_4_SERVICE_BSS_CHANNEL_INFO_64, + WMI_10_4_SERVICE_EXT_RES_CFG_SUPPORT, + WMI_10_4_SERVICE_MESH_NON_11S, + WMI_10_4_SERVICE_RESTRT_CHNL_SUPPORT, + WMI_10_4_SERVICE_PEER_STATS, + WMI_10_4_SERVICE_MESH_11S, + WMI_10_4_SERVICE_PERIODIC_CHAN_STAT_SUPPORT, + WMI_10_4_SERVICE_TX_MODE_PUSH_ONLY, + WMI_10_4_SERVICE_TX_MODE_PUSH_PULL, + WMI_10_4_SERVICE_TX_MODE_DYNAMIC, }; static inline char *wmi_service_name(int service_id) @@ -375,6 +403,15 @@ static inline char *wmi_service_name(int service_id) SVCSTR(WMI_SERVICE_AUX_SPECTRAL_INTF); SVCSTR(WMI_SERVICE_AUX_CHAN_LOAD_INTF); SVCSTR(WMI_SERVICE_BSS_CHANNEL_INFO_64); + SVCSTR(WMI_SERVICE_EXT_RES_CFG_SUPPORT); + SVCSTR(WMI_SERVICE_MESH_11S); + SVCSTR(WMI_SERVICE_MESH_NON_11S); + SVCSTR(WMI_SERVICE_PEER_STATS); + SVCSTR(WMI_SERVICE_RESTRT_CHNL_SUPPORT); + SVCSTR(WMI_SERVICE_PERIODIC_CHAN_STAT_SUPPORT); + SVCSTR(WMI_SERVICE_TX_MODE_PUSH_ONLY); + SVCSTR(WMI_SERVICE_TX_MODE_PUSH_PULL); + SVCSTR(WMI_SERVICE_TX_MODE_DYNAMIC); default: return NULL; } @@ -384,8 +421,8 @@ static inline char *wmi_service_name(int service_id) #define WMI_SERVICE_IS_ENABLED(wmi_svc_bmap, svc_id, len) \ ((svc_id) < (len) && \ - __le32_to_cpu((wmi_svc_bmap)[(svc_id)/(sizeof(u32))]) & \ - BIT((svc_id)%(sizeof(u32)))) + __le32_to_cpu((wmi_svc_bmap)[(svc_id) / (sizeof(u32))]) & \ + BIT((svc_id) % (sizeof(u32)))) #define SVCMAP(x, y, len) \ do { \ @@ -442,6 +479,18 @@ static inline void wmi_10x_svc_map(const __le32 *in, unsigned long *out, WMI_SERVICE_ATF, len); SVCMAP(WMI_10X_SERVICE_COEX_GPIO, WMI_SERVICE_COEX_GPIO, len); + SVCMAP(WMI_10X_SERVICE_AUX_SPECTRAL_INTF, + WMI_SERVICE_AUX_SPECTRAL_INTF, len); + SVCMAP(WMI_10X_SERVICE_AUX_CHAN_LOAD_INTF, + WMI_SERVICE_AUX_CHAN_LOAD_INTF, len); + SVCMAP(WMI_10X_SERVICE_BSS_CHANNEL_INFO_64, + WMI_SERVICE_BSS_CHANNEL_INFO_64, len); + SVCMAP(WMI_10X_SERVICE_MESH, + WMI_SERVICE_MESH_11S, len); + SVCMAP(WMI_10X_SERVICE_EXT_RES_CFG_SUPPORT, + WMI_SERVICE_EXT_RES_CFG_SUPPORT, len); + SVCMAP(WMI_10X_SERVICE_PEER_STATS, + WMI_SERVICE_PEER_STATS, len); } static inline void wmi_main_svc_map(const __le32 *in, unsigned long *out, @@ -600,6 +649,24 @@ static inline void wmi_10_4_svc_map(const __le32 *in, unsigned long *out, WMI_SERVICE_AUX_CHAN_LOAD_INTF, len); SVCMAP(WMI_10_4_SERVICE_BSS_CHANNEL_INFO_64, WMI_SERVICE_BSS_CHANNEL_INFO_64, len); + SVCMAP(WMI_10_4_SERVICE_EXT_RES_CFG_SUPPORT, + WMI_SERVICE_EXT_RES_CFG_SUPPORT, len); + SVCMAP(WMI_10_4_SERVICE_MESH_NON_11S, + WMI_SERVICE_MESH_NON_11S, len); + SVCMAP(WMI_10_4_SERVICE_RESTRT_CHNL_SUPPORT, + WMI_SERVICE_RESTRT_CHNL_SUPPORT, len); + SVCMAP(WMI_10_4_SERVICE_PEER_STATS, + WMI_SERVICE_PEER_STATS, len); + SVCMAP(WMI_10_4_SERVICE_MESH_11S, + WMI_SERVICE_MESH_11S, len); + SVCMAP(WMI_10_4_SERVICE_PERIODIC_CHAN_STAT_SUPPORT, + WMI_SERVICE_PERIODIC_CHAN_STAT_SUPPORT, len); + SVCMAP(WMI_10_4_SERVICE_TX_MODE_PUSH_ONLY, + WMI_SERVICE_TX_MODE_PUSH_ONLY, len); + SVCMAP(WMI_10_4_SERVICE_TX_MODE_PUSH_PULL, + WMI_SERVICE_TX_MODE_PUSH_PULL, len); + SVCMAP(WMI_10_4_SERVICE_TX_MODE_DYNAMIC, + WMI_SERVICE_TX_MODE_DYNAMIC, len); } #undef SVCMAP @@ -657,6 +724,7 @@ struct wmi_cmd_map { u32 bcn_filter_rx_cmdid; u32 prb_req_filter_rx_cmdid; u32 mgmt_tx_cmdid; + u32 mgmt_tx_send_cmdid; u32 prb_tmpl_cmdid; u32 addba_clear_resp_cmdid; u32 addba_send_cmdid; @@ -773,6 +841,7 @@ struct wmi_cmd_map { u32 set_cca_params_cmdid; u32 pdev_bss_chan_info_request_cmdid; u32 pdev_enable_adaptive_cca_cmdid; + u32 ext_resource_cfg_cmdid; }; /* @@ -1265,7 +1334,7 @@ enum wmi_10x_event_id { WMI_10X_PDEV_TPC_CONFIG_EVENTID, WMI_10X_GPIO_INPUT_EVENTID, - WMI_10X_PDEV_UTF_EVENTID = WMI_10X_END_EVENTID-1, + WMI_10X_PDEV_UTF_EVENTID = WMI_10X_END_EVENTID - 1, }; enum wmi_10_2_cmd_id { @@ -1385,6 +1454,7 @@ enum wmi_10_2_cmd_id { WMI_10_2_MU_CAL_START_CMDID, WMI_10_2_SET_LTEU_CONFIG_CMDID, WMI_10_2_SET_CCA_PARAMS, + WMI_10_2_PDEV_BSS_CHAN_INFO_REQUEST_CMDID, WMI_10_2_PDEV_UTF_CMDID = WMI_10_2_END_CMDID - 1, }; @@ -1428,6 +1498,8 @@ enum wmi_10_2_event_id { WMI_10_2_WDS_PEER_EVENTID, WMI_10_2_PEER_STA_PS_STATECHG_EVENTID, WMI_10_2_PDEV_TEMPERATURE_EVENTID, + WMI_10_2_MU_REPORT_EVENTID, + WMI_10_2_PDEV_BSS_CHAN_INFO_EVENTID, WMI_10_2_PDEV_UTF_EVENTID = WMI_10_2_END_EVENTID - 1, }; @@ -1576,6 +1648,9 @@ enum wmi_10_4_cmd_id { WMI_10_4_MU_CAL_START_CMDID, WMI_10_4_SET_CCA_PARAMS_CMDID, WMI_10_4_PDEV_BSS_CHAN_INFO_REQUEST_CMDID, + WMI_10_4_EXT_RESOURCE_CFG_CMDID, + WMI_10_4_VDEV_SET_IE_CMDID, + WMI_10_4_SET_LTEU_CONFIG_CMDID, WMI_10_4_PDEV_UTF_CMDID = WMI_10_4_END_CMDID - 1, }; @@ -1638,6 +1713,7 @@ enum wmi_10_4_event_id { WMI_10_4_PDEV_TEMPERATURE_EVENTID, WMI_10_4_PDEV_NFCAL_POWER_ALL_CHANNELS_EVENTID, WMI_10_4_PDEV_BSS_CHAN_INFO_EVENTID, + WMI_10_4_MU_REPORT_EVENTID, WMI_10_4_PDEV_UTF_EVENTID = WMI_10_4_END_EVENTID - 1, }; @@ -1732,6 +1808,7 @@ struct wmi_channel { __le32 reginfo1; struct { u8 antenna_max; + u8 max_tx_power; } __packed; } __packed; } __packed; @@ -1771,7 +1848,6 @@ enum wmi_channel_change_cause { #define WMI_CHANNEL_CHANGE_CAUSE_CSA (1 << 13) #define WMI_MAX_SPATIAL_STREAM 3 /* default max ss */ -#define WMI_10_4_MAX_SPATIAL_STREAM 4 /* HT Capabilities*/ #define WMI_HT_CAP_ENABLED 0x0001 /* HT Enabled/ disabled */ @@ -1995,8 +2071,8 @@ struct wmi_10x_service_ready_event { struct wlan_host_mem_req mem_reqs[0]; } __packed; -#define WMI_SERVICE_READY_TIMEOUT_HZ (5*HZ) -#define WMI_UNIFIED_READY_TIMEOUT_HZ (5*HZ) +#define WMI_SERVICE_READY_TIMEOUT_HZ (5 * HZ) +#define WMI_UNIFIED_READY_TIMEOUT_HZ (5 * HZ) struct wmi_ready_event { __le32 sw_version; @@ -2016,7 +2092,7 @@ struct wmi_resource_config { * In offload mode target supports features like WOW, chatter and * other protocol offloads. In order to support them some * functionalities like reorder buffering, PN checking need to be - * done in target. This determines maximum number of peers suported + * done in target. This determines maximum number of peers supported * by target in offload mode */ __le32 num_offload_peers; @@ -2197,7 +2273,7 @@ struct wmi_resource_config { * Max. number of Tx fragments per MSDU * This parameter controls the max number of Tx fragments per MSDU. * This is sent by the target as part of the WMI_SERVICE_READY event - * and is overriden by the OS shim as required. + * and is overridden by the OS shim as required. */ __le32 max_frag_entries; } __packed; @@ -2379,7 +2455,7 @@ struct wmi_resource_config_10x { * Max. number of Tx fragments per MSDU * This parameter controls the max number of Tx fragments per MSDU. * This is sent by the target as part of the WMI_SERVICE_READY event - * and is overriden by the OS shim as required. + * and is overridden by the OS shim as required. */ __le32 max_frag_entries; } __packed; @@ -2388,6 +2464,8 @@ enum wmi_10_2_feature_mask { WMI_10_2_RX_BATCH_MODE = BIT(0), WMI_10_2_ATF_CONFIG = BIT(1), WMI_10_2_COEX_GPIO = BIT(3), + WMI_10_2_BSS_CHAN_INFO = BIT(6), + WMI_10_2_PEER_STATS = BIT(7), }; struct wmi_resource_config_10_2 { @@ -2613,13 +2691,43 @@ struct wmi_resource_config_10_4 { */ __le32 iphdr_pad_config; - /* qwrap configuration + /* qwrap configuration (bits 15-0) * 1 - This is qwrap configuration * 0 - This is not qwrap + * + * Bits 31-16 is alloc_frag_desc_for_data_pkt (1 enables, 0 disables) + * In order to get ack-RSSI reporting and to specify the tx-rate for + * individual frames, this option must be enabled. This uses an extra + * 4 bytes per tx-msdu descriptor, so don't enable it unless you need it. */ __le32 qwrap_config; } __packed; +/** + * enum wmi_10_4_feature_mask - WMI 10.4 feature enable/disable flags + * @WMI_10_4_LTEU_SUPPORT: LTEU config + * @WMI_10_4_COEX_GPIO_SUPPORT: COEX GPIO config + * @WMI_10_4_AUX_RADIO_SPECTRAL_INTF: AUX Radio Enhancement for spectral scan + * @WMI_10_4_AUX_RADIO_CHAN_LOAD_INTF: AUX Radio Enhancement for chan load scan + * @WMI_10_4_BSS_CHANNEL_INFO_64: BSS channel info stats + * @WMI_10_4_PEER_STATS: Per station stats + */ +enum wmi_10_4_feature_mask { + WMI_10_4_LTEU_SUPPORT = BIT(0), + WMI_10_4_COEX_GPIO_SUPPORT = BIT(1), + WMI_10_4_AUX_RADIO_SPECTRAL_INTF = BIT(2), + WMI_10_4_AUX_RADIO_CHAN_LOAD_INTF = BIT(3), + WMI_10_4_BSS_CHANNEL_INFO_64 = BIT(4), + WMI_10_4_PEER_STATS = BIT(5), +}; + +struct wmi_ext_resource_config_10_4_cmd { + /* contains enum wmi_host_platform_type */ + __le32 host_platform_config; + /* see enum wmi_10_4_feature_mask */ + __le32 fw_feature_bitmap; +}; + /* strucutre describing host memory chunk. */ struct host_memory_chunk { /* id of the request that is passed up in service ready */ @@ -2641,7 +2749,7 @@ struct wmi_init_cmd { struct wmi_host_mem_chunks mem_chunks; } __packed; -/* _10x stucture is from 10.X FW API */ +/* _10x structure is from 10.X FW API */ struct wmi_init_cmd_10x { struct wmi_resource_config_10x resource_config; struct wmi_host_mem_chunks mem_chunks; @@ -2779,6 +2887,66 @@ struct wmi_start_scan_common { __le32 scan_ctrl_flags; } __packed; +/* ARP-NS offload data structure */ +#define WMI_NS_ARP_OFFLOAD 2 +#define WMI_ARP_NS_OFF_FLAGS_VALID BIT(0) +#define WMI_IPV4_ARP_REPLY_OFFLOAD 0 +#define WMI_ARP_NS_OFFLOAD_DISABLE 0 +#define WMI_ARP_NS_OFFLOAD_ENABLE 1 +#define WMI_NSOFF_IPV6_ANYCAST BIT(3) + +struct wmi_ns_offload_info { + struct in6_addr src_addr; + struct in6_addr self_addr[TARGET_NUM_STATIONS]; + struct in6_addr target_addr[TARGET_NUM_STATIONS]; + struct wmi_mac_addr self_macaddr; + u8 src_ipv6_addr_valid; + struct in6_addr target_addr_valid; + struct in6_addr target_ipv6_ac; + u8 slot_idx; +} __packed; + +struct wmi_ns_arp_offload_req { + u8 offload_type; + u8 enable_offload; + __le32 num_ns_offload_count; + union { + struct in_addr ipv4_addr; + struct in6_addr ipv6_addr; + } params; + struct wmi_ns_offload_info info; + struct wmi_mac_addr bssid; +} __packed; + +struct wmi_ns_offload { + __le32 flags; + struct in6_addr target_ipaddr[WMI_NS_ARP_OFFLOAD]; + struct in6_addr solicitation_ipaddr; + struct in6_addr remote_ipaddr; + struct wmi_mac_addr target_mac; +} __packed; + +struct wmi_arp_offload { + __le32 flags; + struct in_addr target_ipaddr; + struct in_addr remote_ipaddr; + struct wmi_mac_addr target_mac; +} __packed; + +/* GTK offload data structure */ +#define WMI_GTK_OFFLOAD_ENABLE_OPCODE BIT(24) +#define WMI_GTK_OFFLOAD_DISABLE_OPCODE BIT(25) +#define WMI_GTK_OFFLOAD_ENABLE 1 +#define WMI_GTK_OFFLOAD_DISABLE 0 + +struct wmi_gtk_rekey_data { + bool valid; + bool enable_offload; + u8 kck[NL80211_KCK_LEN]; + u8 kek[NL80211_KEK_LEN]; + __le64 replay_ctr; +} __packed; + struct wmi_start_scan_tlvs { /* TLV parameters. These includes channel list, ssid list, bssid list, * extra ies. @@ -2855,6 +3023,8 @@ struct wmi_start_scan_arg { /* Different FW scan engine may choose to bail out on errors. * Allow the driver to have influence over that. */ #define WMI_SCAN_CONTINUE_ON_ERROR 0x80 +/** add DS content in probe req frame */ +#define WMI_SCAN_ADD_DS_IE_IN_PROBE_REQ 0x800 /* WMI_SCAN_CLASS_MASK must be the same value as IEEE80211_SCAN_CLASS_MASK */ #define WMI_SCAN_CLASS_MASK 0xFF000000 @@ -2990,11 +3160,17 @@ struct wmi_10_4_mgmt_rx_event { u8 buf[0]; } __packed; +struct wmi_mgmt_rx_ext_info { + __le64 rx_mac_timestamp; +} __packed __aligned(4); + #define WMI_RX_STATUS_OK 0x00 #define WMI_RX_STATUS_ERR_CRC 0x01 #define WMI_RX_STATUS_ERR_DECRYPT 0x08 #define WMI_RX_STATUS_ERR_MIC 0x10 #define WMI_RX_STATUS_ERR_KEY_CACHE_MISS 0x20 +/* Extension data at the end of mgmt frame */ +#define WMI_RX_STATUS_EXT_INFO 0x40 #define PHY_ERROR_GEN_SPECTRAL_SCAN 0x26 #define PHY_ERROR_GEN_FALSE_RADAR_EXT 0x24 @@ -3003,6 +3179,10 @@ struct wmi_10_4_mgmt_rx_event { #define PHY_ERROR_10_4_RADAR_MASK 0x4 #define PHY_ERROR_10_4_SPECTRAL_SCAN_MASK 0x4000000 +#define WMI_PHY_ERROR_MASK0_RADAR BIT(2) +#define WMI_PHY_ERROR_MASK0_FALSE_RADAR_EXT BIT(24) +#define WMI_PHY_ERROR_MASK0_SPECTRAL_SCAN BIT(26) + enum phy_err_type { PHY_ERROR_UNKNOWN, PHY_ERROR_SPECTRAL_SCAN, @@ -3343,6 +3523,7 @@ struct wmi_pdev_param_map { u32 wapi_mbssid_offset; u32 arp_srcaddr; u32 arp_dstaddr; + u32 enable_btcoex; }; #define WMI_PDEV_PARAM_UNSUPPORTED 0 @@ -3650,6 +3831,15 @@ enum wmi_10_4_pdev_param { WMI_10_4_PDEV_PARAM_WAPI_MBSSID_OFFSET, WMI_10_4_PDEV_PARAM_ARP_SRCADDR, WMI_10_4_PDEV_PARAM_ARP_DSTADDR, + WMI_10_4_PDEV_PARAM_TXPOWER_DECR_DB, + WMI_10_4_PDEV_PARAM_RX_BATCHMODE, + WMI_10_4_PDEV_PARAM_PACKET_AGGR_DELAY, + WMI_10_4_PDEV_PARAM_ATF_OBSS_NOISE_SCH, + WMI_10_4_PDEV_PARAM_ATF_OBSS_NOISE_SCALING_FACTOR, + WMI_10_4_PDEV_PARAM_CUST_TXPOWER_SCALE, + WMI_10_4_PDEV_PARAM_ATF_DYNAMIC_ENABLE, + WMI_10_4_PDEV_PARAM_ATF_SSID_GROUP_POLICY, + WMI_10_4_PDEV_PARAM_ENABLE_BTCOEX, }; struct wmi_pdev_set_param_cmd { @@ -3848,7 +4038,7 @@ struct wmi_pdev_stats_tx { /* illegal rate phy errors */ __le32 illgl_rate_phy_err; - /* wal pdev continous xretry */ + /* wal pdev continuous xretry */ __le32 pdev_cont_xretry; /* wal pdev continous xretry */ @@ -4019,6 +4209,13 @@ enum wmi_stats_id { WMI_STAT_VDEV_RATE = BIT(5), }; +enum wmi_10_4_stats_id { + WMI_10_4_STAT_PEER = BIT(0), + WMI_10_4_STAT_AP = BIT(1), + WMI_10_4_STAT_INST = BIT(2), + WMI_10_4_STAT_PEER_EXTD = BIT(3), +}; + struct wlan_inst_rssi_args { __le16 cfg_retry_count; __le16 retry_count; @@ -4096,10 +4293,10 @@ struct wmi_10_2_stats_event { */ struct wmi_pdev_stats_base { __le32 chan_nf; - __le32 tx_frame_count; - __le32 rx_frame_count; - __le32 rx_clear_count; - __le32 cycle_count; + __le32 tx_frame_count; /* Cycles spent transmitting frames */ + __le32 rx_frame_count; /* Cycles spent receiving frames */ + __le32 rx_clear_count; /* Total channel busy time, evidently */ + __le32 cycle_count; /* Total on-channel time */ __le32 phy_err_count; __le32 chan_tx_pwr; } __packed; @@ -4192,7 +4389,13 @@ struct wmi_10_2_peer_stats { struct wmi_10_2_4_peer_stats { struct wmi_10_2_peer_stats common; - __le32 unknown_value; /* FIXME: what is this word? */ + __le32 peer_rssi_changed; +} __packed; + +struct wmi_10_2_4_ext_peer_stats { + struct wmi_10_2_peer_stats common; + __le32 peer_rssi_changed; + __le32 rx_duration; } __packed; struct wmi_10_4_peer_stats { @@ -4212,6 +4415,27 @@ struct wmi_10_4_peer_stats { __le32 peer_rssi_changed; } __packed; +struct wmi_10_4_peer_extd_stats { + struct wmi_mac_addr peer_macaddr; + __le32 inactive_time; + __le32 peer_chain_rssi; + __le32 rx_duration; + __le32 reserved[10]; +} __packed; + +struct wmi_10_4_bss_bcn_stats { + __le32 vdev_id; + __le32 bss_bcns_dropped; + __le32 bss_bcn_delivered; +} __packed; + +struct wmi_10_4_bss_bcn_filter_stats { + __le32 bcns_dropped; + __le32 bcns_delivered; + __le32 active_filters; + struct wmi_10_4_bss_bcn_stats bss_stats; +} __packed; + struct wmi_10_2_pdev_ext_stats { __le32 rx_rssi_comb; __le32 rx_rssi[4]; @@ -4235,10 +4459,40 @@ enum wmi_vdev_type { }; enum wmi_vdev_subtype { - WMI_VDEV_SUBTYPE_NONE = 0, - WMI_VDEV_SUBTYPE_P2P_DEVICE = 1, - WMI_VDEV_SUBTYPE_P2P_CLIENT = 2, - WMI_VDEV_SUBTYPE_P2P_GO = 3, + WMI_VDEV_SUBTYPE_NONE, + WMI_VDEV_SUBTYPE_P2P_DEVICE, + WMI_VDEV_SUBTYPE_P2P_CLIENT, + WMI_VDEV_SUBTYPE_P2P_GO, + WMI_VDEV_SUBTYPE_PROXY_STA, + WMI_VDEV_SUBTYPE_MESH_11S, + WMI_VDEV_SUBTYPE_MESH_NON_11S, +}; + +enum wmi_vdev_subtype_legacy { + WMI_VDEV_SUBTYPE_LEGACY_NONE = 0, + WMI_VDEV_SUBTYPE_LEGACY_P2P_DEV = 1, + WMI_VDEV_SUBTYPE_LEGACY_P2P_CLI = 2, + WMI_VDEV_SUBTYPE_LEGACY_P2P_GO = 3, + WMI_VDEV_SUBTYPE_LEGACY_PROXY_STA = 4, +}; + +enum wmi_vdev_subtype_10_2_4 { + WMI_VDEV_SUBTYPE_10_2_4_NONE = 0, + WMI_VDEV_SUBTYPE_10_2_4_P2P_DEV = 1, + WMI_VDEV_SUBTYPE_10_2_4_P2P_CLI = 2, + WMI_VDEV_SUBTYPE_10_2_4_P2P_GO = 3, + WMI_VDEV_SUBTYPE_10_2_4_PROXY_STA = 4, + WMI_VDEV_SUBTYPE_10_2_4_MESH_11S = 5, +}; + +enum wmi_vdev_subtype_10_4 { + WMI_VDEV_SUBTYPE_10_4_NONE = 0, + WMI_VDEV_SUBTYPE_10_4_P2P_DEV = 1, + WMI_VDEV_SUBTYPE_10_4_P2P_CLI = 2, + WMI_VDEV_SUBTYPE_10_4_P2P_GO = 3, + WMI_VDEV_SUBTYPE_10_4_PROXY_STA = 4, + WMI_VDEV_SUBTYPE_10_4_MESH_NON_11S = 5, + WMI_VDEV_SUBTYPE_10_4_MESH_11S = 6, }; /* values for vdev_subtype */ @@ -4247,14 +4501,14 @@ enum wmi_vdev_subtype { /* * Indicates that AP VDEV uses hidden ssid. only valid for * AP/GO */ -#define WMI_VDEV_START_HIDDEN_SSID (1<<0) +#define WMI_VDEV_START_HIDDEN_SSID (1 << 0) /* * Indicates if robust management frame/management frame * protection is enabled. For GO/AP vdevs, it indicates that * it may support station/client associations with RMF enabled. * For STA/client vdevs, it indicates that sta will * associate with AP with RMF enabled. */ -#define WMI_VDEV_START_PMF_ENABLED (1<<1) +#define WMI_VDEV_START_PMF_ENABLED (1 << 1) struct wmi_p2p_noa_descriptor { __le32 type_count; /* 255: continuous schedule, 0: reserved */ @@ -4278,9 +4532,9 @@ struct wmi_vdev_start_request_cmd { __le32 flags; /* ssid field. Only valid for AP/GO/IBSS/BTAmp VDEV type. */ struct wmi_ssid ssid; - /* beacon/probe reponse xmit rate. Applicable for SoftAP. */ + /* beacon/probe response xmit rate. Applicable for SoftAP. */ __le32 bcn_tx_rate; - /* beacon/probe reponse xmit power. Applicable for SoftAP. */ + /* beacon/probe response xmit power. Applicable for SoftAP. */ __le32 bcn_tx_power; /* number of p2p NOA descriptor(s) from scan entry */ __le32 num_noa_descriptors; @@ -4493,6 +4747,7 @@ struct wmi_vdev_param_map { u32 meru_vc; u32 rx_decap_type; u32 bw_nss_ratemask; + u32 set_tsf; }; #define WMI_VDEV_PARAM_UNSUPPORTED 0 @@ -4507,7 +4762,7 @@ enum wmi_vdev_param { WMI_VDEV_PARAM_BEACON_INTERVAL, /* Listen interval in TUs */ WMI_VDEV_PARAM_LISTEN_INTERVAL, - /* muticast rate in Mbps */ + /* multicast rate in Mbps */ WMI_VDEV_PARAM_MULTICAST_RATE, /* management frame rate in Mbps */ WMI_VDEV_PARAM_MGMT_TX_RATE, @@ -4638,7 +4893,7 @@ enum wmi_10x_vdev_param { WMI_10X_VDEV_PARAM_BEACON_INTERVAL, /* Listen interval in TUs */ WMI_10X_VDEV_PARAM_LISTEN_INTERVAL, - /* muticast rate in Mbps */ + /* multicast rate in Mbps */ WMI_10X_VDEV_PARAM_MULTICAST_RATE, /* management frame rate in Mbps */ WMI_10X_VDEV_PARAM_MGMT_TX_RATE, @@ -4749,6 +5004,7 @@ enum wmi_10x_vdev_param { WMI_10X_VDEV_PARAM_RTS_FIXED_RATE, WMI_10X_VDEV_PARAM_VHT_SGIMASK, WMI_10X_VDEV_PARAM_VHT80_RATEMASK, + WMI_10X_VDEV_PARAM_TSF_INCREMENT, }; enum wmi_10_4_vdev_param { @@ -4818,6 +5074,12 @@ enum wmi_10_4_vdev_param { WMI_10_4_VDEV_PARAM_MERU_VC, WMI_10_4_VDEV_PARAM_RX_DECAP_TYPE, WMI_10_4_VDEV_PARAM_BW_NSS_RATEMASK, + WMI_10_4_VDEV_PARAM_SENSOR_AP, + WMI_10_4_VDEV_PARAM_BEACON_RATE, + WMI_10_4_VDEV_PARAM_DTIM_ENABLE_CTS, + WMI_10_4_VDEV_PARAM_STA_KICKOUT, + WMI_10_4_VDEV_PARAM_CAPABILITIES, + WMI_10_4_VDEV_PARAM_TSF_INCREMENT, }; #define WMI_VDEV_PARAM_TXBF_SU_TX_BFEE BIT(0) @@ -4877,7 +5139,7 @@ struct wmi_vdev_simple_event { } __packed; /* VDEV start response status codes */ -/* VDEV succesfully started */ +/* VDEV successfully started */ #define WMI_INIFIED_VDEV_START_RESPONSE_STATUS_SUCCESS 0x0 /* requested VDEV not found */ @@ -5193,7 +5455,7 @@ enum wmi_sta_ps_param_pspoll_count { #define WMI_UAPSD_AC_TYPE_TRIG 1 #define WMI_UAPSD_AC_BIT_MASK(ac, type) \ - ((type == WMI_UAPSD_AC_TYPE_DELI) ? (1<<(ac<<1)) : (1<<((ac<<1)+1))) + (type == WMI_UAPSD_AC_TYPE_DELI ? 1 << (ac << 1) : 1 << ((ac << 1) + 1)) enum wmi_sta_ps_param_uapsd { WMI_STA_PS_UAPSD_AC0_DELIVERY_EN = (1 << 0), @@ -5406,6 +5668,16 @@ struct wmi_host_swba_event { struct wmi_bcn_info bcn_info[0]; } __packed; +struct wmi_10_2_4_bcn_info { + struct wmi_tim_info tim_info; + /* The 10.2.4 FW doesn't have p2p NOA info */ +} __packed; + +struct wmi_10_2_4_host_swba_event { + __le32 vdev_map; + struct wmi_10_2_4_bcn_info bcn_info[0]; +} __packed; + /* 16 words = 512 client + 1 word = for guard */ #define WMI_10_4_TIM_BITMAP_ARRAY_SIZE 17 @@ -5598,7 +5870,7 @@ struct wmi_rate_set { * the rates are filled from least significant byte to most * significant byte. */ - __le32 rates[(MAX_SUPPORTED_RATES/4)+1]; + __le32 rates[(MAX_SUPPORTED_RATES / 4) + 1]; } __packed; struct wmi_rate_set_arg { @@ -5642,21 +5914,79 @@ struct wmi_peer_set_q_empty_callback_cmd { __le32 callback_enable; } __packed; -#define WMI_PEER_AUTH 0x00000001 -#define WMI_PEER_QOS 0x00000002 -#define WMI_PEER_NEED_PTK_4_WAY 0x00000004 -#define WMI_PEER_NEED_GTK_2_WAY 0x00000010 -#define WMI_PEER_APSD 0x00000800 -#define WMI_PEER_HT 0x00001000 -#define WMI_PEER_40MHZ 0x00002000 -#define WMI_PEER_STBC 0x00008000 -#define WMI_PEER_LDPC 0x00010000 -#define WMI_PEER_DYN_MIMOPS 0x00020000 -#define WMI_PEER_STATIC_MIMOPS 0x00040000 -#define WMI_PEER_SPATIAL_MUX 0x00200000 -#define WMI_PEER_VHT 0x02000000 -#define WMI_PEER_80MHZ 0x04000000 -#define WMI_PEER_VHT_2G 0x08000000 +struct wmi_peer_flags_map { + u32 auth; + u32 qos; + u32 need_ptk_4_way; + u32 need_gtk_2_way; + u32 apsd; + u32 ht; + u32 bw40; + u32 stbc; + u32 ldbc; + u32 dyn_mimops; + u32 static_mimops; + u32 spatial_mux; + u32 vht; + u32 bw80; + u32 vht_2g; + u32 pmf; +}; + +enum wmi_peer_flags { + WMI_PEER_AUTH = 0x00000001, + WMI_PEER_QOS = 0x00000002, + WMI_PEER_NEED_PTK_4_WAY = 0x00000004, + WMI_PEER_NEED_GTK_2_WAY = 0x00000010, + WMI_PEER_APSD = 0x00000800, + WMI_PEER_HT = 0x00001000, + WMI_PEER_40MHZ = 0x00002000, + WMI_PEER_STBC = 0x00008000, + WMI_PEER_LDPC = 0x00010000, + WMI_PEER_DYN_MIMOPS = 0x00020000, + WMI_PEER_STATIC_MIMOPS = 0x00040000, + WMI_PEER_SPATIAL_MUX = 0x00200000, + WMI_PEER_VHT = 0x02000000, + WMI_PEER_80MHZ = 0x04000000, + WMI_PEER_VHT_2G = 0x08000000, + WMI_PEER_PMF = 0x10000000, +}; + +enum wmi_10x_peer_flags { + WMI_10X_PEER_AUTH = 0x00000001, + WMI_10X_PEER_QOS = 0x00000002, + WMI_10X_PEER_NEED_PTK_4_WAY = 0x00000004, + WMI_10X_PEER_NEED_GTK_2_WAY = 0x00000010, + WMI_10X_PEER_APSD = 0x00000800, + WMI_10X_PEER_HT = 0x00001000, + WMI_10X_PEER_40MHZ = 0x00002000, + WMI_10X_PEER_STBC = 0x00008000, + WMI_10X_PEER_LDPC = 0x00010000, + WMI_10X_PEER_DYN_MIMOPS = 0x00020000, + WMI_10X_PEER_STATIC_MIMOPS = 0x00040000, + WMI_10X_PEER_SPATIAL_MUX = 0x00200000, + WMI_10X_PEER_VHT = 0x02000000, + WMI_10X_PEER_80MHZ = 0x04000000, +}; + +enum wmi_10_2_peer_flags { + WMI_10_2_PEER_AUTH = 0x00000001, + WMI_10_2_PEER_QOS = 0x00000002, + WMI_10_2_PEER_NEED_PTK_4_WAY = 0x00000004, + WMI_10_2_PEER_NEED_GTK_2_WAY = 0x00000010, + WMI_10_2_PEER_APSD = 0x00000800, + WMI_10_2_PEER_HT = 0x00001000, + WMI_10_2_PEER_40MHZ = 0x00002000, + WMI_10_2_PEER_STBC = 0x00008000, + WMI_10_2_PEER_LDPC = 0x00010000, + WMI_10_2_PEER_DYN_MIMOPS = 0x00020000, + WMI_10_2_PEER_STATIC_MIMOPS = 0x00040000, + WMI_10_2_PEER_SPATIAL_MUX = 0x00200000, + WMI_10_2_PEER_VHT = 0x02000000, + WMI_10_2_PEER_80MHZ = 0x04000000, + WMI_10_2_PEER_VHT_2G = 0x08000000, + WMI_10_2_PEER_PMF = 0x10000000, +}; /* * Peer rate capabilities. @@ -5722,6 +6052,11 @@ struct wmi_10_2_peer_assoc_complete_cmd { __le32 info0; /* WMI_PEER_ASSOC_INFO0_ */ } __packed; +struct wmi_10_4_peer_assoc_complete_cmd { + struct wmi_10_2_peer_assoc_complete_cmd cmd; + __le32 peer_bw_rxnss_override; +} __packed; + struct wmi_peer_assoc_complete_arg { u8 addr[ETH_ALEN]; u32 vdev_id; @@ -5769,6 +6104,13 @@ struct wmi_chan_info_event { __le32 noise_floor; __le32 rx_clear_count; __le32 cycle_count; + __le32 chan_tx_pwr_range; + __le32 chan_tx_pwr_tp; + __le32 rx_frame_count; + __le32 my_bss_rx_cycle_count; + __le32 rx_11b_mode_data_duration; + __le32 tx_frame_cnt; + __le32 mac_clk_mhz; } __packed; struct wmi_10_4_chan_info_event { @@ -5911,6 +6253,20 @@ struct wmi_dbglog_cfg_cmd { __le32 config_valid; } __packed; +struct wmi_10_4_dbglog_cfg_cmd { + /* bitmask to hold mod id config*/ + __le64 module_enable; + + /* see ATH10K_DBGLOG_CFG_ */ + __le32 config_enable; + + /* mask of module id bits to be changed */ + __le64 module_valid; + + /* mask of config bits to be changed, see ATH10K_DBGLOG_CFG_ */ + __le32 config_valid; +} __packed; + enum wmi_roam_reason { WMI_ROAM_REASON_BETTER_AP = 1, WMI_ROAM_REASON_BEACON_MISS = 2, @@ -5948,6 +6304,17 @@ struct wmi_scan_ev_arg { __le32 vdev_id; }; +struct wmi_peer_delete_resp_ev_arg { + __le32 vdev_id; + struct wmi_mac_addr peer_addr; +}; + +struct wmi_tlv_mgmt_tx_compl_ev_arg { + __le32 desc_id; + __le32 status; + __le32 pdev_id; +}; + struct wmi_mgmt_rx_ev_arg { __le32 channel; __le32 snr; @@ -5955,6 +6322,7 @@ struct wmi_mgmt_rx_ev_arg { __le32 phy_mode; __le32 buf_len; __le32 status; /* %WMI_RX_STATUS_ */ + struct wmi_mgmt_rx_ext_info ext_info; }; struct wmi_ch_info_ev_arg { @@ -5967,6 +6335,10 @@ struct wmi_ch_info_ev_arg { __le32 chan_tx_pwr_range; __le32 chan_tx_pwr_tp; __le32 rx_frame_count; + __le32 my_bss_rx_cycle_count; + __le32 rx_11b_mode_data_duration; + __le32 tx_frame_cnt; + __le32 mac_clk_mhz; }; struct wmi_vdev_start_ev_arg { @@ -6005,6 +6377,8 @@ struct wmi_phyerr_hdr_arg { u32 tsf_u32; u32 buf_len; const void *phyerrs; + u32 phy_err_mask0; + u32 phy_err_mask1; }; struct wmi_svc_rdy_ev_arg { @@ -6037,11 +6411,26 @@ struct wmi_roam_ev_arg { __le32 rssi; }; +struct wmi_echo_ev_arg { + __le32 value; +}; + struct wmi_pdev_temperature_event { /* temperature value in Celcius degree */ __le32 temperature; } __packed; +struct wmi_pdev_bss_chan_info_event { + __le32 freq; + __le32 noise_floor; + __le64 cycle_busy; + __le64 cycle_total; + __le64 cycle_tx; + __le64 cycle_rx; + __le64 cycle_rx_bss; + __le32 reserved; +} __packed; + /* WOW structures */ enum wmi_wow_wakeup_event { WOW_BMISS_EVENT = 0, @@ -6240,6 +6629,21 @@ struct wmi_pdev_set_adaptive_cca_params { __le32 cca_detect_margin; } __packed; +enum wmi_host_platform_type { + WMI_HOST_PLATFORM_HIGH_PERF, + WMI_HOST_PLATFORM_LOW_PERF, +}; + +enum wmi_bss_survey_req_type { + WMI_BSS_SURVEY_REQ_TYPE_READ = 1, + WMI_BSS_SURVEY_REQ_TYPE_READ_CLEAR, +}; + +struct wmi_pdev_chan_info_req_cmd { + __le32 type; + __le32 reserved; +} __packed; + struct ath10k; struct ath10k_vif; struct ath10k_fw_stats_pdev; @@ -6282,13 +6686,17 @@ void ath10k_wmi_put_wmi_channel(struct wmi_channel *ch, int ath10k_wmi_start_scan_verify(const struct wmi_start_scan_arg *arg); int ath10k_wmi_event_scan(struct ath10k *ar, struct sk_buff *skb); +int ath10k_wmi_tlv_event_peer_delete_resp(struct ath10k *ar, + struct sk_buff *skb); int ath10k_wmi_event_mgmt_rx(struct ath10k *ar, struct sk_buff *skb); +int ath10k_wmi_tlv_event_mgmt_tx_compl(struct ath10k *ar, struct sk_buff *skb); void ath10k_wmi_event_chan_info(struct ath10k *ar, struct sk_buff *skb); void ath10k_wmi_event_echo(struct ath10k *ar, struct sk_buff *skb); int ath10k_wmi_event_debug_mesg(struct ath10k *ar, struct sk_buff *skb); void ath10k_wmi_event_update_stats(struct ath10k *ar, struct sk_buff *skb); void ath10k_wmi_event_vdev_start_resp(struct ath10k *ar, struct sk_buff *skb); void ath10k_wmi_event_vdev_stopped(struct ath10k *ar, struct sk_buff *skb); +void ath10k_wmi_event_vdev_delete_resp(struct ath10k *ar, struct sk_buff *skb); void ath10k_wmi_event_peer_sta_kickout(struct ath10k *ar, struct sk_buff *skb); void ath10k_wmi_event_host_swba(struct ath10k *ar, struct sk_buff *skb); void ath10k_wmi_event_tbttoffset_update(struct ath10k *ar, struct sk_buff *skb); @@ -6337,5 +6745,8 @@ size_t ath10k_wmi_fw_stats_num_vdevs(struct list_head *head); void ath10k_wmi_10_4_op_fw_stats_fill(struct ath10k *ar, struct ath10k_fw_stats *fw_stats, char *buf); +int ath10k_wmi_op_get_vdev_subtype(struct ath10k *ar, + enum wmi_vdev_subtype subtype); +int ath10k_wmi_barrier(struct ath10k *ar); #endif /* _WMI_H_ */ diff --git a/drivers/net/wireless/ath/ath10k/wow.c b/drivers/net/wireless/ath/ath10k/wow.c index 8e02b381990f..2280f47dc227 100644 --- a/drivers/net/wireless/ath/ath10k/wow.c +++ b/drivers/net/wireless/ath/ath10k/wow.c @@ -17,6 +17,7 @@ #include "mac.h" #include <net/mac80211.h> +#include <net/addrconf.h> #include "hif.h" #include "core.h" #include "debug.h" @@ -25,7 +26,9 @@ static const struct wiphy_wowlan_support ath10k_wowlan_support = { .flags = WIPHY_WOWLAN_DISCONNECT | - WIPHY_WOWLAN_MAGIC_PKT, + WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE, .pattern_min_len = WOW_MIN_PATTERN_SIZE, .pattern_max_len = WOW_MAX_PATTERN_SIZE, .max_pkt_offset = WOW_MAX_PKT_OFFSET, @@ -82,6 +85,7 @@ static int ath10k_vif_wow_set_wakeups(struct ath10k_vif *arvif, int ret, i; unsigned long wow_mask = 0; struct ath10k *ar = arvif->ar; + struct ieee80211_bss_conf *bss = &arvif->vif->bss_conf; const struct cfg80211_pkt_pattern *patterns = wowlan->patterns; int pattern_id = 0; @@ -100,15 +104,19 @@ static int ath10k_vif_wow_set_wakeups(struct ath10k_vif *arvif, __set_bit(WOW_RA_MATCH_EVENT, &wow_mask); break; case WMI_VDEV_TYPE_STA: - if (wowlan->disconnect) { - __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask); - __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask); - __set_bit(WOW_BMISS_EVENT, &wow_mask); - __set_bit(WOW_CSA_IE_EVENT, &wow_mask); + if (arvif->is_up && bss->assoc) { + if (wowlan->disconnect) { + __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask); + __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask); + __set_bit(WOW_BMISS_EVENT, &wow_mask); + __set_bit(WOW_CSA_IE_EVENT, &wow_mask); + } + + if (wowlan->magic_pkt) + __set_bit(WOW_MAGIC_PKT_RECVD_EVENT, &wow_mask); + if (wowlan->gtk_rekey_failure) + __set_bit(WOW_GTK_ERR_EVENT, &wow_mask); } - - if (wowlan->magic_pkt) - __set_bit(WOW_MAGIC_PKT_RECVD_EVENT, &wow_mask); break; default: break; @@ -224,6 +232,253 @@ static int ath10k_wow_wakeup(struct ath10k *ar) return 0; } +static int +ath10k_wow_fill_vdev_ns_offload_struct(struct ath10k_vif *arvif, + bool enable_offload) +{ + struct in6_addr addr[TARGET_NUM_STATIONS]; + struct wmi_ns_arp_offload_req *ns; + struct wireless_dev *wdev; + struct inet6_dev *in6_dev; + struct in6_addr addr_type; + struct inet6_ifaddr *ifa; + struct ifacaddr6 *ifaca; + struct list_head *addr_list; + u32 scope, count = 0; + int i; + + ns = &arvif->ns_offload; + if (!enable_offload) { + ns->offload_type = __cpu_to_le16(WMI_NS_ARP_OFFLOAD); + ns->enable_offload = __cpu_to_le16(WMI_ARP_NS_OFFLOAD_DISABLE); + return 0; + } + + wdev = ieee80211_vif_to_wdev(arvif->vif); + if (!wdev) + return -ENODEV; + + in6_dev = __in6_dev_get(wdev->netdev); + if (!in6_dev) + return -ENODEV; + + memset(&addr, 0, TARGET_NUM_STATIONS * sizeof(struct in6_addr)); + memset(&addr_type, 0, sizeof(struct in6_addr)); + + /* Unicast Addresses */ + read_lock_bh(&in6_dev->lock); + list_for_each(addr_list, &in6_dev->addr_list) { + if (count >= TARGET_NUM_STATIONS) { + read_unlock_bh(&in6_dev->lock); + return -EINVAL; + } + + ifa = list_entry(addr_list, struct inet6_ifaddr, if_list); + if (ifa->flags & IFA_F_DADFAILED) + continue; + scope = ipv6_addr_src_scope(&ifa->addr); + switch (scope) { + case IPV6_ADDR_SCOPE_GLOBAL: + case IPV6_ADDR_SCOPE_LINKLOCAL: + memcpy(&addr[count], &ifa->addr.s6_addr, + sizeof(ifa->addr.s6_addr)); + addr_type.s6_addr[count] = IPV6_ADDR_UNICAST; + count += 1; + break; + } + } + + /* Anycast Addresses */ + for (ifaca = in6_dev->ac_list; ifaca; ifaca = ifaca->aca_next) { + if (count >= TARGET_NUM_STATIONS) { + read_unlock_bh(&in6_dev->lock); + return -EINVAL; + } + + scope = ipv6_addr_src_scope(&ifaca->aca_addr); + switch (scope) { + case IPV6_ADDR_SCOPE_GLOBAL: + case IPV6_ADDR_SCOPE_LINKLOCAL: + memcpy(&addr[count], &ifaca->aca_addr, + sizeof(ifaca->aca_addr)); + addr_type.s6_addr[count] = IPV6_ADDR_ANY; + count += 1; + break; + } + } + read_unlock_bh(&in6_dev->lock); + + /* Filling up the request structure + * Filling the self_addr with solicited address + * A Solicited-Node multicast address is created by + * taking the last 24 bits of a unicast or anycast + * address and appending them to the prefix + * + * FF02:0000:0000:0000:0000:0001:FFXX:XXXX + * + * here XX is the unicast/anycast bits + */ + for (i = 0; i < count; i++) { + ns->info.self_addr[i].s6_addr[0] = 0xFF; + ns->info.self_addr[i].s6_addr[1] = 0x02; + ns->info.self_addr[i].s6_addr[11] = 0x01; + ns->info.self_addr[i].s6_addr[12] = 0xFF; + ns->info.self_addr[i].s6_addr[13] = addr[i].s6_addr[13]; + ns->info.self_addr[i].s6_addr[14] = addr[i].s6_addr[14]; + ns->info.self_addr[i].s6_addr[15] = addr[i].s6_addr[15]; + ns->info.slot_idx = i; + memcpy(&ns->info.target_addr[i], &addr[i], + sizeof(struct in6_addr)); + ns->info.target_addr_valid.s6_addr[i] = 1; + ns->info.target_ipv6_ac.s6_addr[i] = addr_type.s6_addr[i]; + memcpy(&ns->params.ipv6_addr, &ns->info.target_addr[i], + sizeof(struct in6_addr)); + } + + ns->offload_type = __cpu_to_le16(WMI_NS_ARP_OFFLOAD); + ns->enable_offload = __cpu_to_le16(WMI_ARP_NS_OFFLOAD_ENABLE); + ns->num_ns_offload_count = __cpu_to_le16(count); + + return 0; +} + +static int +ath10k_wow_fill_vdev_arp_offload_struct(struct ath10k_vif *arvif, + bool enable_offload) +{ + struct in_device *in_dev; + struct in_ifaddr *ifa; + bool offload_params_found = false; + struct wireless_dev *wdev = ieee80211_vif_to_wdev(arvif->vif); + struct wmi_ns_arp_offload_req *arp = &arvif->arp_offload; + + if (!enable_offload) { + arp->offload_type = __cpu_to_le16(WMI_IPV4_ARP_REPLY_OFFLOAD); + arp->enable_offload = __cpu_to_le16(WMI_ARP_NS_OFFLOAD_DISABLE); + return 0; + } + + if (!wdev) + return -ENODEV; + if (!wdev->netdev) + return -ENODEV; + in_dev = __in_dev_get_rtnl(wdev->netdev); + if (!in_dev) + return -ENODEV; + + arp->offload_type = __cpu_to_le16(WMI_IPV4_ARP_REPLY_OFFLOAD); + arp->enable_offload = __cpu_to_le16(WMI_ARP_NS_OFFLOAD_ENABLE); + for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) { + if (!memcmp(ifa->ifa_label, wdev->netdev->name, IFNAMSIZ)) { + offload_params_found = true; + break; + } + } + + if (!offload_params_found) + return -ENODEV; + memcpy(&arp->params.ipv4_addr, &ifa->ifa_local, + sizeof(arp->params.ipv4_addr)); + + return 0; +} + +static int ath10k_wow_enable_ns_arp_offload(struct ath10k *ar, bool offload) +{ + struct ath10k_vif *arvif; + int ret; + + list_for_each_entry(arvif, &ar->arvifs, list) { + if (arvif->vdev_type != WMI_VDEV_TYPE_STA) + continue; + + if (!arvif->is_up) + continue; + + ret = ath10k_wow_fill_vdev_arp_offload_struct(arvif, offload); + if (ret) { + ath10k_err(ar, "ARP-offload config failed, vdev: %d\n", + arvif->vdev_id); + return ret; + } + + ret = ath10k_wow_fill_vdev_ns_offload_struct(arvif, offload); + if (ret) { + ath10k_err(ar, "NS-offload config failed, vdev: %d\n", + arvif->vdev_id); + return ret; + } + + ret = ath10k_wmi_set_arp_ns_offload(ar, arvif); + if (ret) { + ath10k_err(ar, "failed to send offload cmd, vdev: %d\n", + arvif->vdev_id); + return ret; + } + } + + return 0; +} + +static int ath10k_config_wow_listen_interval(struct ath10k *ar) +{ + int ret; + u32 param = ar->wmi.vdev_param->listen_interval; + u8 listen_interval = ar->hw_values->default_listen_interval; + struct ath10k_vif *arvif; + + if (!listen_interval) + return 0; + + list_for_each_entry(arvif, &ar->arvifs, list) { + if (arvif->vdev_type != WMI_VDEV_TYPE_STA) + continue; + ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, + param, listen_interval); + if (ret) { + ath10k_err(ar, "failed to config LI for vdev_id: %d\n", + arvif->vdev_id); + return ret; + } + } + + return 0; +} + +static int ath10k_wow_config_gtk_offload(struct ath10k *ar, bool gtk_offload) +{ + struct ath10k_vif *arvif; + struct ieee80211_bss_conf *bss; + struct wmi_gtk_rekey_data *rekey_data; + int ret; + + list_for_each_entry(arvif, &ar->arvifs, list) { + if (arvif->vdev_type != WMI_VDEV_TYPE_STA) + continue; + + bss = &arvif->vif->bss_conf; + if (!arvif->is_up || !bss->assoc) + continue; + + rekey_data = &arvif->gtk_rekey_data; + if (!rekey_data->valid) + continue; + + if (gtk_offload) + rekey_data->enable_offload = WMI_GTK_OFFLOAD_ENABLE; + else + rekey_data->enable_offload = WMI_GTK_OFFLOAD_DISABLE; + ret = ath10k_wmi_gtk_offload(ar, arvif); + if (ret) { + ath10k_err(ar, "GTK offload failed for vdev_id: %d\n", + arvif->vdev_id); + return ret; + } + } + + return 0; +} + int ath10k_wow_op_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) { @@ -233,7 +488,7 @@ int ath10k_wow_op_suspend(struct ieee80211_hw *hw, mutex_lock(&ar->conf_mutex); if (WARN_ON(!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT, - ar->fw_features))) { + ar->running_fw->fw_file.fw_features))) { ret = 1; goto exit; } @@ -245,10 +500,29 @@ int ath10k_wow_op_suspend(struct ieee80211_hw *hw, goto exit; } + ret = ath10k_wow_config_gtk_offload(ar, true); + if (ret) { + ath10k_warn(ar, "failed to enable GTK offload: %d\n", ret); + goto exit; + } + + ret = ath10k_wow_enable_ns_arp_offload(ar, true); + if (ret) { + ath10k_warn(ar, "failed to enable ARP-NS offload: %d\n", ret); + goto disable_gtk_offload; + } + ret = ath10k_wow_set_wakeups(ar, wowlan); if (ret) { ath10k_warn(ar, "failed to set wow wakeup events: %d\n", ret); + goto disable_ns_arp_offload; + } + + ret = ath10k_config_wow_listen_interval(ar); + if (ret) { + ath10k_warn(ar, "failed to config wow listen interval: %d\n", + ret); goto cleanup; } @@ -272,11 +546,68 @@ wakeup: cleanup: ath10k_wow_cleanup(ar); +disable_ns_arp_offload: + ath10k_wow_enable_ns_arp_offload(ar, false); + +disable_gtk_offload: + ath10k_wow_config_gtk_offload(ar, false); exit: mutex_unlock(&ar->conf_mutex); return ret ? 1 : 0; } +void ath10k_wow_op_set_wakeup(struct ieee80211_hw *hw, bool enabled) +{ + struct ath10k *ar = hw->priv; + + mutex_lock(&ar->conf_mutex); + if (test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT, + ar->running_fw->fw_file.fw_features)) { + device_set_wakeup_enable(ar->dev, enabled); + } + mutex_unlock(&ar->conf_mutex); +} + +static void ath10k_wow_op_report_wakeup_reason(struct ath10k *ar) +{ + struct cfg80211_wowlan_wakeup *wakeup = &ar->wow.wakeup; + struct ath10k_vif *arvif; + + memset(wakeup, 0, sizeof(struct cfg80211_wowlan_wakeup)); + switch (ar->wow.wakeup_reason) { + case WOW_REASON_UNSPECIFIED: + wakeup = NULL; + break; + case WOW_REASON_RECV_MAGIC_PATTERN: + wakeup->magic_pkt = true; + break; + case WOW_REASON_DEAUTH_RECVD: + case WOW_REASON_DISASSOC_RECVD: + case WOW_REASON_AP_ASSOC_LOST: + case WOW_REASON_CSA_EVENT: + wakeup->disconnect = true; + break; + case WOW_REASON_GTK_HS_ERR: + wakeup->gtk_rekey_failure = true; + break; + } + ar->wow.wakeup_reason = WOW_REASON_UNSPECIFIED; + + if (wakeup) { + wakeup->pattern_idx = -1; + list_for_each_entry(arvif, &ar->arvifs, list) { + ieee80211_report_wowlan_wakeup(arvif->vif, + wakeup, GFP_KERNEL); + if (wakeup->disconnect) + ieee80211_resume_disconnect(arvif->vif); + } + } else { + list_for_each_entry(arvif, &ar->arvifs, list) + ieee80211_report_wowlan_wakeup(arvif->vif, + NULL, GFP_KERNEL); + } +} + int ath10k_wow_op_resume(struct ieee80211_hw *hw) { struct ath10k *ar = hw->priv; @@ -285,7 +616,7 @@ int ath10k_wow_op_resume(struct ieee80211_hw *hw) mutex_lock(&ar->conf_mutex); if (WARN_ON(!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT, - ar->fw_features))) { + ar->running_fw->fw_file.fw_features))) { ret = 1; goto exit; } @@ -297,8 +628,20 @@ int ath10k_wow_op_resume(struct ieee80211_hw *hw) } ret = ath10k_wow_wakeup(ar); - if (ret) + if (ret) { ath10k_warn(ar, "failed to wakeup from wow: %d\n", ret); + goto exit; + } + + ret = ath10k_wow_enable_ns_arp_offload(ar, false); + if (ret) { + ath10k_warn(ar, "failed to disable ARP-NS offload: %d\n", ret); + goto exit; + } + + ret = ath10k_wow_config_gtk_offload(ar, false); + if (ret) + ath10k_warn(ar, "failed to disable GTK offload: %d\n", ret); exit: if (ret) { @@ -319,13 +662,15 @@ exit: } } + ath10k_wow_op_report_wakeup_reason(ar); mutex_unlock(&ar->conf_mutex); return ret; } int ath10k_wow_init(struct ath10k *ar) { - if (!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT, ar->fw_features)) + if (!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT, + ar->running_fw->fw_file.fw_features)) return 0; if (WARN_ON(!test_bit(WMI_SERVICE_WOW, ar->wmi.svc_map))) @@ -334,6 +679,15 @@ int ath10k_wow_init(struct ath10k *ar) ar->wow.wowlan_support = ath10k_wowlan_support; ar->wow.wowlan_support.n_patterns = ar->wow.max_num_patterns; ar->hw->wiphy->wowlan = &ar->wow.wowlan_support; + device_init_wakeup(ar->dev, true); return 0; } + +void ath10k_wow_deinit(struct ath10k *ar) +{ + if (test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT, + ar->running_fw->fw_file.fw_features) && + test_bit(WMI_SERVICE_WOW, ar->wmi.svc_map)) + device_init_wakeup(ar->dev, false); +} diff --git a/drivers/net/wireless/ath/ath10k/wow.h b/drivers/net/wireless/ath/ath10k/wow.h index abbb04b6d1bd..2ca4ba4848c9 100644 --- a/drivers/net/wireless/ath/ath10k/wow.h +++ b/drivers/net/wireless/ath/ath10k/wow.h @@ -17,18 +17,21 @@ #define _WOW_H_ struct ath10k_wow { + u32 wakeup_reason; u32 max_num_patterns; struct completion wakeup_completed; + struct cfg80211_wowlan_wakeup wakeup; struct wiphy_wowlan_support wowlan_support; }; #ifdef CONFIG_PM int ath10k_wow_init(struct ath10k *ar); +void ath10k_wow_deinit(struct ath10k *ar); int ath10k_wow_op_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan); int ath10k_wow_op_resume(struct ieee80211_hw *hw); - +void ath10k_wow_op_set_wakeup(struct ieee80211_hw *hw, bool enabled); #else static inline int ath10k_wow_init(struct ath10k *ar) @@ -36,5 +39,8 @@ static inline int ath10k_wow_init(struct ath10k *ar) return 0; } +void ath10k_wow_deinit(struct ath10k *ar) +{ +} #endif /* CONFIG_PM */ #endif /* _WOW_H_ */ diff --git a/drivers/net/wireless/ath/regd.c b/drivers/net/wireless/ath/regd.c index 62077bda8dde..5cde46c82a03 100644 --- a/drivers/net/wireless/ath/regd.c +++ b/drivers/net/wireless/ath/regd.c @@ -38,28 +38,28 @@ static int __ath_regd_init(struct ath_regulatory *reg); /* We enable active scan on these a case by case basis by regulatory domain */ #define ATH9K_2GHZ_CH12_13 REG_RULE(2467-10, 2472+10, 40, 0, 20,\ NL80211_RRF_NO_IR) -#define ATH9K_2GHZ_CH14 REG_RULE(2484-10, 2484+10, 40, 0, 20,\ +#define ATH9K_2GHZ_CH14 REG_RULE(2484 - 10, 2484 + 10, 20, 0, 20,\ NL80211_RRF_NO_IR | \ NL80211_RRF_NO_OFDM) /* We allow IBSS on these on a case by case basis by regulatory domain */ -#define ATH9K_5GHZ_5150_5350 REG_RULE(5150-10, 5350+10, 80, 0, 30,\ +#define ATH9K_5GHZ_5180_5320 REG_RULE(5180 - 10, 5320 + 10, 160, 0, 20,\ NL80211_RRF_NO_IR) -#define ATH9K_5GHZ_5470_5850 REG_RULE(5470-10, 5850+10, 80, 0, 30,\ +#define ATH9K_5GHZ_5500_5825 REG_RULE(5500 - 10, 5825 + 10, 80, 0, 20,\ NL80211_RRF_NO_IR) -#define ATH9K_5GHZ_5725_5850 REG_RULE(5725-10, 5850+10, 80, 0, 30,\ +#define ATH9K_5GHZ_5745_5825 REG_RULE(5745 - 10, 5825 + 10, 80, 0, 20,\ NL80211_RRF_NO_IR) #define ATH9K_2GHZ_ALL ATH9K_2GHZ_CH01_11, \ ATH9K_2GHZ_CH12_13, \ ATH9K_2GHZ_CH14 -#define ATH9K_5GHZ_ALL ATH9K_5GHZ_5150_5350, \ - ATH9K_5GHZ_5470_5850 +#define ATH9K_5GHZ_ALL ATH9K_5GHZ_5180_5320, \ + ATH9K_5GHZ_5500_5825 /* This one skips what we call "mid band" */ -#define ATH9K_5GHZ_NO_MIDBAND ATH9K_5GHZ_5150_5350, \ - ATH9K_5GHZ_5725_5850 +#define ATH9K_5GHZ_NO_MIDBAND ATH9K_5GHZ_5180_5320, \ + ATH9K_5GHZ_5745_5825 /* Can be used for: * 0x60, 0x61, 0x62 */ @@ -260,7 +260,7 @@ static bool ath_is_radar_freq(u16 center_freq, { if (reg->country_code == CTRY_INDIA) return (center_freq >= 5500 && center_freq <= 5700); - return (center_freq >= 5260 && center_freq <= 5700); + return (center_freq >= 5260 && center_freq <= 5720); } static void ath_force_clear_no_ir_chan(struct wiphy *wiphy, @@ -636,6 +636,8 @@ ath_regd_init_wiphy(struct ath_regulatory *reg, struct regulatory_request *request)) { const struct ieee80211_regdomain *regd; + u32 chan_num; + struct ieee80211_channel *chan; wiphy->reg_notifier = reg_notifier; wiphy->regulatory_flags |= REGULATORY_STRICT_REG | @@ -658,6 +660,20 @@ ath_regd_init_wiphy(struct ath_regulatory *reg, } wiphy_apply_custom_regulatory(wiphy, regd); + + /* For regulatory rules similar to the following: + * REG_RULE(2412-10, 2462+10, 40, 0, 20, 0), channels 12/13 are enabled + * due to support of 5/10 MHz. + * Therefore, disable 2.4 Ghz channels that dont have 20 mhz bw + */ + for (chan_num = 0; + chan_num < wiphy->bands[IEEE80211_BAND_2GHZ]->n_channels; + chan_num++) { + chan = &wiphy->bands[IEEE80211_BAND_2GHZ]->channels[chan_num]; + if (chan->flags & IEEE80211_CHAN_NO_20MHZ) + chan->flags |= IEEE80211_CHAN_DISABLED; + } + ath_reg_apply_radar_flags(wiphy, reg); ath_reg_apply_world_flags(wiphy, NL80211_REGDOM_SET_BY_DRIVER, reg); return 0; diff --git a/drivers/net/wireless/ath/wil6210/Kconfig b/drivers/net/wireless/ath/wil6210/Kconfig index 6dfedc8bd6a3..2ab6c5951561 100644 --- a/drivers/net/wireless/ath/wil6210/Kconfig +++ b/drivers/net/wireless/ath/wil6210/Kconfig @@ -40,3 +40,35 @@ config WIL6210_TRACING option if you are interested in debugging the driver. If unsure, say Y to make it easier to debug problems. + +config WIL6210_WRITE_IOCTL + bool "wil6210 write ioctl to the device" + depends on WIL6210 + default y + ---help--- + Say Y here to allow write-access from user-space to + the device memory through ioctl. This is useful for + debugging purposes only. + + If unsure, say N. + +config WIL6210_PLATFORM_MSM + bool "wil6210 MSM platform specific support" + depends on WIL6210 + depends on ARCH_QCOM + default y + ---help--- + Say Y here to enable wil6210 driver support for MSM + platform specific features + +config WIL6210_DEBUGFS + bool "wil6210 debugfs support" + depends on WIL6210 + depends on DEBUG_FS + default y + ---help--- + Say Y here to enable wil6210 debugfs support, using the + kernel debugfs infrastructure. Select this + option if you are interested in debugging the driver. + + If unsure, say Y to make it easier to debug problems. diff --git a/drivers/net/wireless/ath/wil6210/Makefile b/drivers/net/wireless/ath/wil6210/Makefile index fdf63d5fe82b..94df1decae7a 100644 --- a/drivers/net/wireless/ath/wil6210/Makefile +++ b/drivers/net/wireless/ath/wil6210/Makefile @@ -4,7 +4,8 @@ wil6210-y := main.o wil6210-y += netdev.o wil6210-y += cfg80211.o wil6210-y += pcie_bus.o -wil6210-y += debugfs.o +wil6210-$(CONFIG_WIL6210_DEBUGFS) += debugfs.o +wil6210-y += sysfs.o wil6210-y += wmi.o wil6210-y += interrupt.o wil6210-y += txrx.o @@ -18,8 +19,13 @@ wil6210-$(CONFIG_WIL6210_TRACING) += trace.o wil6210-y += wil_platform.o wil6210-y += ethtool.o wil6210-y += wil_crash_dump.o +wil6210-y += p2p.o +wil6210-y += ftm.o # for tracing framework to find trace.h CFLAGS_trace.o := -I$(src) subdir-ccflags-y += -D__CHECK_ENDIAN__ + +MSM_11AD_PATH = drivers/platform/msm/msm_11ad +CFLAGS_wil_platform.o := -I$(MSM_11AD_PATH) diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index 20d07ef679e8..797c157fa0ba 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,8 +15,27 @@ */ #include <linux/etherdevice.h> +#include <linux/moduleparam.h> +#include <net/netlink.h> #include "wil6210.h" #include "wmi.h" +#include "ftm.h" + +#define WIL_MAX_ROC_DURATION_MS 5000 + +bool disable_ap_sme; +module_param(disable_ap_sme, bool, 0444); +MODULE_PARM_DESC(disable_ap_sme, " let user space handle AP mode SME"); + +static bool ignore_reg_hints = true; +module_param(ignore_reg_hints, bool, 0444); +MODULE_PARM_DESC(ignore_reg_hints, " Ignore OTA regulatory hints (Default: true)"); + +#ifdef CONFIG_PM +static struct wiphy_wowlan_support wil_wowlan_support = { + .flags = WIPHY_WOWLAN_ANY | WIPHY_WOWLAN_DISCONNECT, +}; +#endif #define CHAN60G(_channel, _flags) { \ .band = IEEE80211_BAND_60GHZ, \ @@ -34,6 +53,193 @@ static struct ieee80211_channel wil_60ghz_channels[] = { /* channel 4 not supported yet */ }; +/* Vendor id to be used in vendor specific command and events + * to user space. + * NOTE: The authoritative place for definition of QCA_NL80211_VENDOR_ID, + * vendor subcmd definitions prefixed with QCA_NL80211_VENDOR_SUBCMD, and + * qca_wlan_vendor_attr is open source file src/common/qca-vendor.h in + * git://w1.fi/srv/git/hostap.git; the values here are just a copy of that + */ + +#define QCA_NL80211_VENDOR_ID 0x001374 + +#define WIL_MAX_RF_SECTORS (128) +#define WIL_CID_ALL (0xff) + +enum qca_wlan_vendor_attr_rf_sector { + QCA_ATTR_MAC_ADDR = 6, + QCA_ATTR_PAD = 13, + QCA_ATTR_TSF = 29, + QCA_ATTR_DMG_RF_SECTOR_INDEX = 30, + QCA_ATTR_DMG_RF_SECTOR_TYPE = 31, + QCA_ATTR_DMG_RF_MODULE_MASK = 32, + QCA_ATTR_DMG_RF_SECTOR_CFG = 33, + QCA_ATTR_DMG_RF_SECTOR_MAX, +}; + +enum qca_wlan_vendor_attr_dmg_rf_sector_type { + QCA_ATTR_DMG_RF_SECTOR_TYPE_RX, + QCA_ATTR_DMG_RF_SECTOR_TYPE_TX, + QCA_ATTR_DMG_RF_SECTOR_TYPE_MAX +}; + +enum qca_wlan_vendor_attr_dmg_rf_sector_cfg { + QCA_ATTR_DMG_RF_SECTOR_CFG_INVALID = 0, + QCA_ATTR_DMG_RF_SECTOR_CFG_MODULE_INDEX, + QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE0, + QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE1, + QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE2, + QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_HI, + QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_LO, + QCA_ATTR_DMG_RF_SECTOR_CFG_DTYPE_X16, + + /* keep last */ + QCA_ATTR_DMG_RF_SECTOR_CFG_AFTER_LAST, + QCA_ATTR_DMG_RF_SECTOR_CFG_MAX = + QCA_ATTR_DMG_RF_SECTOR_CFG_AFTER_LAST - 1 +}; + +static const struct +nla_policy wil_rf_sector_policy[QCA_ATTR_DMG_RF_SECTOR_MAX + 1] = { + [QCA_ATTR_MAC_ADDR] = { .len = ETH_ALEN }, + [QCA_ATTR_DMG_RF_SECTOR_INDEX] = { .type = NLA_U16 }, + [QCA_ATTR_DMG_RF_SECTOR_TYPE] = { .type = NLA_U8 }, + [QCA_ATTR_DMG_RF_MODULE_MASK] = { .type = NLA_U32 }, + [QCA_ATTR_DMG_RF_SECTOR_CFG] = { .type = NLA_NESTED }, +}; + +static const struct +nla_policy wil_rf_sector_cfg_policy[QCA_ATTR_DMG_RF_SECTOR_CFG_MAX + 1] = { + [QCA_ATTR_DMG_RF_SECTOR_CFG_MODULE_INDEX] = { .type = NLA_U8 }, + [QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE0] = { .type = NLA_U32 }, + [QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE1] = { .type = NLA_U32 }, + [QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE2] = { .type = NLA_U32 }, + [QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_HI] = { .type = NLA_U32 }, + [QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_LO] = { .type = NLA_U32 }, + [QCA_ATTR_DMG_RF_SECTOR_CFG_DTYPE_X16] = { .type = NLA_U32 }, +}; + +enum qca_nl80211_vendor_subcmds { + QCA_NL80211_VENDOR_SUBCMD_LOC_GET_CAPA = 128, + QCA_NL80211_VENDOR_SUBCMD_FTM_START_SESSION = 129, + QCA_NL80211_VENDOR_SUBCMD_FTM_ABORT_SESSION = 130, + QCA_NL80211_VENDOR_SUBCMD_FTM_MEAS_RESULT = 131, + QCA_NL80211_VENDOR_SUBCMD_FTM_SESSION_DONE = 132, + QCA_NL80211_VENDOR_SUBCMD_FTM_CFG_RESPONDER = 133, + QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS = 134, + QCA_NL80211_VENDOR_SUBCMD_AOA_ABORT_MEAS = 135, + QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS_RESULT = 136, + QCA_NL80211_VENDOR_SUBCMD_DMG_RF_GET_SECTOR_CFG = 139, + QCA_NL80211_VENDOR_SUBCMD_DMG_RF_SET_SECTOR_CFG = 140, + QCA_NL80211_VENDOR_SUBCMD_DMG_RF_GET_SELECTED_SECTOR = 141, + QCA_NL80211_VENDOR_SUBCMD_DMG_RF_SET_SELECTED_SECTOR = 142, +}; + +static int wil_rf_sector_get_cfg(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len); +static int wil_rf_sector_set_cfg(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len); +static int wil_rf_sector_get_selected(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len); +static int wil_rf_sector_set_selected(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len); + +/* vendor specific commands */ +static const struct wiphy_vendor_command wil_nl80211_vendor_commands[] = { + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_LOC_GET_CAPA, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_ftm_get_capabilities + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_FTM_START_SESSION, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_ftm_start_session + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_FTM_ABORT_SESSION, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_ftm_abort_session + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_FTM_CFG_RESPONDER, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_ftm_configure_responder + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_aoa_start_measurement + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_AOA_ABORT_MEAS, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_aoa_abort_measurement + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_DMG_RF_GET_SECTOR_CFG, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_rf_sector_get_cfg + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_DMG_RF_SET_SECTOR_CFG, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_rf_sector_set_cfg + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = + QCA_NL80211_VENDOR_SUBCMD_DMG_RF_GET_SELECTED_SECTOR, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_rf_sector_get_selected + }, + { + .info.vendor_id = QCA_NL80211_VENDOR_ID, + .info.subcmd = + QCA_NL80211_VENDOR_SUBCMD_DMG_RF_SET_SELECTED_SECTOR, + .flags = WIPHY_VENDOR_CMD_NEED_WDEV | + WIPHY_VENDOR_CMD_NEED_RUNNING, + .doit = wil_rf_sector_set_selected + }, +}; + +/* vendor specific events */ +static const struct nl80211_vendor_cmd_info wil_nl80211_vendor_events[] = { + [QCA_NL80211_VENDOR_EVENT_FTM_MEAS_RESULT_INDEX] = { + .vendor_id = QCA_NL80211_VENDOR_ID, + .subcmd = QCA_NL80211_VENDOR_SUBCMD_FTM_MEAS_RESULT + }, + [QCA_NL80211_VENDOR_EVENT_FTM_SESSION_DONE_INDEX] = { + .vendor_id = QCA_NL80211_VENDOR_ID, + .subcmd = QCA_NL80211_VENDOR_SUBCMD_FTM_SESSION_DONE + }, + [QCA_NL80211_VENDOR_EVENT_AOA_MEAS_RESULT_INDEX] = { + .vendor_id = QCA_NL80211_VENDOR_ID, + .subcmd = QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS_RESULT + }, +}; + static struct ieee80211_supported_band wil_band_60ghz = { .channels = wil_60ghz_channels, .n_channels = ARRAY_SIZE(wil_60ghz_channels), @@ -60,9 +266,16 @@ wil_mgmt_stypes[NUM_NL80211_IFTYPES] = { }, [NL80211_IFTYPE_AP] = { .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | - BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + BIT(IEEE80211_STYPE_PROBE_RESP >> 4) | + BIT(IEEE80211_STYPE_ASSOC_RESP >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4), .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | - BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) }, [NL80211_IFTYPE_P2P_CLIENT] = { .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | @@ -76,12 +289,24 @@ wil_mgmt_stypes[NUM_NL80211_IFTYPES] = { .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) }, + [NL80211_IFTYPE_P2P_DEVICE] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, }; static const u32 wil_cipher_suites[] = { WLAN_CIPHER_SUITE_GCMP, }; +static const char * const key_usage_str[] = { + [WMI_KEY_USE_PAIRWISE] = "PTK", + [WMI_KEY_USE_RX_GROUP] = "RX_GTK", + [WMI_KEY_USE_TX_GROUP] = "TX_GTK", +}; + int wil_iftype_nl2wmi(enum nl80211_iftype type) { static const struct { @@ -113,7 +338,7 @@ int wil_cid_fill_sinfo(struct wil6210_priv *wil, int cid, .interval_usec = 0, }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_notify_req_done_event evt; } __packed reply; struct wil_net_stats *stats = &wil->sta[cid].stats; @@ -126,12 +351,12 @@ int wil_cid_fill_sinfo(struct wil6210_priv *wil, int cid, wil_dbg_wmi(wil, "Link status for CID %d: {\n" " MCS %d TSF 0x%016llx\n" - " BF status 0x%08x SNR 0x%08x SQI %d%%\n" + " BF status 0x%08x RSSI %d SQI %d%%\n" " Tx Tpt %d goodput %d Rx goodput %d\n" " Sectors(rx:tx) my %d:%d peer %d:%d\n""}\n", cid, le16_to_cpu(reply.evt.bf_mcs), le64_to_cpu(reply.evt.tsf), reply.evt.status, - le32_to_cpu(reply.evt.snr_val), + reply.evt.rssi, reply.evt.sqi, le32_to_cpu(reply.evt.tx_tpt), le32_to_cpu(reply.evt.tx_goodput), @@ -165,7 +390,11 @@ int wil_cid_fill_sinfo(struct wil6210_priv *wil, int cid, if (test_bit(wil_status_fwconnected, wil->status)) { sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL); - sinfo->signal = reply.evt.sqi; + if (test_bit(WMI_FW_CAPABILITY_RSSI_REPORTING, + wil->fw_capabilities)) + sinfo->signal = reply.evt.rssi; + else + sinfo->signal = reply.evt.sqi; } return rc; @@ -180,7 +409,7 @@ static int wil_cfg80211_get_station(struct wiphy *wiphy, int cid = wil_find_cid(wil, mac); - wil_dbg_misc(wil, "%s(%pM) CID %d\n", __func__, mac, cid); + wil_dbg_misc(wil, "get_station: %pM CID %d\n", mac, cid); if (cid < 0) return cid; @@ -219,20 +448,116 @@ static int wil_cfg80211_dump_station(struct wiphy *wiphy, return -ENOENT; ether_addr_copy(mac, wil->sta[cid].addr); - wil_dbg_misc(wil, "%s(%pM) CID %d\n", __func__, mac, cid); + wil_dbg_misc(wil, "dump_station: %pM CID %d\n", mac, cid); rc = wil_cid_fill_sinfo(wil, cid, sinfo); return rc; } +static int wil_cfg80211_start_p2p_device(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_misc(wil, "start_p2p_device: entered\n"); + wil->p2p.p2p_dev_started = 1; + return 0; +} + +static void wil_cfg80211_stop_p2p_device(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct wil_p2p_info *p2p = &wil->p2p; + + if (!p2p->p2p_dev_started) + return; + + wil_dbg_misc(wil, "stop_p2p_device: entered\n"); + mutex_lock(&wil->mutex); + mutex_lock(&wil->p2p_wdev_mutex); + wil_p2p_stop_radio_operations(wil); + p2p->p2p_dev_started = 0; + mutex_unlock(&wil->p2p_wdev_mutex); + mutex_unlock(&wil->mutex); +} + +static struct wireless_dev * +wil_cfg80211_add_iface(struct wiphy *wiphy, const char *name, + unsigned char name_assign_type, + enum nl80211_iftype type, + u32 *flags, struct vif_params *params) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct net_device *ndev = wil_to_ndev(wil); + struct wireless_dev *p2p_wdev; + + wil_dbg_misc(wil, "add_iface\n"); + + if (type != NL80211_IFTYPE_P2P_DEVICE) { + wil_err(wil, "unsupported iftype %d\n", type); + return ERR_PTR(-EINVAL); + } + + if (wil->p2p_wdev) { + wil_err(wil, "P2P_DEVICE interface already created\n"); + return ERR_PTR(-EINVAL); + } + + p2p_wdev = kzalloc(sizeof(*p2p_wdev), GFP_KERNEL); + if (!p2p_wdev) + return ERR_PTR(-ENOMEM); + + p2p_wdev->iftype = type; + p2p_wdev->wiphy = wiphy; + /* use our primary ethernet address */ + ether_addr_copy(p2p_wdev->address, ndev->perm_addr); + + wil->p2p_wdev = p2p_wdev; + + return p2p_wdev; +} + +static int wil_cfg80211_del_iface(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_misc(wil, "del_iface\n"); + + if (wdev != wil->p2p_wdev) { + wil_err(wil, "delete of incorrect interface 0x%p\n", wdev); + return -EINVAL; + } + + wil_cfg80211_stop_p2p_device(wiphy, wdev); + wil_p2p_wdev_free(wil); + + return 0; +} + static int wil_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, enum nl80211_iftype type, u32 *flags, struct vif_params *params) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); - struct wireless_dev *wdev = wil->wdev; + struct wireless_dev *wdev = wil_to_wdev(wil); + int rc; + + wil_dbg_misc(wil, "change_iface: type=%d\n", type); + + if (netif_running(wil_to_ndev(wil)) && !wil_is_recovery_blocked(wil)) { + wil_dbg_misc(wil, "interface is up. resetting...\n"); + mutex_lock(&wil->mutex); + __wil_down(wil); + rc = __wil_up(wil); + mutex_unlock(&wil->mutex); + + if (rc) + return rc; + } switch (type) { case NL80211_IFTYPE_STATION: @@ -260,7 +585,7 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); - struct wireless_dev *wdev = wil->wdev; + struct wireless_dev *wdev = request->wdev; struct { struct wmi_start_scan_cmd cmd; u16 chnl[4]; @@ -268,15 +593,13 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, uint i, n; int rc; - if (wil->scan_request) { - wil_err(wil, "Already scanning\n"); - return -EAGAIN; - } + wil_dbg_misc(wil, "scan: wdev=0x%p iftype=%d\n", wdev, wdev->iftype); /* check we are client side */ switch (wdev->iftype) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_DEVICE: break; default: return -EOPNOTSUPP; @@ -288,14 +611,46 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, return -EBUSY; } + mutex_lock(&wil->mutex); + + mutex_lock(&wil->p2p_wdev_mutex); + if (wil->scan_request || wil->p2p.discovery_started) { + wil_err(wil, "Already scanning\n"); + mutex_unlock(&wil->p2p_wdev_mutex); + rc = -EAGAIN; + goto out; + } + mutex_unlock(&wil->p2p_wdev_mutex); + + if (wdev->iftype == NL80211_IFTYPE_P2P_DEVICE) { + if (!wil->p2p.p2p_dev_started) { + wil_err(wil, "P2P search requested on stopped P2P device\n"); + rc = -EIO; + goto out; + } + /* social scan on P2P_DEVICE is handled as p2p search */ + if (wil_p2p_is_social_scan(request)) { + wil->scan_request = request; + wil->radio_wdev = wdev; + rc = wil_p2p_search(wil, request); + if (rc) { + wil->radio_wdev = wil_to_wdev(wil); + wil->scan_request = NULL; + } + goto out; + } + } + + (void)wil_p2p_stop_discovery(wil); + wil_dbg_misc(wil, "Start scan_request 0x%p\n", request); wil_dbg_misc(wil, "SSID count: %d", request->n_ssids); for (i = 0; i < request->n_ssids; i++) { wil_dbg_misc(wil, "SSID[%d]", i); - print_hex_dump_bytes("SSID ", DUMP_PREFIX_OFFSET, - request->ssids[i].ssid, - request->ssids[i].ssid_len); + wil_hex_dump_misc("SSID ", DUMP_PREFIX_OFFSET, 16, 1, + request->ssids[i].ssid, + request->ssids[i].ssid_len, true); } if (request->n_ssids) @@ -306,13 +661,14 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, if (rc) { wil_err(wil, "set SSID for scan request failed: %d\n", rc); - return rc; + goto out; } wil->scan_request = request; mod_timer(&wil->scan_timer, jiffies + WIL6210_SCAN_TO); memset(&cmd, 0, sizeof(cmd)); + cmd.cmd.scan_type = WMI_ACTIVE_SCAN; cmd.cmd.num_channels = 0; n = min(request->n_channels, 4U); for (i = 0; i < n; i++) { @@ -331,27 +687,63 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, } if (request->ie_len) - print_hex_dump_bytes("Scan IE ", DUMP_PREFIX_OFFSET, - request->ie, request->ie_len); + wil_hex_dump_misc("Scan IE ", DUMP_PREFIX_OFFSET, 16, 1, + request->ie, request->ie_len, true); else wil_dbg_misc(wil, "Scan has no IE's\n"); rc = wmi_set_ie(wil, WMI_FRAME_PROBE_REQ, request->ie_len, request->ie); if (rc) - goto out; + goto out_restore; + if (wil->discovery_mode && cmd.cmd.scan_type == WMI_ACTIVE_SCAN) { + cmd.cmd.discovery_mode = 1; + wil_dbg_misc(wil, "active scan with discovery_mode=1\n"); + } + + wil->radio_wdev = wdev; rc = wmi_send(wil, WMI_START_SCAN_CMDID, &cmd, sizeof(cmd.cmd) + cmd.cmd.num_channels * sizeof(cmd.cmd.channel_list[0])); -out: +out_restore: if (rc) { del_timer_sync(&wil->scan_timer); + wil->radio_wdev = wil_to_wdev(wil); wil->scan_request = NULL; } - +out: + mutex_unlock(&wil->mutex); return rc; } +static void wil_cfg80211_abort_scan(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_misc(wil, "wdev=0x%p iftype=%d\n", wdev, wdev->iftype); + + mutex_lock(&wil->mutex); + mutex_lock(&wil->p2p_wdev_mutex); + + if (!wil->scan_request) + goto out; + + if (wdev != wil->scan_request->wdev) { + wil_dbg_misc(wil, "abort scan was called on the wrong iface\n"); + goto out; + } + + if (wil->radio_wdev == wil->p2p_wdev) + wil_p2p_stop_radio_operations(wil); + else + wil_abort_scan(wil, true); + +out: + mutex_unlock(&wil->p2p_wdev_mutex); + mutex_unlock(&wil->mutex); +} + static void wil_print_crypto(struct wil6210_priv *wil, struct cfg80211_crypto_settings *c) { @@ -390,6 +782,7 @@ static void wil_print_connect_params(struct wil6210_priv *wil, print_hex_dump(KERN_INFO, " SSID: ", DUMP_PREFIX_OFFSET, 16, 1, sme->ssid, sme->ssid_len, true); wil_info(wil, " Privacy: %s\n", sme->privacy ? "secure" : "open"); + wil_info(wil, " PBSS: %d\n", sme->pbss); wil_print_crypto(wil, &sme->crypto); } @@ -404,7 +797,9 @@ static int wil_cfg80211_connect(struct wiphy *wiphy, const u8 *rsn_eid; int ch; int rc = 0; + enum ieee80211_bss_type bss_type = IEEE80211_BSS_TYPE_ESS; + wil_dbg_misc(wil, "connect\n"); wil_print_connect_params(wil, sme); if (test_bit(wil_status_fwconnecting, wil->status) || @@ -422,9 +817,12 @@ static int wil_cfg80211_connect(struct wiphy *wiphy, if (sme->privacy && !rsn_eid) wil_info(wil, "WSC connection\n"); + if (sme->pbss) + bss_type = IEEE80211_BSS_TYPE_PBSS; + bss = cfg80211_get_bss(wiphy, sme->channel, sme->bssid, sme->ssid, sme->ssid_len, - IEEE80211_BSS_TYPE_ESS, IEEE80211_PRIVACY_ANY); + bss_type, IEEE80211_PRIVACY_ANY); if (!bss) { wil_err(wil, "Unable to find BSS\n"); return -ENOENT; @@ -437,6 +835,7 @@ static int wil_cfg80211_connect(struct wiphy *wiphy, goto out; } wil->privacy = sme->privacy; + wil->pbss = sme->pbss; if (wil->privacy) { /* For secure assoc, remove old keys */ @@ -513,9 +912,11 @@ static int wil_cfg80211_connect(struct wiphy *wiphy, rc = wmi_send(wil, WMI_CONNECT_CMDID, &conn, sizeof(conn)); if (rc == 0) { netif_carrier_on(ndev); + wil6210_bus_request(wil, WIL_MAX_BUS_REQUEST_KBPS); + wil->bss = bss; /* Connect can take lots of time */ mod_timer(&wil->connect_timer, - jiffies + msecs_to_jiffies(2000)); + jiffies + msecs_to_jiffies(5000)); } else { clear_bit(wil_status_fwconnecting, wil->status); } @@ -533,30 +934,79 @@ static int wil_cfg80211_disconnect(struct wiphy *wiphy, int rc; struct wil6210_priv *wil = wiphy_to_wil(wiphy); - wil_dbg_misc(wil, "%s(reason=%d)\n", __func__, reason_code); + wil_dbg_misc(wil, "disconnect: reason=%d\n", reason_code); - rc = wmi_send(wil, WMI_DISCONNECT_CMDID, NULL, 0); + if (!(test_bit(wil_status_fwconnecting, wil->status) || + test_bit(wil_status_fwconnected, wil->status))) { + wil_err(wil, "Disconnect was called while disconnected\n"); + return 0; + } + + wil->locally_generated_disc = true; + rc = wmi_call(wil, WMI_DISCONNECT_CMDID, NULL, 0, + WMI_DISCONNECT_EVENTID, NULL, 0, + WIL6210_DISCONNECT_TO_MS); + if (rc) + wil_err(wil, "disconnect error %d\n", rc); return rc; } +static int wil_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + int rc; + + /* these parameters are explicitly not supported */ + if (changed & (WIPHY_PARAM_RETRY_LONG | + WIPHY_PARAM_FRAG_THRESHOLD | + WIPHY_PARAM_RTS_THRESHOLD)) + return -ENOTSUPP; + + if (changed & WIPHY_PARAM_RETRY_SHORT) { + rc = wmi_set_mgmt_retry(wil, wiphy->retry_short); + if (rc) + return rc; + } + + return 0; +} + int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, u64 *cookie) { const u8 *buf = params->buf; - size_t len = params->len; + size_t len = params->len, total; struct wil6210_priv *wil = wiphy_to_wil(wiphy); int rc; bool tx_status = false; struct ieee80211_mgmt *mgmt_frame = (void *)buf; struct wmi_sw_tx_req_cmd *cmd; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_sw_tx_complete_event evt; } __packed evt; - cmd = kmalloc(sizeof(*cmd) + len, GFP_KERNEL); + /* Note, currently we do not support the "wait" parameter, user-space + * must call remain_on_channel before mgmt_tx or listen on a channel + * another way (AP/PCP or connected station) + * in addition we need to check if specified "chan" argument is + * different from currently "listened" channel and fail if it is. + */ + + wil_dbg_misc(wil, "mgmt_tx\n"); + wil_hex_dump_misc("mgmt tx frame ", DUMP_PREFIX_OFFSET, 16, 1, buf, + len, true); + + if (len < sizeof(struct ieee80211_hdr_3addr)) + return -EINVAL; + + total = sizeof(*cmd) + len; + if (total < len) + return -EINVAL; + + cmd = kmalloc(total, GFP_KERNEL); if (!cmd) { rc = -ENOMEM; goto out; @@ -566,7 +1016,7 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, cmd->len = cpu_to_le16(len); memcpy(cmd->payload, buf, len); - rc = wmi_call(wil, WMI_SW_TX_REQ_CMDID, cmd, sizeof(*cmd) + len, + rc = wmi_call(wil, WMI_SW_TX_REQ_CMDID, cmd, total, WMI_SW_TX_COMPLETE_EVENTID, &evt, sizeof(evt), 2000); if (rc == 0) tx_status = !evt.evt.status; @@ -582,7 +1032,7 @@ static int wil_cfg80211_set_channel(struct wiphy *wiphy, struct cfg80211_chan_def *chandef) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); - struct wireless_dev *wdev = wil->wdev; + struct wireless_dev *wdev = wil_to_wdev(wil); wdev->preset_chandef = *chandef; @@ -592,22 +1042,19 @@ static int wil_cfg80211_set_channel(struct wiphy *wiphy, static enum wmi_key_usage wil_detect_key_usage(struct wil6210_priv *wil, bool pairwise) { - struct wireless_dev *wdev = wil->wdev; + struct wireless_dev *wdev = wil_to_wdev(wil); enum wmi_key_usage rc; - static const char * const key_usage_str[] = { - [WMI_KEY_USE_PAIRWISE] = "WMI_KEY_USE_PAIRWISE", - [WMI_KEY_USE_RX_GROUP] = "WMI_KEY_USE_RX_GROUP", - [WMI_KEY_USE_TX_GROUP] = "WMI_KEY_USE_TX_GROUP", - }; if (pairwise) { rc = WMI_KEY_USE_PAIRWISE; } else { switch (wdev->iftype) { case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: rc = WMI_KEY_USE_RX_GROUP; break; case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: rc = WMI_KEY_USE_TX_GROUP; break; default: @@ -617,25 +1064,139 @@ static enum wmi_key_usage wil_detect_key_usage(struct wil6210_priv *wil, break; } } - wil_dbg_misc(wil, "%s() -> %s\n", __func__, key_usage_str[rc]); + wil_dbg_misc(wil, "detect_key_usage: -> %s\n", key_usage_str[rc]); return rc; } +static struct wil_sta_info * +wil_find_sta_by_key_usage(struct wil6210_priv *wil, + enum wmi_key_usage key_usage, const u8 *mac_addr) +{ + int cid = -EINVAL; + + if (key_usage == WMI_KEY_USE_TX_GROUP) + return NULL; /* not needed */ + + /* supplicant provides Rx group key in STA mode with NULL MAC address */ + if (mac_addr) + cid = wil_find_cid(wil, mac_addr); + else if (key_usage == WMI_KEY_USE_RX_GROUP) + cid = wil_find_cid_by_idx(wil, 0); + if (cid < 0) { + wil_err(wil, "No CID for %pM %s\n", mac_addr, + key_usage_str[key_usage]); + return ERR_PTR(cid); + } + + return &wil->sta[cid]; +} + +static void wil_set_crypto_rx(u8 key_index, enum wmi_key_usage key_usage, + struct wil_sta_info *cs, + struct key_params *params) +{ + struct wil_tid_crypto_rx_single *cc; + int tid; + + if (!cs) + return; + + switch (key_usage) { + case WMI_KEY_USE_PAIRWISE: + for (tid = 0; tid < WIL_STA_TID_NUM; tid++) { + cc = &cs->tid_crypto_rx[tid].key_id[key_index]; + if (params->seq) + memcpy(cc->pn, params->seq, + IEEE80211_GCMP_PN_LEN); + else + memset(cc->pn, 0, IEEE80211_GCMP_PN_LEN); + cc->key_set = true; + } + break; + case WMI_KEY_USE_RX_GROUP: + cc = &cs->group_crypto_rx.key_id[key_index]; + if (params->seq) + memcpy(cc->pn, params->seq, IEEE80211_GCMP_PN_LEN); + else + memset(cc->pn, 0, IEEE80211_GCMP_PN_LEN); + cc->key_set = true; + break; + default: + break; + } +} + +static void wil_del_rx_key(u8 key_index, enum wmi_key_usage key_usage, + struct wil_sta_info *cs) +{ + struct wil_tid_crypto_rx_single *cc; + int tid; + + if (!cs) + return; + + switch (key_usage) { + case WMI_KEY_USE_PAIRWISE: + for (tid = 0; tid < WIL_STA_TID_NUM; tid++) { + cc = &cs->tid_crypto_rx[tid].key_id[key_index]; + cc->key_set = false; + } + break; + case WMI_KEY_USE_RX_GROUP: + cc = &cs->group_crypto_rx.key_id[key_index]; + cc->key_set = false; + break; + default: + break; + } +} + static int wil_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, u8 key_index, bool pairwise, const u8 *mac_addr, struct key_params *params) { + int rc; struct wil6210_priv *wil = wiphy_to_wil(wiphy); enum wmi_key_usage key_usage = wil_detect_key_usage(wil, pairwise); + struct wil_sta_info *cs = wil_find_sta_by_key_usage(wil, key_usage, + mac_addr); + + if (!params) { + wil_err(wil, "NULL params\n"); + return -EINVAL; + } + + wil_dbg_misc(wil, "add_key: %pM %s[%d] PN %*phN\n", + mac_addr, key_usage_str[key_usage], key_index, + params->seq_len, params->seq); + + if (IS_ERR(cs)) { + wil_err(wil, "Not connected, %pM %s[%d] PN %*phN\n", + mac_addr, key_usage_str[key_usage], key_index, + params->seq_len, params->seq); + return -EINVAL; + } - wil_dbg_misc(wil, "%s(%pM[%d] %s)\n", __func__, mac_addr, key_index, - pairwise ? "PTK" : "GTK"); + wil_del_rx_key(key_index, key_usage, cs); - return wmi_add_cipher_key(wil, key_index, mac_addr, params->key_len, - params->key, key_usage); + if (params->seq && params->seq_len != IEEE80211_GCMP_PN_LEN) { + wil_err(wil, + "Wrong PN len %d, %pM %s[%d] PN %*phN\n", + params->seq_len, mac_addr, + key_usage_str[key_usage], key_index, + params->seq_len, params->seq); + return -EINVAL; + } + + rc = wmi_add_cipher_key(wil, key_index, mac_addr, params->key_len, + params->key, key_usage); + if (!rc) + wil_set_crypto_rx(key_index, key_usage, cs, params); + + return rc; } static int wil_cfg80211_del_key(struct wiphy *wiphy, @@ -645,9 +1206,18 @@ static int wil_cfg80211_del_key(struct wiphy *wiphy, { struct wil6210_priv *wil = wiphy_to_wil(wiphy); enum wmi_key_usage key_usage = wil_detect_key_usage(wil, pairwise); + struct wil_sta_info *cs = wil_find_sta_by_key_usage(wil, key_usage, + mac_addr); + + wil_dbg_misc(wil, "del_key: %pM %s[%d]\n", mac_addr, + key_usage_str[key_usage], key_index); - wil_dbg_misc(wil, "%s(%pM[%d] %s)\n", __func__, mac_addr, key_index, - pairwise ? "PTK" : "GTK"); + if (IS_ERR(cs)) + wil_info(wil, "Not connected, %pM %s[%d]\n", + mac_addr, key_usage_str[key_usage], key_index); + + if (!IS_ERR_OR_NULL(cs)) + wil_del_rx_key(key_index, key_usage, cs); return wmi_del_cipher_key(wil, key_index, mac_addr, key_usage); } @@ -658,6 +1228,9 @@ static int wil_cfg80211_set_default_key(struct wiphy *wiphy, u8 key_index, bool unicast, bool multicast) { + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_misc(wil, "set_default_key: entered\n"); return 0; } @@ -670,15 +1243,11 @@ static int wil_remain_on_channel(struct wiphy *wiphy, struct wil6210_priv *wil = wiphy_to_wil(wiphy); int rc; - /* TODO: handle duration */ - wil_info(wil, "%s(%d, %d ms)\n", __func__, chan->center_freq, duration); - - rc = wmi_set_channel(wil, chan->hw_value); - if (rc) - return rc; - - rc = wmi_rxon(wil, true); + wil_dbg_misc(wil, + "remain_on_channel: center_freq=%d, duration=%d iftype=%d\n", + chan->center_freq, duration, wdev->iftype); + rc = wil_p2p_listen(wil, wdev, duration, chan, cookie); return rc; } @@ -687,51 +1256,99 @@ static int wil_cancel_remain_on_channel(struct wiphy *wiphy, u64 cookie) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); - int rc; - - wil_info(wil, "%s()\n", __func__); - rc = wmi_rxon(wil, false); + wil_dbg_misc(wil, "cancel_remain_on_channel\n"); - return rc; + return wil_p2p_cancel_listen(wil, cookie); } -static void wil_print_bcon_data(struct cfg80211_beacon_data *b) +/** + * find a specific IE in a list of IEs + * return a pointer to the beginning of IE in the list + * or NULL if not found + */ +static const u8 *_wil_cfg80211_find_ie(const u8 *ies, u16 ies_len, const u8 *ie, + u16 ie_len) { - print_hex_dump_bytes("head ", DUMP_PREFIX_OFFSET, - b->head, b->head_len); - print_hex_dump_bytes("tail ", DUMP_PREFIX_OFFSET, - b->tail, b->tail_len); - print_hex_dump_bytes("BCON IE ", DUMP_PREFIX_OFFSET, - b->beacon_ies, b->beacon_ies_len); - print_hex_dump_bytes("PROBE ", DUMP_PREFIX_OFFSET, - b->probe_resp, b->probe_resp_len); - print_hex_dump_bytes("PROBE IE ", DUMP_PREFIX_OFFSET, - b->proberesp_ies, b->proberesp_ies_len); - print_hex_dump_bytes("ASSOC IE ", DUMP_PREFIX_OFFSET, - b->assocresp_ies, b->assocresp_ies_len); + struct ieee80211_vendor_ie *vie; + u32 oui; + + /* IE tag at offset 0, length at offset 1 */ + if (ie_len < 2 || 2 + ie[1] > ie_len) + return NULL; + + if (ie[0] != WLAN_EID_VENDOR_SPECIFIC) + return cfg80211_find_ie(ie[0], ies, ies_len); + + /* make sure there is room for 3 bytes OUI + 1 byte OUI type */ + if (ie[1] < 4) + return NULL; + vie = (struct ieee80211_vendor_ie *)ie; + oui = vie->oui[0] << 16 | vie->oui[1] << 8 | vie->oui[2]; + return cfg80211_find_vendor_ie(oui, vie->oui_type, ies, + ies_len); } -static int wil_fix_bcon(struct wil6210_priv *wil, - struct cfg80211_beacon_data *bcon) +/** + * merge the IEs in two lists into a single list. + * do not include IEs from the second list which exist in the first list. + * add only vendor specific IEs from second list to keep + * the merged list sorted (since vendor-specific IE has the + * highest tag number) + * caller must free the allocated memory for merged IEs + */ +static int _wil_cfg80211_merge_extra_ies(const u8 *ies1, u16 ies1_len, + const u8 *ies2, u16 ies2_len, + u8 **merged_ies, u16 *merged_len) { - struct ieee80211_mgmt *f = (struct ieee80211_mgmt *)bcon->probe_resp; - size_t hlen = offsetof(struct ieee80211_mgmt, u.probe_resp.variable); + u8 *buf, *dpos; + const u8 *spos; - if (bcon->probe_resp_len <= hlen) + if (ies1_len == 0 && ies2_len == 0) { + *merged_ies = NULL; + *merged_len = 0; return 0; + } -/* always use IE's from full probe frame, they has more info - * notable RSN - */ - bcon->proberesp_ies = f->u.probe_resp.variable; - bcon->proberesp_ies_len = bcon->probe_resp_len - hlen; - if (!bcon->assocresp_ies) { - bcon->assocresp_ies = bcon->proberesp_ies; - bcon->assocresp_ies_len = bcon->proberesp_ies_len; + buf = kmalloc(ies1_len + ies2_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + memcpy(buf, ies1, ies1_len); + dpos = buf + ies1_len; + spos = ies2; + while (spos + 1 < ies2 + ies2_len) { + /* IE tag at offset 0, length at offset 1 */ + u16 ielen = 2 + spos[1]; + + if (spos + ielen > ies2 + ies2_len) + break; + if (spos[0] == WLAN_EID_VENDOR_SPECIFIC && + !_wil_cfg80211_find_ie(ies1, ies1_len, spos, ielen)) { + memcpy(dpos, spos, ielen); + dpos += ielen; + } + spos += ielen; } - return 1; + *merged_ies = buf; + *merged_len = dpos - buf; + return 0; +} + +static void wil_print_bcon_data(struct cfg80211_beacon_data *b) +{ + wil_hex_dump_misc("head ", DUMP_PREFIX_OFFSET, 16, 1, + b->head, b->head_len, true); + wil_hex_dump_misc("tail ", DUMP_PREFIX_OFFSET, 16, 1, + b->tail, b->tail_len, true); + wil_hex_dump_misc("BCON IE ", DUMP_PREFIX_OFFSET, 16, 1, + b->beacon_ies, b->beacon_ies_len, true); + wil_hex_dump_misc("PROBE ", DUMP_PREFIX_OFFSET, 16, 1, + b->probe_resp, b->probe_resp_len, true); + wil_hex_dump_misc("PROBE IE ", DUMP_PREFIX_OFFSET, 16, 1, + b->proberesp_ies, b->proberesp_ies_len, true); + wil_hex_dump_misc("ASSOC IE ", DUMP_PREFIX_OFFSET, 16, 1, + b->assocresp_ies, b->assocresp_ies_len, true); } /* internal functions for device reset and starting AP */ @@ -740,21 +1357,43 @@ static int _wil_cfg80211_set_ies(struct wiphy *wiphy, { int rc; struct wil6210_priv *wil = wiphy_to_wil(wiphy); + u16 len = 0, proberesp_len = 0; + u8 *ies = NULL, *proberesp = NULL; + + if (bcon->probe_resp) { + struct ieee80211_mgmt *f = + (struct ieee80211_mgmt *)bcon->probe_resp; + size_t hlen = offsetof(struct ieee80211_mgmt, + u.probe_resp.variable); + proberesp = f->u.probe_resp.variable; + proberesp_len = bcon->probe_resp_len - hlen; + } + rc = _wil_cfg80211_merge_extra_ies(proberesp, + proberesp_len, + bcon->proberesp_ies, + bcon->proberesp_ies_len, + &ies, &len); - rc = wmi_set_ie(wil, WMI_FRAME_PROBE_RESP, bcon->proberesp_ies_len, - bcon->proberesp_ies); if (rc) - return rc; + goto out; - rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_RESP, bcon->assocresp_ies_len, - bcon->assocresp_ies); + rc = wmi_set_ie(wil, WMI_FRAME_PROBE_RESP, len, ies); + if (rc) + goto out; + + if (bcon->assocresp_ies) + rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_RESP, + bcon->assocresp_ies_len, bcon->assocresp_ies); + else + rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_RESP, len, ies); #if 0 /* to use beacon IE's, remove this #if 0 */ if (rc) - return rc; + goto out; rc = wmi_set_ie(wil, WMI_FRAME_BEACON, bcon->tail_len, bcon->tail); #endif - +out: + kfree(ies); return rc; } @@ -763,12 +1402,22 @@ static int _wil_cfg80211_start_ap(struct wiphy *wiphy, const u8 *ssid, size_t ssid_len, u32 privacy, int bi, u8 chan, struct cfg80211_beacon_data *bcon, - u8 hidden_ssid) + u8 hidden_ssid, u32 pbss) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); int rc; struct wireless_dev *wdev = ndev->ieee80211_ptr; u8 wmi_nettype = wil_iftype_nl2wmi(wdev->iftype); + u8 is_go = (wdev->iftype == NL80211_IFTYPE_P2P_GO); + + if (pbss) + wmi_nettype = WMI_NETTYPE_P2P; + + wil_dbg_misc(wil, "start_ap: is_go=%d\n", is_go); + if (is_go && !pbss) { + wil_err(wil, "P2P GO must be in PBSS\n"); + return -ENOTSUPP; + } wil_set_recovery_state(wil, fw_recovery_idle); @@ -790,10 +1439,12 @@ static int _wil_cfg80211_start_ap(struct wiphy *wiphy, wil->privacy = privacy; wil->channel = chan; wil->hidden_ssid = hidden_ssid; + wil->pbss = pbss; netif_carrier_on(ndev); + wil6210_bus_request(wil, WIL_MAX_BUS_REQUEST_KBPS); - rc = wmi_pcp_start(wil, bi, wmi_nettype, chan, hidden_ssid); + rc = wmi_pcp_start(wil, bi, wmi_nettype, chan, hidden_ssid, is_go); if (rc) goto err_pcp_start; @@ -807,6 +1458,7 @@ err_bcast: wmi_pcp_stop(wil); err_pcp_start: netif_carrier_off(ndev); + wil6210_bus_request(wil, WIL_DEFAULT_BUS_REQUEST_KBPS); out: mutex_unlock(&wil->mutex); return rc; @@ -820,17 +1472,12 @@ static int wil_cfg80211_change_beacon(struct wiphy *wiphy, int rc; u32 privacy = 0; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "change_beacon\n"); wil_print_bcon_data(bcon); - if (wil_fix_bcon(wil, bcon)) { - wil_dbg_misc(wil, "Fixed bcon\n"); - wil_print_bcon_data(bcon); - } - - if (bcon->proberesp_ies && - cfg80211_find_ie(WLAN_EID_RSN, bcon->proberesp_ies, - bcon->proberesp_ies_len)) + if (bcon->tail && + cfg80211_find_ie(WLAN_EID_RSN, bcon->tail, + bcon->tail_len)) privacy = 1; /* in case privacy has changed, need to restart the AP */ @@ -844,7 +1491,8 @@ static int wil_cfg80211_change_beacon(struct wiphy *wiphy, wdev->ssid_len, privacy, wdev->beacon_interval, wil->channel, bcon, - wil->hidden_ssid); + wil->hidden_ssid, + wil->pbss); } else { rc = _wil_cfg80211_set_ies(wiphy, bcon); } @@ -863,7 +1511,7 @@ static int wil_cfg80211_start_ap(struct wiphy *wiphy, struct cfg80211_crypto_settings *crypto = &info->crypto; u8 hidden_ssid; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "start_ap\n"); if (!channel) { wil_err(wil, "AP: No channel???\n"); @@ -895,20 +1543,16 @@ static int wil_cfg80211_start_ap(struct wiphy *wiphy, info->hidden_ssid); wil_dbg_misc(wil, "BI %d DTIM %d\n", info->beacon_interval, info->dtim_period); - print_hex_dump_bytes("SSID ", DUMP_PREFIX_OFFSET, - info->ssid, info->ssid_len); + wil_dbg_misc(wil, "PBSS %d\n", info->pbss); + wil_hex_dump_misc("SSID ", DUMP_PREFIX_OFFSET, 16, 1, + info->ssid, info->ssid_len, true); wil_print_bcon_data(bcon); wil_print_crypto(wil, crypto); - if (wil_fix_bcon(wil, bcon)) { - wil_dbg_misc(wil, "Fixed bcon\n"); - wil_print_bcon_data(bcon); - } - rc = _wil_cfg80211_start_ap(wiphy, ndev, info->ssid, info->ssid_len, info->privacy, info->beacon_interval, channel->hw_value, - bcon, hidden_ssid); + bcon, hidden_ssid, info->pbss); return rc; } @@ -918,11 +1562,14 @@ static int wil_cfg80211_stop_ap(struct wiphy *wiphy, { struct wil6210_priv *wil = wiphy_to_wil(wiphy); - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "stop_ap\n"); netif_carrier_off(ndev); + wil6210_bus_request(wil, WIL_DEFAULT_BUS_REQUEST_KBPS); wil_set_recovery_state(wil, fw_recovery_idle); + set_bit(wil_status_resetting, wil->status); + mutex_lock(&wil->mutex); wmi_pcp_stop(wil); @@ -934,13 +1581,35 @@ static int wil_cfg80211_stop_ap(struct wiphy *wiphy, return 0; } +static int wil_cfg80211_add_station(struct wiphy *wiphy, + struct net_device *dev, + const u8 *mac, + struct station_parameters *params) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_misc(wil, "add station %pM aid %d\n", mac, params->aid); + + if (!disable_ap_sme) { + wil_err(wil, "not supported with AP SME enabled\n"); + return -EOPNOTSUPP; + } + + if (params->aid > WIL_MAX_DMG_AID) { + wil_err(wil, "invalid aid\n"); + return -EINVAL; + } + + return wmi_new_sta(wil, mac, params->aid); +} + static int wil_cfg80211_del_station(struct wiphy *wiphy, struct net_device *dev, struct station_del_parameters *params) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); - wil_dbg_misc(wil, "%s(%pM, reason=%d)\n", __func__, params->mac, + wil_dbg_misc(wil, "del_station: %pM, reason=%d\n", params->mac, params->reason_code); mutex_lock(&wil->mutex); @@ -950,6 +1619,52 @@ static int wil_cfg80211_del_station(struct wiphy *wiphy, return 0; } +static int wil_cfg80211_change_station(struct wiphy *wiphy, + struct net_device *dev, + const u8 *mac, + struct station_parameters *params) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + int authorize; + int cid, i; + struct vring_tx_data *txdata = NULL; + + wil_dbg_misc(wil, "change station %pM mask 0x%x set 0x%x\n", mac, + params->sta_flags_mask, params->sta_flags_set); + + if (!disable_ap_sme) { + wil_dbg_misc(wil, "not supported with AP SME enabled\n"); + return -EOPNOTSUPP; + } + + if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))) + return 0; + + cid = wil_find_cid(wil, mac); + if (cid < 0) { + wil_err(wil, "station not found\n"); + return -ENOLINK; + } + + for (i = 0; i < ARRAY_SIZE(wil->vring2cid_tid); i++) + if (wil->vring2cid_tid[i][0] == cid) { + txdata = &wil->vring_tx_data[i]; + break; + } + + if (!txdata) { + wil_err(wil, "vring data not found\n"); + return -ENOLINK; + } + + authorize = params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED); + txdata->dot1x_open = authorize ? 1 : 0; + wil_dbg_misc(wil, "cid %d vring %d authorize %d\n", cid, i, + txdata->dot1x_open); + + return 0; +} + /* probe_client handling */ static void wil_probe_client_handle(struct wil6210_priv *wil, struct wil_probe_client_req *req) @@ -999,7 +1714,7 @@ void wil_probe_client_flush(struct wil6210_priv *wil) { struct wil_probe_client_req *req, *t; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "probe_client_flush\n"); mutex_lock(&wil->probe_client_mutex); @@ -1019,7 +1734,7 @@ static int wil_cfg80211_probe_client(struct wiphy *wiphy, struct wil_probe_client_req *req; int cid = wil_find_cid(wil, peer); - wil_dbg_misc(wil, "%s(%pM => CID %d)\n", __func__, peer, cid); + wil_dbg_misc(wil, "probe_client: %pM => CID %d\n", peer, cid); if (cid < 0) return -ENOLINK; @@ -1047,7 +1762,7 @@ static int wil_cfg80211_change_bss(struct wiphy *wiphy, struct wil6210_priv *wil = wiphy_to_wil(wiphy); if (params->ap_isolate >= 0) { - wil_dbg_misc(wil, "%s(ap_isolate %d => %d)\n", __func__, + wil_dbg_misc(wil, "change_bss: ap_isolate %d => %d\n", wil->ap_isolate, params->ap_isolate); wil->ap_isolate = params->ap_isolate; } @@ -1055,10 +1770,68 @@ static int wil_cfg80211_change_bss(struct wiphy *wiphy, return 0; } +static int wil_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, + bool enabled, int timeout) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + enum wmi_ps_profile_type ps_profile; + + wil_dbg_misc(wil, "enabled=%d, timeout=%d\n", + enabled, timeout); + + if (enabled) + ps_profile = WMI_PS_PROFILE_TYPE_DEFAULT; + else + ps_profile = WMI_PS_PROFILE_TYPE_PS_DISABLED; + + return wil_ps_update(wil, ps_profile); +} + +static int wil_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wow) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + int rc; + + /* Setting the wakeup trigger based on wow is TBD */ + + if (test_bit(wil_status_suspended, wil->status)) { + wil_dbg_pm(wil, "trying to suspend while suspended\n"); + return 0; + } + + rc = wil_can_suspend(wil, false); + if (rc) + goto out; + + wil_dbg_pm(wil, "suspending\n"); + + wil_p2p_stop_discovery(wil); + + wil_abort_scan(wil, true); + +out: + return rc; +} + +static int wil_cfg80211_resume(struct wiphy *wiphy) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_pm(wil, "resuming\n"); + + return 0; +} + static struct cfg80211_ops wil_cfg80211_ops = { + .add_virtual_intf = wil_cfg80211_add_iface, + .del_virtual_intf = wil_cfg80211_del_iface, .scan = wil_cfg80211_scan, + .abort_scan = wil_cfg80211_abort_scan, .connect = wil_cfg80211_connect, .disconnect = wil_cfg80211_disconnect, + .set_wiphy_params = wil_cfg80211_set_wiphy_params, .change_virtual_intf = wil_cfg80211_change_iface, .get_station = wil_cfg80211_get_station, .dump_station = wil_cfg80211_dump_station, @@ -1073,24 +1846,36 @@ static struct cfg80211_ops wil_cfg80211_ops = { .change_beacon = wil_cfg80211_change_beacon, .start_ap = wil_cfg80211_start_ap, .stop_ap = wil_cfg80211_stop_ap, + .add_station = wil_cfg80211_add_station, .del_station = wil_cfg80211_del_station, + .change_station = wil_cfg80211_change_station, .probe_client = wil_cfg80211_probe_client, .change_bss = wil_cfg80211_change_bss, + /* P2P device */ + .start_p2p_device = wil_cfg80211_start_p2p_device, + .stop_p2p_device = wil_cfg80211_stop_p2p_device, + .set_power_mgmt = wil_cfg80211_set_power_mgmt, + .suspend = wil_cfg80211_suspend, + .resume = wil_cfg80211_resume, }; static void wil_wiphy_init(struct wiphy *wiphy) { wiphy->max_scan_ssids = 1; wiphy->max_scan_ie_len = WMI_MAX_IE_LEN; + wiphy->max_remain_on_channel_duration = WIL_MAX_ROC_DURATION_MS; wiphy->max_num_pmkids = 0 /* TODO: */; wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_DEVICE) | BIT(NL80211_IFTYPE_MONITOR); - /* TODO: enable P2P when integrated with supplicant: - * BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) - */ - wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME | - WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; + wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD | + WIPHY_FLAG_PS_ON_BY_DEFAULT; + if (!disable_ap_sme) + wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME; dev_dbg(wiphy_dev(wiphy), "%s : flags = 0x%08x\n", __func__, wiphy->flags); wiphy->probe_resp_offload = @@ -1100,13 +1885,27 @@ static void wil_wiphy_init(struct wiphy *wiphy) wiphy->bands[IEEE80211_BAND_60GHZ] = &wil_band_60ghz; - /* TODO: figure this out */ + /* may change after reading FW capabilities */ wiphy->signal_type = CFG80211_SIGNAL_TYPE_UNSPEC; wiphy->cipher_suites = wil_cipher_suites; wiphy->n_cipher_suites = ARRAY_SIZE(wil_cipher_suites); wiphy->mgmt_stypes = wil_mgmt_stypes; wiphy->features |= NL80211_FEATURE_SK_TX_STATUS; + + wiphy->n_vendor_commands = ARRAY_SIZE(wil_nl80211_vendor_commands); + wiphy->vendor_commands = wil_nl80211_vendor_commands; + wiphy->vendor_events = wil_nl80211_vendor_events; + wiphy->n_vendor_events = ARRAY_SIZE(wil_nl80211_vendor_events); + + if (ignore_reg_hints) { + wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS; + wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE; + } + +#ifdef CONFIG_PM + wiphy->wowlan = &wil_wowlan_support; +#endif } struct wireless_dev *wil_cfg80211_init(struct device *dev) @@ -1130,14 +1929,8 @@ struct wireless_dev *wil_cfg80211_init(struct device *dev) set_wiphy_dev(wdev->wiphy, dev); wil_wiphy_init(wdev->wiphy); - rc = wiphy_register(wdev->wiphy); - if (rc < 0) - goto out_failed_reg; - return wdev; -out_failed_reg: - wiphy_free(wdev->wiphy); out: kfree(wdev); @@ -1153,7 +1946,467 @@ void wil_wdev_free(struct wil6210_priv *wil) if (!wdev) return; - wiphy_unregister(wdev->wiphy); wiphy_free(wdev->wiphy); kfree(wdev); } + +void wil_p2p_wdev_free(struct wil6210_priv *wil) +{ + struct wireless_dev *p2p_wdev; + + mutex_lock(&wil->p2p_wdev_mutex); + p2p_wdev = wil->p2p_wdev; + wil->p2p_wdev = NULL; + wil->radio_wdev = wil_to_wdev(wil); + mutex_unlock(&wil->p2p_wdev_mutex); + if (p2p_wdev) { + cfg80211_unregister_wdev(p2p_wdev); + kfree(p2p_wdev); + } +} + +static int wil_rf_sector_status_to_rc(u8 status) +{ + switch (status) { + case WMI_RF_SECTOR_STATUS_SUCCESS: + return 0; + case WMI_RF_SECTOR_STATUS_BAD_PARAMETERS_ERROR: + return -EINVAL; + case WMI_RF_SECTOR_STATUS_BUSY_ERROR: + return -EAGAIN; + case WMI_RF_SECTOR_STATUS_NOT_SUPPORTED_ERROR: + return -EOPNOTSUPP; + default: + return -EINVAL; + } +} + +static int wil_rf_sector_get_cfg(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wdev_to_wil(wdev); + int rc; + struct nlattr *tb[QCA_ATTR_DMG_RF_SECTOR_MAX + 1]; + u16 sector_index; + u8 sector_type; + u32 rf_modules_vec; + struct wmi_get_rf_sector_params_cmd cmd; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_get_rf_sector_params_done_event evt; + } __packed reply; + struct sk_buff *msg; + struct nlattr *nl_cfgs, *nl_cfg; + u32 i; + struct wmi_rf_sector_info *si; + + if (!test_bit(WMI_FW_CAPABILITY_RF_SECTORS, wil->fw_capabilities)) + return -EOPNOTSUPP; + + rc = nla_parse(tb, QCA_ATTR_DMG_RF_SECTOR_MAX, data, data_len, + wil_rf_sector_policy); + if (rc) { + wil_err(wil, "Invalid rf sector ATTR\n"); + return rc; + } + + if (!tb[QCA_ATTR_DMG_RF_SECTOR_INDEX] || + !tb[QCA_ATTR_DMG_RF_SECTOR_TYPE] || + !tb[QCA_ATTR_DMG_RF_MODULE_MASK]) { + wil_err(wil, "Invalid rf sector spec\n"); + return -EINVAL; + } + + sector_index = nla_get_u16( + tb[QCA_ATTR_DMG_RF_SECTOR_INDEX]); + if (sector_index >= WIL_MAX_RF_SECTORS) { + wil_err(wil, "Invalid sector index %d\n", sector_index); + return -EINVAL; + } + + sector_type = nla_get_u8(tb[QCA_ATTR_DMG_RF_SECTOR_TYPE]); + if (sector_type >= QCA_ATTR_DMG_RF_SECTOR_TYPE_MAX) { + wil_err(wil, "Invalid sector type %d\n", sector_type); + return -EINVAL; + } + + rf_modules_vec = nla_get_u32( + tb[QCA_ATTR_DMG_RF_MODULE_MASK]); + if (rf_modules_vec >= BIT(WMI_MAX_RF_MODULES_NUM)) { + wil_err(wil, "Invalid rf module mask 0x%x\n", rf_modules_vec); + return -EINVAL; + } + + cmd.sector_idx = cpu_to_le16(sector_index); + cmd.sector_type = sector_type; + cmd.rf_modules_vec = rf_modules_vec & 0xFF; + memset(&reply, 0, sizeof(reply)); + rc = wmi_call(wil, WMI_GET_RF_SECTOR_PARAMS_CMDID, &cmd, sizeof(cmd), + WMI_GET_RF_SECTOR_PARAMS_DONE_EVENTID, + &reply, sizeof(reply), + 500); + if (rc) + return rc; + if (reply.evt.status) { + wil_err(wil, "get rf sector cfg failed with status %d\n", + reply.evt.status); + return wil_rf_sector_status_to_rc(reply.evt.status); + } + + msg = cfg80211_vendor_cmd_alloc_reply_skb( + wiphy, 64 * WMI_MAX_RF_MODULES_NUM); + if (!msg) + return -ENOMEM; + + if (nla_put_u64(msg, QCA_ATTR_TSF, + le64_to_cpu(reply.evt.tsf))) + goto nla_put_failure; + + nl_cfgs = nla_nest_start(msg, QCA_ATTR_DMG_RF_SECTOR_CFG); + if (!nl_cfgs) + goto nla_put_failure; + for (i = 0; i < WMI_MAX_RF_MODULES_NUM; i++) { + if (!(rf_modules_vec & BIT(i))) + continue; + nl_cfg = nla_nest_start(msg, i); + if (!nl_cfg) + goto nla_put_failure; + si = &reply.evt.sectors_info[i]; + if (nla_put_u8(msg, QCA_ATTR_DMG_RF_SECTOR_CFG_MODULE_INDEX, + i) || + nla_put_u32(msg, QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE0, + le32_to_cpu(si->etype0)) || + nla_put_u32(msg, QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE1, + le32_to_cpu(si->etype1)) || + nla_put_u32(msg, QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE2, + le32_to_cpu(si->etype2)) || + nla_put_u32(msg, QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_HI, + le32_to_cpu(si->psh_hi)) || + nla_put_u32(msg, QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_LO, + le32_to_cpu(si->psh_lo)) || + nla_put_u32(msg, QCA_ATTR_DMG_RF_SECTOR_CFG_DTYPE_X16, + le32_to_cpu(si->dtype_swch_off))) + goto nla_put_failure; + nla_nest_end(msg, nl_cfg); + } + + nla_nest_end(msg, nl_cfgs); + rc = cfg80211_vendor_cmd_reply(msg); + return rc; +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +static int wil_rf_sector_set_cfg(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wdev_to_wil(wdev); + int rc, tmp; + struct nlattr *tb[QCA_ATTR_DMG_RF_SECTOR_MAX + 1]; + struct nlattr *tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_MAX + 1]; + u16 sector_index, rf_module_index; + u8 sector_type; + u32 rf_modules_vec = 0; + struct wmi_set_rf_sector_params_cmd cmd; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_set_rf_sector_params_done_event evt; + } __packed reply; + struct nlattr *nl_cfg; + struct wmi_rf_sector_info *si; + + if (!test_bit(WMI_FW_CAPABILITY_RF_SECTORS, wil->fw_capabilities)) + return -EOPNOTSUPP; + + rc = nla_parse(tb, QCA_ATTR_DMG_RF_SECTOR_MAX, data, data_len, + wil_rf_sector_policy); + if (rc) { + wil_err(wil, "Invalid rf sector ATTR\n"); + return rc; + } + + if (!tb[QCA_ATTR_DMG_RF_SECTOR_INDEX] || + !tb[QCA_ATTR_DMG_RF_SECTOR_TYPE] || + !tb[QCA_ATTR_DMG_RF_SECTOR_CFG]) { + wil_err(wil, "Invalid rf sector spec\n"); + return -EINVAL; + } + + sector_index = nla_get_u16( + tb[QCA_ATTR_DMG_RF_SECTOR_INDEX]); + if (sector_index >= WIL_MAX_RF_SECTORS) { + wil_err(wil, "Invalid sector index %d\n", sector_index); + return -EINVAL; + } + + sector_type = nla_get_u8(tb[QCA_ATTR_DMG_RF_SECTOR_TYPE]); + if (sector_type >= QCA_ATTR_DMG_RF_SECTOR_TYPE_MAX) { + wil_err(wil, "Invalid sector type %d\n", sector_type); + return -EINVAL; + } + + memset(&cmd, 0, sizeof(cmd)); + + cmd.sector_idx = cpu_to_le16(sector_index); + cmd.sector_type = sector_type; + nla_for_each_nested(nl_cfg, tb[QCA_ATTR_DMG_RF_SECTOR_CFG], + tmp) { + rc = nla_parse_nested(tb2, QCA_ATTR_DMG_RF_SECTOR_CFG_MAX, + nl_cfg, wil_rf_sector_cfg_policy); + if (rc) { + wil_err(wil, "invalid sector cfg\n"); + return -EINVAL; + } + + if (!tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_MODULE_INDEX] || + !tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE0] || + !tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE1] || + !tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE2] || + !tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_HI] || + !tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_LO] || + !tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_DTYPE_X16]) { + wil_err(wil, "missing cfg params\n"); + return -EINVAL; + } + + rf_module_index = nla_get_u8( + tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_MODULE_INDEX]); + if (rf_module_index >= WMI_MAX_RF_MODULES_NUM) { + wil_err(wil, "invalid RF module index %d\n", + rf_module_index); + return -EINVAL; + } + rf_modules_vec |= BIT(rf_module_index); + si = &cmd.sectors_info[rf_module_index]; + si->etype0 = cpu_to_le32(nla_get_u32( + tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE0])); + si->etype1 = cpu_to_le32(nla_get_u32( + tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE1])); + si->etype2 = cpu_to_le32(nla_get_u32( + tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_ETYPE2])); + si->psh_hi = cpu_to_le32(nla_get_u32( + tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_HI])); + si->psh_lo = cpu_to_le32(nla_get_u32( + tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_PSH_LO])); + si->dtype_swch_off = cpu_to_le32(nla_get_u32( + tb2[QCA_ATTR_DMG_RF_SECTOR_CFG_DTYPE_X16])); + } + + cmd.rf_modules_vec = rf_modules_vec & 0xFF; + memset(&reply, 0, sizeof(reply)); + rc = wmi_call(wil, WMI_SET_RF_SECTOR_PARAMS_CMDID, &cmd, sizeof(cmd), + WMI_SET_RF_SECTOR_PARAMS_DONE_EVENTID, + &reply, sizeof(reply), + 500); + if (rc) + return rc; + return wil_rf_sector_status_to_rc(reply.evt.status); +} + +static int wil_rf_sector_get_selected(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wdev_to_wil(wdev); + int rc; + struct nlattr *tb[QCA_ATTR_DMG_RF_SECTOR_MAX + 1]; + u8 sector_type, mac_addr[ETH_ALEN]; + int cid = 0; + struct wmi_get_selected_rf_sector_index_cmd cmd; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_get_selected_rf_sector_index_done_event evt; + } __packed reply; + struct sk_buff *msg; + + if (!test_bit(WMI_FW_CAPABILITY_RF_SECTORS, wil->fw_capabilities)) + return -EOPNOTSUPP; + + rc = nla_parse(tb, QCA_ATTR_DMG_RF_SECTOR_MAX, data, data_len, + wil_rf_sector_policy); + if (rc) { + wil_err(wil, "Invalid rf sector ATTR\n"); + return rc; + } + + if (!tb[QCA_ATTR_DMG_RF_SECTOR_TYPE]) { + wil_err(wil, "Invalid rf sector spec\n"); + return -EINVAL; + } + sector_type = nla_get_u8(tb[QCA_ATTR_DMG_RF_SECTOR_TYPE]); + if (sector_type >= QCA_ATTR_DMG_RF_SECTOR_TYPE_MAX) { + wil_err(wil, "Invalid sector type %d\n", sector_type); + return -EINVAL; + } + + if (tb[QCA_ATTR_MAC_ADDR]) { + ether_addr_copy(mac_addr, nla_data(tb[QCA_ATTR_MAC_ADDR])); + cid = wil_find_cid(wil, mac_addr); + if (cid < 0) { + wil_err(wil, "invalid MAC address %pM\n", mac_addr); + return -ENOENT; + } + } else { + if (test_bit(wil_status_fwconnected, wil->status)) { + wil_err(wil, "must specify MAC address when connected\n"); + return -EINVAL; + } + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.cid = (u8)cid; + cmd.sector_type = sector_type; + memset(&reply, 0, sizeof(reply)); + rc = wmi_call(wil, WMI_GET_SELECTED_RF_SECTOR_INDEX_CMDID, + &cmd, sizeof(cmd), + WMI_GET_SELECTED_RF_SECTOR_INDEX_DONE_EVENTID, + &reply, sizeof(reply), + 500); + if (rc) + return rc; + if (reply.evt.status) { + wil_err(wil, "get rf selected sector cfg failed with status %d\n", + reply.evt.status); + return wil_rf_sector_status_to_rc(reply.evt.status); + } + + msg = cfg80211_vendor_cmd_alloc_reply_skb( + wiphy, 64 * WMI_MAX_RF_MODULES_NUM); + if (!msg) + return -ENOMEM; + + if (nla_put_u64(msg, QCA_ATTR_TSF, + le64_to_cpu(reply.evt.tsf)) || + nla_put_u16(msg, QCA_ATTR_DMG_RF_SECTOR_INDEX, + le16_to_cpu(reply.evt.sector_idx))) + goto nla_put_failure; + + rc = cfg80211_vendor_cmd_reply(msg); + return rc; +nla_put_failure: + kfree_skb(msg); + return -ENOBUFS; +} + +static int wil_rf_sector_wmi_set_selected(struct wil6210_priv *wil, + u16 sector_index, + u8 sector_type, u8 cid) +{ + struct wmi_set_selected_rf_sector_index_cmd cmd; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_set_selected_rf_sector_index_done_event evt; + } __packed reply; + int rc; + + memset(&cmd, 0, sizeof(cmd)); + cmd.sector_idx = cpu_to_le16(sector_index); + cmd.sector_type = sector_type; + cmd.cid = (u8)cid; + memset(&reply, 0, sizeof(reply)); + rc = wmi_call(wil, WMI_SET_SELECTED_RF_SECTOR_INDEX_CMDID, + &cmd, sizeof(cmd), + WMI_SET_SELECTED_RF_SECTOR_INDEX_DONE_EVENTID, + &reply, sizeof(reply), + 500); + if (rc) + return rc; + return wil_rf_sector_status_to_rc(reply.evt.status); +} + +static int wil_rf_sector_set_selected(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wdev_to_wil(wdev); + int rc; + struct nlattr *tb[QCA_ATTR_DMG_RF_SECTOR_MAX + 1]; + u16 sector_index; + u8 sector_type, mac_addr[ETH_ALEN], i; + int cid = 0; + + if (!test_bit(WMI_FW_CAPABILITY_RF_SECTORS, wil->fw_capabilities)) + return -EOPNOTSUPP; + + rc = nla_parse(tb, QCA_ATTR_DMG_RF_SECTOR_MAX, data, data_len, + wil_rf_sector_policy); + if (rc) { + wil_err(wil, "Invalid rf sector ATTR\n"); + return rc; + } + + if (!tb[QCA_ATTR_DMG_RF_SECTOR_INDEX] || + !tb[QCA_ATTR_DMG_RF_SECTOR_TYPE]) { + wil_err(wil, "Invalid rf sector spec\n"); + return -EINVAL; + } + + sector_index = nla_get_u16( + tb[QCA_ATTR_DMG_RF_SECTOR_INDEX]); + if (sector_index >= WIL_MAX_RF_SECTORS && + sector_index != WMI_INVALID_RF_SECTOR_INDEX) { + wil_err(wil, "Invalid sector index %d\n", sector_index); + return -EINVAL; + } + + sector_type = nla_get_u8(tb[QCA_ATTR_DMG_RF_SECTOR_TYPE]); + if (sector_type >= QCA_ATTR_DMG_RF_SECTOR_TYPE_MAX) { + wil_err(wil, "Invalid sector type %d\n", sector_type); + return -EINVAL; + } + + if (tb[QCA_ATTR_MAC_ADDR]) { + ether_addr_copy(mac_addr, nla_data(tb[QCA_ATTR_MAC_ADDR])); + if (!is_broadcast_ether_addr(mac_addr)) { + cid = wil_find_cid(wil, mac_addr); + if (cid < 0) { + wil_err(wil, "invalid MAC address %pM\n", + mac_addr); + return -ENOENT; + } + } else { + if (sector_index != WMI_INVALID_RF_SECTOR_INDEX) { + wil_err(wil, "broadcast MAC valid only with unlocking\n"); + return -EINVAL; + } + cid = -1; + } + } else { + if (test_bit(wil_status_fwconnected, wil->status)) { + wil_err(wil, "must specify MAC address when connected\n"); + return -EINVAL; + } + /* otherwise, using cid=0 for unassociated station */ + } + + if (cid >= 0) { + rc = wil_rf_sector_wmi_set_selected(wil, sector_index, + sector_type, cid); + } else { + /* unlock all cids */ + rc = wil_rf_sector_wmi_set_selected( + wil, WMI_INVALID_RF_SECTOR_INDEX, sector_type, + WIL_CID_ALL); + if (rc == -EINVAL) { + for (i = 0; i < WIL6210_MAX_CID; i++) { + rc = wil_rf_sector_wmi_set_selected( + wil, WMI_INVALID_RF_SECTOR_INDEX, + sector_type, i); + /* the FW will silently ignore and return + * success for unused cid, so abort the loop + * on any other error + */ + if (rc) { + wil_err(wil, "unlock cid %d failed with status %d\n", + i, rc); + break; + } + } + } + } + + return rc; +} diff --git a/drivers/net/wireless/ath/wil6210/debug.c b/drivers/net/wireless/ath/wil6210/debug.c index 3249562d93b4..217a4591bde4 100644 --- a/drivers/net/wireless/ath/wil6210/debug.c +++ b/drivers/net/wireless/ath/wil6210/debug.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Qualcomm Atheros, Inc. + * Copyright (c) 2013,2016 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,61 +17,71 @@ #include "wil6210.h" #include "trace.h" -void wil_err(struct wil6210_priv *wil, const char *fmt, ...) +void __wil_err(struct wil6210_priv *wil, const char *fmt, ...) { - struct net_device *ndev = wil_to_ndev(wil); - struct va_format vaf = { - .fmt = fmt, - }; + struct va_format vaf; va_list args; va_start(args, fmt); + vaf.fmt = fmt; vaf.va = &args; - netdev_err(ndev, "%pV", &vaf); + netdev_err(wil_to_ndev(wil), "%pV", &vaf); trace_wil6210_log_err(&vaf); va_end(args); } -void wil_err_ratelimited(struct wil6210_priv *wil, const char *fmt, ...) +void __wil_err_ratelimited(struct wil6210_priv *wil, const char *fmt, ...) { - if (net_ratelimit()) { - struct net_device *ndev = wil_to_ndev(wil); - struct va_format vaf = { - .fmt = fmt, - }; - va_list args; + struct va_format vaf; + va_list args; + + if (!net_ratelimit()) + return; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + netdev_err(wil_to_ndev(wil), "%pV", &vaf); + trace_wil6210_log_err(&vaf); + va_end(args); +} - va_start(args, fmt); - vaf.va = &args; - netdev_err(ndev, "%pV", &vaf); - trace_wil6210_log_err(&vaf); - va_end(args); - } +void wil_dbg_ratelimited(const struct wil6210_priv *wil, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (!net_ratelimit()) + return; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + netdev_dbg(wil_to_ndev(wil), "%pV", &vaf); + trace_wil6210_log_dbg(&vaf); + va_end(args); } -void wil_info(struct wil6210_priv *wil, const char *fmt, ...) +void __wil_info(struct wil6210_priv *wil, const char *fmt, ...) { - struct net_device *ndev = wil_to_ndev(wil); - struct va_format vaf = { - .fmt = fmt, - }; + struct va_format vaf; va_list args; va_start(args, fmt); + vaf.fmt = fmt; vaf.va = &args; - netdev_info(ndev, "%pV", &vaf); + netdev_info(wil_to_ndev(wil), "%pV", &vaf); trace_wil6210_log_info(&vaf); va_end(args); } void wil_dbg_trace(struct wil6210_priv *wil, const char *fmt, ...) { - struct va_format vaf = { - .fmt = fmt, - }; + struct va_format vaf; va_list args; va_start(args, fmt); + vaf.fmt = fmt; vaf.va = &args; trace_wil6210_log_dbg(&vaf); va_end(args); diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c index 97bc186f9728..65f39a4343ff 100644 --- a/drivers/net/wireless/ath/wil6210/debugfs.c +++ b/drivers/net/wireless/ath/wil6210/debugfs.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,7 +20,6 @@ #include <linux/pci.h> #include <linux/rtnetlink.h> #include <linux/power_supply.h> - #include "wil6210.h" #include "wmi.h" #include "txrx.h" @@ -30,13 +29,13 @@ static u32 mem_addr; static u32 dbg_txdesc_index; static u32 dbg_vring_index; /* 24+ for Rx, 0..23 for Tx */ -u32 vring_idle_trsh = 16; /* HW fetches up to 16 descriptors at once */ enum dbg_off_type { doff_u32 = 0, doff_x32 = 1, doff_ulong = 2, doff_io32 = 3, + doff_u8 = 4 }; /* offset to "wil" */ @@ -68,13 +67,13 @@ static void wil_print_vring(struct seq_file *s, struct wil6210_priv *wil, seq_puts(s, "???\n"); } - if (vring->va && (vring->size < 1025)) { + if (vring->va && (vring->size <= (1 << WIL_RING_SIZE_ORDER_MAX))) { uint i; for (i = 0; i < vring->size; i++) { volatile struct vring_tx_desc *d = &vring->va[i].tx; - if ((i % 64) == 0 && (i != 0)) + if ((i % 128) == 0 && (i != 0)) seq_puts(s, "\n"); seq_printf(s, "%c", (d->dma.status & BIT(0)) ? _s : (vring->ctx[i].skb ? _h : 'h')); @@ -170,6 +169,8 @@ static void wil_print_ring(struct seq_file *s, const char *prefix, int rsize; uint i; + wil_halp_vote(wil); + wil_memcpy_fromio_32(&r, off, sizeof(r)); wil_mbox_ring_le2cpus(&r); /* @@ -235,6 +236,7 @@ static void wil_print_ring(struct seq_file *s, const char *prefix, } out: seq_puts(s, "}\n"); + wil_halp_unvote(wil); } static int wil_mbox_debugfs_show(struct seq_file *s, void *data) @@ -346,6 +348,10 @@ static void wil6210_debugfs_init_offset(struct wil6210_priv *wil, tbl[i].mode, dbg, base + tbl[i].off); break; + case doff_u8: + f = debugfs_create_u8(tbl[i].name, tbl[i].mode, dbg, + base + tbl[i].off); + break; default: f = ERR_PTR(-EINVAL); } @@ -356,13 +362,13 @@ static void wil6210_debugfs_init_offset(struct wil6210_priv *wil, } static const struct dbg_off isr_off[] = { - {"ICC", S_IRUGO | S_IWUSR, offsetof(struct RGF_ICR, ICC), doff_io32}, - {"ICR", S_IRUGO | S_IWUSR, offsetof(struct RGF_ICR, ICR), doff_io32}, - {"ICM", S_IRUGO | S_IWUSR, offsetof(struct RGF_ICR, ICM), doff_io32}, - {"ICS", S_IWUSR, offsetof(struct RGF_ICR, ICS), doff_io32}, - {"IMV", S_IRUGO | S_IWUSR, offsetof(struct RGF_ICR, IMV), doff_io32}, - {"IMS", S_IWUSR, offsetof(struct RGF_ICR, IMS), doff_io32}, - {"IMC", S_IWUSR, offsetof(struct RGF_ICR, IMC), doff_io32}, + {"ICC", 0644, offsetof(struct RGF_ICR, ICC), doff_io32}, + {"ICR", 0644, offsetof(struct RGF_ICR, ICR), doff_io32}, + {"ICM", 0644, offsetof(struct RGF_ICR, ICM), doff_io32}, + {"ICS", 0244, offsetof(struct RGF_ICR, ICS), doff_io32}, + {"IMV", 0644, offsetof(struct RGF_ICR, IMV), doff_io32}, + {"IMS", 0244, offsetof(struct RGF_ICR, IMS), doff_io32}, + {"IMC", 0244, offsetof(struct RGF_ICR, IMC), doff_io32}, {}, }; @@ -382,9 +388,9 @@ static int wil6210_debugfs_create_ISR(struct wil6210_priv *wil, } static const struct dbg_off pseudo_isr_off[] = { - {"CAUSE", S_IRUGO, HOSTADDR(RGF_DMA_PSEUDO_CAUSE), doff_io32}, - {"MASK_SW", S_IRUGO, HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW), doff_io32}, - {"MASK_FW", S_IRUGO, HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_FW), doff_io32}, + {"CAUSE", 0444, HOSTADDR(RGF_DMA_PSEUDO_CAUSE), doff_io32}, + {"MASK_SW", 0444, HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW), doff_io32}, + {"MASK_FW", 0444, HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_FW), doff_io32}, {}, }; @@ -403,40 +409,40 @@ static int wil6210_debugfs_create_pseudo_ISR(struct wil6210_priv *wil, } static const struct dbg_off lgc_itr_cnt_off[] = { - {"TRSH", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_CNT_TRSH), doff_io32}, - {"DATA", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_CNT_DATA), doff_io32}, - {"CTL", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_CNT_CRL), doff_io32}, + {"TRSH", 0644, HOSTADDR(RGF_DMA_ITR_CNT_TRSH), doff_io32}, + {"DATA", 0644, HOSTADDR(RGF_DMA_ITR_CNT_DATA), doff_io32}, + {"CTL", 0644, HOSTADDR(RGF_DMA_ITR_CNT_CRL), doff_io32}, {}, }; static const struct dbg_off tx_itr_cnt_off[] = { - {"TRSH", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_TX_CNT_TRSH), + {"TRSH", 0644, HOSTADDR(RGF_DMA_ITR_TX_CNT_TRSH), doff_io32}, - {"DATA", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_TX_CNT_DATA), + {"DATA", 0644, HOSTADDR(RGF_DMA_ITR_TX_CNT_DATA), doff_io32}, - {"CTL", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_TX_CNT_CTL), + {"CTL", 0644, HOSTADDR(RGF_DMA_ITR_TX_CNT_CTL), doff_io32}, - {"IDL_TRSH", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_TRSH), + {"IDL_TRSH", 0644, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_TRSH), doff_io32}, - {"IDL_DATA", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_DATA), + {"IDL_DATA", 0644, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_DATA), doff_io32}, - {"IDL_CTL", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_CTL), + {"IDL_CTL", 0644, HOSTADDR(RGF_DMA_ITR_TX_IDL_CNT_CTL), doff_io32}, {}, }; static const struct dbg_off rx_itr_cnt_off[] = { - {"TRSH", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_RX_CNT_TRSH), + {"TRSH", 0644, HOSTADDR(RGF_DMA_ITR_RX_CNT_TRSH), doff_io32}, - {"DATA", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_RX_CNT_DATA), + {"DATA", 0644, HOSTADDR(RGF_DMA_ITR_RX_CNT_DATA), doff_io32}, - {"CTL", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_RX_CNT_CTL), + {"CTL", 0644, HOSTADDR(RGF_DMA_ITR_RX_CNT_CTL), doff_io32}, - {"IDL_TRSH", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_TRSH), + {"IDL_TRSH", 0644, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_TRSH), doff_io32}, - {"IDL_DATA", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_DATA), + {"IDL_DATA", 0644, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_DATA), doff_io32}, - {"IDL_CTL", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_CTL), + {"IDL_CTL", 0644, HOSTADDR(RGF_DMA_ITR_RX_IDL_CNT_CTL), doff_io32}, {}, }; @@ -495,12 +501,16 @@ static ssize_t wil_read_file_ioblob(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { enum { max_count = 4096 }; - struct debugfs_blob_wrapper *blob = file->private_data; + struct wil_blob_wrapper *wil_blob = file->private_data; loff_t pos = *ppos; - size_t available = blob->size; + size_t available = wil_blob->blob.size; void *buf; size_t ret; + if (test_bit(wil_status_suspending, wil_blob->wil->status) || + test_bit(wil_status_suspended, wil_blob->wil->status)) + return 0; + if (pos < 0) return -EINVAL; @@ -516,8 +526,8 @@ static ssize_t wil_read_file_ioblob(struct file *file, char __user *user_buf, if (!buf) return -ENOMEM; - wil_memcpy_fromio_32(buf, (const volatile void __iomem *)blob->data + - pos, count); + wil_memcpy_fromio_32(buf, (const void __iomem *) + wil_blob->blob.data + pos, count); ret = copy_to_user(user_buf, buf, count); kfree(buf); @@ -540,9 +550,9 @@ static struct dentry *wil_debugfs_create_ioblob(const char *name, umode_t mode, struct dentry *parent, - struct debugfs_blob_wrapper *blob) + struct wil_blob_wrapper *wil_blob) { - return debugfs_create_file(name, mode, parent, blob, &fops_ioblob); + return debugfs_create_file(name, mode, parent, wil_blob, &fops_ioblob); } /*---reset---*/ @@ -793,8 +803,12 @@ static ssize_t wil_write_file_txmgmt(struct file *file, const char __user *buf, struct wireless_dev *wdev = wil_to_wdev(wil); struct cfg80211_mgmt_tx_params params; int rc; - void *frame = kmalloc(len, GFP_KERNEL); + void *frame; + if (!len) + return -EINVAL; + + frame = kmalloc(len, GFP_KERNEL); if (!frame) return -ENOMEM; @@ -810,7 +824,7 @@ static ssize_t wil_write_file_txmgmt(struct file *file, const char __user *buf, rc = wil_cfg80211_mgmt_tx(wiphy, wdev, ¶ms, NULL); kfree(frame); - wil_info(wil, "%s() -> %d\n", __func__, rc); + wil_info(wil, "-> %d\n", rc); return len; } @@ -827,13 +841,13 @@ static ssize_t wil_write_file_wmi(struct file *file, const char __user *buf, size_t len, loff_t *ppos) { struct wil6210_priv *wil = file->private_data; - struct wil6210_mbox_hdr_wmi *wmi; + struct wmi_cmd_hdr *wmi; void *cmd; - int cmdlen = len - sizeof(struct wil6210_mbox_hdr_wmi); + int cmdlen = len - sizeof(struct wmi_cmd_hdr); u16 cmdid; int rc, rc1; - if (cmdlen <= 0) + if (cmdlen < 0) return -EINVAL; wmi = kmalloc(len, GFP_KERNEL); @@ -846,13 +860,13 @@ static ssize_t wil_write_file_wmi(struct file *file, const char __user *buf, return rc; } - cmd = &wmi[1]; - cmdid = le16_to_cpu(wmi->id); + cmd = (cmdlen > 0) ? &wmi[1] : NULL; + cmdid = le16_to_cpu(wmi->command_id); rc1 = wmi_send(wil, cmdid, cmd, cmdlen); kfree(wmi); - wil_info(wil, "%s(0x%04x[%d]) -> %d\n", __func__, cmdid, cmdlen, rc1); + wil_info(wil, "0x%04x[%d] -> %d\n", cmdid, cmdlen, rc1); return rc; } @@ -991,7 +1005,7 @@ static int wil_bf_debugfs_show(struct seq_file *s, void *data) .interval_usec = 0, }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_notify_req_done_event evt; } __packed reply; @@ -1011,6 +1025,7 @@ static int wil_bf_debugfs_show(struct seq_file *s, void *data) " TSF = 0x%016llx\n" " TxMCS = %2d TxTpt = %4d\n" " SQI = %4d\n" + " RSSI = %4d\n" " Status = 0x%08x %s\n" " Sectors(rx:tx) my %2d:%2d peer %2d:%2d\n" " Goodput(rx:tx) %4d:%4d\n" @@ -1020,6 +1035,7 @@ static int wil_bf_debugfs_show(struct seq_file *s, void *data) le16_to_cpu(reply.evt.bf_mcs), le32_to_cpu(reply.evt.tx_tpt), reply.evt.sqi, + reply.evt.rssi, status, wil_bfstatus_str(status), le16_to_cpu(reply.evt.my_rx_sector), le16_to_cpu(reply.evt.my_tx_sector), @@ -1339,6 +1355,34 @@ static void wil_print_rxtid(struct seq_file *s, struct wil_tid_ampdu_rx *r) r->ssn_last_drop); } +static void wil_print_rxtid_crypto(struct seq_file *s, int tid, + struct wil_tid_crypto_rx *c) +{ + int i; + + for (i = 0; i < 4; i++) { + struct wil_tid_crypto_rx_single *cc = &c->key_id[i]; + + if (cc->key_set) + goto has_keys; + } + return; + +has_keys: + if (tid < WIL_STA_TID_NUM) + seq_printf(s, " [%2d] PN", tid); + else + seq_puts(s, " [GR] PN"); + + for (i = 0; i < 4; i++) { + struct wil_tid_crypto_rx_single *cc = &c->key_id[i]; + + seq_printf(s, " [%i%s]%6phN", i, cc->key_set ? "+" : "-", + cc->pn); + } + seq_puts(s, "\n"); +} + static int wil_sta_debugfs_show(struct seq_file *s, void *data) __acquires(&p->tid_rx_lock) __releases(&p->tid_rx_lock) { @@ -1348,6 +1392,7 @@ __acquires(&p->tid_rx_lock) __releases(&p->tid_rx_lock) for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { struct wil_sta_info *p = &wil->sta[i]; char *status = "unknown"; + u8 aid = 0; switch (p->status) { case wil_sta_unused: @@ -1358,26 +1403,34 @@ __acquires(&p->tid_rx_lock) __releases(&p->tid_rx_lock) break; case wil_sta_connected: status = "connected"; + aid = p->aid; break; } - seq_printf(s, "[%d] %pM %s\n", i, p->addr, status); + seq_printf(s, "[%d] %pM %s AID %d\n", i, p->addr, status, aid); if (p->status == wil_sta_connected) { spin_lock_bh(&p->tid_rx_lock); for (tid = 0; tid < WIL_STA_TID_NUM; tid++) { struct wil_tid_ampdu_rx *r = p->tid_rx[tid]; + struct wil_tid_crypto_rx *c = + &p->tid_crypto_rx[tid]; if (r) { - seq_printf(s, "[%2d] ", tid); + seq_printf(s, " [%2d] ", tid); wil_print_rxtid(s, r); } + + wil_print_rxtid_crypto(s, tid, c); } + wil_print_rxtid_crypto(s, WIL_STA_TID_NUM, + &p->group_crypto_rx); spin_unlock_bh(&p->tid_rx_lock); seq_printf(s, - "Rx invalid frame: non-data %lu, short %lu, large %lu\n", + "Rx invalid frame: non-data %lu, short %lu, large %lu, replay %lu\n", p->stats.rx_non_data_frame, p->stats.rx_short_frame, - p->stats.rx_large_frame); + p->stats.rx_large_frame, + p->stats.rx_replay); seq_puts(s, "Rx/MCS:"); for (mcs = 0; mcs < ARRAY_SIZE(p->stats.rx_per_mcs); @@ -1403,6 +1456,222 @@ static const struct file_operations fops_sta = { .llseek = seq_lseek, }; +static ssize_t wil_read_file_led_cfg(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[80]; + int n; + + n = snprintf(buf, sizeof(buf), + "led_id is set to %d, echo 1 to enable, 0 to disable\n", + led_id); + + n = min_t(int, n, sizeof(buf)); + + return simple_read_from_buffer(user_buf, count, ppos, + buf, n); +} + +static ssize_t wil_write_file_led_cfg(struct file *file, + const char __user *buf_, + size_t count, loff_t *ppos) +{ + struct wil6210_priv *wil = file->private_data; + int val; + int rc; + + rc = kstrtoint_from_user(buf_, count, 0, &val); + if (rc) { + wil_err(wil, "Invalid argument\n"); + return rc; + } + + wil_info(wil, "%s led %d\n", val ? "Enabling" : "Disabling", led_id); + rc = wmi_led_cfg(wil, val); + if (rc) { + wil_info(wil, "%s led %d failed\n", + val ? "Enabling" : "Disabling", led_id); + return rc; + } + + return count; +} + +static const struct file_operations fops_led_cfg = { + .read = wil_read_file_led_cfg, + .write = wil_write_file_led_cfg, + .open = simple_open, +}; + +/* led_blink_time, write: + * "<blink_on_slow> <blink_off_slow> <blink_on_med> <blink_off_med> <blink_on_fast> <blink_off_fast> + */ +static ssize_t wil_write_led_blink_time(struct file *file, + const char __user *buf, + size_t len, loff_t *ppos) +{ + int rc; + char *kbuf = kmalloc(len + 1, GFP_KERNEL); + + if (!kbuf) + return -ENOMEM; + + rc = simple_write_to_buffer(kbuf, len, ppos, buf, len); + if (rc != len) { + kfree(kbuf); + return rc >= 0 ? -EIO : rc; + } + + kbuf[len] = '\0'; + rc = sscanf(kbuf, "%d %d %d %d %d %d", + &led_blink_time[WIL_LED_TIME_SLOW].on_ms, + &led_blink_time[WIL_LED_TIME_SLOW].off_ms, + &led_blink_time[WIL_LED_TIME_MED].on_ms, + &led_blink_time[WIL_LED_TIME_MED].off_ms, + &led_blink_time[WIL_LED_TIME_FAST].on_ms, + &led_blink_time[WIL_LED_TIME_FAST].off_ms); + kfree(kbuf); + + if (rc < 0) + return rc; + if (rc < 6) + return -EINVAL; + + return len; +} + +static ssize_t wil_read_led_blink_time(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + static char text[400]; + + snprintf(text, sizeof(text), + "To set led blink on/off time variables write:\n" + "<blink_on_slow> <blink_off_slow> <blink_on_med> " + "<blink_off_med> <blink_on_fast> <blink_off_fast>\n" + "The current values are:\n" + "%d %d %d %d %d %d\n", + led_blink_time[WIL_LED_TIME_SLOW].on_ms, + led_blink_time[WIL_LED_TIME_SLOW].off_ms, + led_blink_time[WIL_LED_TIME_MED].on_ms, + led_blink_time[WIL_LED_TIME_MED].off_ms, + led_blink_time[WIL_LED_TIME_FAST].on_ms, + led_blink_time[WIL_LED_TIME_FAST].off_ms); + + return simple_read_from_buffer(user_buf, count, ppos, text, + sizeof(text)); +} + +static const struct file_operations fops_led_blink_time = { + .read = wil_read_led_blink_time, + .write = wil_write_led_blink_time, + .open = simple_open, +}; + +/*---------FW capabilities------------*/ +static int wil_fw_capabilities_debugfs_show(struct seq_file *s, void *data) +{ + struct wil6210_priv *wil = s->private; + + seq_printf(s, "fw_capabilities : %*pb\n", WMI_FW_CAPABILITY_MAX, + wil->fw_capabilities); + + return 0; +} + +static int wil_fw_capabilities_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, wil_fw_capabilities_debugfs_show, + inode->i_private); +} + +static const struct file_operations fops_fw_capabilities = { + .open = wil_fw_capabilities_seq_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +/*---------FW version------------*/ +static int wil_fw_version_debugfs_show(struct seq_file *s, void *data) +{ + struct wil6210_priv *wil = s->private; + + if (wil->fw_version[0]) + seq_printf(s, "%s\n", wil->fw_version); + else + seq_puts(s, "N/A\n"); + + return 0; +} + +static int wil_fw_version_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, wil_fw_version_debugfs_show, + inode->i_private); +} + +static const struct file_operations fops_fw_version = { + .open = wil_fw_version_seq_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +/*---------suspend_stats---------*/ +static ssize_t wil_write_suspend_stats(struct file *file, + const char __user *buf, + size_t len, loff_t *ppos) +{ + struct wil6210_priv *wil = file->private_data; + + memset(&wil->suspend_stats, 0, sizeof(wil->suspend_stats)); + wil->suspend_stats.min_suspend_time = ULONG_MAX; + wil->suspend_stats.collection_start = ktime_get(); + + return len; +} + +static ssize_t wil_read_suspend_stats(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wil6210_priv *wil = file->private_data; + static char text[400]; + int n; + unsigned long long stats_collection_time = + ktime_to_us(ktime_sub(ktime_get(), + wil->suspend_stats.collection_start)); + + n = snprintf(text, sizeof(text), + "Suspend statistics:\n" + "successful suspends:%ld failed suspends:%ld\n" + "successful resumes:%ld failed resumes:%ld\n" + "rejected by host:%ld rejected by device:%ld\n" + "total suspend time:%lld min suspend time:%lld\n" + "max suspend time:%lld stats collection time: %lld\n", + wil->suspend_stats.successful_suspends, + wil->suspend_stats.failed_suspends, + wil->suspend_stats.successful_resumes, + wil->suspend_stats.failed_resumes, + wil->suspend_stats.rejected_by_host, + wil->suspend_stats.rejected_by_device, + wil->suspend_stats.total_suspend_time, + wil->suspend_stats.min_suspend_time, + wil->suspend_stats.max_suspend_time, + stats_collection_time); + + n = min_t(int, n, sizeof(text)); + + return simple_read_from_buffer(user_buf, count, ppos, text, n); +} + +static const struct file_operations fops_suspend_stats = { + .read = wil_read_suspend_stats, + .write = wil_write_suspend_stats, + .open = simple_open, +}; + /*----------------*/ static void wil6210_debugfs_init_blobs(struct wil6210_priv *wil, struct dentry *dbg) @@ -1411,16 +1680,18 @@ static void wil6210_debugfs_init_blobs(struct wil6210_priv *wil, char name[32]; for (i = 0; i < ARRAY_SIZE(fw_mapping); i++) { - struct debugfs_blob_wrapper *blob = &wil->blobs[i]; + struct wil_blob_wrapper *wil_blob = &wil->blobs[i]; + struct debugfs_blob_wrapper *blob = &wil_blob->blob; const struct fw_map *map = &fw_mapping[i]; if (!map->name) continue; + wil_blob->wil = wil; blob->data = (void * __force)wil->csr + HOSTADDR(map->host); blob->size = map->to - map->from; snprintf(name, sizeof(name), "blob_%s", map->name); - wil_debugfs_create_ioblob(name, S_IRUGO, dbg, blob); + wil_debugfs_create_ioblob(name, 0444, dbg, wil_blob); } } @@ -1430,25 +1701,30 @@ static const struct { umode_t mode; const struct file_operations *fops; } dbg_files[] = { - {"mbox", S_IRUGO, &fops_mbox}, - {"vrings", S_IRUGO, &fops_vring}, - {"stations", S_IRUGO, &fops_sta}, - {"desc", S_IRUGO, &fops_txdesc}, - {"bf", S_IRUGO, &fops_bf}, - {"ssid", S_IRUGO | S_IWUSR, &fops_ssid}, - {"mem_val", S_IRUGO, &fops_memread}, - {"reset", S_IWUSR, &fops_reset}, - {"rxon", S_IWUSR, &fops_rxon}, - {"tx_mgmt", S_IWUSR, &fops_txmgmt}, - {"wmi_send", S_IWUSR, &fops_wmi}, - {"back", S_IRUGO | S_IWUSR, &fops_back}, - {"pmccfg", S_IRUGO | S_IWUSR, &fops_pmccfg}, - {"pmcdata", S_IRUGO, &fops_pmcdata}, - {"temp", S_IRUGO, &fops_temp}, - {"freq", S_IRUGO, &fops_freq}, - {"link", S_IRUGO, &fops_link}, - {"info", S_IRUGO, &fops_info}, - {"recovery", S_IRUGO | S_IWUSR, &fops_recovery}, + {"mbox", 0444, &fops_mbox}, + {"vrings", 0444, &fops_vring}, + {"stations", 0444, &fops_sta}, + {"desc", 0444, &fops_txdesc}, + {"bf", 0444, &fops_bf}, + {"ssid", 0644, &fops_ssid}, + {"mem_val", 0644, &fops_memread}, + {"reset", 0244, &fops_reset}, + {"rxon", 0244, &fops_rxon}, + {"tx_mgmt", 0244, &fops_txmgmt}, + {"wmi_send", 0244, &fops_wmi}, + {"back", 0644, &fops_back}, + {"pmccfg", 0644, &fops_pmccfg}, + {"pmcdata", 0444, &fops_pmcdata}, + {"temp", 0444, &fops_temp}, + {"freq", 0444, &fops_freq}, + {"link", 0444, &fops_link}, + {"info", 0444, &fops_info}, + {"recovery", 0644, &fops_recovery}, + {"led_cfg", 0644, &fops_led_cfg}, + {"led_blink_time", 0644, &fops_led_blink_time}, + {"fw_capabilities", 0444, &fops_fw_capabilities}, + {"fw_version", 0444, &fops_fw_version}, + {"suspend_stats", 0644, &fops_suspend_stats}, }; static void wil6210_debugfs_init_files(struct wil6210_priv *wil, @@ -1487,29 +1763,32 @@ static void wil6210_debugfs_init_isr(struct wil6210_priv *wil, /* fields in struct wil6210_priv */ static const struct dbg_off dbg_wil_off[] = { - WIL_FIELD(privacy, S_IRUGO, doff_u32), - WIL_FIELD(status[0], S_IRUGO | S_IWUSR, doff_ulong), - WIL_FIELD(fw_version, S_IRUGO, doff_u32), - WIL_FIELD(hw_version, S_IRUGO, doff_x32), - WIL_FIELD(recovery_count, S_IRUGO, doff_u32), - WIL_FIELD(ap_isolate, S_IRUGO, doff_u32), + WIL_FIELD(privacy, 0444, doff_u32), + WIL_FIELD(status[0], 0644, doff_ulong), + WIL_FIELD(hw_version, 0444, doff_x32), + WIL_FIELD(recovery_count, 0444, doff_u32), + WIL_FIELD(ap_isolate, 0444, doff_u32), + WIL_FIELD(discovery_mode, 0644, doff_u8), + WIL_FIELD(chip_revision, 0444, doff_u8), + WIL_FIELD(abft_len, 0644, doff_u8), + WIL_FIELD(wakeup_trigger, 0644, doff_u8), + WIL_FIELD(vring_idle_trsh, 0644, doff_u32), {}, }; static const struct dbg_off dbg_wil_regs[] = { - {"RGF_MAC_MTRL_COUNTER_0", S_IRUGO, HOSTADDR(RGF_MAC_MTRL_COUNTER_0), + {"RGF_MAC_MTRL_COUNTER_0", 0444, HOSTADDR(RGF_MAC_MTRL_COUNTER_0), doff_io32}, - {"RGF_USER_USAGE_1", S_IRUGO, HOSTADDR(RGF_USER_USAGE_1), doff_io32}, + {"RGF_USER_USAGE_1", 0444, HOSTADDR(RGF_USER_USAGE_1), doff_io32}, {}, }; /* static parameters */ static const struct dbg_off dbg_statics[] = { - {"desc_index", S_IRUGO | S_IWUSR, (ulong)&dbg_txdesc_index, doff_u32}, - {"vring_index", S_IRUGO | S_IWUSR, (ulong)&dbg_vring_index, doff_u32}, - {"mem_addr", S_IRUGO | S_IWUSR, (ulong)&mem_addr, doff_u32}, - {"vring_idle_trsh", S_IRUGO | S_IWUSR, (ulong)&vring_idle_trsh, - doff_u32}, + {"desc_index", 0644, (ulong)&dbg_txdesc_index, doff_u32}, + {"vring_index", 0644, (ulong)&dbg_vring_index, doff_u32}, + {"mem_addr", 0644, (ulong)&mem_addr, doff_u32}, + {"led_polarity", 0644, (ulong)&led_polarity, doff_u8}, {}, }; @@ -1535,6 +1814,8 @@ int wil6210_debugfs_init(struct wil6210_priv *wil) wil6210_debugfs_create_ITR_CNT(wil, dbg); + wil->suspend_stats.collection_start = ktime_get(); + return 0; } diff --git a/drivers/net/wireless/ath/wil6210/ethtool.c b/drivers/net/wireless/ath/wil6210/ethtool.c index 7053b62ca8d3..adcfef4dabf7 100644 --- a/drivers/net/wireless/ath/wil6210/ethtool.c +++ b/drivers/net/wireless/ath/wil6210/ethtool.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Qualcomm Atheros, Inc. + * Copyright (c) 2014,2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -27,7 +27,7 @@ static int wil_ethtoolops_begin(struct net_device *ndev) mutex_lock(&wil->mutex); - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "ethtoolops_begin\n"); return 0; } @@ -36,7 +36,7 @@ static void wil_ethtoolops_complete(struct net_device *ndev) { struct wil6210_priv *wil = ndev_to_wil(ndev); - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "ethtoolops_complete\n"); mutex_unlock(&wil->mutex); } @@ -48,7 +48,7 @@ static int wil_ethtoolops_get_coalesce(struct net_device *ndev, u32 tx_itr_en, tx_itr_val = 0; u32 rx_itr_en, rx_itr_val = 0; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "ethtoolops_get_coalesce\n"); tx_itr_en = wil_r(wil, RGF_DMA_ITR_TX_CNT_CTL); if (tx_itr_en & BIT_DMA_ITR_TX_CNT_CTL_EN) @@ -68,7 +68,7 @@ static int wil_ethtoolops_set_coalesce(struct net_device *ndev, { struct wil6210_priv *wil = ndev_to_wil(ndev); - wil_dbg_misc(wil, "%s(rx %d usec, tx %d usec)\n", __func__, + wil_dbg_misc(wil, "ethtoolops_set_coalesce: rx %d usec, tx %d usec\n", cp->rx_coalesce_usecs, cp->tx_coalesce_usecs); if (wil->wdev->iftype == NL80211_IFTYPE_MONITOR) { diff --git a/drivers/net/wireless/ath/wil6210/ftm.c b/drivers/net/wireless/ath/wil6210/ftm.c new file mode 100644 index 000000000000..5906b90b337d --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/ftm.c @@ -0,0 +1,957 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/etherdevice.h> +#include <net/netlink.h> +#include "wil6210.h" +#include "ftm.h" +#include "wmi.h" + +/* FTM session ID we use with FW */ +#define WIL_FTM_FW_SESSION_ID 1 + +/* fixed spare allocation we reserve in NL messages we allocate */ +#define WIL_FTM_NL_EXTRA_ALLOC 32 + +/* approx maximum length for FTM_MEAS_RESULT NL80211 event */ +#define WIL_FTM_MEAS_RESULT_MAX_LENGTH 2048 + +/* timeout for waiting for standalone AOA measurement, milliseconds */ +#define WIL_AOA_MEASUREMENT_TIMEOUT 1000 + +/* maximum number of allowed FTM measurements per burst */ +#define WIL_FTM_MAX_MEAS_PER_BURST 31 + +/* initial token to use on non-secure FTM measurement */ +#define WIL_TOF_FTM_DEFAULT_INITIAL_TOKEN 2 + +/* maximum AOA burst period, limited by FW */ +#define WIL_AOA_MAX_BURST_PERIOD 255 + +#define WIL_TOF_FTM_MAX_LCI_LENGTH (240) +#define WIL_TOF_FTM_MAX_LCR_LENGTH (240) + +static const struct +nla_policy wil_nl80211_loc_policy[QCA_WLAN_VENDOR_ATTR_LOC_MAX + 1] = { + [QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE] = { .type = NLA_U64 }, + [QCA_WLAN_VENDOR_ATTR_LOC_CAPA] = { .type = NLA_NESTED }, + [QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS] = { .type = NLA_NESTED }, + [QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEER_RESULTS] = { .type = NLA_NESTED }, + [QCA_WLAN_VENDOR_ATTR_FTM_RESPONDER_ENABLE] = { .type = NLA_FLAG }, + [QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS] = { .type = NLA_U32 }, + [QCA_WLAN_VENDOR_ATTR_FTM_INITIAL_TOKEN] = { .type = NLA_U8 }, + [QCA_WLAN_VENDOR_ATTR_AOA_TYPE] = { .type = NLA_U32 }, + [QCA_WLAN_VENDOR_ATTR_LOC_ANTENNA_ARRAY_MASK] = { .type = NLA_U32 }, + [QCA_WLAN_VENDOR_ATTR_FREQ] = { .type = NLA_U32 }, +}; + +static const struct +nla_policy wil_nl80211_ftm_peer_policy[ + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAX + 1] = { + [QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR] = { .len = ETH_ALEN }, + [QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS] = { .type = NLA_U32 }, + [QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_PARAMS] = { .type = NLA_NESTED }, + [QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID] = { .type = NLA_U8 }, + [QCA_WLAN_VENDOR_ATTR_FTM_PEER_AOA_BURST_PERIOD] = { .type = NLA_U16 }, + [QCA_WLAN_VENDOR_ATTR_FTM_PEER_FREQ] = { .type = NLA_U32 }, +}; + +static const struct +nla_policy wil_nl80211_ftm_meas_param_policy[ + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MAX + 1] = { + [QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST] = { .type = NLA_U8 }, + [QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP] = { .type = NLA_U8 }, + [QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION] = { .type = NLA_U8 }, + [QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD] = { .type = NLA_U16 }, +}; + +static u8 wil_ftm_get_channel(struct wil6210_priv *wil, + const u8 *mac_addr, u32 freq) +{ + struct wiphy *wiphy = wil_to_wiphy(wil); + struct cfg80211_bss *bss; + struct ieee80211_channel *chan; + u8 channel; + + if (freq) { + chan = ieee80211_get_channel(wiphy, freq); + if (!chan) { + wil_err(wil, "invalid freq: %d\n", freq); + return 0; + } + channel = chan->hw_value; + } else { + bss = cfg80211_get_bss(wiphy, NULL, mac_addr, + NULL, 0, IEEE80211_BSS_TYPE_ANY, + IEEE80211_PRIVACY_ANY); + if (!bss) { + wil_err(wil, "Unable to find BSS\n"); + return 0; + } + channel = bss->channel->hw_value; + cfg80211_put_bss(wiphy, bss); + } + + wil_dbg_misc(wil, "target %pM at channel %d\n", mac_addr, channel); + return channel; +} + +static int wil_ftm_parse_meas_params(struct wil6210_priv *wil, + struct nlattr *attr, + struct wil_ftm_meas_params *params) +{ + struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MAX + 1]; + int rc; + + if (!attr) { + /* temporary defaults for one-shot measurement */ + params->meas_per_burst = 1; + params->burst_period = 5; /* 500 milliseconds */ + return 0; + } + rc = nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MAX, + attr, wil_nl80211_ftm_meas_param_policy); + if (rc) { + wil_err(wil, "invalid measurement params\n"); + return rc; + } + if (tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST]) + params->meas_per_burst = nla_get_u8( + tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST]); + if (tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP]) + params->num_of_bursts_exp = nla_get_u8( + tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP]); + if (tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION]) + params->burst_duration = nla_get_u8( + tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION]); + if (tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD]) + params->burst_period = nla_get_u16( + tb[QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD]); + return 0; +} + +static int wil_ftm_validate_meas_params(struct wil6210_priv *wil, + struct wil_ftm_meas_params *params) +{ + /* temporary allow only single-burst */ + if (params->meas_per_burst > WIL_FTM_MAX_MEAS_PER_BURST || + params->num_of_bursts_exp != 0) { + wil_err(wil, "invalid measurement params\n"); + return -EINVAL; + } + + return 0; +} + +static int wil_ftm_append_meas_params(struct wil6210_priv *wil, + struct sk_buff *msg, + struct wil_ftm_meas_params *params) +{ + struct nlattr *nl_p; + + nl_p = nla_nest_start( + msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MEAS_PARAMS); + if (!nl_p) + goto out_put_failure; + if (nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST, + params->meas_per_burst) || + nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP, + params->num_of_bursts_exp) || + nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION, + params->burst_duration) || + nla_put_u16(msg, QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD, + params->burst_period)) + goto out_put_failure; + nla_nest_end(msg, nl_p); + return 0; +out_put_failure: + return -ENOBUFS; +} + +static int wil_ftm_append_peer_meas_res(struct wil6210_priv *wil, + struct sk_buff *msg, + struct wil_ftm_peer_meas_res *res) +{ + struct nlattr *nl_mres, *nl_f; + int i; + + if (nla_put(msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MAC_ADDR, + ETH_ALEN, res->mac_addr) || + nla_put_u32(msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_FLAGS, + res->flags) || + nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS, + res->status)) + goto out_put_failure; + if (res->status == QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_FAILED && + nla_put_u8(msg, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_VALUE_SECONDS, + res->value_seconds)) + goto out_put_failure; + if (res->has_params && + wil_ftm_append_meas_params(wil, msg, &res->params)) + goto out_put_failure; + nl_mres = nla_nest_start(msg, QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MEAS); + if (!nl_mres) + goto out_put_failure; + for (i = 0; i < res->n_meas; i++) { + nl_f = nla_nest_start(msg, i); + if (!nl_f) + goto out_put_failure; + if (nla_put_u64(msg, QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T1, + res->meas[i].t1) || + nla_put_u64(msg, QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T2, + res->meas[i].t2) || + nla_put_u64(msg, QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T3, + res->meas[i].t3) || + nla_put_u64(msg, QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T4, + res->meas[i].t4)) + goto out_put_failure; + nla_nest_end(msg, nl_f); + } + nla_nest_end(msg, nl_mres); + return 0; +out_put_failure: + wil_err(wil, "fail to append peer result\n"); + return -ENOBUFS; +} + +static void wil_ftm_send_meas_result(struct wil6210_priv *wil, + struct wil_ftm_peer_meas_res *res) +{ + struct sk_buff *vendor_event = NULL; + struct nlattr *nl_res; + int rc = 0; + + wil_dbg_misc(wil, "sending %d results for peer %pM\n", + res->n_meas, res->mac_addr); + + vendor_event = cfg80211_vendor_event_alloc( + wil_to_wiphy(wil), + wil->wdev, + WIL_FTM_MEAS_RESULT_MAX_LENGTH, + QCA_NL80211_VENDOR_EVENT_FTM_MEAS_RESULT_INDEX, + GFP_KERNEL); + if (!vendor_event) { + wil_err(wil, "fail to allocate measurement result\n"); + rc = -ENOMEM; + goto out; + } + + if (nla_put_u64( + vendor_event, QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE, + wil->ftm.session_cookie)) { + rc = -ENOBUFS; + goto out; + } + + nl_res = nla_nest_start(vendor_event, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEER_RESULTS); + if (!nl_res) { + rc = -ENOBUFS; + goto out; + } + + rc = wil_ftm_append_peer_meas_res(wil, vendor_event, res); + if (rc) + goto out; + + nla_nest_end(vendor_event, nl_res); + cfg80211_vendor_event(vendor_event, GFP_KERNEL); + vendor_event = NULL; +out: + if (vendor_event) + kfree_skb(vendor_event); + if (rc) + wil_err(wil, "send peer result failed, err %d\n", rc); +} + +static void wil_ftm_send_peer_res(struct wil6210_priv *wil) +{ + if (!wil->ftm.has_ftm_res || !wil->ftm.ftm_res) + return; + + wil_ftm_send_meas_result(wil, wil->ftm.ftm_res); + wil->ftm.has_ftm_res = 0; + wil->ftm.ftm_res->n_meas = 0; +} + +static void wil_aoa_measurement_timeout(struct work_struct *work) +{ + struct wil_ftm_priv *ftm = container_of(work, struct wil_ftm_priv, + aoa_timeout_work); + struct wil6210_priv *wil = container_of(ftm, struct wil6210_priv, ftm); + struct wil_aoa_meas_result res; + + wil_dbg_misc(wil, "AOA measurement timeout\n"); + + memset(&res, 0, sizeof(res)); + ether_addr_copy(res.mac_addr, wil->ftm.aoa_peer_mac_addr); + res.type = wil->ftm.aoa_type; + res.status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED; + wil_aoa_cfg80211_meas_result(wil, &res); +} + +static int +wil_ftm_cfg80211_start_session(struct wil6210_priv *wil, + struct wil_ftm_session_request *request) +{ + int rc = 0; + bool has_lci = false, has_lcr = false; + u8 max_meas = 0, channel, *ptr; + u32 i, cmd_len; + struct wmi_tof_session_start_cmd *cmd; + + mutex_lock(&wil->ftm.lock); + if (wil->ftm.session_started || wil->ftm.aoa_started) { + wil_err(wil, "FTM or AOA session already running\n"); + rc = -EAGAIN; + goto out; + } + + for (i = 0; i < request->n_peers; i++) { + if (request->peers[i].flags & + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCI) + has_lci = true; + if (request->peers[i].flags & + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCR) + has_lcr = true; + max_meas = max(max_meas, + request->peers[i].params.meas_per_burst); + } + + wil->ftm.ftm_res = kzalloc(sizeof(*wil->ftm.ftm_res) + + max_meas * sizeof(struct wil_ftm_peer_meas) + + (has_lci ? WIL_TOF_FTM_MAX_LCI_LENGTH : 0) + + (has_lcr ? WIL_TOF_FTM_MAX_LCR_LENGTH : 0), GFP_KERNEL); + if (!wil->ftm.ftm_res) { + rc = -ENOMEM; + goto out; + } + ptr = (u8 *)wil->ftm.ftm_res; + ptr += sizeof(struct wil_ftm_peer_meas_res) + + max_meas * sizeof(struct wil_ftm_peer_meas); + if (has_lci) { + wil->ftm.ftm_res->lci = ptr; + ptr += WIL_TOF_FTM_MAX_LCI_LENGTH; + } + if (has_lcr) + wil->ftm.ftm_res->lcr = ptr; + wil->ftm.max_ftm_meas = max_meas; + + cmd_len = sizeof(struct wmi_tof_session_start_cmd) + + request->n_peers * sizeof(struct wmi_ftm_dest_info); + cmd = kzalloc(cmd_len, GFP_KERNEL); + if (!cmd) { + rc = -ENOMEM; + goto out_ftm_res; + } + + cmd->session_id = cpu_to_le32(WIL_FTM_FW_SESSION_ID); + cmd->aoa_type = request->aoa_type; + cmd->num_of_dest = cpu_to_le16(request->n_peers); + for (i = 0; i < request->n_peers; i++) { + ether_addr_copy(cmd->ftm_dest_info[i].dst_mac, + request->peers[i].mac_addr); + channel = wil_ftm_get_channel(wil, request->peers[i].mac_addr, + request->peers[i].freq); + if (!channel) { + wil_err(wil, "can't find FTM target at index %d\n", i); + rc = -EINVAL; + goto out_cmd; + } + cmd->ftm_dest_info[i].channel = channel - 1; + if (request->peers[i].flags & + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_SECURE) { + cmd->ftm_dest_info[i].flags |= + WMI_TOF_SESSION_START_FLAG_SECURED; + cmd->ftm_dest_info[i].initial_token = + request->peers[i].secure_token_id; + } else { + cmd->ftm_dest_info[i].initial_token = + WIL_TOF_FTM_DEFAULT_INITIAL_TOKEN; + } + if (request->peers[i].flags & + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_ASAP) + cmd->ftm_dest_info[i].flags |= + WMI_TOF_SESSION_START_FLAG_ASAP; + if (request->peers[i].flags & + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCI) + cmd->ftm_dest_info[i].flags |= + WMI_TOF_SESSION_START_FLAG_LCI_REQ; + if (request->peers[i].flags & + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCR) + cmd->ftm_dest_info[i].flags |= + WMI_TOF_SESSION_START_FLAG_LCR_REQ; + cmd->ftm_dest_info[i].num_of_ftm_per_burst = + request->peers[i].params.meas_per_burst; + cmd->ftm_dest_info[i].num_of_bursts_exp = + request->peers[i].params.num_of_bursts_exp; + cmd->ftm_dest_info[i].burst_duration = + request->peers[i].params.burst_duration; + cmd->ftm_dest_info[i].burst_period = + cpu_to_le16(request->peers[i].params.burst_period); + cmd->ftm_dest_info[i].num_burst_per_aoa_meas = + request->peers[i].aoa_burst_period; + } + + rc = wmi_send(wil, WMI_TOF_SESSION_START_CMDID, cmd, cmd_len); + + if (!rc) { + wil->ftm.session_cookie = request->session_cookie; + wil->ftm.session_started = 1; + } +out_cmd: + kfree(cmd); +out_ftm_res: + if (rc) { + kfree(wil->ftm.ftm_res); + wil->ftm.ftm_res = NULL; + } +out: + mutex_unlock(&wil->ftm.lock); + return rc; +} + +static void +wil_ftm_cfg80211_session_ended(struct wil6210_priv *wil, u32 status) +{ + struct sk_buff *vendor_event = NULL; + + mutex_lock(&wil->ftm.lock); + + if (!wil->ftm.session_started) { + wil_dbg_misc(wil, "FTM session not started, ignoring event\n"); + goto out; + } + + /* finish the session */ + wil_dbg_misc(wil, "finishing FTM session\n"); + + /* send left-over results if any */ + wil_ftm_send_peer_res(wil); + + wil->ftm.session_started = 0; + kfree(wil->ftm.ftm_res); + wil->ftm.ftm_res = NULL; + + vendor_event = cfg80211_vendor_event_alloc( + wil_to_wiphy(wil), + wil->wdev, + WIL_FTM_NL_EXTRA_ALLOC, + QCA_NL80211_VENDOR_EVENT_FTM_SESSION_DONE_INDEX, + GFP_KERNEL); + if (!vendor_event) + goto out; + + if (nla_put_u64(vendor_event, + QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE, + wil->ftm.session_cookie) || + nla_put_u32(vendor_event, + QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS, status)) { + wil_err(wil, "failed to fill session done event\n"); + goto out; + } + cfg80211_vendor_event(vendor_event, GFP_KERNEL); + vendor_event = NULL; +out: + kfree_skb(vendor_event); + mutex_unlock(&wil->ftm.lock); +} + +static void wil_aoa_timer_fn(ulong x) +{ + struct wil6210_priv *wil = (void *)x; + + wil_dbg_misc(wil, "AOA timer\n"); + schedule_work(&wil->ftm.aoa_timeout_work); +} + +static int +wil_aoa_cfg80211_start_measurement(struct wil6210_priv *wil, + struct wil_aoa_meas_request *request) +{ + int rc = 0; + struct wmi_aoa_meas_cmd cmd; + u8 channel; + + mutex_lock(&wil->ftm.lock); + + if (wil->ftm.aoa_started || wil->ftm.session_started) { + wil_err(wil, "AOA or FTM measurement already running\n"); + rc = -EAGAIN; + goto out; + } + if (request->type >= QCA_WLAN_VENDOR_ATTR_AOA_TYPE_MAX) { + wil_err(wil, "invalid AOA type: %d\n", request->type); + rc = -EINVAL; + goto out; + } + + channel = wil_ftm_get_channel(wil, request->mac_addr, request->freq); + if (!channel) { + rc = -EINVAL; + goto out; + } + + memset(&cmd, 0, sizeof(cmd)); + ether_addr_copy(cmd.mac_addr, request->mac_addr); + cmd.channel = channel - 1; + cmd.aoa_meas_type = request->type; + + rc = wmi_send(wil, WMI_AOA_MEAS_CMDID, &cmd, sizeof(cmd)); + if (rc) + goto out; + + ether_addr_copy(wil->ftm.aoa_peer_mac_addr, request->mac_addr); + mod_timer(&wil->ftm.aoa_timer, + jiffies + msecs_to_jiffies(WIL_AOA_MEASUREMENT_TIMEOUT)); + wil->ftm.aoa_started = 1; +out: + mutex_unlock(&wil->ftm.lock); + return rc; +} + +void wil_aoa_cfg80211_meas_result(struct wil6210_priv *wil, + struct wil_aoa_meas_result *result) +{ + struct sk_buff *vendor_event = NULL; + + mutex_lock(&wil->ftm.lock); + + if (!wil->ftm.aoa_started && !wil->ftm.session_started) { + wil_info(wil, "AOA/FTM not started, not sending result\n"); + goto out; + } + + wil_dbg_misc(wil, "sending AOA measurement result\n"); + + vendor_event = cfg80211_vendor_event_alloc( + wil_to_wiphy(wil), + wil->wdev, + result->length + WIL_FTM_NL_EXTRA_ALLOC, + QCA_NL80211_VENDOR_EVENT_AOA_MEAS_RESULT_INDEX, + GFP_KERNEL); + if (!vendor_event) { + wil_err(wil, "fail to allocate measurement result\n"); + goto out; + } + + if (nla_put(vendor_event, QCA_WLAN_VENDOR_ATTR_MAC_ADDR, + ETH_ALEN, result->mac_addr) || + nla_put_u32(vendor_event, QCA_WLAN_VENDOR_ATTR_AOA_TYPE, + result->type) || + nla_put_u32(vendor_event, QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS, + result->status) || + nla_put_u32(vendor_event, + QCA_WLAN_VENDOR_ATTR_LOC_ANTENNA_ARRAY_MASK, + result->antenna_array_mask)) { + wil_err(wil, "failed to fill vendor event\n"); + goto out; + } + + if (result->length > 0 && + nla_put(vendor_event, QCA_WLAN_VENDOR_ATTR_AOA_MEAS_RESULT, + result->length, result->data)) { + wil_err(wil, "failed to fill vendor event with AOA data\n"); + goto out; + } + + cfg80211_vendor_event(vendor_event, GFP_KERNEL); + + del_timer_sync(&wil->ftm.aoa_timer); + wil->ftm.aoa_started = 0; +out: + mutex_unlock(&wil->ftm.lock); +} + +void wil_ftm_evt_session_ended(struct wil6210_priv *wil, + struct wmi_tof_session_end_event *evt) +{ + u32 status; + + switch (evt->status) { + case WMI_TOF_SESSION_END_NO_ERROR: + status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_OK; + break; + case WMI_TOF_SESSION_END_PARAMS_ERROR: + status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_INVALID; + break; + case WMI_TOF_SESSION_END_FAIL: + status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_FAILED; + break; + case WMI_TOF_SESSION_END_ABORTED: + status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED; + break; + default: + status = QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_FAILED; + break; + } + + wil_ftm_cfg80211_session_ended(wil, status); +} + +void wil_ftm_evt_per_dest_res(struct wil6210_priv *wil, + struct wmi_tof_ftm_per_dest_res_event *evt) +{ + u32 i, index; + __le64 tmp = 0; + u8 n_meas; + + mutex_lock(&wil->ftm.lock); + + if (!wil->ftm.session_started || !wil->ftm.ftm_res) { + wil_dbg_misc(wil, "Session not running, ignoring res event\n"); + goto out; + } + if (wil->ftm.has_ftm_res && + !ether_addr_equal(evt->dst_mac, wil->ftm.ftm_res->mac_addr)) { + wil_dbg_misc(wil, + "Results for previous peer not properly terminated\n"); + wil_ftm_send_peer_res(wil); + } + + if (!wil->ftm.has_ftm_res) { + ether_addr_copy(wil->ftm.ftm_res->mac_addr, evt->dst_mac); + wil->ftm.has_ftm_res = 1; + } + + n_meas = evt->actual_ftm_per_burst; + switch (evt->status) { + case WMI_PER_DEST_RES_NO_ERROR: + wil->ftm.ftm_res->status = + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_OK; + break; + case WMI_PER_DEST_RES_TX_RX_FAIL: + /* FW reports corrupted results here, discard. */ + n_meas = 0; + wil->ftm.ftm_res->status = + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_OK; + break; + case WMI_PER_DEST_RES_PARAM_DONT_MATCH: + wil->ftm.ftm_res->status = + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_INVALID; + break; + default: + wil_err(wil, "unexpected status %d\n", evt->status); + wil->ftm.ftm_res->status = + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_INVALID; + break; + } + + for (i = 0; i < n_meas; i++) { + index = wil->ftm.ftm_res->n_meas; + if (index >= wil->ftm.max_ftm_meas) { + wil_dbg_misc(wil, "Too many measurements, some lost\n"); + break; + } + memcpy(&tmp, evt->responder_ftm_res[i].t1, + sizeof(evt->responder_ftm_res[i].t1)); + wil->ftm.ftm_res->meas[index].t1 = le64_to_cpu(tmp); + memcpy(&tmp, evt->responder_ftm_res[i].t2, + sizeof(evt->responder_ftm_res[i].t2)); + wil->ftm.ftm_res->meas[index].t2 = le64_to_cpu(tmp); + memcpy(&tmp, evt->responder_ftm_res[i].t3, + sizeof(evt->responder_ftm_res[i].t3)); + wil->ftm.ftm_res->meas[index].t3 = le64_to_cpu(tmp); + memcpy(&tmp, evt->responder_ftm_res[i].t4, + sizeof(evt->responder_ftm_res[i].t4)); + wil->ftm.ftm_res->meas[index].t4 = le64_to_cpu(tmp); + wil->ftm.ftm_res->n_meas++; + } + + if (evt->flags & WMI_PER_DEST_RES_BURST_REPORT_END) + wil_ftm_send_peer_res(wil); +out: + mutex_unlock(&wil->ftm.lock); +} + +void wil_aoa_evt_meas(struct wil6210_priv *wil, + struct wmi_aoa_meas_event *evt, + int len) +{ + int data_len = len - offsetof(struct wmi_aoa_meas_event, meas_data); + struct wil_aoa_meas_result *res; + + if (data_len < 0) { + wil_err(wil, "AOA event too short (%d)\n", len); + return; + } + data_len = min_t(int, le16_to_cpu(evt->length), data_len); + + res = kmalloc(sizeof(*res) + data_len, GFP_KERNEL); + if (!res) + return; + + ether_addr_copy(res->mac_addr, evt->mac_addr); + res->type = evt->aoa_meas_type; + res->antenna_array_mask = le32_to_cpu(evt->meas_rf_mask); + res->status = evt->meas_status; + res->length = data_len; + memcpy(res->data, evt->meas_data, data_len); + + wil_dbg_misc(wil, "AOA result status %d type %d mask %d length %d\n", + res->status, res->type, + res->antenna_array_mask, res->length); + + wil_aoa_cfg80211_meas_result(wil, res); + kfree(res); +} + +int wil_ftm_get_capabilities(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct sk_buff *skb; + struct nlattr *attr; + + if (!test_bit(WMI_FW_CAPABILITY_FTM, wil->fw_capabilities)) + return -ENOTSUPP; + + /* we should get the capabilities from the FW. for now, + * report dummy capabilities for one shot measurement + */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 128); + if (!skb) + return -ENOMEM; + attr = nla_nest_start(skb, QCA_WLAN_VENDOR_ATTR_LOC_CAPA); + if (!attr || + nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAGS, + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_RESPONDER | + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_INITIATOR | + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_ASAP | + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_AOA) || + nla_put_u16(skb, QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_SESSIONS, + 1) || + nla_put_u16(skb, QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_PEERS, 1) || + nla_put_u8(skb, QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_BURSTS_EXP, + 0) || + nla_put_u8(skb, QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_MEAS_PER_BURST, + 4) || + nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_AOA_CAPA_SUPPORTED_TYPES, + BIT(QCA_WLAN_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE))) { + wil_err(wil, "fail to fill get_capabilities reply\n"); + kfree_skb(skb); + return -ENOMEM; + } + nla_nest_end(skb, attr); + + return cfg80211_vendor_cmd_reply(skb); +} + +int wil_ftm_start_session(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct wil_ftm_session_request *request; + struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_LOC_MAX + 1]; + struct nlattr *tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAX + 1]; + struct nlattr *peer; + int rc, n_peers = 0, index = 0, tmp; + u32 aoa_type = 0; + + if (!test_bit(WMI_FW_CAPABILITY_FTM, wil->fw_capabilities)) + return -ENOTSUPP; + + rc = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_LOC_MAX, data, data_len, + wil_nl80211_loc_policy); + if (rc) { + wil_err(wil, "Invalid ATTR\n"); + return rc; + } + + if (!tb[QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS]) { + wil_err(wil, "no peers specified\n"); + return -EINVAL; + } + + if (!tb[QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE]) { + wil_err(wil, "session cookie not specified\n"); + return -EINVAL; + } + + if (tb[QCA_WLAN_VENDOR_ATTR_AOA_TYPE]) { + aoa_type = nla_get_u32(tb[QCA_WLAN_VENDOR_ATTR_AOA_TYPE]); + if (aoa_type >= QCA_WLAN_VENDOR_ATTR_AOA_TYPE_MAX) { + wil_err(wil, "invalid AOA type: %d\n", aoa_type); + return -EINVAL; + } + } + + nla_for_each_nested(peer, tb[QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS], + tmp) + n_peers++; + + if (!n_peers) { + wil_err(wil, "empty peer list\n"); + return -EINVAL; + } + + /* for now only allow measurement for a single peer */ + if (n_peers != 1) { + wil_err(wil, "only single peer allowed\n"); + return -EINVAL; + } + + request = kzalloc(sizeof(*request) + + n_peers * sizeof(struct wil_ftm_meas_peer_info), + GFP_KERNEL); + if (!request) + return -ENOMEM; + + request->session_cookie = + nla_get_u64(tb[QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE]); + request->aoa_type = aoa_type; + request->n_peers = n_peers; + nla_for_each_nested(peer, tb[QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS], + tmp) { + rc = nla_parse_nested(tb2, QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAX, + peer, wil_nl80211_ftm_peer_policy); + if (rc) { + wil_err(wil, "Invalid peer ATTR\n"); + goto out; + } + if (!tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR] || + nla_len(tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR]) + != ETH_ALEN) { + wil_err(wil, "Peer MAC address missing or invalid\n"); + rc = -EINVAL; + goto out; + } + memcpy(request->peers[index].mac_addr, + nla_data(tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR]), + ETH_ALEN); + if (tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_FREQ]) + request->peers[index].freq = nla_get_u32( + tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_FREQ]); + if (tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS]) + request->peers[index].flags = nla_get_u32( + tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS]); + if (tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID]) + request->peers[index].secure_token_id = nla_get_u8( + tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID]); + if (tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_AOA_BURST_PERIOD]) { + request->peers[index].aoa_burst_period = nla_get_u16( + tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_AOA_BURST_PERIOD]); + if (request->peers[index].aoa_burst_period > + WIL_AOA_MAX_BURST_PERIOD) { + wil_err(wil, "Invalid AOA burst period at index: %d\n", + index); + rc = -EINVAL; + goto out; + } + } + + rc = wil_ftm_parse_meas_params( + wil, + tb2[QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_PARAMS], + &request->peers[index].params); + if (!rc) + rc = wil_ftm_validate_meas_params( + wil, &request->peers[index].params); + if (rc) + goto out; + index++; + } + + rc = wil_ftm_cfg80211_start_session(wil, request); +out: + kfree(request); + return rc; +} + +int wil_ftm_abort_session(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_misc(wil, "stub\n"); + return -ENOTSUPP; +} + +int wil_ftm_configure_responder(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_misc(wil, "stub\n"); + return -ENOTSUPP; +} + +int wil_aoa_start_measurement(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct wil_aoa_meas_request request; + struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_LOC_MAX + 1]; + int rc; + + if (!test_bit(WMI_FW_CAPABILITY_FTM, wil->fw_capabilities)) + return -ENOTSUPP; + + wil_dbg_misc(wil, "AOA start measurement\n"); + + rc = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_LOC_MAX, data, data_len, + wil_nl80211_loc_policy); + if (rc) { + wil_err(wil, "Invalid ATTR\n"); + return rc; + } + + if (!tb[QCA_WLAN_VENDOR_ATTR_MAC_ADDR] || + !tb[QCA_WLAN_VENDOR_ATTR_AOA_TYPE]) { + wil_err(wil, "Must specify MAC address and type\n"); + return -EINVAL; + } + + memset(&request, 0, sizeof(request)); + ether_addr_copy(request.mac_addr, + nla_data(tb[QCA_WLAN_VENDOR_ATTR_MAC_ADDR])); + request.type = nla_get_u32(tb[QCA_WLAN_VENDOR_ATTR_AOA_TYPE]); + if (tb[QCA_WLAN_VENDOR_ATTR_FREQ]) + request.freq = nla_get_u32(tb[QCA_WLAN_VENDOR_ATTR_FREQ]); + + rc = wil_aoa_cfg80211_start_measurement(wil, &request); + return rc; +} + +int wil_aoa_abort_measurement(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + wil_dbg_misc(wil, "stub\n"); + return -ENOTSUPP; +} + +void wil_ftm_init(struct wil6210_priv *wil) +{ + mutex_init(&wil->ftm.lock); + setup_timer(&wil->ftm.aoa_timer, wil_aoa_timer_fn, (ulong)wil); + INIT_WORK(&wil->ftm.aoa_timeout_work, wil_aoa_measurement_timeout); +} + +void wil_ftm_deinit(struct wil6210_priv *wil) +{ + del_timer_sync(&wil->ftm.aoa_timer); + cancel_work_sync(&wil->ftm.aoa_timeout_work); + kfree(wil->ftm.ftm_res); +} + +void wil_ftm_stop_operations(struct wil6210_priv *wil) +{ + wil_ftm_cfg80211_session_ended( + wil, QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED); +} diff --git a/drivers/net/wireless/ath/wil6210/ftm.h b/drivers/net/wireless/ath/wil6210/ftm.h new file mode 100644 index 000000000000..21923c27ec06 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/ftm.h @@ -0,0 +1,522 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __WIL6210_FTM_H__ +#define __WIL6210_FTM_H__ + +/** + * NOTE: The authoritative place for definition of QCA_NL80211_VENDOR_ID, + * vendor subcmd definitions prefixed with QCA_NL80211_VENDOR_SUBCMD, and + * qca_wlan_vendor_attr is open source file src/common/qca-vendor.h in + * git://w1.fi/srv/git/hostap.git; the values here are just a copy of that + */ + +/** + * enum qca_vendor_attr_loc - attributes for FTM and AOA commands + * + * @QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE: Session cookie, specified in + * %QCA_NL80211_VENDOR_SUBCMD_FTM_START_SESSION. It will be provided by driver + * events and can be used to identify events targeted for this session. + * @QCA_WLAN_VENDOR_ATTR_LOC_CAPA: Nested attribute containing extra + * FTM/AOA capabilities, returned by %QCA_NL80211_VENDOR_SUBCMD_LOC_GET_CAPA. + * see %enum qca_wlan_vendor_attr_loc_capa. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS: array of nested attributes + * containing information about each peer in measurement session + * request. See %enum qca_wlan_vendor_attr_peer_info for supported + * attributes for each peer + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RESULTS: nested attribute containing + * measurement results for a peer. reported by the + * %QCA_NL80211_VENDOR_SUBCMD_FTM_MEAS_RESULT event. + * See %enum qca_wlan_vendor_attr_peer_result for list of supported + * attributes. + * @QCA_WLAN_VENDOR_ATTR_FTM_RESPONDER_ENABLE: flag attribute for + * enabling or disabling responder functionality. + * @QCA_WLAN_VENDOR_ATTR_FTM_LCI: used in the + * %QCA_NL80211_VENDOR_SUBCMD_FTM_CFG_RESPONDER command in order to + * specify the LCI report that will be sent by the responder during + * a measurement exchange. The format is defined in IEEE P802.11-REVmc/D5.0, + * 9.4.2.22.10 + * @QCA_WLAN_VENDOR_ATTR_FTM_LCR: provided with the + * %QCA_NL80211_VENDOR_SUBCMD_FTM_CFG_RESPONDER command in order to + * specify the location civic report that will be sent by the responder during + * a measurement exchange. The format is defined in IEEE P802.11-REVmc/D5.0, + * 9.4.2.22.13 + * @QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS: session/measurement completion + * status code, reported in %QCA_NL80211_VENDOR_SUBCMD_FTM_SESSION_DONE + * and %QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS_RESULT + * @QCA_WLAN_VENDOR_ATTR_FTM_INITIAL_TOKEN: initial dialog token used + * by responder (0 if not specified) + * @QCA_WLAN_VENDOR_ATTR_AOA_TYPE: AOA measurement type. Requested in + * %QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS and optionally in + * %QCA_NL80211_VENDOR_SUBCMD_FTM_START_SESSION if AOA measurements + * are needed as part of an FTM session. + * Reported by QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS_RESULT. + * See enum qca_wlan_vendor_attr_aoa_type. + * @QCA_WLAN_VENDOR_ATTR_LOC_ANTENNA_ARRAY_MASK: bit mask indicating + * which antenna arrays were used in location measurement. + * Reported in %QCA_NL80211_VENDOR_SUBCMD_FTM_MEAS_RESULT and + * %QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS_RESULT + * @QCA_WLAN_VENDOR_ATTR_AOA_MEAS_RESULT: AOA measurement data. + * Its contents depends on the AOA type and antenna array mask: + * %QCA_WLAN_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE: array of U16 values, + * phase of the strongest CIR path for each antenna in the measured + * array(s). + * %QCA_WLAN_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE_AMP: array of 2 U16 + * values, phase and amplitude of the strongest CIR path for each + * antenna in the measured array(s) + * @QCA_WLAN_VENDOR_ATTR_FREQ: Frequency where peer is listening, in MHz. + * Unsigned 32 bit value. + */ +enum qca_wlan_vendor_attr_loc { + /* we reuse these attributes */ + QCA_WLAN_VENDOR_ATTR_MAC_ADDR = 6, + QCA_WLAN_VENDOR_ATTR_PAD = 13, + QCA_WLAN_VENDOR_ATTR_FTM_SESSION_COOKIE = 14, + QCA_WLAN_VENDOR_ATTR_LOC_CAPA = 15, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEERS = 16, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PEER_RESULTS = 17, + QCA_WLAN_VENDOR_ATTR_FTM_RESPONDER_ENABLE = 18, + QCA_WLAN_VENDOR_ATTR_FTM_LCI = 19, + QCA_WLAN_VENDOR_ATTR_FTM_LCR = 20, + QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS = 21, + QCA_WLAN_VENDOR_ATTR_FTM_INITIAL_TOKEN = 22, + QCA_WLAN_VENDOR_ATTR_AOA_TYPE = 23, + QCA_WLAN_VENDOR_ATTR_LOC_ANTENNA_ARRAY_MASK = 24, + QCA_WLAN_VENDOR_ATTR_AOA_MEAS_RESULT = 25, + QCA_WLAN_VENDOR_ATTR_FREQ = 28, + /* keep last */ + QCA_WLAN_VENDOR_ATTR_LOC_AFTER_LAST, + QCA_WLAN_VENDOR_ATTR_LOC_MAX = QCA_WLAN_VENDOR_ATTR_LOC_AFTER_LAST - 1, +}; + +/** + * enum qca_wlan_vendor_attr_loc_capa - indoor location capabilities + * + * @QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAGS: various flags. See + * %enum qca_wlan_vendor_attr_loc_capa_flags + * @QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_SESSIONS: Maximum number + * of measurement sessions that can run concurrently. + * Default is one session (no session concurrency) + * @QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_PEERS: The total number of unique + * peers that are supported in running sessions. For example, + * if the value is 8 and maximum number of sessions is 2, you can + * have one session with 8 unique peers, or 2 sessions with 4 unique + * peers each, and so on. + * @QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_BURSTS_EXP: Maximum number + * of bursts per peer, as an exponent (2^value). Default is 0, + * meaning no multi-burst support. + * @QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_MEAS_PER_BURST: Maximum number + * of measurement exchanges allowed in a single burst + * @QCA_WLAN_VENDOR_ATTR_AOA_CAPA_SUPPORTED_TYPES: Supported AOA measurement + * types. A bit mask (unsigned 32 bit value), each bit corresponds + * to an AOA type as defined by %enum qca_vendor_attr_aoa_type. + */ +enum qca_wlan_vendor_attr_loc_capa { + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_INVALID, + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAGS, + QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_SESSIONS, + QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_PEERS, + QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_NUM_BURSTS_EXP, + QCA_WLAN_VENDOR_ATTR_FTM_CAPA_MAX_MEAS_PER_BURST, + QCA_WLAN_VENDOR_ATTR_AOA_CAPA_SUPPORTED_TYPES, + /* keep last */ + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_AFTER_LAST, + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_MAX = + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_AFTER_LAST - 1, +}; + +/** + * enum qca_wlan_vendor_attr_loc_capa_flags: Indoor location capability flags + * + * @QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_RESPONDER: Set if driver + * can be configured as an FTM responder (for example, an AP that + * services FTM requests). %QCA_NL80211_VENDOR_SUBCMD_FTM_CFG_RESPONDER + * will be supported if set. + * @QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_INITIATOR: Set if driver + * can run FTM sessions. %QCA_NL80211_VENDOR_SUBCMD_FTM_START_SESSION + * will be supported if set. + * @QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_ASAP: Set if FTM responder + * supports immediate (ASAP) response. + * @QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_AOA: Set if driver supports standalone + * AOA measurement using %QCA_NL80211_VENDOR_SUBCMD_AOA_MEAS + * @QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_AOA_IN_FTM: Set if driver supports + * requesting AOA measurements as part of an FTM session. + */ +enum qca_wlan_vendor_attr_loc_capa_flags { + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_RESPONDER = 1 << 0, + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_FTM_INITIATOR = 1 << 1, + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_ASAP = 1 << 2, + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_AOA = 1 << 3, + QCA_WLAN_VENDOR_ATTR_LOC_CAPA_FLAG_AOA_IN_FTM = 1 << 4, +}; + +/** + * enum qca_wlan_vendor_attr_peer_info: information about + * a single peer in a measurement session. + * + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR: The MAC address of the peer. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS: Various flags related + * to measurement. See %enum qca_wlan_vendor_attr_ftm_peer_meas_flags. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_PARAMS: Nested attribute of + * FTM measurement parameters, as specified by IEEE P802.11-REVmc/D7.0, + * 9.4.2.167. See %enum qca_wlan_vendor_attr_ftm_meas_param for + * list of supported attributes. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID: Initial token ID for + * secure measurement + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_AOA_BURST_PERIOD: Request AOA + * measurement every _value_ bursts. If 0 or not specified, + * AOA measurements will be disabled for this peer. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_FREQ: Frequency in MHz where + * peer is listening. Optional; if not specified, use the + * entry from the kernel scan results cache. + */ +enum qca_wlan_vendor_attr_ftm_peer_info { + QCA_WLAN_VENDOR_ATTR_FTM_PEER_INVALID, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAC_ADDR, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAGS, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_PARAMS, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_AOA_BURST_PERIOD, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_FREQ, + /* keep last */ + QCA_WLAN_VENDOR_ATTR_FTM_PEER_AFTER_LAST, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MAX = + QCA_WLAN_VENDOR_ATTR_FTM_PEER_AFTER_LAST - 1, +}; + +/** + * enum qca_wlan_vendor_attr_ftm_peer_meas_flags: Measurement request flags, + * per-peer + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_ASAP: If set, request + * immediate (ASAP) response from peer + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCI: If set, request + * LCI report from peer. The LCI report includes the absolute + * location of the peer in "official" coordinates (similar to GPS). + * See IEEE P802.11-REVmc/D7.0, 11.24.6.7 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCR: If set, request + * Location civic report from peer. The LCR includes the location + * of the peer in free-form format. See IEEE P802.11-REVmc/D7.0, + * 11.24.6.7 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_SECURE: If set, + * request a secure measurement. + * %QCA_WLAN_VENDOR_ATTR_FTM_PEER_SECURE_TOKEN_ID must also be provided. + */ +enum qca_wlan_vendor_attr_ftm_peer_meas_flags { + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_ASAP = 1 << 0, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCI = 1 << 1, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_LCR = 1 << 2, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_MEAS_FLAG_SECURE = 1 << 3, +}; + +/** + * enum qca_wlan_vendor_attr_ftm_meas_param: Measurement parameters + * + * @QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST: Number of measurements + * to perform in a single burst. + * @QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP: Number of bursts to + * perform, specified as an exponent (2^value) + * @QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION: Duration of burst + * instance, as specified in IEEE P802.11-REVmc/D7.0, 9.4.2.167 + * @QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD: Time between bursts, + * as specified in IEEE P802.11-REVmc/D7.0, 9.4.2.167. Must + * be larger than %QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION + */ +enum qca_wlan_vendor_attr_ftm_meas_param { + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_INVALID, + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MEAS_PER_BURST, + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_NUM_BURSTS_EXP, + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_DURATION, + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_BURST_PERIOD, + /* keep last */ + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_AFTER_LAST, + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_MAX = + QCA_WLAN_VENDOR_ATTR_FTM_PARAM_AFTER_LAST - 1, +}; + +/** + * enum qca_wlan_vendor_attr_ftm_peer_result: Per-peer results + * + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MAC_ADDR: MAC address of the reported + * peer + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS: Status of measurement + * request for this peer. + * See %enum qca_wlan_vendor_attr_ftm_peer_result_status + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_FLAGS: Various flags related + * to measurement results for this peer. + * See %enum qca_wlan_vendor_attr_ftm_peer_result_flags + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_VALUE_SECONDS: Specified when + * request failed and peer requested not to send an additional request + * for this number of seconds. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_LCI: LCI report when received + * from peer. In the format specified by IEEE P802.11-REVmc/D7.0, + * 9.4.2.22.10 + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_LCR: Location civic report when + * received from peer.In the format specified by IEEE P802.11-REVmc/D7.0, + * 9.4.2.22.13 + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MEAS_PARAMS: Reported when peer + * overridden some measurement request parameters. See + * enum qca_wlan_vendor_attr_ftm_meas_param. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_AOA_MEAS: AOA measurement + * for this peer. Same contents as %QCA_WLAN_VENDOR_ATTR_AOA_MEAS_RESULT + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MEAS: Array of measurement + * results. Each entry is a nested attribute defined + * by enum qca_wlan_vendor_attr_ftm_meas. + */ +enum qca_wlan_vendor_attr_ftm_peer_result { + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_INVALID, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MAC_ADDR, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_FLAGS, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_VALUE_SECONDS, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_LCI, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_LCR, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MEAS_PARAMS, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_AOA_MEAS, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MEAS, + /* keep last */ + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_AFTER_LAST, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MAX = + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_AFTER_LAST - 1, +}; + +/** + * enum qca_wlan_vendor_attr_ftm_peer_result_status + * + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_OK: Request sent ok and results + * will be provided. Peer may have overridden some measurement parameters, + * in which case overridden parameters will be report by + * %QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_MEAS_PARAMS attribute + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_INCAPABLE: Peer is incapable + * of performing the measurement request. No more results will be sent + * for this peer in this session. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_FAILED: Peer reported request + * failed, and requested not to send an additional request for number + * of seconds specified by %QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_VALUE_SECONDS + * attribute. + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_INVALID: Request validation + * failed. Request was not sent over the air. + */ +enum qca_wlan_vendor_attr_ftm_peer_result_status { + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_OK, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_INCAPABLE, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_FAILED, + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_STATUS_INVALID, +}; + +/** + * enum qca_wlan_vendor_attr_ftm_peer_result_flags : Various flags + * for measurement result, per-peer + * + * @QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_FLAG_DONE: If set, + * measurement completed for this peer. No more results will be reported + * for this peer in this session. + */ +enum qca_wlan_vendor_attr_ftm_peer_result_flags { + QCA_WLAN_VENDOR_ATTR_FTM_PEER_RES_FLAG_DONE = 1 << 0, +}; + +/** + * enum qca_vendor_attr_loc_session_status: Session completion status code + * + * @QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_OK: Session completed + * successfully. + * @QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED: Session aborted + * by request + * @QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_INVALID: Session request + * was invalid and was not started + * @QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_FAILED: Session had an error + * and did not complete normally (for example out of resources) + * + */ +enum qca_vendor_attr_loc_session_status { + QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_OK, + QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_ABORTED, + QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_INVALID, + QCA_WLAN_VENDOR_ATTR_LOC_SESSION_STATUS_FAILED, +}; + +/** + * enum qca_wlan_vendor_attr_ftm_meas: Single measurement data + * + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T1: Time of departure(TOD) of FTM packet as + * recorded by responder, in picoseconds. + * See IEEE P802.11-REVmc/D7.0, 11.24.6.4 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T2: Time of arrival(TOA) of FTM packet at + * initiator, in picoseconds. + * See IEEE P802.11-REVmc/D7.0, 11.24.6.4 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T3: TOD of ACK packet as recorded by + * initiator, in picoseconds. + * See IEEE P802.11-REVmc/D7.0, 11.24.6.4 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T4: TOA of ACK packet at + * responder, in picoseconds. + * See IEEE P802.11-REVmc/D7.0, 11.24.6.4 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_RSSI: RSSI (signal level) as recorded + * during this measurement exchange. Optional and will be provided if + * the hardware can measure it. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_TOD_ERR: TOD error reported by + * responder. Not always provided. + * See IEEE P802.11-REVmc/D7.0, 9.6.8.33 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_TOA_ERR: TOA error reported by + * responder. Not always provided. + * See IEEE P802.11-REVmc/D7.0, 9.6.8.33 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_INITIATOR_TOD_ERR: TOD error measured by + * initiator. Not always provided. + * See IEEE P802.11-REVmc/D7.0, 9.6.8.33 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_INITIATOR_TOA_ERR: TOA error measured by + * initiator. Not always provided. + * See IEEE P802.11-REVmc/D7.0, 9.6.8.33 for more information. + * @QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PAD: Dummy attribute for padding. + */ +enum qca_wlan_vendor_attr_ftm_meas { + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_INVALID, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T1, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T2, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T3, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_T4, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_RSSI, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_TOD_ERR, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_TOA_ERR, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_INITIATOR_TOD_ERR, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_INITIATOR_TOA_ERR, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_PAD, + /* keep last */ + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_AFTER_LAST, + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_MAX = + QCA_WLAN_VENDOR_ATTR_FTM_MEAS_AFTER_LAST - 1, +}; + +/** + * enum qca_wlan_vendor_attr_aoa_type: AOA measurement type + * + * @QCA_WLAN_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE: Phase of the strongest + * CIR (channel impulse response) path for each antenna. + * @QCA_WLAN_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE_AMP: Phase and amplitude + * of the strongest CIR path for each antenna. + */ +enum qca_wlan_vendor_attr_aoa_type { + QCA_WLAN_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE, + QCA_WLAN_VENDOR_ATTR_AOA_TYPE_TOP_CIR_PHASE_AMP, + QCA_WLAN_VENDOR_ATTR_AOA_TYPE_MAX, +}; + +/* vendor event indices, used from both cfg80211.c and ftm.c */ +enum qca_nl80211_vendor_events_index { + QCA_NL80211_VENDOR_EVENT_FTM_MEAS_RESULT_INDEX, + QCA_NL80211_VENDOR_EVENT_FTM_SESSION_DONE_INDEX, + QCA_NL80211_VENDOR_EVENT_AOA_MEAS_RESULT_INDEX, +}; + +/* measurement parameters. Specified for each peer as part + * of measurement request, or provided with measurement + * results for peer in case peer overridden parameters + */ +struct wil_ftm_meas_params { + u8 meas_per_burst; + u8 num_of_bursts_exp; + u8 burst_duration; + u16 burst_period; +}; + +/* measurement request for a single peer */ +struct wil_ftm_meas_peer_info { + u8 mac_addr[ETH_ALEN]; + u32 freq; + u32 flags; /* enum qca_wlan_vendor_attr_ftm_peer_meas_flags */ + struct wil_ftm_meas_params params; + u8 secure_token_id; + u16 aoa_burst_period; /* 0 if no AOA, >0 every <value> bursts */ +}; + +/* session request, passed to wil_ftm_cfg80211_start_session */ +struct wil_ftm_session_request { + u64 session_cookie; + u32 n_peers; + u32 aoa_type; /* enum qca_wlan_vendor_attr_aoa_type */ + /* keep last, variable size according to n_peers */ + struct wil_ftm_meas_peer_info peers[0]; +}; + +/* single measurement for a peer */ +struct wil_ftm_peer_meas { + u64 t1, t2, t3, t4; +}; + +/* measurement results for a single peer */ +struct wil_ftm_peer_meas_res { + u8 mac_addr[ETH_ALEN]; + u32 flags; /* enum qca_wlan_vendor_attr_ftm_peer_result_flags */ + u8 status; /* enum qca_wlan_vendor_attr_ftm_peer_result_status */ + u8 value_seconds; + bool has_params; /* true if params is valid */ + struct wil_ftm_meas_params params; /* peer overridden params */ + u8 *lci; + u8 lci_length; + u8 *lcr; + u8 lcr_length; + u32 n_meas; + /* keep last, variable size according to n_meas */ + struct wil_ftm_peer_meas meas[0]; +}; + +/* standalone AOA measurement request */ +struct wil_aoa_meas_request { + u8 mac_addr[ETH_ALEN]; + u32 freq; + u32 type; +}; + +/* AOA measurement result */ +struct wil_aoa_meas_result { + u8 mac_addr[ETH_ALEN]; + u32 type; + u32 antenna_array_mask; + u32 status; + u32 length; + /* keep last, variable size according to length */ + u8 data[0]; +}; + +/* private data related to FTM. Part of the wil6210_priv structure */ +struct wil_ftm_priv { + struct mutex lock; /* protects the FTM data */ + u8 session_started; + u64 session_cookie; + struct wil_ftm_peer_meas_res *ftm_res; + u8 has_ftm_res; + u32 max_ftm_meas; + + /* standalone AOA measurement */ + u8 aoa_started; + u8 aoa_peer_mac_addr[ETH_ALEN]; + u32 aoa_type; + struct timer_list aoa_timer; + struct work_struct aoa_timeout_work; +}; + +int wil_ftm_get_capabilities(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len); +int wil_ftm_start_session(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len); +int wil_ftm_abort_session(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len); +int wil_ftm_configure_responder(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len); +int wil_aoa_start_measurement(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len); +int wil_aoa_abort_measurement(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len); + +#endif /* __WIL6210_FTM_H__ */ diff --git a/drivers/net/wireless/ath/wil6210/fw.c b/drivers/net/wireless/ath/wil6210/fw.c index 82aae2d705b4..540fc20984d8 100644 --- a/drivers/net/wireless/ath/wil6210/fw.c +++ b/drivers/net/wireless/ath/wil6210/fw.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2014-2015,2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,8 +19,9 @@ #include "wil6210.h" #include "fw.h" -MODULE_FIRMWARE(WIL_FW_NAME); -MODULE_FIRMWARE(WIL_FW2_NAME); +MODULE_FIRMWARE(WIL_FW_NAME_DEFAULT); +MODULE_FIRMWARE(WIL_FW_NAME_SPARROW_PLUS); +MODULE_FIRMWARE(WIL_BOARD_FILE_NAME); static void wil_memset_toio_32(volatile void __iomem *dst, u32 val, diff --git a/drivers/net/wireless/ath/wil6210/fw.h b/drivers/net/wireless/ath/wil6210/fw.h index 7a2c6c129ad5..2f2b910501ba 100644 --- a/drivers/net/wireless/ath/wil6210/fw.h +++ b/drivers/net/wireless/ath/wil6210/fw.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Qualcomm Atheros, Inc. + * Copyright (c) 2014,2016 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -58,6 +58,15 @@ struct wil_fw_record_comment { /* type == wil_fw_type_comment */ u8 data[0]; /* free-form data [data_size], see above */ } __packed; +/* FW capabilities encoded inside a comment record */ +#define WIL_FW_CAPABILITIES_MAGIC (0xabcddcba) +struct wil_fw_record_capabilities { /* type == wil_fw_type_comment */ + /* identifies capabilities record */ + __le32 magic; + /* capabilities (variable size), see enum wmi_fw_capability */ + u8 capabilities[0]; +}; + /* perform action * data_size = @head.size - offsetof(struct wil_fw_record_action, data) */ @@ -93,6 +102,9 @@ struct wil_fw_record_verify { /* type == wil_fw_verify */ /* file header * First record of every file */ +/* the FW version prefix in the comment */ +#define WIL_FW_VERSION_PREFIX "FW version: " +#define WIL_FW_VERSION_PREFIX_LEN (sizeof(WIL_FW_VERSION_PREFIX) - 1) struct wil_fw_record_file_header { __le32 signature ; /* Wilocity signature */ __le32 reserved; diff --git a/drivers/net/wireless/ath/wil6210/fw_inc.c b/drivers/net/wireless/ath/wil6210/fw_inc.c index d30657ee7e83..77d1902947e3 100644 --- a/drivers/net/wireless/ath/wil6210/fw_inc.c +++ b/drivers/net/wireless/ath/wil6210/fw_inc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2014-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,14 +26,17 @@ prefix_type, rowsize, \ groupsize, buf, len, ascii) -#define FW_ADDR_CHECK(ioaddr, val, msg) do { \ - ioaddr = wmi_buffer(wil, val); \ - if (!ioaddr) { \ - wil_err_fw(wil, "bad " msg ": 0x%08x\n", \ - le32_to_cpu(val)); \ - return -EINVAL; \ - } \ - } while (0) +static bool wil_fw_addr_check(struct wil6210_priv *wil, + void __iomem **ioaddr, __le32 val, + u32 size, const char *msg) +{ + *ioaddr = wmi_buffer_block(wil, val, size); + if (!(*ioaddr)) { + wil_err_fw(wil, "bad %s: 0x%08x\n", msg, le32_to_cpu(val)); + return false; + } + return true; +} /** * wil_fw_verify - verify firmware file validity @@ -118,11 +121,33 @@ static int wil_fw_verify(struct wil6210_priv *wil, const u8 *data, size_t size) return (int)dlen; } -static int fw_handle_comment(struct wil6210_priv *wil, const void *data, +static int fw_ignore_section(struct wil6210_priv *wil, const void *data, size_t size) { - wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, data, size, true); + return 0; +} + +static int +fw_handle_comment(struct wil6210_priv *wil, const void *data, + size_t size) +{ + const struct wil_fw_record_capabilities *rec = data; + size_t capa_size; + + if (size < sizeof(*rec) || + le32_to_cpu(rec->magic) != WIL_FW_CAPABILITIES_MAGIC) { + wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, + data, size, true); + return 0; + } + capa_size = size - offsetof(struct wil_fw_record_capabilities, + capabilities); + bitmap_zero(wil->fw_capabilities, WMI_FW_CAPABILITY_MAX); + memcpy(wil->fw_capabilities, rec->capabilities, + min(sizeof(wil->fw_capabilities), capa_size)); + wil_hex_dump_fw("CAPA", DUMP_PREFIX_OFFSET, 16, 1, + rec->capabilities, capa_size, false); return 0; } @@ -138,7 +163,8 @@ static int fw_handle_data(struct wil6210_priv *wil, const void *data, return -EINVAL; } - FW_ADDR_CHECK(dst, d->addr, "address"); + if (!wil_fw_addr_check(wil, &dst, d->addr, s, "address")) + return -EINVAL; wil_dbg_fw(wil, "write [0x%08x] <== %zu bytes\n", le32_to_cpu(d->addr), s); wil_memcpy_toio_32(dst, d->data, s); @@ -170,7 +196,8 @@ static int fw_handle_fill(struct wil6210_priv *wil, const void *data, return -EINVAL; } - FW_ADDR_CHECK(dst, d->addr, "address"); + if (!wil_fw_addr_check(wil, &dst, d->addr, s, "address")) + return -EINVAL; v = le32_to_cpu(d->value); wil_dbg_fw(wil, "fill [0x%08x] <== 0x%08x, %zu bytes\n", @@ -196,6 +223,13 @@ static int fw_handle_file_header(struct wil6210_priv *wil, const void *data, wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, d->comment, sizeof(d->comment), true); + if (!memcmp(d->comment, WIL_FW_VERSION_PREFIX, + WIL_FW_VERSION_PREFIX_LEN)) + memcpy(wil->fw_version, + d->comment + WIL_FW_VERSION_PREFIX_LEN, + min(sizeof(d->comment) - WIL_FW_VERSION_PREFIX_LEN, + sizeof(wil->fw_version) - 1)); + return 0; } @@ -219,7 +253,8 @@ static int fw_handle_direct_write(struct wil6210_priv *wil, const void *data, u32 v = le32_to_cpu(block[i].value); u32 x, y; - FW_ADDR_CHECK(dst, block[i].addr, "address"); + if (!wil_fw_addr_check(wil, &dst, block[i].addr, 0, "address")) + return -EINVAL; x = readl(dst); y = (x & m) | (v & ~m); @@ -285,10 +320,15 @@ static int fw_handle_gateway_data(struct wil6210_priv *wil, const void *data, wil_dbg_fw(wil, "gw write record [%3d] blocks, cmd 0x%08x\n", n, gw_cmd); - FW_ADDR_CHECK(gwa_addr, d->gateway_addr_addr, "gateway_addr_addr"); - FW_ADDR_CHECK(gwa_val, d->gateway_value_addr, "gateway_value_addr"); - FW_ADDR_CHECK(gwa_cmd, d->gateway_cmd_addr, "gateway_cmd_addr"); - FW_ADDR_CHECK(gwa_ctl, d->gateway_ctrl_address, "gateway_ctrl_address"); + if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0, + "gateway_addr_addr") || + !wil_fw_addr_check(wil, &gwa_val, d->gateway_value_addr, 0, + "gateway_value_addr") || + !wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0, + "gateway_cmd_addr") || + !wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0, + "gateway_ctrl_address")) + return -EINVAL; wil_dbg_fw(wil, "gw addresses: addr 0x%08x val 0x%08x" " cmd 0x%08x ctl 0x%08x\n", @@ -344,12 +384,19 @@ static int fw_handle_gateway_data4(struct wil6210_priv *wil, const void *data, wil_dbg_fw(wil, "gw4 write record [%3d] blocks, cmd 0x%08x\n", n, gw_cmd); - FW_ADDR_CHECK(gwa_addr, d->gateway_addr_addr, "gateway_addr_addr"); + if (!wil_fw_addr_check(wil, &gwa_addr, d->gateway_addr_addr, 0, + "gateway_addr_addr")) + return -EINVAL; for (k = 0; k < ARRAY_SIZE(block->value); k++) - FW_ADDR_CHECK(gwa_val[k], d->gateway_value_addr[k], - "gateway_value_addr"); - FW_ADDR_CHECK(gwa_cmd, d->gateway_cmd_addr, "gateway_cmd_addr"); - FW_ADDR_CHECK(gwa_ctl, d->gateway_ctrl_address, "gateway_ctrl_address"); + if (!wil_fw_addr_check(wil, &gwa_val[k], + d->gateway_value_addr[k], + 0, "gateway_value_addr")) + return -EINVAL; + if (!wil_fw_addr_check(wil, &gwa_cmd, d->gateway_cmd_addr, 0, + "gateway_cmd_addr") || + !wil_fw_addr_check(wil, &gwa_ctl, d->gateway_ctrl_address, 0, + "gateway_ctrl_address")) + return -EINVAL; wil_dbg_fw(wil, "gw4 addresses: addr 0x%08x cmd 0x%08x ctl 0x%08x\n", le32_to_cpu(d->gateway_addr_addr), @@ -383,42 +430,51 @@ static int fw_handle_gateway_data4(struct wil6210_priv *wil, const void *data, static const struct { int type; - int (*handler)(struct wil6210_priv *wil, const void *data, size_t size); + int (*load_handler)(struct wil6210_priv *wil, const void *data, + size_t size); + int (*parse_handler)(struct wil6210_priv *wil, const void *data, + size_t size); } wil_fw_handlers[] = { - {wil_fw_type_comment, fw_handle_comment}, - {wil_fw_type_data, fw_handle_data}, - {wil_fw_type_fill, fw_handle_fill}, + {wil_fw_type_comment, fw_handle_comment, fw_handle_comment}, + {wil_fw_type_data, fw_handle_data, fw_ignore_section}, + {wil_fw_type_fill, fw_handle_fill, fw_ignore_section}, /* wil_fw_type_action */ /* wil_fw_type_verify */ - {wil_fw_type_file_header, fw_handle_file_header}, - {wil_fw_type_direct_write, fw_handle_direct_write}, - {wil_fw_type_gateway_data, fw_handle_gateway_data}, - {wil_fw_type_gateway_data4, fw_handle_gateway_data4}, + {wil_fw_type_file_header, fw_handle_file_header, + fw_handle_file_header}, + {wil_fw_type_direct_write, fw_handle_direct_write, fw_ignore_section}, + {wil_fw_type_gateway_data, fw_handle_gateway_data, fw_ignore_section}, + {wil_fw_type_gateway_data4, fw_handle_gateway_data4, + fw_ignore_section}, }; static int wil_fw_handle_record(struct wil6210_priv *wil, int type, - const void *data, size_t size) + const void *data, size_t size, bool load) { int i; - for (i = 0; i < ARRAY_SIZE(wil_fw_handlers); i++) { + for (i = 0; i < ARRAY_SIZE(wil_fw_handlers); i++) if (wil_fw_handlers[i].type == type) - return wil_fw_handlers[i].handler(wil, data, size); - } + return load ? + wil_fw_handlers[i].load_handler( + wil, data, size) : + wil_fw_handlers[i].parse_handler( + wil, data, size); wil_err_fw(wil, "unknown record type: %d\n", type); return -EINVAL; } /** - * wil_fw_load - load FW into device - * - * Load the FW and uCode code and data to the corresponding device - * memory regions + * wil_fw_process - process section from FW file + * if load is true: Load the FW and uCode code and data to the + * corresponding device memory regions, + * otherwise only parse and look for capabilities * * Return error code */ -static int wil_fw_load(struct wil6210_priv *wil, const void *data, size_t size) +static int wil_fw_process(struct wil6210_priv *wil, const void *data, + size_t size, bool load) { int rc = 0; const struct wil_fw_record_head *hdr; @@ -437,7 +493,7 @@ static int wil_fw_load(struct wil6210_priv *wil, const void *data, size_t size) return -EINVAL; } rc = wil_fw_handle_record(wil, le16_to_cpu(hdr->type), - &hdr[1], hdr_sz); + &hdr[1], hdr_sz, load); if (rc) return rc; } @@ -456,13 +512,16 @@ static int wil_fw_load(struct wil6210_priv *wil, const void *data, size_t size) } /** - * wil_request_firmware - Request firmware and load to device + * wil_request_firmware - Request firmware * - * Request firmware image from the file and load it to device + * Request firmware image from the file + * If load is true, load firmware to device, otherwise + * only parse and extract capabilities * * Return error code */ -int wil_request_firmware(struct wil6210_priv *wil, const char *name) +int wil_request_firmware(struct wil6210_priv *wil, const char *name, + bool load) { int rc, rc1; const struct firmware *fw; @@ -471,7 +530,7 @@ int wil_request_firmware(struct wil6210_priv *wil, const char *name) rc = request_firmware(&fw, name, wil_to_dev(wil)); if (rc) { - wil_err_fw(wil, "Failed to load firmware %s\n", name); + wil_err_fw(wil, "Failed to load firmware %s rc %d\n", name, rc); return rc; } wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, fw->size); @@ -482,7 +541,7 @@ int wil_request_firmware(struct wil6210_priv *wil, const char *name) rc = rc1; goto out; } - rc = wil_fw_load(wil, d, rc1); + rc = wil_fw_process(wil, d, rc1, load); if (rc < 0) goto out; } @@ -491,3 +550,24 @@ out: release_firmware(fw); return rc; } + +/** + * wil_fw_verify_file_exists - checks if firmware file exist + * + * @wil: driver context + * @name: firmware file name + * + * return value - boolean, true for success, false for failure + */ +bool wil_fw_verify_file_exists(struct wil6210_priv *wil, const char *name) +{ + const struct firmware *fw; + int rc; + + rc = request_firmware(&fw, name, wil_to_dev(wil)); + if (!rc) + release_firmware(fw); + else + wil_dbg_fw(wil, "<%s> not available: %d\n", name, rc); + return !rc; +} diff --git a/drivers/net/wireless/ath/wil6210/interrupt.c b/drivers/net/wireless/ath/wil6210/interrupt.c index 50c136e843c4..5cf341702dc1 100644 --- a/drivers/net/wireless/ath/wil6210/interrupt.c +++ b/drivers/net/wireless/ath/wil6210/interrupt.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -35,15 +35,19 @@ * */ -#define WIL6210_IRQ_DISABLE (0xFFFFFFFFUL) +#define WIL6210_IRQ_DISABLE (0xFFFFFFFFUL) +#define WIL6210_IRQ_DISABLE_NO_HALP (0xF7FFFFFFUL) #define WIL6210_IMC_RX (BIT_DMA_EP_RX_ICR_RX_DONE | \ BIT_DMA_EP_RX_ICR_RX_HTRSH) +#define WIL6210_IMC_RX_NO_RX_HTRSH (WIL6210_IMC_RX & \ + (~(BIT_DMA_EP_RX_ICR_RX_HTRSH))) #define WIL6210_IMC_TX (BIT_DMA_EP_TX_ICR_TX_DONE | \ BIT_DMA_EP_TX_ICR_TX_DONE_N(0)) -#define WIL6210_IMC_MISC (ISR_MISC_FW_READY | \ - ISR_MISC_MBOX_EVT | \ - ISR_MISC_FW_ERROR) - +#define WIL6210_IMC_MISC_NO_HALP (ISR_MISC_FW_READY | \ + ISR_MISC_MBOX_EVT | \ + ISR_MISC_FW_ERROR) +#define WIL6210_IMC_MISC (WIL6210_IMC_MISC_NO_HALP | \ + BIT_DMA_EP_MISC_ICR_HALP) #define WIL6210_IRQ_PSEUDO_MASK (u32)(~(BIT_DMA_PSEUDO_CAUSE_RX | \ BIT_DMA_PSEUDO_CAUSE_TX | \ BIT_DMA_PSEUDO_CAUSE_MISC)) @@ -51,6 +55,7 @@ #if defined(CONFIG_WIL6210_ISR_COR) /* configure to Clear-On-Read mode */ #define WIL_ICR_ICC_VALUE (0xFFFFFFFFUL) +#define WIL_ICR_ICC_MISC_VALUE (0xF7FFFFFFUL) static inline void wil_icr_clear(u32 x, void __iomem *addr) { @@ -58,6 +63,7 @@ static inline void wil_icr_clear(u32 x, void __iomem *addr) #else /* defined(CONFIG_WIL6210_ISR_COR) */ /* configure to Write-1-to-Clear mode */ #define WIL_ICR_ICC_VALUE (0UL) +#define WIL_ICR_ICC_MISC_VALUE (0UL) static inline void wil_icr_clear(u32 x, void __iomem *addr) { @@ -86,15 +92,26 @@ static void wil6210_mask_irq_rx(struct wil6210_priv *wil) WIL6210_IRQ_DISABLE); } -static void wil6210_mask_irq_misc(struct wil6210_priv *wil) +static void wil6210_mask_irq_misc(struct wil6210_priv *wil, bool mask_halp) { + wil_dbg_irq(wil, "mask_irq_misc: mask_halp(%s)\n", + mask_halp ? "true" : "false"); + wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMS), - WIL6210_IRQ_DISABLE); + mask_halp ? WIL6210_IRQ_DISABLE : WIL6210_IRQ_DISABLE_NO_HALP); +} + +void wil6210_mask_halp(struct wil6210_priv *wil) +{ + wil_dbg_irq(wil, "mask_halp\n"); + + wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMS), + BIT_DMA_EP_MISC_ICR_HALP); } static void wil6210_mask_irq_pseudo(struct wil6210_priv *wil) { - wil_dbg_irq(wil, "%s()\n", __func__); + wil_dbg_irq(wil, "mask_irq_pseudo\n"); wil_w(wil, RGF_DMA_PSEUDO_CAUSE_MASK_SW, WIL6210_IRQ_DISABLE); @@ -109,19 +126,32 @@ void wil6210_unmask_irq_tx(struct wil6210_priv *wil) void wil6210_unmask_irq_rx(struct wil6210_priv *wil) { + bool unmask_rx_htrsh = test_bit(wil_status_fwconnected, wil->status); + wil_w(wil, RGF_DMA_EP_RX_ICR + offsetof(struct RGF_ICR, IMC), - WIL6210_IMC_RX); + unmask_rx_htrsh ? WIL6210_IMC_RX : WIL6210_IMC_RX_NO_RX_HTRSH); } -static void wil6210_unmask_irq_misc(struct wil6210_priv *wil) +static void wil6210_unmask_irq_misc(struct wil6210_priv *wil, bool unmask_halp) { + wil_dbg_irq(wil, "unmask_irq_misc: unmask_halp(%s)\n", + unmask_halp ? "true" : "false"); + wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMC), - WIL6210_IMC_MISC); + unmask_halp ? WIL6210_IMC_MISC : WIL6210_IMC_MISC_NO_HALP); +} + +static void wil6210_unmask_halp(struct wil6210_priv *wil) +{ + wil_dbg_irq(wil, "unmask_halp\n"); + + wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMC), + BIT_DMA_EP_MISC_ICR_HALP); } static void wil6210_unmask_irq_pseudo(struct wil6210_priv *wil) { - wil_dbg_irq(wil, "%s()\n", __func__); + wil_dbg_irq(wil, "unmask_irq_pseudo\n"); set_bit(wil_status_irqen, wil->status); @@ -130,34 +160,34 @@ static void wil6210_unmask_irq_pseudo(struct wil6210_priv *wil) void wil_mask_irq(struct wil6210_priv *wil) { - wil_dbg_irq(wil, "%s()\n", __func__); + wil_dbg_irq(wil, "mask_irq\n"); wil6210_mask_irq_tx(wil); wil6210_mask_irq_rx(wil); - wil6210_mask_irq_misc(wil); + wil6210_mask_irq_misc(wil, true); wil6210_mask_irq_pseudo(wil); } void wil_unmask_irq(struct wil6210_priv *wil) { - wil_dbg_irq(wil, "%s()\n", __func__); + wil_dbg_irq(wil, "unmask_irq\n"); wil_w(wil, RGF_DMA_EP_RX_ICR + offsetof(struct RGF_ICR, ICC), WIL_ICR_ICC_VALUE); wil_w(wil, RGF_DMA_EP_TX_ICR + offsetof(struct RGF_ICR, ICC), WIL_ICR_ICC_VALUE); wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, ICC), - WIL_ICR_ICC_VALUE); + WIL_ICR_ICC_MISC_VALUE); wil6210_unmask_irq_pseudo(wil); wil6210_unmask_irq_tx(wil); wil6210_unmask_irq_rx(wil); - wil6210_unmask_irq_misc(wil); + wil6210_unmask_irq_misc(wil, true); } void wil_configure_interrupt_moderation(struct wil6210_priv *wil) { - wil_dbg_irq(wil, "%s()\n", __func__); + wil_dbg_irq(wil, "configure_interrupt_moderation\n"); /* disable interrupt moderation for monitor * to get better timestamp precision @@ -214,7 +244,7 @@ static irqreturn_t wil6210_irq_rx(int irq, void *cookie) wil_dbg_irq(wil, "ISR RX 0x%08x\n", isr); if (unlikely(!isr)) { - wil_err(wil, "spurious IRQ: RX\n"); + wil_err_ratelimited(wil, "spurious IRQ: RX\n"); return IRQ_NONE; } @@ -228,11 +258,8 @@ static irqreturn_t wil6210_irq_rx(int irq, void *cookie) */ if (likely(isr & (BIT_DMA_EP_RX_ICR_RX_DONE | BIT_DMA_EP_RX_ICR_RX_HTRSH))) { - wil_dbg_irq(wil, "RX done\n"); - - if (unlikely(isr & BIT_DMA_EP_RX_ICR_RX_HTRSH)) - wil_err_ratelimited(wil, - "Received \"Rx buffer is in risk of overflow\" interrupt\n"); + wil_dbg_irq(wil, "RX done / RX_HTRSH received, ISR (0x%x)\n", + isr); isr &= ~(BIT_DMA_EP_RX_ICR_RX_DONE | BIT_DMA_EP_RX_ICR_RX_HTRSH); @@ -242,11 +269,12 @@ static irqreturn_t wil6210_irq_rx(int irq, void *cookie) need_unmask = false; napi_schedule(&wil->napi_rx); } else { - wil_err(wil, + wil_err_ratelimited( + wil, "Got Rx interrupt while stopping interface\n"); } } else { - wil_err(wil, "Got Rx interrupt while in reset\n"); + wil_err_ratelimited(wil, "Got Rx interrupt while in reset\n"); } } @@ -275,7 +303,7 @@ static irqreturn_t wil6210_irq_tx(int irq, void *cookie) wil_dbg_irq(wil, "ISR TX 0x%08x\n", isr); if (unlikely(!isr)) { - wil_err(wil, "spurious IRQ: TX\n"); + wil_err_ratelimited(wil, "spurious IRQ: TX\n"); return IRQ_NONE; } @@ -291,12 +319,13 @@ static irqreturn_t wil6210_irq_tx(int irq, void *cookie) need_unmask = false; napi_schedule(&wil->napi_tx); } else { - wil_err(wil, "Got Tx interrupt while in reset\n"); + wil_err_ratelimited(wil, "Got Tx interrupt while in reset\n"); } } if (unlikely(isr)) - wil_err(wil, "un-handled TX ISR bits 0x%08x\n", isr); + wil_err_ratelimited(wil, "un-handled TX ISR bits 0x%08x\n", + isr); /* Tx IRQ will be enabled when NAPI processing finished */ @@ -329,6 +358,25 @@ static void wil_cache_mbox_regs(struct wil6210_priv *wil) wil_mbox_ring_le2cpus(&wil->mbox_ctl.tx); } +static bool wil_validate_mbox_regs(struct wil6210_priv *wil) +{ + size_t min_size = sizeof(struct wil6210_mbox_hdr) + + sizeof(struct wmi_cmd_hdr); + + if (wil->mbox_ctl.rx.entry_size < min_size) { + wil_err(wil, "rx mbox entry too small (%d)\n", + wil->mbox_ctl.rx.entry_size); + return false; + } + if (wil->mbox_ctl.tx.entry_size < min_size) { + wil_err(wil, "tx mbox entry too small (%d)\n", + wil->mbox_ctl.tx.entry_size); + return false; + } + + return true; +} + static irqreturn_t wil6210_irq_misc(int irq, void *cookie) { struct wil6210_priv *wil = cookie; @@ -344,7 +392,7 @@ static irqreturn_t wil6210_irq_misc(int irq, void *cookie) return IRQ_NONE; } - wil6210_mask_irq_misc(wil); + wil6210_mask_irq_misc(wil, false); if (isr & ISR_MISC_FW_ERROR) { u32 fw_assert_code = wil_r(wil, RGF_FW_ASSERT_CODE); @@ -364,7 +412,8 @@ static irqreturn_t wil6210_irq_misc(int irq, void *cookie) if (isr & ISR_MISC_FW_READY) { wil_dbg_irq(wil, "IRQ: FW ready\n"); wil_cache_mbox_regs(wil); - set_bit(wil_status_mbox_ready, wil->status); + if (wil_validate_mbox_regs(wil)) + set_bit(wil_status_mbox_ready, wil->status); /** * Actual FW ready indicated by the * WMI_FW_READY_EVENTID @@ -372,12 +421,19 @@ static irqreturn_t wil6210_irq_misc(int irq, void *cookie) isr &= ~ISR_MISC_FW_READY; } + if (isr & BIT_DMA_EP_MISC_ICR_HALP) { + wil_dbg_irq(wil, "irq_misc: HALP IRQ invoked\n"); + wil6210_mask_halp(wil); + isr &= ~BIT_DMA_EP_MISC_ICR_HALP; + complete(&wil->halp.comp); + } + wil->isr_misc = isr; if (isr) { return IRQ_WAKE_THREAD; } else { - wil6210_unmask_irq_misc(wil); + wil6210_unmask_irq_misc(wil, false); return IRQ_HANDLED; } } @@ -391,12 +447,18 @@ static irqreturn_t wil6210_irq_misc_thread(int irq, void *cookie) wil_dbg_irq(wil, "Thread ISR MISC 0x%08x\n", isr); if (isr & ISR_MISC_FW_ERROR) { + wil->recovery_state = fw_recovery_pending; wil_fw_core_dump(wil); wil_notify_fw_error(wil); isr &= ~ISR_MISC_FW_ERROR; - wil_fw_error_recovery(wil); + if (wil->platform_ops.notify) { + wil_err(wil, "notify platform driver about FW crash"); + wil->platform_ops.notify(wil->platform_handle, + WIL_PLATFORM_EVT_FW_CRASH); + } else { + wil_fw_error_recovery(wil); + } } - if (isr & ISR_MISC_MBOX_EVT) { wil_dbg_irq(wil, "MBOX event\n"); wmi_recv_cmd(wil); @@ -408,7 +470,7 @@ static irqreturn_t wil6210_irq_misc_thread(int irq, void *cookie) wil->isr_misc = 0; - wil6210_unmask_irq_misc(wil); + wil6210_unmask_irq_misc(wil, false); return IRQ_HANDLED; } @@ -427,6 +489,12 @@ static irqreturn_t wil6210_thread_irq(int irq, void *cookie) wil6210_unmask_irq_pseudo(wil); + if (wil->suspend_resp_rcvd) { + wil_dbg_irq(wil, "set suspend_resp_comp to true\n"); + wil->suspend_resp_comp = true; + wake_up_interruptible(&wil->wq); + } + return IRQ_HANDLED; } @@ -463,6 +531,13 @@ static int wil6210_debug_irq_mask(struct wil6210_priv *wil, u32 pseudo_cause) offsetof(struct RGF_ICR, ICR)); u32 imv_misc = wil_r(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, IMV)); + + /* HALP interrupt can be unmasked when misc interrupts are + * masked + */ + if (icr_misc & BIT_DMA_EP_MISC_ICR_HALP) + return 0; + wil_err(wil, "IRQ when it should be masked: pseudo 0x%08x\n" "Rx icm:icr:imv 0x%08x 0x%08x 0x%08x\n" "Tx icm:icr:imv 0x%08x 0x%08x 0x%08x\n" @@ -550,11 +625,28 @@ void wil6210_clear_irq(struct wil6210_priv *wil) wmb(); /* make sure write completed */ } +void wil6210_set_halp(struct wil6210_priv *wil) +{ + wil_dbg_irq(wil, "set_halp\n"); + + wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, ICS), + BIT_DMA_EP_MISC_ICR_HALP); +} + +void wil6210_clear_halp(struct wil6210_priv *wil) +{ + wil_dbg_irq(wil, "clear_halp\n"); + + wil_w(wil, RGF_DMA_EP_MISC_ICR + offsetof(struct RGF_ICR, ICR), + BIT_DMA_EP_MISC_ICR_HALP); + wil6210_unmask_halp(wil); +} + int wil6210_init_irq(struct wil6210_priv *wil, int irq, bool use_msi) { int rc; - wil_dbg_misc(wil, "%s(%s)\n", __func__, use_msi ? "MSI" : "INTx"); + wil_dbg_misc(wil, "init_irq: %s\n", use_msi ? "MSI" : "INTx"); rc = request_threaded_irq(irq, wil6210_hardirq, wil6210_thread_irq, @@ -565,7 +657,7 @@ int wil6210_init_irq(struct wil6210_priv *wil, int irq, bool use_msi) void wil6210_fini_irq(struct wil6210_priv *wil, int irq) { - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "fini_irq:\n"); wil_mask_irq(wil); free_irq(irq, wil); diff --git a/drivers/net/wireless/ath/wil6210/ioctl.c b/drivers/net/wireless/ath/wil6210/ioctl.c index f7f948621951..f8d2c209482c 100644 --- a/drivers/net/wireless/ath/wil6210/ioctl.c +++ b/drivers/net/wireless/ath/wil6210/ioctl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Qualcomm Atheros, Inc. + * Copyright (c) 2014,2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -24,6 +24,14 @@ 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, uint32_t addr, uint32_t size, enum wil_memio_op op) { @@ -46,7 +54,7 @@ static void __iomem *wil_ioc_addr(struct wil6210_priv *wil, uint32_t addr, } off = a - wil->csr; - if (size >= WIL6210_MEM_SIZE - off) { + if (size >= wil->bar_size - off) { wil_err(wil, "Requested block does not fit into memory: " "off = 0x%08x size = 0x%08x\n", off, size); return NULL; @@ -79,10 +87,12 @@ static int wil_ioc_memio_dword(struct wil6210_priv *wil, void __user *data) io.val = readl(a); need_copy = true; break; +#if defined(CONFIG_WIL6210_WRITE_IOCTL) case wil_mmio_write: writel(io.val, a); wmb(); /* make sure write propagated to HW */ break; +#endif default: wil_err(wil, "Unsupported operation, op = 0x%08x\n", io.op); return -EINVAL; @@ -139,6 +149,7 @@ static int wil_ioc_memio_block(struct wil6210_priv *wil, void __user *data) goto out_free; } break; +#if defined(CONFIG_WIL6210_WRITE_IOCTL) case wil_mmio_write: if (copy_from_user(block, io.block, io.size)) { rc = -EFAULT; @@ -148,6 +159,7 @@ static int wil_ioc_memio_block(struct wil6210_priv *wil, void __user *data) 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; @@ -159,15 +171,71 @@ out_free: 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, "%s()\n", __func__); + + 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, "%s: invalid data len %d\n", + __func__, 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, "%s(command = %s)\n", __func__, 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; + switch (cmd) { case WIL_IOCTL_MEMIO: - return wil_ioc_memio_dword(wil, data); + ret = wil_ioc_memio_dword(wil, data); + break; case WIL_IOCTL_MEMIO_BLOCK: - return wil_ioc_memio_block(wil, data); + 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); return -ENOIOCTLCMD; } + + wil_dbg_ioctl(wil, "ioctl(0x%04x) -> %d\n", cmd, ret); + return ret; } diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c index f09fafaaaf1a..2375704ae860 100644 --- a/drivers/net/wireless/ath/wil6210/main.c +++ b/drivers/net/wireless/ath/wil6210/main.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -23,22 +23,27 @@ #include "wmi.h" #include "boot_loader.h" -#define WAIT_FOR_DISCONNECT_TIMEOUT_MS 2000 -#define WAIT_FOR_DISCONNECT_INTERVAL_MS 10 +#define WAIT_FOR_HALP_VOTE_MS 100 +#define WAIT_FOR_SCAN_ABORT_MS 1000 bool debug_fw; /* = false; */ -module_param(debug_fw, bool, S_IRUGO); +module_param(debug_fw, bool, 0444); MODULE_PARM_DESC(debug_fw, " do not perform card reset. For FW debug"); +static u8 oob_mode; +module_param(oob_mode, byte, 0444); +MODULE_PARM_DESC(oob_mode, + " enable out of the box (OOB) mode in FW, for diagnostics and certification"); + bool no_fw_recovery; -module_param(no_fw_recovery, bool, S_IRUGO | S_IWUSR); +module_param(no_fw_recovery, bool, 0644); MODULE_PARM_DESC(no_fw_recovery, " disable automatic FW error recovery"); /* if not set via modparam, will be set to default value of 1/8 of * rx ring size during init flow */ unsigned short rx_ring_overflow_thrsh = WIL6210_RX_HIGH_TRSH_INIT; -module_param(rx_ring_overflow_thrsh, ushort, S_IRUGO); +module_param(rx_ring_overflow_thrsh, ushort, 0444); MODULE_PARM_DESC(rx_ring_overflow_thrsh, " RX ring overflow threshold in descriptors."); @@ -68,7 +73,7 @@ static const struct kernel_param_ops mtu_max_ops = { .get = param_get_uint, }; -module_param_cb(mtu_max, &mtu_max_ops, &mtu_max, S_IRUGO); +module_param_cb(mtu_max, &mtu_max_ops, &mtu_max, 0444); MODULE_PARM_DESC(mtu_max, " Max MTU value."); static uint rx_ring_order = WIL_RX_RING_SIZE_ORDER_DEFAULT; @@ -97,11 +102,11 @@ static const struct kernel_param_ops ring_order_ops = { .get = param_get_uint, }; -module_param_cb(rx_ring_order, &ring_order_ops, &rx_ring_order, S_IRUGO); +module_param_cb(rx_ring_order, &ring_order_ops, &rx_ring_order, 0444); MODULE_PARM_DESC(rx_ring_order, " Rx ring order; size = 1 << order"); -module_param_cb(tx_ring_order, &ring_order_ops, &tx_ring_order, S_IRUGO); +module_param_cb(tx_ring_order, &ring_order_ops, &tx_ring_order, 0444); MODULE_PARM_DESC(tx_ring_order, " Tx ring order; size = 1 << order"); -module_param_cb(bcast_ring_order, &ring_order_ops, &bcast_ring_order, S_IRUGO); +module_param_cb(bcast_ring_order, &ring_order_ops, &bcast_ring_order, 0444); MODULE_PARM_DESC(bcast_ring_order, " Bcast ring order; size = 1 << order"); #define RST_DELAY (20) /* msec, for loop in @wil_target_reset */ @@ -164,12 +169,16 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) struct wil_sta_info *sta = &wil->sta[cid]; might_sleep(); - wil_dbg_misc(wil, "%s(CID %d, status %d)\n", __func__, cid, - sta->status); - + wil_dbg_misc(wil, "disconnect_cid: CID %d, status %d\n", + cid, sta->status); + /* inform upper/lower layers */ if (sta->status != wil_sta_unused) { - if (!from_event) - wmi_disconnect_sta(wil, sta->addr, reason_code); + if (!from_event) { + bool del_sta = (wdev->iftype == NL80211_IFTYPE_AP) ? + disable_ap_sme : false; + wmi_disconnect_sta(wil, sta->addr, reason_code, + true, del_sta); + } switch (wdev->iftype) { case NL80211_IFTYPE_AP: @@ -181,8 +190,9 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) break; } sta->status = wil_sta_unused; + sta->fst_link_loss = false; } - + /* reorder buffers */ for (i = 0; i < WIL_STA_TID_NUM; i++) { struct wil_tid_ampdu_rx *r; @@ -194,13 +204,30 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) spin_unlock_bh(&sta->tid_rx_lock); } + /* crypto context */ + memset(sta->tid_crypto_rx, 0, sizeof(sta->tid_crypto_rx)); + memset(&sta->group_crypto_rx, 0, sizeof(sta->group_crypto_rx)); + /* release vrings */ for (i = 0; i < ARRAY_SIZE(wil->vring_tx); i++) { if (wil->vring2cid_tid[i][0] == cid) wil_vring_fini_tx(wil, i); } + /* statistics */ memset(&sta->stats, 0, sizeof(sta->stats)); } +static bool wil_is_connected(struct wil6210_priv *wil) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { + if (wil->sta[i].status == wil_sta_connected) + return true; + } + + return false; +} + static void _wil6210_disconnect(struct wil6210_priv *wil, const u8 *bssid, u16 reason_code, bool from_event) { @@ -208,9 +235,12 @@ static void _wil6210_disconnect(struct wil6210_priv *wil, const u8 *bssid, struct net_device *ndev = wil_to_ndev(wil); struct wireless_dev *wdev = wil->wdev; + if (unlikely(!ndev)) + return; + might_sleep(); - wil_dbg_misc(wil, "%s(bssid=%pM, reason=%d, ev%s)\n", __func__, bssid, - reason_code, from_event ? "+" : "-"); + wil_info(wil, "bssid=%pM, reason=%d, ev%s\n", bssid, + reason_code, from_event ? "+" : "-"); /* Cases are: * - disconnect single STA, still connected @@ -240,20 +270,34 @@ static void _wil6210_disconnect(struct wil6210_priv *wil, const u8 *bssid, case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: wil_bcast_fini(wil); - netif_tx_stop_all_queues(ndev); + wil_update_net_queues_bh(wil, NULL, true); netif_carrier_off(ndev); + wil6210_bus_request(wil, WIL_DEFAULT_BUS_REQUEST_KBPS); if (test_bit(wil_status_fwconnected, wil->status)) { clear_bit(wil_status_fwconnected, wil->status); cfg80211_disconnected(ndev, reason_code, - NULL, 0, false, GFP_KERNEL); + NULL, 0, + wil->locally_generated_disc, + GFP_KERNEL); + wil->locally_generated_disc = false; } else if (test_bit(wil_status_fwconnecting, wil->status)) { cfg80211_connect_result(ndev, bssid, NULL, 0, NULL, 0, WLAN_STATUS_UNSPECIFIED_FAILURE, GFP_KERNEL); + wil->bss = NULL; } clear_bit(wil_status_fwconnecting, wil->status); break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + if (!wil_is_connected(wil)) { + wil_update_net_queues_bh(wil, NULL, true); + clear_bit(wil_status_fwconnected, wil->status); + } else { + wil_update_net_queues_bh(wil, NULL, false); + } + break; default: break; } @@ -263,22 +307,49 @@ static void wil_disconnect_worker(struct work_struct *work) { struct wil6210_priv *wil = container_of(work, struct wil6210_priv, disconnect_worker); + struct net_device *ndev = wil_to_ndev(wil); + int rc; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_disconnect_event evt; + } __packed reply; - mutex_lock(&wil->mutex); - _wil6210_disconnect(wil, NULL, WLAN_REASON_UNSPECIFIED, false); - mutex_unlock(&wil->mutex); + if (test_bit(wil_status_fwconnected, wil->status)) + /* connect succeeded after all */ + return; + + if (!test_bit(wil_status_fwconnecting, wil->status)) + /* already disconnected */ + return; + + rc = wmi_call(wil, WMI_DISCONNECT_CMDID, NULL, 0, + WMI_DISCONNECT_EVENTID, &reply, sizeof(reply), + WIL6210_DISCONNECT_TO_MS); + if (rc) { + wil_err(wil, "disconnect error %d\n", rc); + return; + } + + wil_update_net_queues_bh(wil, NULL, true); + netif_carrier_off(ndev); + cfg80211_connect_result(ndev, NULL, NULL, 0, NULL, 0, + WLAN_STATUS_UNSPECIFIED_FAILURE, GFP_KERNEL); + clear_bit(wil_status_fwconnecting, wil->status); } static void wil_connect_timer_fn(ulong x) { struct wil6210_priv *wil = (void *)x; + bool q; - wil_dbg_misc(wil, "Connect timeout\n"); + wil_err(wil, "Connect timeout detected, disconnect station\n"); /* reschedule to thread context - disconnect won't - * run from atomic context + * run from atomic context. + * queue on wmi_wq to prevent race with connect event. */ - schedule_work(&wil->disconnect_worker); + q = queue_work(wil->wmi_wq, &wil->disconnect_worker); + wil_dbg_wmi(wil, "queue_work of disconnect_worker -> %d\n", q); } static void wil_scan_timer_fn(ulong x) @@ -307,22 +378,28 @@ static int wil_wait_for_recovery(struct wil6210_priv *wil) void wil_set_recovery_state(struct wil6210_priv *wil, int state) { - wil_dbg_misc(wil, "%s(%d -> %d)\n", __func__, + wil_dbg_misc(wil, "set_recovery_state: %d -> %d\n", wil->recovery_state, state); wil->recovery_state = state; wake_up_interruptible(&wil->wq); } +bool wil_is_recovery_blocked(struct wil6210_priv *wil) +{ + return no_fw_recovery && (wil->recovery_state == fw_recovery_pending); +} + static void wil_fw_error_worker(struct work_struct *work) { struct wil6210_priv *wil = container_of(work, struct wil6210_priv, fw_error_worker); struct wireless_dev *wdev = wil->wdev; + struct net_device *ndev = wil_to_ndev(wil); wil_dbg_misc(wil, "fw error worker\n"); - if (!netif_running(wil_to_ndev(wil))) { + if (!(ndev->flags & IFF_UP)) { wil_info(wil, "No recovery - interface is down\n"); return; } @@ -384,6 +461,32 @@ static int wil_find_free_vring(struct wil6210_priv *wil) return -EINVAL; } +int wil_tx_init(struct wil6210_priv *wil, int cid) +{ + int rc = -EINVAL, ringid; + + if (cid < 0) { + wil_err(wil, "No connection pending\n"); + goto out; + } + ringid = wil_find_free_vring(wil); + if (ringid < 0) { + wil_err(wil, "No free vring found\n"); + goto out; + } + + wil_dbg_wmi(wil, "Configure for connection CID %d vring %d\n", + cid, ringid); + + rc = wil_vring_init_tx(wil, ringid, 1 << tx_ring_order, cid, 0); + if (rc) + wil_err(wil, "wil_vring_init_tx for CID %d vring %d failed\n", + cid, ringid); + +out: + return rc; +} + int wil_bcast_init(struct wil6210_priv *wil) { int ri = wil->bcast_vring, rc; @@ -414,72 +517,50 @@ void wil_bcast_fini(struct wil6210_priv *wil) wil_vring_fini_tx(wil, ri); } -static void wil_connect_worker(struct work_struct *work) -{ - int rc; - struct wil6210_priv *wil = container_of(work, struct wil6210_priv, - connect_worker); - struct net_device *ndev = wil_to_ndev(wil); - - int cid = wil->pending_connect_cid; - int ringid = wil_find_free_vring(wil); - - if (cid < 0) { - wil_err(wil, "No connection pending\n"); - return; - } - - wil_dbg_wmi(wil, "Configure for connection CID %d\n", cid); - - rc = wil_vring_init_tx(wil, ringid, 1 << tx_ring_order, cid, 0); - wil->pending_connect_cid = -1; - if (rc == 0) { - wil->sta[cid].status = wil_sta_connected; - netif_tx_wake_all_queues(ndev); - } else { - wil_disconnect_cid(wil, cid, WLAN_REASON_UNSPECIFIED, true); - } -} - int wil_priv_init(struct wil6210_priv *wil) { uint i; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "priv_init\n"); memset(wil->sta, 0, sizeof(wil->sta)); for (i = 0; i < WIL6210_MAX_CID; i++) spin_lock_init(&wil->sta[i].tid_rx_lock); + for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) + spin_lock_init(&wil->vring_tx_data[i].lock); + mutex_init(&wil->mutex); mutex_init(&wil->wmi_mutex); - mutex_init(&wil->back_rx_mutex); - mutex_init(&wil->back_tx_mutex); mutex_init(&wil->probe_client_mutex); + mutex_init(&wil->p2p_wdev_mutex); + mutex_init(&wil->halp.lock); init_completion(&wil->wmi_ready); init_completion(&wil->wmi_call); + init_completion(&wil->halp.comp); - wil->pending_connect_cid = -1; wil->bcast_vring = -1; setup_timer(&wil->connect_timer, wil_connect_timer_fn, (ulong)wil); setup_timer(&wil->scan_timer, wil_scan_timer_fn, (ulong)wil); + setup_timer(&wil->p2p.discovery_timer, wil_p2p_discovery_timer_fn, + (ulong)wil); - INIT_WORK(&wil->connect_worker, wil_connect_worker); INIT_WORK(&wil->disconnect_worker, wil_disconnect_worker); INIT_WORK(&wil->wmi_event_worker, wmi_event_worker); INIT_WORK(&wil->fw_error_worker, wil_fw_error_worker); - INIT_WORK(&wil->back_rx_worker, wil_back_rx_worker); - INIT_WORK(&wil->back_tx_worker, wil_back_tx_worker); INIT_WORK(&wil->probe_client_worker, wil_probe_client_worker); + INIT_WORK(&wil->p2p.delayed_listen_work, wil_p2p_delayed_listen_work); INIT_LIST_HEAD(&wil->pending_wmi_ev); - INIT_LIST_HEAD(&wil->back_rx_pending); - INIT_LIST_HEAD(&wil->back_tx_pending); INIT_LIST_HEAD(&wil->probe_client_pending); spin_lock_init(&wil->wmi_ev_lock); + spin_lock_init(&wil->net_queue_lock); + wil->net_queue_stopped = 1; init_waitqueue_head(&wil->wq); + wil_ftm_init(wil); + wil->wmi_wq = create_singlethread_workqueue(WIL_NAME "_wmi"); if (!wil->wmi_wq) return -EAGAIN; @@ -496,6 +577,15 @@ int wil_priv_init(struct wil6210_priv *wil) if (rx_ring_overflow_thrsh == WIL6210_RX_HIGH_TRSH_INIT) rx_ring_overflow_thrsh = WIL6210_RX_HIGH_TRSH_DEFAULT; + + wil->ps_profile = WMI_PS_PROFILE_TYPE_DEFAULT; + + wil->wakeup_trigger = WMI_WAKEUP_TRIGGER_UCAST | + WMI_WAKEUP_TRIGGER_BCAST; + memset(&wil->suspend_stats, 0, sizeof(wil->suspend_stats)); + wil->suspend_stats.min_suspend_time = ULONG_MAX; + wil->vring_idle_trsh = 16; + return 0; out_wmi_wq: @@ -504,6 +594,14 @@ out_wmi_wq: return -EAGAIN; } +void wil6210_bus_request(struct wil6210_priv *wil, u32 kbps) +{ + if (wil->platform_ops.bus_request) { + wil->bus_request_kbps = kbps; + wil->platform_ops.bus_request(wil->platform_handle, kbps); + } +} + /** * wil6210_disconnect - disconnect one connection * @wil: driver context @@ -517,7 +615,7 @@ out_wmi_wq: void wil6210_disconnect(struct wil6210_priv *wil, const u8 *bssid, u16 reason_code, bool from_event) { - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "disconnect\n"); del_timer_sync(&wil->connect_timer); _wil6210_disconnect(wil, bssid, reason_code, from_event); @@ -525,20 +623,20 @@ void wil6210_disconnect(struct wil6210_priv *wil, const u8 *bssid, void wil_priv_deinit(struct wil6210_priv *wil) { - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "priv_deinit\n"); + wil_ftm_deinit(wil); wil_set_recovery_state(wil, fw_recovery_idle); del_timer_sync(&wil->scan_timer); + del_timer_sync(&wil->p2p.discovery_timer); cancel_work_sync(&wil->disconnect_worker); cancel_work_sync(&wil->fw_error_worker); + cancel_work_sync(&wil->p2p.discovery_expired_work); + cancel_work_sync(&wil->p2p.delayed_listen_work); mutex_lock(&wil->mutex); wil6210_disconnect(wil, NULL, WLAN_REASON_DEAUTH_LEAVING, false); mutex_unlock(&wil->mutex); wmi_event_flush(wil); - wil_back_rx_flush(wil); - cancel_work_sync(&wil->back_rx_worker); - wil_back_tx_flush(wil); - cancel_work_sync(&wil->back_tx_worker); wil_probe_client_flush(wil); cancel_work_sync(&wil->probe_client_worker); destroy_workqueue(wil->wq_service); @@ -557,6 +655,27 @@ static inline void wil_release_cpu(struct wil6210_priv *wil) wil_w(wil, RGF_USER_USER_CPU_0, 1); } +static void wil_set_oob_mode(struct wil6210_priv *wil, u8 mode) +{ + wil_info(wil, "oob_mode to %d\n", mode); + switch (mode) { + case 0: + wil_c(wil, RGF_USER_USAGE_6, BIT_USER_OOB_MODE | + BIT_USER_OOB_R2_MODE); + break; + case 1: + wil_c(wil, RGF_USER_USAGE_6, BIT_USER_OOB_R2_MODE); + wil_s(wil, RGF_USER_USAGE_6, BIT_USER_OOB_MODE); + break; + case 2: + wil_c(wil, RGF_USER_USAGE_6, BIT_USER_OOB_MODE); + wil_s(wil, RGF_USER_USAGE_6, BIT_USER_OOB_R2_MODE); + break; + default: + wil_err(wil, "invalid oob_mode: %d\n", mode); + } +} + static int wil_target_reset(struct wil6210_priv *wil) { int delay = 0; @@ -640,6 +759,40 @@ static int wil_target_reset(struct wil6210_priv *wil) return 0; } +static void wil_collect_fw_info(struct wil6210_priv *wil) +{ + struct wiphy *wiphy = wil_to_wiphy(wil); + u8 retry_short; + int rc; + + wil_refresh_fw_capabilities(wil); + + rc = wmi_get_mgmt_retry(wil, &retry_short); + if (!rc) { + wiphy->retry_short = retry_short; + wil_dbg_misc(wil, "FW retry_short: %d\n", retry_short); + } +} + +void wil_refresh_fw_capabilities(struct wil6210_priv *wil) +{ + struct wiphy *wiphy = wil_to_wiphy(wil); + + wil->keep_radio_on_during_sleep = + wil->platform_ops.keep_radio_on_during_sleep && + wil->platform_ops.keep_radio_on_during_sleep( + wil->platform_handle) && + test_bit(WMI_FW_CAPABILITY_D3_SUSPEND, wil->fw_capabilities); + + wil_info(wil, "keep_radio_on_during_sleep (%d)\n", + wil->keep_radio_on_during_sleep); + + if (test_bit(WMI_FW_CAPABILITY_RSSI_REPORTING, wil->fw_capabilities)) + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + else + wiphy->signal_type = CFG80211_SIGNAL_TYPE_UNSPEC; +} + void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r) { le32_to_cpus(&r->base); @@ -652,6 +805,7 @@ void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r) static int wil_get_bl_info(struct wil6210_priv *wil) { struct net_device *ndev = wil_to_ndev(wil); + struct wiphy *wiphy = wil_to_wiphy(wil); union { struct bl_dedicated_registers_v0 bl0; struct bl_dedicated_registers_v1 bl1; @@ -696,6 +850,7 @@ static int wil_get_bl_info(struct wil6210_priv *wil) } ether_addr_copy(ndev->perm_addr, mac); + ether_addr_copy(wiphy->perm_addr, mac); if (!is_valid_ether_addr(ndev->dev_addr)) ether_addr_copy(ndev->dev_addr, mac); @@ -754,6 +909,72 @@ static int wil_wait_for_fw_ready(struct wil6210_priv *wil) return 0; } +void wil_abort_scan(struct wil6210_priv *wil, bool sync) +{ + int rc; + + lockdep_assert_held(&wil->p2p_wdev_mutex); + + if (!wil->scan_request) + return; + + wil_dbg_misc(wil, "Abort scan_request 0x%p\n", wil->scan_request); + del_timer_sync(&wil->scan_timer); + mutex_unlock(&wil->p2p_wdev_mutex); + rc = wmi_abort_scan(wil); + if (!rc && sync) + wait_event_interruptible_timeout(wil->wq, !wil->scan_request, + msecs_to_jiffies( + WAIT_FOR_SCAN_ABORT_MS)); + + mutex_lock(&wil->p2p_wdev_mutex); + if (wil->scan_request) { + cfg80211_scan_done(wil->scan_request, true); + wil->scan_request = NULL; + } +} + +int wil_ps_update(struct wil6210_priv *wil, enum wmi_ps_profile_type ps_profile) +{ + int rc; + + if (!test_bit(WMI_FW_CAPABILITY_PS_CONFIG, wil->fw_capabilities)) { + wil_err(wil, "set_power_mgmt not supported\n"); + return -EOPNOTSUPP; + } + + rc = wmi_ps_dev_profile_cfg(wil, ps_profile); + if (rc) + wil_err(wil, "wmi_ps_dev_profile_cfg failed (%d)\n", rc); + else + wil->ps_profile = ps_profile; + + return rc; +} + +static void wil_pre_fw_config(struct wil6210_priv *wil) +{ + /* Mark FW as loaded from host */ + wil_s(wil, RGF_USER_USAGE_6, 1); + + /* clear any interrupts which on-card-firmware + * may have set + */ + wil6210_clear_irq(wil); + /* CAF_ICR - clear and mask */ + /* it is W1C, clear by writing back same value */ + wil_s(wil, RGF_CAF_ICR + offsetof(struct RGF_ICR, ICR), 0); + wil_w(wil, RGF_CAF_ICR + offsetof(struct RGF_ICR, IMV), ~0); + /* clear PAL_UNIT_ICR (potential D0->D3 leftover) */ + wil_s(wil, RGF_PAL_UNIT_ICR + offsetof(struct RGF_ICR, ICR), 0); + + if (wil->fw_calib_result > 0) { + __le32 val = cpu_to_le32(wil->fw_calib_result | + (CALIB_RESULT_SIGNATURE << 8)); + wil_w(wil, RGF_USER_FW_CALIB_RESULT, (u32 __force)val); + } +} + /* * We reset all the structures, and we reset the UMAC. * After calling this routine, you're expected to reload @@ -763,7 +984,7 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw) { int rc; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "reset\n"); WARN_ON(!mutex_is_locked(&wil->mutex)); WARN_ON(test_bit(wil_status_napi_en, wil->status)); @@ -782,22 +1003,35 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw) if (wil->hw_version == HW_VER_UNKNOWN) return -ENODEV; + wil_dbg_misc(wil, "Prevent DS in BL & mark FW to set T_POWER_ON=0\n"); + wil_s(wil, RGF_USER_USAGE_8, BIT_USER_PREVENT_DEEP_SLEEP | + BIT_USER_SUPPORT_T_POWER_ON_0); + + if (wil->platform_ops.notify) { + rc = wil->platform_ops.notify(wil->platform_handle, + WIL_PLATFORM_EVT_PRE_RESET); + if (rc) + wil_err(wil, "PRE_RESET platform notify failed, rc %d\n", + rc); + } + set_bit(wil_status_resetting, wil->status); cancel_work_sync(&wil->disconnect_worker); wil6210_disconnect(wil, NULL, WLAN_REASON_DEAUTH_LEAVING, false); wil_bcast_fini(wil); - /* prevent NAPI from being scheduled */ - bitmap_zero(wil->status, wil_status_last); + /* Disable device led before reset*/ + wmi_led_cfg(wil, false); - if (wil->scan_request) { - wil_dbg_misc(wil, "Abort scan_request 0x%p\n", - wil->scan_request); - del_timer_sync(&wil->scan_timer); - cfg80211_scan_done(wil->scan_request, true); - wil->scan_request = NULL; - } + mutex_lock(&wil->p2p_wdev_mutex); + wil_abort_scan(wil, false); + mutex_unlock(&wil->p2p_wdev_mutex); + + /* prevent NAPI from being scheduled and prevent wmi commands */ + mutex_lock(&wil->wmi_mutex); + bitmap_zero(wil->status, wil_status_last); + mutex_unlock(&wil->wmi_mutex); wil_mask_irq(wil); @@ -807,7 +1041,10 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw) flush_workqueue(wil->wmi_wq); wil_bl_crash_info(wil, false); + wil_disable_irq(wil); rc = wil_target_reset(wil); + wil6210_clear_irq(wil); + wil_enable_irq(wil); wil_rx_fini(wil); if (rc) { wil_bl_crash_info(wil, true); @@ -820,39 +1057,30 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw) if (rc) return rc; + wil_set_oob_mode(wil, oob_mode); if (load_fw) { - wil_info(wil, "Use firmware <%s> + board <%s>\n", WIL_FW_NAME, - WIL_FW2_NAME); + wil_info(wil, "Use firmware <%s> + board <%s>\n", + wil->wil_fw_name, WIL_BOARD_FILE_NAME); wil_halt_cpu(wil); + memset(wil->fw_version, 0, sizeof(wil->fw_version)); /* Loading f/w from the file */ - rc = wil_request_firmware(wil, WIL_FW_NAME); + rc = wil_request_firmware(wil, wil->wil_fw_name, true); if (rc) return rc; - rc = wil_request_firmware(wil, WIL_FW2_NAME); + rc = wil_request_firmware(wil, WIL_BOARD_FILE_NAME, true); if (rc) return rc; - /* Mark FW as loaded from host */ - wil_s(wil, RGF_USER_USAGE_6, 1); - - /* clear any interrupts which on-card-firmware - * may have set - */ - wil6210_clear_irq(wil); - /* CAF_ICR - clear and mask */ - /* it is W1C, clear by writing back same value */ - wil_s(wil, RGF_CAF_ICR + offsetof(struct RGF_ICR, ICR), 0); - wil_w(wil, RGF_CAF_ICR + offsetof(struct RGF_ICR, IMV), ~0); - + wil_pre_fw_config(wil); wil_release_cpu(wil); } /* init after reset */ - wil->pending_connect_cid = -1; wil->ap_isolate = 0; reinit_completion(&wil->wmi_ready); reinit_completion(&wil->wmi_call); + reinit_completion(&wil->halp.comp); if (load_fw) { wil_configure_interrupt_moderation(wil); @@ -860,8 +1088,37 @@ int wil_reset(struct wil6210_priv *wil, bool load_fw) /* we just started MAC, wait for FW ready */ rc = wil_wait_for_fw_ready(wil); - if (rc == 0) /* check FW is responsive */ - rc = wmi_echo(wil); + if (rc) + return rc; + + /* check FW is responsive */ + rc = wmi_echo(wil); + if (rc) { + wil_err(wil, "wmi_echo failed, rc %d\n", rc); + return rc; + } + + wil_collect_fw_info(wil); + + if (wil->ps_profile != WMI_PS_PROFILE_TYPE_DEFAULT) + wil_ps_update(wil, wil->ps_profile); + + if (wil->tt_data_set) + wmi_set_tt_cfg(wil, &wil->tt_data); + + if (wil->snr_thresh.enabled) + wmi_set_snr_thresh(wil, wil->snr_thresh.omni, + wil->snr_thresh.direct); + + if (wil->platform_ops.notify) { + rc = wil->platform_ops.notify(wil->platform_handle, + WIL_PLATFORM_EVT_FW_RDY); + if (rc) { + wil_err(wil, "FW_RDY notify failed, rc %d\n", + rc); + rc = 0; + } + } } return rc; @@ -931,9 +1188,7 @@ int __wil_up(struct wil6210_priv *wil) napi_enable(&wil->napi_tx); set_bit(wil_status_napi_en, wil->status); - if (wil->platform_ops.bus_request) - wil->platform_ops.bus_request(wil->platform_handle, - WIL_MAX_BUS_REQUEST_KBPS); + wil6210_bus_request(wil, WIL_DEFAULT_BUS_REQUEST_KBPS); return 0; } @@ -942,7 +1197,7 @@ int wil_up(struct wil6210_priv *wil) { int rc; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "up\n"); mutex_lock(&wil->mutex); rc = __wil_up(wil); @@ -953,13 +1208,11 @@ int wil_up(struct wil6210_priv *wil) int __wil_down(struct wil6210_priv *wil) { - int iter = WAIT_FOR_DISCONNECT_TIMEOUT_MS / - WAIT_FOR_DISCONNECT_INTERVAL_MS; - WARN_ON(!mutex_is_locked(&wil->mutex)); - if (wil->platform_ops.bus_request) - wil->platform_ops.bus_request(wil->platform_handle, 0); + set_bit(wil_status_resetting, wil->status); + + wil6210_bus_request(wil, 0); wil_disable_irq(wil); if (test_and_clear_bit(wil_status_napi_en, wil->status)) { @@ -969,31 +1222,12 @@ int __wil_down(struct wil6210_priv *wil) } wil_enable_irq(wil); - if (wil->scan_request) { - wil_dbg_misc(wil, "Abort scan_request 0x%p\n", - wil->scan_request); - del_timer_sync(&wil->scan_timer); - cfg80211_scan_done(wil->scan_request, true); - wil->scan_request = NULL; - } + wil_ftm_stop_operations(wil); - if (test_bit(wil_status_fwconnected, wil->status) || - test_bit(wil_status_fwconnecting, wil->status)) - wmi_send(wil, WMI_DISCONNECT_CMDID, NULL, 0); - - /* make sure wil is idle (not connected) */ - mutex_unlock(&wil->mutex); - while (iter--) { - int idle = !test_bit(wil_status_fwconnected, wil->status) && - !test_bit(wil_status_fwconnecting, wil->status); - if (idle) - break; - msleep(WAIT_FOR_DISCONNECT_INTERVAL_MS); - } - mutex_lock(&wil->mutex); - - if (!iter) - wil_err(wil, "timeout waiting for idle FW/HW\n"); + mutex_lock(&wil->p2p_wdev_mutex); + wil_p2p_stop_radio_operations(wil); + wil_abort_scan(wil, false); + mutex_unlock(&wil->p2p_wdev_mutex); wil_reset(wil, false); @@ -1004,7 +1238,7 @@ int wil_down(struct wil6210_priv *wil) { int rc; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "down\n"); wil_set_recovery_state(wil, fw_recovery_idle); mutex_lock(&wil->mutex); @@ -1029,3 +1263,54 @@ int wil_find_cid(struct wil6210_priv *wil, const u8 *mac) return rc; } + +void wil_halp_vote(struct wil6210_priv *wil) +{ + unsigned long rc; + unsigned long to_jiffies = msecs_to_jiffies(WAIT_FOR_HALP_VOTE_MS); + + mutex_lock(&wil->halp.lock); + + wil_dbg_irq(wil, "halp_vote: start, HALP ref_cnt (%d)\n", + wil->halp.ref_cnt); + + if (++wil->halp.ref_cnt == 1) { + reinit_completion(&wil->halp.comp); + wil6210_set_halp(wil); + rc = wait_for_completion_timeout(&wil->halp.comp, to_jiffies); + if (!rc) { + wil_err(wil, "HALP vote timed out\n"); + /* Mask HALP as done in case the interrupt is raised */ + wil6210_mask_halp(wil); + } else { + wil_dbg_irq(wil, + "halp_vote: HALP vote completed after %d ms\n", + jiffies_to_msecs(to_jiffies - rc)); + } + } + + wil_dbg_irq(wil, "halp_vote: end, HALP ref_cnt (%d)\n", + wil->halp.ref_cnt); + + mutex_unlock(&wil->halp.lock); +} + +void wil_halp_unvote(struct wil6210_priv *wil) +{ + WARN_ON(wil->halp.ref_cnt == 0); + + mutex_lock(&wil->halp.lock); + + wil_dbg_irq(wil, "halp_unvote: start, HALP ref_cnt (%d)\n", + wil->halp.ref_cnt); + + if (--wil->halp.ref_cnt == 0) { + wil6210_clear_halp(wil); + wil_dbg_irq(wil, "HALP unvote\n"); + } + + wil_dbg_irq(wil, "halp_unvote:end, HALP ref_cnt (%d)\n", + wil->halp.ref_cnt); + + mutex_unlock(&wil->halp.lock); +} diff --git a/drivers/net/wireless/ath/wil6210/netdev.c b/drivers/net/wireless/ath/wil6210/netdev.c index e3b3c8fb4605..f8e5ccd7b9ca 100644 --- a/drivers/net/wireless/ath/wil6210/netdev.c +++ b/drivers/net/wireless/ath/wil6210/netdev.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,18 +14,24 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <linux/moduleparam.h> #include <linux/etherdevice.h> #include "wil6210.h" #include "txrx.h" +static bool alt_ifname; /* = false; */ +module_param(alt_ifname, bool, S_IRUGO); +MODULE_PARM_DESC(alt_ifname, " use an alternate interface name wigigN instead of wlanN"); + static int wil_open(struct net_device *ndev) { struct wil6210_priv *wil = ndev_to_wil(ndev); - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "open\n"); - if (debug_fw) { - wil_err(wil, "%s() while in debug_fw mode\n", __func__); + if (debug_fw || + test_bit(WMI_FW_CAPABILITY_WMI_ONLY, wil->fw_capabilities)) { + wil_err(wil, "while in debug_fw or wmi_only mode\n"); return -EINVAL; } @@ -36,7 +42,7 @@ static int wil_stop(struct net_device *ndev) { struct wil6210_priv *wil = ndev_to_wil(ndev); - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "stop\n"); return wil_down(wil); } @@ -60,11 +66,7 @@ static int wil_do_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) { struct wil6210_priv *wil = ndev_to_wil(ndev); - int ret = wil_ioctl(wil, ifr->ifr_data, cmd); - - wil_dbg_misc(wil, "ioctl(0x%04x) -> %d\n", cmd, ret); - - return ret; + return wil_ioctl(wil, ifr->ifr_data, cmd); } static const struct net_device_ops wil_netdev_ops = { @@ -88,7 +90,7 @@ static int wil6210_netdev_poll_rx(struct napi_struct *napi, int budget) done = budget - quota; if (done < budget) { - napi_complete(napi); + napi_complete_done(napi, done); wil6210_unmask_irq_rx(wil); wil_dbg_txrx(wil, "NAPI RX complete\n"); } @@ -108,8 +110,9 @@ static int wil6210_netdev_poll_tx(struct napi_struct *napi, int budget) /* always process ALL Tx complete, regardless budget - it is fast */ for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) { struct vring *vring = &wil->vring_tx[i]; + struct vring_tx_data *txdata = &wil->vring_tx_data[i]; - if (!vring->va) + if (!vring->va || !txdata->enabled) continue; tx_done += wil_tx_complete(wil, i); @@ -139,6 +142,7 @@ void *wil_if_alloc(struct device *dev) struct wil6210_priv *wil; struct ieee80211_channel *ch; int rc = 0; + const char *ifname = alt_ifname ? "wigig%d" : "wlan%d"; wdev = wil_cfg80211_init(dev); if (IS_ERR(wdev)) { @@ -148,8 +152,9 @@ void *wil_if_alloc(struct device *dev) wil = wdev_to_wil(wdev); wil->wdev = wdev; + wil->radio_wdev = wdev; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "if_alloc\n"); rc = wil_priv_init(wil); if (rc) { @@ -162,7 +167,7 @@ void *wil_if_alloc(struct device *dev) ch = wdev->wiphy->bands[IEEE80211_BAND_60GHZ]->channels; cfg80211_chandef_create(&wdev->preset_chandef, ch, NL80211_CHAN_NO_HT); - ndev = alloc_netdev(0, "wlan%d", NET_NAME_UNKNOWN, wil_dev_setup); + ndev = alloc_netdev(0, ifname, NET_NAME_UNKNOWN, wil_dev_setup); if (!ndev) { dev_err(dev, "alloc_netdev_mqs failed\n"); rc = -ENOMEM; @@ -181,13 +186,6 @@ void *wil_if_alloc(struct device *dev) SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy)); wdev->netdev = ndev; - netif_napi_add(ndev, &wil->napi_rx, wil6210_netdev_poll_rx, - WIL6210_NAPI_BUDGET); - netif_napi_add(ndev, &wil->napi_tx, wil6210_netdev_poll_tx, - WIL6210_NAPI_BUDGET); - - netif_tx_stop_all_queues(ndev); - return wil; out_priv: @@ -203,7 +201,7 @@ void wil_if_free(struct wil6210_priv *wil) { struct net_device *ndev = wil_to_ndev(wil); - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "if_free\n"); if (!ndev) return; @@ -218,25 +216,48 @@ void wil_if_free(struct wil6210_priv *wil) int wil_if_add(struct wil6210_priv *wil) { + struct wireless_dev *wdev = wil_to_wdev(wil); + struct wiphy *wiphy = wdev->wiphy; struct net_device *ndev = wil_to_ndev(wil); int rc; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "entered"); + + strlcpy(wiphy->fw_version, wil->fw_version, sizeof(wiphy->fw_version)); + + rc = wiphy_register(wiphy); + if (rc < 0) { + wil_err(wil, "failed to register wiphy, err %d\n", rc); + return rc; + } + + netif_napi_add(ndev, &wil->napi_rx, wil6210_netdev_poll_rx, + WIL6210_NAPI_BUDGET); + netif_napi_add(ndev, &wil->napi_tx, wil6210_netdev_poll_tx, + WIL6210_NAPI_BUDGET); + + wil_update_net_queues_bh(wil, NULL, true); rc = register_netdev(ndev); if (rc < 0) { dev_err(&ndev->dev, "Failed to register netdev: %d\n", rc); - return rc; + goto out_wiphy; } return 0; + +out_wiphy: + wiphy_unregister(wdev->wiphy); + return rc; } void wil_if_remove(struct wil6210_priv *wil) { struct net_device *ndev = wil_to_ndev(wil); + struct wireless_dev *wdev = wil_to_wdev(wil); - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "if_remove\n"); unregister_netdev(ndev); + wiphy_unregister(wdev->wiphy); } diff --git a/drivers/net/wireless/ath/wil6210/p2p.c b/drivers/net/wireless/ath/wil6210/p2p.c new file mode 100644 index 000000000000..ccd98487ea09 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/p2p.c @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2014-2017 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "wil6210.h" +#include "wmi.h" + +#define P2P_WILDCARD_SSID "DIRECT-" +#define P2P_DMG_SOCIAL_CHANNEL 2 +#define P2P_SEARCH_DURATION_MS 500 +#define P2P_DEFAULT_BI 100 + +static int wil_p2p_start_listen(struct wil6210_priv *wil) +{ + struct wil_p2p_info *p2p = &wil->p2p; + u8 channel = p2p->listen_chan.hw_value; + int rc; + + lockdep_assert_held(&wil->mutex); + + rc = wmi_p2p_cfg(wil, channel, P2P_DEFAULT_BI); + if (rc) { + wil_err(wil, "wmi_p2p_cfg failed\n"); + goto out; + } + + rc = wmi_set_ssid(wil, strlen(P2P_WILDCARD_SSID), P2P_WILDCARD_SSID); + if (rc) { + wil_err(wil, "wmi_set_ssid failed\n"); + goto out_stop; + } + + rc = wmi_start_listen(wil); + if (rc) { + wil_err(wil, "wmi_start_listen failed\n"); + goto out_stop; + } + + INIT_WORK(&p2p->discovery_expired_work, wil_p2p_listen_expired); + mod_timer(&p2p->discovery_timer, + jiffies + msecs_to_jiffies(p2p->listen_duration)); +out_stop: + if (rc) + wmi_stop_discovery(wil); + +out: + return rc; +} + +bool wil_p2p_is_social_scan(struct cfg80211_scan_request *request) +{ + return (request->n_channels == 1) && + (request->channels[0]->hw_value == P2P_DMG_SOCIAL_CHANNEL); +} + +void wil_p2p_discovery_timer_fn(ulong x) +{ + struct wil6210_priv *wil = (void *)x; + + wil_dbg_misc(wil, "p2p_discovery_timer_fn\n"); + + schedule_work(&wil->p2p.discovery_expired_work); +} + +int wil_p2p_search(struct wil6210_priv *wil, + struct cfg80211_scan_request *request) +{ + int rc; + struct wil_p2p_info *p2p = &wil->p2p; + + wil_dbg_misc(wil, "p2p_search: channel %d\n", P2P_DMG_SOCIAL_CHANNEL); + + lockdep_assert_held(&wil->mutex); + + if (p2p->discovery_started) { + wil_err(wil, "search failed. discovery already ongoing\n"); + rc = -EBUSY; + goto out; + } + + rc = wmi_p2p_cfg(wil, P2P_DMG_SOCIAL_CHANNEL, P2P_DEFAULT_BI); + if (rc) { + wil_err(wil, "wmi_p2p_cfg failed\n"); + goto out; + } + + rc = wmi_set_ssid(wil, strlen(P2P_WILDCARD_SSID), P2P_WILDCARD_SSID); + if (rc) { + wil_err(wil, "wmi_set_ssid failed\n"); + goto out_stop; + } + + /* Set application IE to probe request and probe response */ + rc = wmi_set_ie(wil, WMI_FRAME_PROBE_REQ, + request->ie_len, request->ie); + if (rc) { + wil_err(wil, "wmi_set_ie(WMI_FRAME_PROBE_REQ) failed\n"); + goto out_stop; + } + + /* supplicant doesn't provide Probe Response IEs. As a workaround - + * re-use Probe Request IEs + */ + rc = wmi_set_ie(wil, WMI_FRAME_PROBE_RESP, + request->ie_len, request->ie); + if (rc) { + wil_err(wil, "wmi_set_ie(WMI_FRAME_PROBE_RESP) failed\n"); + goto out_stop; + } + + rc = wmi_start_search(wil); + if (rc) { + wil_err(wil, "wmi_start_search failed\n"); + goto out_stop; + } + + p2p->discovery_started = 1; + INIT_WORK(&p2p->discovery_expired_work, wil_p2p_search_expired); + mod_timer(&p2p->discovery_timer, + jiffies + msecs_to_jiffies(P2P_SEARCH_DURATION_MS)); + +out_stop: + if (rc) + wmi_stop_discovery(wil); + +out: + return rc; +} + +int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, + unsigned int duration, struct ieee80211_channel *chan, + u64 *cookie) +{ + struct wil_p2p_info *p2p = &wil->p2p; + int rc; + + if (!chan) + return -EINVAL; + + wil_dbg_misc(wil, "p2p_listen: duration %d\n", duration); + + mutex_lock(&wil->mutex); + + if (p2p->discovery_started) { + wil_err(wil, "discovery already ongoing\n"); + rc = -EBUSY; + goto out; + } + + memcpy(&p2p->listen_chan, chan, sizeof(*chan)); + *cookie = ++p2p->cookie; + p2p->listen_duration = duration; + + mutex_lock(&wil->p2p_wdev_mutex); + if (wil->scan_request) { + wil_dbg_misc(wil, "Delaying p2p listen until scan done\n"); + p2p->pending_listen_wdev = wdev; + p2p->discovery_started = 1; + rc = 0; + mutex_unlock(&wil->p2p_wdev_mutex); + goto out; + } + mutex_unlock(&wil->p2p_wdev_mutex); + + rc = wil_p2p_start_listen(wil); + if (rc) + goto out; + + p2p->discovery_started = 1; + wil->radio_wdev = wdev; + + cfg80211_ready_on_channel(wdev, *cookie, chan, duration, + GFP_KERNEL); + +out: + mutex_unlock(&wil->mutex); + return rc; +} + +u8 wil_p2p_stop_discovery(struct wil6210_priv *wil) +{ + struct wil_p2p_info *p2p = &wil->p2p; + u8 started = p2p->discovery_started; + + if (p2p->discovery_started) { + if (p2p->pending_listen_wdev) { + /* discovery not really started, only pending */ + p2p->pending_listen_wdev = NULL; + } else { + del_timer_sync(&p2p->discovery_timer); + wmi_stop_discovery(wil); + } + p2p->discovery_started = 0; + } + + return started; +} + +int wil_p2p_cancel_listen(struct wil6210_priv *wil, u64 cookie) +{ + struct wil_p2p_info *p2p = &wil->p2p; + u8 started; + + mutex_lock(&wil->mutex); + + if (cookie != p2p->cookie) { + wil_info(wil, "Cookie mismatch: 0x%016llx vs. 0x%016llx\n", + p2p->cookie, cookie); + mutex_unlock(&wil->mutex); + return -ENOENT; + } + + started = wil_p2p_stop_discovery(wil); + + mutex_unlock(&wil->mutex); + + if (!started) { + wil_err(wil, "listen not started\n"); + return -ENOENT; + } + + mutex_lock(&wil->p2p_wdev_mutex); + cfg80211_remain_on_channel_expired(wil->radio_wdev, + p2p->cookie, + &p2p->listen_chan, + GFP_KERNEL); + wil->radio_wdev = wil->wdev; + mutex_unlock(&wil->p2p_wdev_mutex); + return 0; +} + +void wil_p2p_listen_expired(struct work_struct *work) +{ + struct wil_p2p_info *p2p = container_of(work, + struct wil_p2p_info, discovery_expired_work); + struct wil6210_priv *wil = container_of(p2p, + struct wil6210_priv, p2p); + u8 started; + + wil_dbg_misc(wil, "p2p_listen_expired\n"); + + mutex_lock(&wil->mutex); + started = wil_p2p_stop_discovery(wil); + mutex_unlock(&wil->mutex); + + if (started) { + mutex_lock(&wil->p2p_wdev_mutex); + cfg80211_remain_on_channel_expired(wil->radio_wdev, + p2p->cookie, + &p2p->listen_chan, + GFP_KERNEL); + wil->radio_wdev = wil->wdev; + mutex_unlock(&wil->p2p_wdev_mutex); + } + +} + +void wil_p2p_search_expired(struct work_struct *work) +{ + struct wil_p2p_info *p2p = container_of(work, + struct wil_p2p_info, discovery_expired_work); + struct wil6210_priv *wil = container_of(p2p, + struct wil6210_priv, p2p); + u8 started; + + wil_dbg_misc(wil, "p2p_search_expired\n"); + + mutex_lock(&wil->mutex); + started = wil_p2p_stop_discovery(wil); + mutex_unlock(&wil->mutex); + + if (started) { + mutex_lock(&wil->p2p_wdev_mutex); + if (wil->scan_request) { + cfg80211_scan_done(wil->scan_request, 0); + wil->scan_request = NULL; + wil->radio_wdev = wil->wdev; + } + mutex_unlock(&wil->p2p_wdev_mutex); + } +} + +void wil_p2p_delayed_listen_work(struct work_struct *work) +{ + struct wil_p2p_info *p2p = container_of(work, + struct wil_p2p_info, delayed_listen_work); + struct wil6210_priv *wil = container_of(p2p, + struct wil6210_priv, p2p); + int rc; + + mutex_lock(&wil->mutex); + + wil_dbg_misc(wil, "Checking delayed p2p listen\n"); + if (!p2p->discovery_started || !p2p->pending_listen_wdev) + goto out; + + mutex_lock(&wil->p2p_wdev_mutex); + if (wil->scan_request) { + /* another scan started, wait again... */ + mutex_unlock(&wil->p2p_wdev_mutex); + goto out; + } + mutex_unlock(&wil->p2p_wdev_mutex); + + rc = wil_p2p_start_listen(wil); + + mutex_lock(&wil->p2p_wdev_mutex); + if (rc) { + cfg80211_remain_on_channel_expired(p2p->pending_listen_wdev, + p2p->cookie, + &p2p->listen_chan, + GFP_KERNEL); + wil->radio_wdev = wil->wdev; + } else { + cfg80211_ready_on_channel(p2p->pending_listen_wdev, p2p->cookie, + &p2p->listen_chan, + p2p->listen_duration, GFP_KERNEL); + wil->radio_wdev = p2p->pending_listen_wdev; + } + p2p->pending_listen_wdev = NULL; + mutex_unlock(&wil->p2p_wdev_mutex); + +out: + mutex_unlock(&wil->mutex); +} + +void wil_p2p_stop_radio_operations(struct wil6210_priv *wil) +{ + struct wil_p2p_info *p2p = &wil->p2p; + + lockdep_assert_held(&wil->mutex); + lockdep_assert_held(&wil->p2p_wdev_mutex); + + if (wil->radio_wdev != wil->p2p_wdev) + goto out; + + if (!p2p->discovery_started) { + /* Regular scan on the p2p device */ + if (wil->scan_request && + wil->scan_request->wdev == wil->p2p_wdev) + wil_abort_scan(wil, true); + goto out; + } + + /* Search or listen on p2p device */ + mutex_unlock(&wil->p2p_wdev_mutex); + wil_p2p_stop_discovery(wil); + mutex_lock(&wil->p2p_wdev_mutex); + + if (wil->scan_request) { + /* search */ + cfg80211_scan_done(wil->scan_request, 1); + wil->scan_request = NULL; + } else { + /* listen */ + cfg80211_remain_on_channel_expired(wil->radio_wdev, + p2p->cookie, + &p2p->listen_chan, + GFP_KERNEL); + } + +out: + wil->radio_wdev = wil->wdev; +} diff --git a/drivers/net/wireless/ath/wil6210/pcie_bus.c b/drivers/net/wireless/ath/wil6210/pcie_bus.c index 1a3142c332e1..5432b319a52e 100644 --- a/drivers/net/wireless/ath/wil6210/pcie_bus.c +++ b/drivers/net/wireless/ath/wil6210/pcie_bus.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,32 +18,73 @@ #include <linux/pci.h> #include <linux/moduleparam.h> #include <linux/interrupt.h> - +#include <linux/suspend.h> #include "wil6210.h" +#include <linux/rtnetlink.h> static bool use_msi = true; -module_param(use_msi, bool, S_IRUGO); +module_param(use_msi, bool, 0444); MODULE_PARM_DESC(use_msi, " Use MSI interrupt, default - true"); +static bool ftm_mode; +module_param(ftm_mode, bool, 0444); +MODULE_PARM_DESC(ftm_mode, " Set factory test mode, default - false"); + +#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP +static int wil6210_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused); +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ + static void wil_set_capabilities(struct wil6210_priv *wil) { - u32 rev_id = wil_r(wil, RGF_USER_JTAG_DEV_ID); + const char *wil_fw_name; + u32 jtag_id = wil_r(wil, RGF_USER_JTAG_DEV_ID); + u8 chip_revision = (wil_r(wil, RGF_USER_REVISION_ID) & + RGF_USER_REVISION_ID_MASK); bitmap_zero(wil->hw_capabilities, hw_capability_last); - - switch (rev_id) { - case JTAG_DEV_ID_SPARROW_B0: - wil->hw_name = "Sparrow B0"; - wil->hw_version = HW_VER_SPARROW_B0; + bitmap_zero(wil->fw_capabilities, WMI_FW_CAPABILITY_MAX); + wil->wil_fw_name = ftm_mode ? WIL_FW_NAME_FTM_DEFAULT : + WIL_FW_NAME_DEFAULT; + wil->chip_revision = chip_revision; + + switch (jtag_id) { + case JTAG_DEV_ID_SPARROW: + switch (chip_revision) { + case REVISION_ID_SPARROW_D0: + wil->hw_name = "Sparrow D0"; + wil->hw_version = HW_VER_SPARROW_D0; + wil_fw_name = ftm_mode ? WIL_FW_NAME_FTM_SPARROW_PLUS : + WIL_FW_NAME_SPARROW_PLUS; + + if (wil_fw_verify_file_exists(wil, wil_fw_name)) + wil->wil_fw_name = wil_fw_name; + break; + case REVISION_ID_SPARROW_B0: + wil->hw_name = "Sparrow B0"; + wil->hw_version = HW_VER_SPARROW_B0; + break; + default: + wil->hw_name = "Unknown"; + wil->hw_version = HW_VER_UNKNOWN; + break; + } break; default: - wil_err(wil, "Unknown board hardware 0x%08x\n", rev_id); + wil_err(wil, "Unknown board hardware, chip_id 0x%08x, chip_revision 0x%08x\n", + jtag_id, chip_revision); wil->hw_name = "Unknown"; wil->hw_version = HW_VER_UNKNOWN; } wil_info(wil, "Board hardware is %s\n", wil->hw_name); + + /* extract FW capabilities from file without loading the FW */ + wil_request_firmware(wil, wil->wil_fw_name, false); + wil_refresh_fw_capabilities(wil); } void wil_disable_irq(struct wil6210_priv *wil) @@ -67,10 +108,10 @@ static int wil_if_pcie_enable(struct wil6210_priv *wil) */ int msi_only = pdev->msi_enabled; bool _use_msi = use_msi; + bool wmi_only = test_bit(WMI_FW_CAPABILITY_WMI_ONLY, + wil->fw_capabilities); - wil_dbg_misc(wil, "%s()\n", __func__); - - pdev->msi_enabled = 0; + wil_dbg_misc(wil, "if_pcie_enable, wmi_only %d\n", wmi_only); pci_set_master(pdev); @@ -91,9 +132,11 @@ static int wil_if_pcie_enable(struct wil6210_priv *wil) if (rc) goto stop_master; - /* need reset here to obtain MAC */ + /* need reset here to obtain MAC or in case of WMI-only FW, full reset + * and fw loading takes place + */ mutex_lock(&wil->mutex); - rc = wil_reset(wil, false); + rc = wil_reset(wil, wmi_only); mutex_unlock(&wil->mutex); if (rc) goto release_irq; @@ -113,7 +156,7 @@ static int wil_if_pcie_disable(struct wil6210_priv *wil) { struct pci_dev *pdev = wil->pdev; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "if_pcie_disable\n"); pci_clear_master(pdev); /* disable and release IRQ */ @@ -125,21 +168,49 @@ static int wil_if_pcie_disable(struct wil6210_priv *wil) return 0; } +static int wil_platform_rop_ramdump(void *wil_handle, void *buf, uint32_t size) +{ + struct wil6210_priv *wil = wil_handle; + + if (!wil) + return -EINVAL; + + return wil_fw_copy_crash_dump(wil, buf, size); +} + +static int wil_platform_rop_fw_recovery(void *wil_handle) +{ + struct wil6210_priv *wil = wil_handle; + + if (!wil) + return -EINVAL; + + wil_fw_error_recovery(wil); + + return 0; +} + static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct wil6210_priv *wil; struct device *dev = &pdev->dev; int rc; + const struct wil_platform_rops rops = { + .ramdump = wil_platform_rop_ramdump, + .fw_recovery = wil_platform_rop_fw_recovery, + }; + u32 bar_size = pci_resource_len(pdev, 0); /* check HW */ dev_info(&pdev->dev, WIL_NAME - " device found [%04x:%04x] (rev %x)\n", - (int)pdev->vendor, (int)pdev->device, (int)pdev->revision); - - if (pci_resource_len(pdev, 0) != WIL6210_MEM_SIZE) { - dev_err(&pdev->dev, "Not " WIL_NAME "? " - "BAR0 size is %lu while expecting %lu\n", - (ulong)pci_resource_len(pdev, 0), WIL6210_MEM_SIZE); + " device found [%04x:%04x] (rev %x) bar size 0x%x\n", + (int)pdev->vendor, (int)pdev->device, (int)pdev->revision, + bar_size); + + if ((bar_size < WIL6210_MIN_MEM_SIZE) || + (bar_size > WIL6210_MAX_MEM_SIZE)) { + dev_err(&pdev->dev, "Unexpected BAR0 size 0x%x\n", + bar_size); return -ENODEV; } @@ -149,12 +220,14 @@ static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) dev_err(dev, "wil_if_alloc failed: %d\n", rc); return rc; } + wil->pdev = pdev; pci_set_drvdata(pdev, wil); + wil->bar_size = bar_size; /* rollback to if_free */ wil->platform_handle = - wil_platform_init(&pdev->dev, &wil->platform_ops); + wil_platform_init(&pdev->dev, &wil->platform_ops, &rops, wil); if (!wil->platform_handle) { rc = -ENODEV; wil_err(wil, "wil_platform_init failed\n"); @@ -162,8 +235,23 @@ static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) } /* rollback to err_plat */ - rc = pci_enable_device(pdev); + /* device supports 48bit addresses */ + rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48)); if (rc) { + dev_err(dev, "dma_set_mask_and_coherent(48) failed: %d\n", rc); + rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (rc) { + dev_err(dev, + "dma_set_mask_and_coherent(32) failed: %d\n", + rc); + goto err_plat; + } + } else { + wil->use_extended_dma_addr = 1; + } + + rc = pci_enable_device(pdev); + if (rc && pdev->msi_enabled == 0) { wil_err(wil, "pci_enable_device failed, retry with MSI only\n"); /* Work around for platforms that can't allocate IRQ: @@ -178,6 +266,7 @@ static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto err_plat; } /* rollback to err_disable_pdev */ + pci_set_power_state(pdev, PCI_D0); rc = pci_request_region(pdev, 0, WIL_NAME); if (rc) { @@ -212,8 +301,20 @@ static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto bus_disable; } - wil6210_debugfs_init(wil); +#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP + wil->pm_notify.notifier_call = wil6210_pm_notify; + rc = register_pm_notifier(&wil->pm_notify); + if (rc) + /* Do not fail the driver initialization, as suspend can + * be prevented in a later phase if needed + */ + wil_err(wil, "register_pm_notifier failed: %d\n", rc); +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ + wil6210_debugfs_init(wil); + wil6210_sysfs_init(wil); return 0; @@ -239,9 +340,19 @@ static void wil_pcie_remove(struct pci_dev *pdev) struct wil6210_priv *wil = pci_get_drvdata(pdev); void __iomem *csr = wil->csr; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "pcie_remove\n"); + +#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP + unregister_pm_notifier(&wil->pm_notify); +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ + wil6210_sysfs_remove(wil); wil6210_debugfs_remove(wil); + rtnl_lock(); + wil_p2p_wdev_free(wil); + rtnl_unlock(); wil_if_remove(wil); wil_if_pcie_disable(wil); pci_iounmap(pdev, csr); @@ -268,23 +379,23 @@ static int wil6210_suspend(struct device *dev, bool is_runtime) struct pci_dev *pdev = to_pci_dev(dev); struct wil6210_priv *wil = pci_get_drvdata(pdev); - wil_dbg_pm(wil, "%s(%s)\n", __func__, - is_runtime ? "runtime" : "system"); + wil_dbg_pm(wil, "suspend: %s\n", is_runtime ? "runtime" : "system"); rc = wil_can_suspend(wil, is_runtime); if (rc) goto out; rc = wil_suspend(wil, is_runtime); - if (rc) - goto out; - - /* TODO: how do I bring card in low power state? */ - - /* disable bus mastering */ - pci_clear_master(pdev); - /* PCI will call pci_save_state(pdev) and pci_prepare_to_sleep(pdev) */ + if (!rc) { + wil->suspend_stats.successful_suspends++; + /* If platform device supports keep_radio_on_during_sleep + * it will control PCIe master + */ + if (!wil->keep_radio_on_during_sleep) + /* disable bus mastering */ + pci_clear_master(pdev); + } out: return rc; } @@ -295,16 +406,63 @@ static int wil6210_resume(struct device *dev, bool is_runtime) struct pci_dev *pdev = to_pci_dev(dev); struct wil6210_priv *wil = pci_get_drvdata(pdev); - wil_dbg_pm(wil, "%s(%s)\n", __func__, - is_runtime ? "runtime" : "system"); - - /* allow master */ - pci_set_master(pdev); + wil_dbg_pm(wil, "resume: %s\n", is_runtime ? "runtime" : "system"); + /* If platform device supports keep_radio_on_during_sleep it will + * control PCIe master + */ + if (!wil->keep_radio_on_during_sleep) + /* allow master */ + pci_set_master(pdev); rc = wil_resume(wil, is_runtime); - if (rc) - pci_clear_master(pdev); + if (rc) { + wil_err(wil, "device failed to resume (%d)\n", rc); + wil->suspend_stats.failed_resumes++; + if (!wil->keep_radio_on_during_sleep) + pci_clear_master(pdev); + } else { + wil->suspend_stats.successful_resumes++; + } + + return rc; +} + +static int wil6210_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused) +{ + struct wil6210_priv *wil = container_of( + notify_block, struct wil6210_priv, pm_notify); + int rc = 0; + enum wil_platform_event evt; + + wil_dbg_pm(wil, "pm_notify: mode (%ld)\n", mode); + + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + case PM_RESTORE_PREPARE: + rc = wil_can_suspend(wil, false); + if (rc) + break; + evt = WIL_PLATFORM_EVT_PRE_SUSPEND; + if (wil->platform_ops.notify) + rc = wil->platform_ops.notify(wil->platform_handle, + evt); + break; + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + evt = WIL_PLATFORM_EVT_POST_SUSPEND; + if (wil->platform_ops.notify) + rc = wil->platform_ops.notify(wil->platform_handle, + evt); + break; + default: + wil_dbg_pm(wil, "unhandled notify mode %ld\n", mode); + break; + } + wil_dbg_pm(wil, "notification mode %ld: rc (%d)\n", mode, rc); return rc; } diff --git a/drivers/net/wireless/ath/wil6210/pm.c b/drivers/net/wireless/ath/wil6210/pm.c index 0b7ecbcac19c..8f5d1b447aaa 100644 --- a/drivers/net/wireless/ath/wil6210/pm.c +++ b/drivers/net/wireless/ath/wil6210/pm.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Qualcomm Atheros, Inc. + * Copyright (c) 2014,2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,19 +15,42 @@ */ #include "wil6210.h" +#include <linux/jiffies.h> int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime) { int rc = 0; struct wireless_dev *wdev = wil->wdev; + struct net_device *ndev = wil_to_ndev(wil); + + wil_dbg_pm(wil, "can_suspend: %s\n", is_runtime ? "runtime" : "system"); - wil_dbg_pm(wil, "%s(%s)\n", __func__, - is_runtime ? "runtime" : "system"); + if (!(ndev->flags & IFF_UP)) { + /* can always sleep when down */ + wil_dbg_pm(wil, "Interface is down\n"); + goto out; + } + if (test_bit(wil_status_resetting, wil->status)) { + wil_dbg_pm(wil, "Delay suspend when resetting\n"); + rc = -EBUSY; + goto out; + } + if (wil->recovery_state != fw_recovery_idle) { + wil_dbg_pm(wil, "Delay suspend during recovery\n"); + rc = -EBUSY; + goto out; + } + /* interface is running */ switch (wdev->iftype) { case NL80211_IFTYPE_MONITOR: case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: + if (test_bit(wil_status_fwconnecting, wil->status)) { + wil_dbg_pm(wil, "Delay suspend when connecting\n"); + rc = -EBUSY; + goto out; + } break; /* AP-like interface - can't suspend */ default: @@ -36,19 +59,176 @@ int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime) break; } - wil_dbg_pm(wil, "%s(%s) => %s (%d)\n", __func__, +out: + wil_dbg_pm(wil, "can_suspend: %s => %s (%d)\n", is_runtime ? "runtime" : "system", rc ? "No" : "Yes", rc); + if (rc) + wil->suspend_stats.rejected_by_host++; + return rc; } -int wil_suspend(struct wil6210_priv *wil, bool is_runtime) +static int wil_resume_keep_radio_on(struct wil6210_priv *wil) +{ + int rc = 0; + + /* wil_status_resuming will be cleared when getting + * WMI_TRAFFIC_RESUME_EVENTID + */ + set_bit(wil_status_resuming, wil->status); + clear_bit(wil_status_suspended, wil->status); + wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD); + wil_unmask_irq(wil); + + wil6210_bus_request(wil, wil->bus_request_kbps_pre_suspend); + + /* Send WMI resume request to the device */ + rc = wmi_resume(wil); + if (rc) { + wil_err(wil, "device failed to resume (%d)\n", rc); + if (no_fw_recovery) + goto out; + rc = wil_down(wil); + if (rc) { + wil_err(wil, "wil_down failed (%d)\n", rc); + goto out; + } + rc = wil_up(wil); + if (rc) { + wil_err(wil, "wil_up failed (%d)\n", rc); + goto out; + } + } + + /* Wake all queues */ + if (test_bit(wil_status_fwconnected, wil->status)) + wil_update_net_queues_bh(wil, NULL, false); + +out: + if (rc) + set_bit(wil_status_suspended, wil->status); + return rc; +} + +static int wil_suspend_keep_radio_on(struct wil6210_priv *wil) +{ + int rc = 0; + unsigned long start, data_comp_to; + + wil_dbg_pm(wil, "suspend keep radio on\n"); + + /* Prevent handling of new tx and wmi commands */ + set_bit(wil_status_suspending, wil->status); + wil_update_net_queues_bh(wil, NULL, true); + + if (!wil_is_tx_idle(wil)) { + wil_dbg_pm(wil, "Pending TX data, reject suspend\n"); + wil->suspend_stats.rejected_by_host++; + goto reject_suspend; + } + + if (!wil_is_rx_idle(wil)) { + wil_dbg_pm(wil, "Pending RX data, reject suspend\n"); + wil->suspend_stats.rejected_by_host++; + goto reject_suspend; + } + + if (!wil_is_wmi_idle(wil)) { + wil_dbg_pm(wil, "Pending WMI events, reject suspend\n"); + wil->suspend_stats.rejected_by_host++; + goto reject_suspend; + } + + /* Send WMI suspend request to the device */ + rc = wmi_suspend(wil); + if (rc) { + wil_dbg_pm(wil, "wmi_suspend failed, reject suspend (%d)\n", + rc); + goto reject_suspend; + } + + /* Wait for completion of the pending RX packets */ + start = jiffies; + data_comp_to = jiffies + msecs_to_jiffies(WIL_DATA_COMPLETION_TO_MS); + if (test_bit(wil_status_napi_en, wil->status)) { + while (!wil_is_rx_idle(wil)) { + if (time_after(jiffies, data_comp_to)) { + if (wil_is_rx_idle(wil)) + break; + wil_err(wil, + "TO waiting for idle RX, suspend failed\n"); + wil->suspend_stats.failed_suspends++; + goto resume_after_fail; + } + wil_dbg_ratelimited(wil, "rx vring is not empty -> NAPI\n"); + napi_synchronize(&wil->napi_rx); + msleep(20); + } + } + + /* In case of pending WMI events, reject the suspend + * and resume the device. + * This can happen if the device sent the WMI events before + * approving the suspend. + */ + if (!wil_is_wmi_idle(wil)) { + wil_err(wil, "suspend failed due to pending WMI events\n"); + wil->suspend_stats.failed_suspends++; + goto resume_after_fail; + } + + wil_mask_irq(wil); + + /* Disable device reset on PERST */ + wil_s(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD); + + if (wil->platform_ops.suspend) { + rc = wil->platform_ops.suspend(wil->platform_handle, true); + if (rc) { + wil_err(wil, "platform device failed to suspend (%d)\n", + rc); + wil->suspend_stats.failed_suspends++; + wil_c(wil, RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD); + wil_unmask_irq(wil); + goto resume_after_fail; + } + } + + /* Save the current bus request to return to the same in resume */ + wil->bus_request_kbps_pre_suspend = wil->bus_request_kbps; + wil6210_bus_request(wil, 0); + + set_bit(wil_status_suspended, wil->status); + clear_bit(wil_status_suspending, wil->status); + + return rc; + +resume_after_fail: + set_bit(wil_status_resuming, wil->status); + clear_bit(wil_status_suspending, wil->status); + rc = wmi_resume(wil); + /* if resume succeeded, reject the suspend */ + if (!rc) { + rc = -EBUSY; + if (test_bit(wil_status_fwconnected, wil->status)) + wil_update_net_queues_bh(wil, NULL, false); + } + return rc; + +reject_suspend: + clear_bit(wil_status_suspending, wil->status); + if (test_bit(wil_status_fwconnected, wil->status)) + wil_update_net_queues_bh(wil, NULL, false); + return -EBUSY; +} + +static int wil_suspend_radio_off(struct wil6210_priv *wil) { int rc = 0; struct net_device *ndev = wil_to_ndev(wil); - wil_dbg_pm(wil, "%s(%s)\n", __func__, - is_runtime ? "runtime" : "system"); + wil_dbg_pm(wil, "suspend radio off\n"); /* if netif up, hardware is alive, shut it down */ if (ndev->flags & IFF_UP) { @@ -59,12 +239,71 @@ int wil_suspend(struct wil6210_priv *wil, bool is_runtime) } } - if (wil->platform_ops.suspend) - rc = wil->platform_ops.suspend(wil->platform_handle); + /* Disable PCIe IRQ to prevent sporadic IRQs when PCIe is suspending */ + wil_dbg_pm(wil, "Disabling PCIe IRQ before suspending\n"); + wil_disable_irq(wil); + + if (wil->platform_ops.suspend) { + rc = wil->platform_ops.suspend(wil->platform_handle, false); + if (rc) { + wil_enable_irq(wil); + goto out; + } + } + + set_bit(wil_status_suspended, wil->status); out: - wil_dbg_pm(wil, "%s(%s) => %d\n", __func__, + wil_dbg_pm(wil, "suspend radio off: %d\n", rc); + + return rc; +} + +static int wil_resume_radio_off(struct wil6210_priv *wil) +{ + int rc = 0; + struct net_device *ndev = wil_to_ndev(wil); + + wil_dbg_pm(wil, "Enabling PCIe IRQ\n"); + wil_enable_irq(wil); + /* if netif up, bring hardware up + * During open(), IFF_UP set after actual device method + * invocation. This prevent recursive call to wil_up() + * wil_status_suspended will be cleared in wil_reset + */ + if (ndev->flags & IFF_UP) + rc = wil_up(wil); + else + clear_bit(wil_status_suspended, wil->status); + + return rc; +} + +int wil_suspend(struct wil6210_priv *wil, bool is_runtime) +{ + int rc = 0; + struct net_device *ndev = wil_to_ndev(wil); + bool keep_radio_on = ndev->flags & IFF_UP && + wil->keep_radio_on_during_sleep; + + wil_dbg_pm(wil, "suspend: %s\n", is_runtime ? "runtime" : "system"); + + if (test_bit(wil_status_suspended, wil->status)) { + wil_dbg_pm(wil, "trying to suspend while suspended\n"); + return 0; + } + + if (!keep_radio_on) + rc = wil_suspend_radio_off(wil); + else + rc = wil_suspend_keep_radio_on(wil); + + wil_dbg_pm(wil, "suspend: %s => %d\n", is_runtime ? "runtime" : "system", rc); + + if (!rc) + wil->suspend_stats.suspend_start_time = ktime_get(); + return rc; } @@ -72,27 +311,40 @@ int wil_resume(struct wil6210_priv *wil, bool is_runtime) { int rc = 0; struct net_device *ndev = wil_to_ndev(wil); + bool keep_radio_on = ndev->flags & IFF_UP && + wil->keep_radio_on_during_sleep; + unsigned long long suspend_time_usec = 0; - wil_dbg_pm(wil, "%s(%s)\n", __func__, - is_runtime ? "runtime" : "system"); + wil_dbg_pm(wil, "resume: %s\n", is_runtime ? "runtime" : "system"); if (wil->platform_ops.resume) { - rc = wil->platform_ops.resume(wil->platform_handle); + rc = wil->platform_ops.resume(wil->platform_handle, + keep_radio_on); if (rc) { wil_err(wil, "platform_ops.resume : %d\n", rc); goto out; } } - /* if netif up, bring hardware up - * During open(), IFF_UP set after actual device method - * invocation. This prevent recursive call to wil_up() - */ - if (ndev->flags & IFF_UP) - rc = wil_up(wil); + if (keep_radio_on) + rc = wil_resume_keep_radio_on(wil); + else + rc = wil_resume_radio_off(wil); + + if (rc) + goto out; + + suspend_time_usec = + ktime_to_us(ktime_sub(ktime_get(), + wil->suspend_stats.suspend_start_time)); + wil->suspend_stats.total_suspend_time += suspend_time_usec; + if (suspend_time_usec < wil->suspend_stats.min_suspend_time) + wil->suspend_stats.min_suspend_time = suspend_time_usec; + if (suspend_time_usec > wil->suspend_stats.max_suspend_time) + wil->suspend_stats.max_suspend_time = suspend_time_usec; out: - wil_dbg_pm(wil, "%s(%s) => %d\n", __func__, - is_runtime ? "runtime" : "system", rc); + wil_dbg_pm(wil, "resume: %s => %d, suspend time %lld usec\n", + is_runtime ? "runtime" : "system", rc, suspend_time_usec); return rc; } diff --git a/drivers/net/wireless/ath/wil6210/pmc.c b/drivers/net/wireless/ath/wil6210/pmc.c index 5ca0307a3274..2e301b6b32a9 100644 --- a/drivers/net/wireless/ath/wil6210/pmc.c +++ b/drivers/net/wireless/ath/wil6210/pmc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2015,2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -54,53 +54,90 @@ void wil_pmc_alloc(struct wil6210_priv *wil, struct pmc_ctx *pmc = &wil->pmc; struct device *dev = wil_to_dev(wil); struct wmi_pmc_cmd pmc_cmd = {0}; + int last_cmd_err = -ENOMEM; mutex_lock(&pmc->lock); if (wil_is_pmc_allocated(pmc)) { /* sanity check */ - wil_err(wil, "%s: ERROR pmc is already allocated\n", __func__); + wil_err(wil, "ERROR pmc is already allocated\n"); + goto no_release_err; + } + if ((num_descriptors <= 0) || (descriptor_size <= 0)) { + wil_err(wil, + "Invalid params num_descriptors(%d), descriptor_size(%d)\n", + num_descriptors, descriptor_size); + last_cmd_err = -EINVAL; + goto no_release_err; + } + + if (num_descriptors > (1 << WIL_RING_SIZE_ORDER_MAX)) { + wil_err(wil, + "num_descriptors(%d) exceeds max ring size %d\n", + num_descriptors, 1 << WIL_RING_SIZE_ORDER_MAX); + last_cmd_err = -EINVAL; + goto no_release_err; + } + + if (num_descriptors > INT_MAX / descriptor_size) { + wil_err(wil, + "Overflow in num_descriptors(%d)*descriptor_size(%d)\n", + num_descriptors, descriptor_size); + last_cmd_err = -EINVAL; goto no_release_err; } pmc->num_descriptors = num_descriptors; pmc->descriptor_size = descriptor_size; - wil_dbg_misc(wil, "%s: %d descriptors x %d bytes each\n", - __func__, num_descriptors, descriptor_size); + wil_dbg_misc(wil, "pmc_alloc: %d descriptors x %d bytes each\n", + num_descriptors, descriptor_size); /* allocate descriptors info list in pmc context*/ pmc->descriptors = kcalloc(num_descriptors, sizeof(struct desc_alloc_info), GFP_KERNEL); if (!pmc->descriptors) { - wil_err(wil, "%s: ERROR allocating pmc skb list\n", __func__); + wil_err(wil, "ERROR allocating pmc skb list\n"); goto no_release_err; } - wil_dbg_misc(wil, - "%s: allocated descriptors info list %p\n", - __func__, pmc->descriptors); + wil_dbg_misc(wil, "pmc_alloc: allocated descriptors info list %p\n", + pmc->descriptors); /* Allocate pring buffer and descriptors. * vring->va should be aligned on its size rounded up to power of 2 - * This is granted by the dma_alloc_coherent + * This is granted by the dma_alloc_coherent. + * + * HW has limitation that all vrings addresses must share the same + * upper 16 msb bits part of 48 bits address. To workaround that, + * if we are using 48 bit addresses switch to 32 bit allocation + * before allocating vring memory. + * + * There's no check for the return value of dma_set_mask_and_coherent, + * since we assume if we were able to set the mask during + * initialization in this system it will not fail if we set it again */ + if (wil->use_extended_dma_addr) + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + pmc->pring_va = dma_alloc_coherent(dev, sizeof(struct vring_tx_desc) * num_descriptors, &pmc->pring_pa, GFP_KERNEL); + if (wil->use_extended_dma_addr) + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48)); + wil_dbg_misc(wil, - "%s: allocated pring %p => %pad. %zd x %d = total %zd bytes\n", - __func__, + "pmc_alloc: allocated pring %p => %pad. %zd x %d = total %zd bytes\n", pmc->pring_va, &pmc->pring_pa, sizeof(struct vring_tx_desc), num_descriptors, sizeof(struct vring_tx_desc) * num_descriptors); if (!pmc->pring_va) { - wil_err(wil, "%s: ERROR allocating pmc pring\n", __func__); + wil_err(wil, "ERROR allocating pmc pring\n"); goto release_pmc_skb_list; } @@ -119,9 +156,7 @@ void wil_pmc_alloc(struct wil6210_priv *wil, GFP_KERNEL); if (unlikely(!pmc->descriptors[i].va)) { - wil_err(wil, - "%s: ERROR allocating pmc descriptor %d", - __func__, i); + wil_err(wil, "ERROR allocating pmc descriptor %d", i); goto release_pmc_skbs; } @@ -141,21 +176,21 @@ void wil_pmc_alloc(struct wil6210_priv *wil, *_d = *d; } - wil_dbg_misc(wil, "%s: allocated successfully\n", __func__); + wil_dbg_misc(wil, "pmc_alloc: allocated successfully\n"); pmc_cmd.op = WMI_PMC_ALLOCATE; pmc_cmd.ring_size = cpu_to_le16(pmc->num_descriptors); pmc_cmd.mem_base = cpu_to_le64(pmc->pring_pa); - wil_dbg_misc(wil, "%s: send WMI_PMC_CMD with ALLOCATE op\n", __func__); + wil_dbg_misc(wil, "pmc_alloc: send WMI_PMC_CMD with ALLOCATE op\n"); pmc->last_cmd_status = wmi_send(wil, WMI_PMC_CMDID, &pmc_cmd, sizeof(pmc_cmd)); if (pmc->last_cmd_status) { wil_err(wil, - "%s: WMI_PMC_CMD with ALLOCATE op failed with status %d", - __func__, pmc->last_cmd_status); + "WMI_PMC_CMD with ALLOCATE op failed with status %d", + pmc->last_cmd_status); goto release_pmc_skbs; } @@ -164,8 +199,8 @@ void wil_pmc_alloc(struct wil6210_priv *wil, return; release_pmc_skbs: - wil_err(wil, "%s: exit on error: Releasing skbs...\n", __func__); - for (i = 0; pmc->descriptors[i].va && i < num_descriptors; i++) { + wil_err(wil, "exit on error: Releasing skbs...\n"); + for (i = 0; i < num_descriptors && pmc->descriptors[i].va; i++) { dma_free_coherent(dev, descriptor_size, pmc->descriptors[i].va, @@ -173,7 +208,7 @@ release_pmc_skbs: pmc->descriptors[i].va = NULL; } - wil_err(wil, "%s: exit on error: Releasing pring...\n", __func__); + wil_err(wil, "exit on error: Releasing pring...\n"); dma_free_coherent(dev, sizeof(struct vring_tx_desc) * num_descriptors, @@ -183,13 +218,12 @@ release_pmc_skbs: pmc->pring_va = NULL; release_pmc_skb_list: - wil_err(wil, "%s: exit on error: Releasing descriptors info list...\n", - __func__); + wil_err(wil, "exit on error: Releasing descriptors info list...\n"); kfree(pmc->descriptors); pmc->descriptors = NULL; no_release_err: - pmc->last_cmd_status = -ENOMEM; + pmc->last_cmd_status = last_cmd_err; mutex_unlock(&pmc->lock); } @@ -208,24 +242,23 @@ void wil_pmc_free(struct wil6210_priv *wil, int send_pmc_cmd) pmc->last_cmd_status = 0; if (!wil_is_pmc_allocated(pmc)) { - wil_dbg_misc(wil, "%s: Error, can't free - not allocated\n", - __func__); + wil_dbg_misc(wil, + "pmc_free: Error, can't free - not allocated\n"); pmc->last_cmd_status = -EPERM; mutex_unlock(&pmc->lock); return; } if (send_pmc_cmd) { - wil_dbg_misc(wil, "%s: send WMI_PMC_CMD with RELEASE op\n", - __func__); + wil_dbg_misc(wil, "send WMI_PMC_CMD with RELEASE op\n"); pmc_cmd.op = WMI_PMC_RELEASE; pmc->last_cmd_status = wmi_send(wil, WMI_PMC_CMDID, &pmc_cmd, sizeof(pmc_cmd)); if (pmc->last_cmd_status) { wil_err(wil, - "%s WMI_PMC_CMD with RELEASE op failed, status %d", - __func__, pmc->last_cmd_status); + "WMI_PMC_CMD with RELEASE op failed, status %d", + pmc->last_cmd_status); /* There's nothing we can do with this error. * Normally, it should never occur. * Continue to freeing all memory allocated for pmc. @@ -237,8 +270,8 @@ void wil_pmc_free(struct wil6210_priv *wil, int send_pmc_cmd) size_t buf_size = sizeof(struct vring_tx_desc) * pmc->num_descriptors; - wil_dbg_misc(wil, "%s: free pring va %p\n", - __func__, pmc->pring_va); + wil_dbg_misc(wil, "pmc_free: free pring va %p\n", + pmc->pring_va); dma_free_coherent(dev, buf_size, pmc->pring_va, pmc->pring_pa); pmc->pring_va = NULL; @@ -250,18 +283,18 @@ void wil_pmc_free(struct wil6210_priv *wil, int send_pmc_cmd) int i; for (i = 0; - pmc->descriptors[i].va && i < pmc->num_descriptors; i++) { + i < pmc->num_descriptors && pmc->descriptors[i].va; i++) { dma_free_coherent(dev, pmc->descriptor_size, pmc->descriptors[i].va, pmc->descriptors[i].pa); pmc->descriptors[i].va = NULL; } - wil_dbg_misc(wil, "%s: free descriptor info %d/%d\n", - __func__, i, pmc->num_descriptors); + wil_dbg_misc(wil, "pmc_free: free descriptor info %d/%d\n", i, + pmc->num_descriptors); wil_dbg_misc(wil, - "%s: free pmc descriptors info list %p\n", - __func__, pmc->descriptors); + "pmc_free: free pmc descriptors info list %p\n", + pmc->descriptors); kfree(pmc->descriptors); pmc->descriptors = NULL; } else { @@ -277,7 +310,7 @@ void wil_pmc_free(struct wil6210_priv *wil, int send_pmc_cmd) */ int wil_pmc_last_cmd_status(struct wil6210_priv *wil) { - wil_dbg_misc(wil, "%s: status %d\n", __func__, + wil_dbg_misc(wil, "pmc_last_cmd_status: status %d\n", wil->pmc.last_cmd_status); return wil->pmc.last_cmd_status; @@ -295,20 +328,22 @@ ssize_t wil_pmc_read(struct file *filp, char __user *buf, size_t count, size_t retval = 0; unsigned long long idx; loff_t offset; - size_t pmc_size = pmc->descriptor_size * pmc->num_descriptors; + size_t pmc_size; mutex_lock(&pmc->lock); if (!wil_is_pmc_allocated(pmc)) { - wil_err(wil, "%s: error, pmc is not allocated!\n", __func__); + wil_err(wil, "error, pmc is not allocated!\n"); pmc->last_cmd_status = -EPERM; mutex_unlock(&pmc->lock); return -EPERM; } + pmc_size = pmc->descriptor_size * pmc->num_descriptors; + wil_dbg_misc(wil, - "%s: size %u, pos %lld\n", - __func__, (unsigned)count, *f_pos); + "pmc_read: size %u, pos %lld\n", + (u32)count, *f_pos); pmc->last_cmd_status = 0; @@ -317,15 +352,16 @@ ssize_t wil_pmc_read(struct file *filp, char __user *buf, size_t count, offset = *f_pos - (idx * pmc->descriptor_size); if (*f_pos >= pmc_size) { - wil_dbg_misc(wil, "%s: reached end of pmc buf: %lld >= %u\n", - __func__, *f_pos, (unsigned)pmc_size); + wil_dbg_misc(wil, + "pmc_read: reached end of pmc buf: %lld >= %u\n", + *f_pos, (u32)pmc_size); pmc->last_cmd_status = -ERANGE; goto out; } wil_dbg_misc(wil, - "%s: read from pos %lld (descriptor %llu, offset %llu) %zu bytes\n", - __func__, *f_pos, idx, offset, count); + "pmc_read: read from pos %lld (descriptor %llu, offset %llu) %zu bytes\n", + *f_pos, idx, offset, count); /* if no errors, return the copied byte count */ retval = simple_read_from_buffer(buf, @@ -345,7 +381,18 @@ loff_t wil_pmc_llseek(struct file *filp, loff_t off, int whence) loff_t newpos; struct wil6210_priv *wil = filp->private_data; struct pmc_ctx *pmc = &wil->pmc; - size_t pmc_size = pmc->descriptor_size * pmc->num_descriptors; + size_t pmc_size; + + mutex_lock(&pmc->lock); + + if (!wil_is_pmc_allocated(pmc)) { + wil_err(wil, "error, pmc is not allocated!\n"); + pmc->last_cmd_status = -EPERM; + mutex_unlock(&pmc->lock); + return -EPERM; + } + + pmc_size = pmc->descriptor_size * pmc->num_descriptors; switch (whence) { case 0: /* SEEK_SET */ @@ -361,15 +408,21 @@ loff_t wil_pmc_llseek(struct file *filp, loff_t off, int whence) break; default: /* can't happen */ - return -EINVAL; + newpos = -EINVAL; + goto out; } - if (newpos < 0) - return -EINVAL; + if (newpos < 0) { + newpos = -EINVAL; + goto out; + } if (newpos > pmc_size) newpos = pmc_size; filp->f_pos = newpos; +out: + mutex_unlock(&pmc->lock); + return newpos; } diff --git a/drivers/net/wireless/ath/wil6210/rx_reorder.c b/drivers/net/wireless/ath/wil6210/rx_reorder.c index e3d1be82f314..a43cffcf1bbf 100644 --- a/drivers/net/wireless/ath/wil6210/rx_reorder.c +++ b/drivers/net/wireless/ath/wil6210/rx_reorder.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2014-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -261,9 +261,19 @@ struct wil_tid_ampdu_rx *wil_tid_ampdu_rx_alloc(struct wil6210_priv *wil, void wil_tid_ampdu_rx_free(struct wil6210_priv *wil, struct wil_tid_ampdu_rx *r) { + int i; + if (!r) return; - wil_release_reorder_frames(wil, r, r->head_seq_num + r->buf_size); + + /* Do not pass remaining frames to the network stack - it may be + * not expecting to get any more Rx. Rx from here may lead to + * kernel OOPS since some per-socket accounting info was already + * released. + */ + for (i = 0; i < r->buf_size; i++) + kfree_skb(r->reorder_buf[i]); + kfree(r->reorder_buf); kfree(r->reorder_time); kfree(r); @@ -281,35 +291,15 @@ static u16 wil_agg_size(struct wil6210_priv *wil, u16 req_agg_wsize) return min(max_agg_size, req_agg_wsize); } -/* Block Ack - Rx side (recipient */ +/* Block Ack - Rx side (recipient) */ int wil_addba_rx_request(struct wil6210_priv *wil, u8 cidxtid, u8 dialog_token, __le16 ba_param_set, __le16 ba_timeout, __le16 ba_seq_ctrl) -{ - struct wil_back_rx *req = kzalloc(sizeof(*req), GFP_KERNEL); - - if (!req) - return -ENOMEM; - - req->cidxtid = cidxtid; - req->dialog_token = dialog_token; - req->ba_param_set = le16_to_cpu(ba_param_set); - req->ba_timeout = le16_to_cpu(ba_timeout); - req->ba_seq_ctrl = le16_to_cpu(ba_seq_ctrl); - - mutex_lock(&wil->back_rx_mutex); - list_add_tail(&req->list, &wil->back_rx_pending); - mutex_unlock(&wil->back_rx_mutex); - - queue_work(wil->wq_service, &wil->back_rx_worker); - - return 0; -} - -static void wil_back_rx_handle(struct wil6210_priv *wil, - struct wil_back_rx *req) __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) { + u16 param_set = le16_to_cpu(ba_param_set); + u16 agg_timeout = le16_to_cpu(ba_timeout); + u16 seq_ctrl = le16_to_cpu(ba_seq_ctrl); struct wil_sta_info *sta; u8 cid, tid; u16 agg_wsize = 0; @@ -318,47 +308,59 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) * bits 2..5: TID * bits 6..15: buffer size */ - u16 req_agg_wsize = WIL_GET_BITS(req->ba_param_set, 6, 15); - bool agg_amsdu = !!(req->ba_param_set & BIT(0)); - int ba_policy = req->ba_param_set & BIT(1); - u16 agg_timeout = req->ba_timeout; + u16 req_agg_wsize = WIL_GET_BITS(param_set, 6, 15); + bool agg_amsdu = !!(param_set & BIT(0)); + int ba_policy = param_set & BIT(1); u16 status = WLAN_STATUS_SUCCESS; - u16 ssn = req->ba_seq_ctrl >> 4; + u16 ssn = seq_ctrl >> 4; struct wil_tid_ampdu_rx *r; - int rc; + int rc = 0; might_sleep(); - parse_cidxtid(req->cidxtid, &cid, &tid); + parse_cidxtid(cidxtid, &cid, &tid); /* sanity checks */ if (cid >= WIL6210_MAX_CID) { wil_err(wil, "BACK: invalid CID %d\n", cid); - return; + rc = -EINVAL; + goto out; } sta = &wil->sta[cid]; if (sta->status != wil_sta_connected) { wil_err(wil, "BACK: CID %d not connected\n", cid); - return; + rc = -EINVAL; + goto out; } wil_dbg_wmi(wil, "ADDBA request for CID %d %pM TID %d size %d timeout %d AMSDU%s policy %d token %d SSN 0x%03x\n", - cid, sta->addr, tid, req_agg_wsize, req->ba_timeout, - agg_amsdu ? "+" : "-", !!ba_policy, req->dialog_token, ssn); + cid, sta->addr, tid, req_agg_wsize, agg_timeout, + agg_amsdu ? "+" : "-", !!ba_policy, dialog_token, ssn); /* apply policies */ if (ba_policy) { wil_err(wil, "BACK requested unsupported ba_policy == 1\n"); status = WLAN_STATUS_INVALID_QOS_PARAM; } - if (status == WLAN_STATUS_SUCCESS) - agg_wsize = wil_agg_size(wil, req_agg_wsize); + if (status == WLAN_STATUS_SUCCESS) { + if (req_agg_wsize == 0) { + wil_dbg_misc(wil, "Suggest BACK wsize %d\n", + WIL_MAX_AGG_WSIZE); + agg_wsize = WIL_MAX_AGG_WSIZE; + } else { + agg_wsize = min_t(u16, + WIL_MAX_AGG_WSIZE, req_agg_wsize); + } + } - rc = wmi_addba_rx_resp(wil, cid, tid, req->dialog_token, status, + rc = wmi_addba_rx_resp(wil, cid, tid, dialog_token, status, agg_amsdu, agg_wsize, agg_timeout); - if (rc || (status != WLAN_STATUS_SUCCESS)) - return; + if (rc || (status != WLAN_STATUS_SUCCESS)) { + wil_err(wil, "do not apply ba, rc(%d), status(%d)\n", rc, + status); + goto out; + } /* apply */ r = wil_tid_ampdu_rx_alloc(wil, agg_wsize, ssn); @@ -366,143 +368,37 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) wil_tid_ampdu_rx_free(wil, sta->tid_rx[tid]); sta->tid_rx[tid] = r; spin_unlock_bh(&sta->tid_rx_lock); -} - -void wil_back_rx_flush(struct wil6210_priv *wil) -{ - struct wil_back_rx *evt, *t; - - wil_dbg_misc(wil, "%s()\n", __func__); - - mutex_lock(&wil->back_rx_mutex); - - list_for_each_entry_safe(evt, t, &wil->back_rx_pending, list) { - list_del(&evt->list); - kfree(evt); - } - - mutex_unlock(&wil->back_rx_mutex); -} - -/* Retrieve next ADDBA request from the pending list */ -static struct list_head *next_back_rx(struct wil6210_priv *wil) -{ - struct list_head *ret = NULL; - - mutex_lock(&wil->back_rx_mutex); - - if (!list_empty(&wil->back_rx_pending)) { - ret = wil->back_rx_pending.next; - list_del(ret); - } - - mutex_unlock(&wil->back_rx_mutex); - - return ret; -} - -void wil_back_rx_worker(struct work_struct *work) -{ - struct wil6210_priv *wil = container_of(work, struct wil6210_priv, - back_rx_worker); - struct wil_back_rx *evt; - struct list_head *lh; - - while ((lh = next_back_rx(wil)) != NULL) { - evt = list_entry(lh, struct wil_back_rx, list); - wil_back_rx_handle(wil, evt); - kfree(evt); - } +out: + return rc; } -/* BACK - Tx (originator) side */ -static void wil_back_tx_handle(struct wil6210_priv *wil, - struct wil_back_tx *req) +/* BACK - Tx side (originator) */ +int wil_addba_tx_request(struct wil6210_priv *wil, u8 ringid, u16 wsize) { - struct vring_tx_data *txdata = &wil->vring_tx_data[req->ringid]; - int rc; + u8 agg_wsize = wil_agg_size(wil, wsize); + u16 agg_timeout = 0; + struct vring_tx_data *txdata = &wil->vring_tx_data[ringid]; + int rc = 0; if (txdata->addba_in_progress) { wil_dbg_misc(wil, "ADDBA for vring[%d] already in progress\n", - req->ringid); - return; + ringid); + goto out; } if (txdata->agg_wsize) { wil_dbg_misc(wil, - "ADDBA for vring[%d] already established wsize %d\n", - req->ringid, txdata->agg_wsize); - return; + "ADDBA for vring[%d] already done for wsize %d\n", + ringid, txdata->agg_wsize); + goto out; } txdata->addba_in_progress = true; - rc = wmi_addba(wil, req->ringid, req->agg_wsize, req->agg_timeout); - if (rc) + rc = wmi_addba(wil, ringid, agg_wsize, agg_timeout); + if (rc) { + wil_err(wil, "wmi_addba failed, rc (%d)", rc); txdata->addba_in_progress = false; -} - -static struct list_head *next_back_tx(struct wil6210_priv *wil) -{ - struct list_head *ret = NULL; - - mutex_lock(&wil->back_tx_mutex); - - if (!list_empty(&wil->back_tx_pending)) { - ret = wil->back_tx_pending.next; - list_del(ret); } - mutex_unlock(&wil->back_tx_mutex); - - return ret; -} - -void wil_back_tx_worker(struct work_struct *work) -{ - struct wil6210_priv *wil = container_of(work, struct wil6210_priv, - back_tx_worker); - struct wil_back_tx *evt; - struct list_head *lh; - - while ((lh = next_back_tx(wil)) != NULL) { - evt = list_entry(lh, struct wil_back_tx, list); - - wil_back_tx_handle(wil, evt); - kfree(evt); - } -} - -void wil_back_tx_flush(struct wil6210_priv *wil) -{ - struct wil_back_tx *evt, *t; - - wil_dbg_misc(wil, "%s()\n", __func__); - - mutex_lock(&wil->back_tx_mutex); - - list_for_each_entry_safe(evt, t, &wil->back_tx_pending, list) { - list_del(&evt->list); - kfree(evt); - } - - mutex_unlock(&wil->back_tx_mutex); -} - -int wil_addba_tx_request(struct wil6210_priv *wil, u8 ringid, u16 wsize) -{ - struct wil_back_tx *req = kzalloc(sizeof(*req), GFP_KERNEL); - - if (!req) - return -ENOMEM; - - req->ringid = ringid; - req->agg_wsize = wil_agg_size(wil, wsize); - req->agg_timeout = 0; - - mutex_lock(&wil->back_tx_mutex); - list_add_tail(&req->list, &wil->back_tx_pending); - mutex_unlock(&wil->back_tx_mutex); - - queue_work(wil->wq_service, &wil->back_tx_worker); - - return 0; +out: + return rc; } diff --git a/drivers/net/wireless/ath/wil6210/sysfs.c b/drivers/net/wireless/ath/wil6210/sysfs.c new file mode 100644 index 000000000000..7c9a7900c5ed --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/sysfs.c @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/device.h> +#include <linux/sysfs.h> + +#include "wil6210.h" +#include "wmi.h" + +static ssize_t +wil_ftm_txrx_offset_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct wil6210_priv *wil = dev_get_drvdata(dev); + struct { + struct wmi_cmd_hdr wmi; + struct wmi_tof_get_tx_rx_offset_event evt; + } __packed reply; + int rc; + ssize_t len; + + if (!test_bit(WMI_FW_CAPABILITY_FTM, wil->fw_capabilities)) + return -EOPNOTSUPP; + + memset(&reply, 0, sizeof(reply)); + rc = wmi_call(wil, WMI_TOF_GET_TX_RX_OFFSET_CMDID, NULL, 0, + WMI_TOF_GET_TX_RX_OFFSET_EVENTID, + &reply, sizeof(reply), 100); + if (rc < 0) + return rc; + if (reply.evt.status) { + wil_err(wil, "get_tof_tx_rx_offset failed, error %d\n", + reply.evt.status); + return -EIO; + } + len = snprintf(buf, PAGE_SIZE, "%u %u\n", + le32_to_cpu(reply.evt.tx_offset), + le32_to_cpu(reply.evt.rx_offset)); + return len; +} + +static ssize_t +wil_ftm_txrx_offset_sysfs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wil6210_priv *wil = dev_get_drvdata(dev); + struct wmi_tof_set_tx_rx_offset_cmd cmd; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_tof_set_tx_rx_offset_event evt; + } __packed reply; + unsigned int tx_offset, rx_offset; + int rc; + + if (sscanf(buf, "%u %u", &tx_offset, &rx_offset) != 2) + return -EINVAL; + + if (!test_bit(WMI_FW_CAPABILITY_FTM, wil->fw_capabilities)) + return -EOPNOTSUPP; + + memset(&cmd, 0, sizeof(cmd)); + cmd.tx_offset = cpu_to_le32(tx_offset); + cmd.rx_offset = cpu_to_le32(rx_offset); + memset(&reply, 0, sizeof(reply)); + rc = wmi_call(wil, WMI_TOF_SET_TX_RX_OFFSET_CMDID, &cmd, sizeof(cmd), + WMI_TOF_SET_TX_RX_OFFSET_EVENTID, + &reply, sizeof(reply), 100); + if (rc < 0) + return rc; + if (reply.evt.status) { + wil_err(wil, "set_tof_tx_rx_offset failed, error %d\n", + reply.evt.status); + return -EIO; + } + return count; +} + +static DEVICE_ATTR(ftm_txrx_offset, 0644, + wil_ftm_txrx_offset_sysfs_show, + wil_ftm_txrx_offset_sysfs_store); + +static ssize_t +wil_tt_sysfs_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct wil6210_priv *wil = dev_get_drvdata(dev); + ssize_t len; + struct wmi_tt_data tt_data; + int i, rc; + + rc = wmi_get_tt_cfg(wil, &tt_data); + if (rc) + return rc; + + len = snprintf(buf, PAGE_SIZE, " high max critical\n"); + + len += snprintf(buf + len, PAGE_SIZE - len, "bb: "); + if (tt_data.bb_enabled) + for (i = 0; i < WMI_NUM_OF_TT_ZONES; ++i) + len += snprintf(buf + len, PAGE_SIZE - len, + "%03d-%03d ", + tt_data.bb_zones[i].temperature_high, + tt_data.bb_zones[i].temperature_low); + else + len += snprintf(buf + len, PAGE_SIZE - len, "* disabled *"); + len += snprintf(buf + len, PAGE_SIZE - len, "\nrf: "); + if (tt_data.rf_enabled) + for (i = 0; i < WMI_NUM_OF_TT_ZONES; ++i) + len += snprintf(buf + len, PAGE_SIZE - len, + "%03d-%03d ", + tt_data.rf_zones[i].temperature_high, + tt_data.rf_zones[i].temperature_low); + else + len += snprintf(buf + len, PAGE_SIZE - len, "* disabled *"); + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + + return len; +} + +static ssize_t +wil_tt_sysfs_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wil6210_priv *wil = dev_get_drvdata(dev); + int i, rc = -EINVAL; + char *token, *dupbuf, *tmp; + struct wmi_tt_data tt_data = { + .bb_enabled = 0, + .rf_enabled = 0, + }; + + tmp = kmemdup(buf, count + 1, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + tmp[count] = '\0'; + dupbuf = tmp; + + /* Format for writing is 12 unsigned bytes separated by spaces: + * <bb_z1_h> <bb_z1_l> <bb_z2_h> <bb_z2_l> <bb_z3_h> <bb_z3_l> \ + * <rf_z1_h> <rf_z1_l> <rf_z2_h> <rf_z2_l> <rf_z3_h> <rf_z3_l> + * To disable thermal throttling for bb or for rf, use 0 for all + * its six set points. + */ + + /* bb */ + for (i = 0; i < WMI_NUM_OF_TT_ZONES; ++i) { + token = strsep(&dupbuf, " "); + if (!token) + goto out; + if (kstrtou8(token, 0, &tt_data.bb_zones[i].temperature_high)) + goto out; + token = strsep(&dupbuf, " "); + if (!token) + goto out; + if (kstrtou8(token, 0, &tt_data.bb_zones[i].temperature_low)) + goto out; + + if (tt_data.bb_zones[i].temperature_high > 0 || + tt_data.bb_zones[i].temperature_low > 0) + tt_data.bb_enabled = 1; + } + /* rf */ + for (i = 0; i < WMI_NUM_OF_TT_ZONES; ++i) { + token = strsep(&dupbuf, " "); + if (!token) + goto out; + if (kstrtou8(token, 0, &tt_data.rf_zones[i].temperature_high)) + goto out; + token = strsep(&dupbuf, " "); + if (!token) + goto out; + if (kstrtou8(token, 0, &tt_data.rf_zones[i].temperature_low)) + goto out; + + if (tt_data.rf_zones[i].temperature_high > 0 || + tt_data.rf_zones[i].temperature_low > 0) + tt_data.rf_enabled = 1; + } + + rc = wmi_set_tt_cfg(wil, &tt_data); + if (rc) + goto out; + + rc = count; +out: + kfree(tmp); + return rc; +} + +static DEVICE_ATTR(thermal_throttling, 0644, + wil_tt_sysfs_show, wil_tt_sysfs_store); + +static ssize_t +wil_fst_link_loss_sysfs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wil6210_priv *wil = dev_get_drvdata(dev); + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(wil->sta); i++) + if (wil->sta[i].status == wil_sta_connected) + len += snprintf(buf + len, PAGE_SIZE - len, + "[%d] %pM %s\n", i, wil->sta[i].addr, + wil->sta[i].fst_link_loss ? + "On" : "Off"); + + return len; +} + +static ssize_t +wil_fst_link_loss_sysfs_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wil6210_priv *wil = dev_get_drvdata(dev); + u8 addr[ETH_ALEN]; + char *token, *dupbuf, *tmp; + int rc = -EINVAL; + bool fst_link_loss; + + tmp = kmemdup(buf, count + 1, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + tmp[count] = '\0'; + dupbuf = tmp; + + token = strsep(&dupbuf, " "); + if (!token) + goto out; + + /* mac address */ + if (sscanf(token, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &addr[0], &addr[1], &addr[2], + &addr[3], &addr[4], &addr[5]) != 6) + goto out; + + /* On/Off */ + if (strtobool(dupbuf, &fst_link_loss)) + goto out; + + wil_dbg_misc(wil, "set [%pM] with %d\n", addr, fst_link_loss); + + rc = wmi_link_maintain_cfg_write(wil, addr, fst_link_loss); + if (!rc) + rc = count; + +out: + kfree(tmp); + return rc; +} + +static DEVICE_ATTR(fst_link_loss, 0644, + wil_fst_link_loss_sysfs_show, + wil_fst_link_loss_sysfs_store); + +static ssize_t +wil_snr_thresh_sysfs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct wil6210_priv *wil = dev_get_drvdata(dev); + ssize_t len = 0; + + if (wil->snr_thresh.enabled) + len = snprintf(buf, PAGE_SIZE, "omni=%d, direct=%d\n", + wil->snr_thresh.omni, wil->snr_thresh.direct); + + return len; +} + +static ssize_t +wil_snr_thresh_sysfs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wil6210_priv *wil = dev_get_drvdata(dev); + int rc; + short omni, direct; + + /* to disable snr threshold, set both omni and direct to 0 */ + if (sscanf(buf, "%hd %hd", &omni, &direct) != 2) + return -EINVAL; + + rc = wmi_set_snr_thresh(wil, omni, direct); + if (!rc) + rc = count; + + return rc; +} + +static DEVICE_ATTR(snr_thresh, 0644, + wil_snr_thresh_sysfs_show, + wil_snr_thresh_sysfs_store); + +static struct attribute *wil6210_sysfs_entries[] = { + &dev_attr_ftm_txrx_offset.attr, + &dev_attr_thermal_throttling.attr, + &dev_attr_fst_link_loss.attr, + &dev_attr_snr_thresh.attr, + NULL +}; + +static struct attribute_group wil6210_attribute_group = { + .name = "wil6210", + .attrs = wil6210_sysfs_entries, +}; + +int wil6210_sysfs_init(struct wil6210_priv *wil) +{ + struct device *dev = wil_to_dev(wil); + int err; + + err = sysfs_create_group(&dev->kobj, &wil6210_attribute_group); + if (err) { + wil_err(wil, "failed to create sysfs group: %d\n", err); + return err; + } + + kobject_uevent(&dev->kobj, KOBJ_CHANGE); + + return 0; +} + +void wil6210_sysfs_remove(struct wil6210_priv *wil) +{ + struct device *dev = wil_to_dev(wil); + + sysfs_remove_group(&dev->kobj, &wil6210_attribute_group); + kobject_uevent(&dev->kobj, KOBJ_CHANGE); +} diff --git a/drivers/net/wireless/ath/wil6210/trace.h b/drivers/net/wireless/ath/wil6210/trace.h index e59239d22b94..c4db2a9d9f7f 100644 --- a/drivers/net/wireless/ath/wil6210/trace.h +++ b/drivers/net/wireless/ath/wil6210/trace.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Qualcomm Atheros, Inc. + * Copyright (c) 2013-2016 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -37,39 +37,40 @@ static inline void trace_ ## name(proto) {} #endif /* !CONFIG_WIL6210_TRACING || defined(__CHECKER__) */ DECLARE_EVENT_CLASS(wil6210_wmi, - TP_PROTO(struct wil6210_mbox_hdr_wmi *wmi, void *buf, u16 buf_len), + TP_PROTO(struct wmi_cmd_hdr *wmi, void *buf, u16 buf_len), TP_ARGS(wmi, buf, buf_len), TP_STRUCT__entry( __field(u8, mid) - __field(u16, id) - __field(u32, timestamp) + __field(u16, command_id) + __field(u32, fw_timestamp) __field(u16, buf_len) __dynamic_array(u8, buf, buf_len) ), TP_fast_assign( __entry->mid = wmi->mid; - __entry->id = le16_to_cpu(wmi->id); - __entry->timestamp = le32_to_cpu(wmi->timestamp); + __entry->command_id = le16_to_cpu(wmi->command_id); + __entry->fw_timestamp = le32_to_cpu(wmi->fw_timestamp); __entry->buf_len = buf_len; memcpy(__get_dynamic_array(buf), buf, buf_len); ), TP_printk( "MID %d id 0x%04x len %d timestamp %d", - __entry->mid, __entry->id, __entry->buf_len, __entry->timestamp + __entry->mid, __entry->command_id, __entry->buf_len, + __entry->fw_timestamp ) ); DEFINE_EVENT(wil6210_wmi, wil6210_wmi_cmd, - TP_PROTO(struct wil6210_mbox_hdr_wmi *wmi, void *buf, u16 buf_len), + TP_PROTO(struct wmi_cmd_hdr *wmi, void *buf, u16 buf_len), TP_ARGS(wmi, buf, buf_len) ); DEFINE_EVENT(wil6210_wmi, wil6210_wmi_event, - TP_PROTO(struct wil6210_mbox_hdr_wmi *wmi, void *buf, u16 buf_len), + TP_PROTO(struct wmi_cmd_hdr *wmi, void *buf, u16 buf_len), TP_ARGS(wmi, buf, buf_len) ); diff --git a/drivers/net/wireless/ath/wil6210/txrx.c b/drivers/net/wireless/ath/wil6210/txrx.c index 3bc9bc0efbac..8fe2239603d1 100644 --- a/drivers/net/wireless/ath/wil6210/txrx.c +++ b/drivers/net/wireless/ath/wil6210/txrx.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,14 +29,18 @@ #include "trace.h" static bool rtap_include_phy_info; -module_param(rtap_include_phy_info, bool, S_IRUGO); +module_param(rtap_include_phy_info, bool, 0444); MODULE_PARM_DESC(rtap_include_phy_info, " Include PHY info in the radiotap header, default - no"); bool rx_align_2; -module_param(rx_align_2, bool, S_IRUGO); +module_param(rx_align_2, bool, 0444); MODULE_PARM_DESC(rx_align_2, " align Rx buffers on 4*n+2, default - no"); +bool rx_large_buf; +module_param(rx_large_buf, bool, 0444); +MODULE_PARM_DESC(rx_large_buf, " allocate 8KB RX buffers, default - no"); + static inline uint wil_rx_snaplen(void) { return rx_align_2 ? 6 : 0; @@ -88,6 +92,63 @@ static inline int wil_vring_wmark_high(struct vring *vring) return vring->size/4; } +/* returns true if num avail descriptors is lower than wmark_low */ +static inline int wil_vring_avail_low(struct vring *vring) +{ + return wil_vring_avail_tx(vring) < wil_vring_wmark_low(vring); +} + +/* returns true if num avail descriptors is higher than wmark_high */ +static inline int wil_vring_avail_high(struct vring *vring) +{ + return wil_vring_avail_tx(vring) > wil_vring_wmark_high(vring); +} + +/* returns true when all tx vrings are empty */ +bool wil_is_tx_idle(struct wil6210_priv *wil) +{ + int i; + unsigned long data_comp_to; + + for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) { + struct vring *vring = &wil->vring_tx[i]; + int vring_index = vring - wil->vring_tx; + struct vring_tx_data *txdata = &wil->vring_tx_data[vring_index]; + + spin_lock(&txdata->lock); + + if (!vring->va || !txdata->enabled) { + spin_unlock(&txdata->lock); + continue; + } + + data_comp_to = jiffies + msecs_to_jiffies( + WIL_DATA_COMPLETION_TO_MS); + if (test_bit(wil_status_napi_en, wil->status)) { + while (!wil_vring_is_empty(vring)) { + if (time_after(jiffies, data_comp_to)) { + wil_dbg_pm(wil, + "TO waiting for idle tx\n"); + spin_unlock(&txdata->lock); + return false; + } + wil_dbg_ratelimited(wil, + "tx vring is not empty -> NAPI\n"); + spin_unlock(&txdata->lock); + napi_synchronize(&wil->napi_tx); + msleep(20); + spin_lock(&txdata->lock); + if (!vring->va || !txdata->enabled) + break; + } + } + + spin_unlock(&txdata->lock); + } + + return true; +} + /* wil_val_in_range - check if value in [min,max) */ static inline bool wil_val_in_range(int val, int min, int max) { @@ -100,7 +161,7 @@ static int wil_vring_alloc(struct wil6210_priv *wil, struct vring *vring) size_t sz = vring->size * sizeof(vring->va[0]); uint i; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "vring_alloc:\n"); BUILD_BUG_ON(sizeof(vring->va[0]) != 32); @@ -111,15 +172,32 @@ static int wil_vring_alloc(struct wil6210_priv *wil, struct vring *vring) vring->va = NULL; return -ENOMEM; } + /* vring->va should be aligned on its size rounded up to power of 2 - * This is granted by the dma_alloc_coherent + * This is granted by the dma_alloc_coherent. + * + * HW has limitation that all vrings addresses must share the same + * upper 16 msb bits part of 48 bits address. To workaround that, + * if we are using 48 bit addresses switch to 32 bit allocation + * before allocating vring memory. + * + * There's no check for the return value of dma_set_mask_and_coherent, + * since we assume if we were able to set the mask during + * initialization in this system it will not fail if we set it again */ + if (wil->use_extended_dma_addr) + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + vring->va = dma_alloc_coherent(dev, sz, &vring->pa, GFP_KERNEL); if (!vring->va) { kfree(vring->ctx); vring->ctx = NULL; return -ENOMEM; } + + if (wil->use_extended_dma_addr) + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48)); + /* initially, all descriptors are SW owned * For Tx and Rx, ownership bit is at the same location, thus * we can use any @@ -160,6 +238,7 @@ static void wil_vring_free(struct wil6210_priv *wil, struct vring *vring, struct device *dev = wil_to_dev(wil); size_t sz = vring->size * sizeof(vring->va[0]); + lockdep_assert_held(&wil->mutex); if (tx) { int vring_index = vring - wil->vring_tx; @@ -183,6 +262,13 @@ static void wil_vring_free(struct wil6210_priv *wil, struct vring *vring, &vring->va[vring->swtail].tx; ctx = &vring->ctx[vring->swtail]; + if (!ctx) { + wil_dbg_txrx(wil, + "ctx(%d) was already completed\n", + vring->swtail); + vring->swtail = wil_vring_next_tail(vring); + continue; + } *d = *_d; wil_txdesc_unmap(dev, d, ctx); if (ctx->skb) @@ -218,7 +304,7 @@ static int wil_vring_alloc_skb(struct wil6210_priv *wil, struct vring *vring, u32 i, int headroom) { struct device *dev = wil_to_dev(wil); - unsigned int sz = mtu_max + ETH_HLEN + wil_rx_snaplen(); + unsigned int sz = wil->rx_buf_len + ETH_HLEN + wil_rx_snaplen(); struct vring_rx_desc dd, *d = ⅆ volatile struct vring_rx_desc *_d = &vring->va[i].rx; dma_addr_t pa; @@ -365,6 +451,18 @@ static inline int wil_is_back_req(u8 fc) (IEEE80211_FTYPE_CTL | IEEE80211_STYPE_BACK_REQ); } +bool wil_is_rx_idle(struct wil6210_priv *wil) +{ + struct vring_rx_desc *_d; + struct vring *vring = &wil->vring_rx; + + _d = (struct vring_rx_desc *)&vring->va[vring->swhead].rx; + if (_d->dma.status & RX_DMA_STATUS_DU) + return false; + + return true; +} + /** * reap 1 frame from @swhead * @@ -382,7 +480,7 @@ static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil, struct sk_buff *skb; dma_addr_t pa; unsigned int snaplen = wil_rx_snaplen(); - unsigned int sz = mtu_max + ETH_HLEN + snaplen; + unsigned int sz = wil->rx_buf_len + ETH_HLEN + snaplen; u16 dmalen; u8 ftype; int cid; @@ -543,11 +641,71 @@ static int wil_rx_refill(struct wil6210_priv *wil, int count) break; } } + + /* make sure all writes to descriptors (shared memory) are done before + * committing them to HW + */ + wmb(); + wil_w(wil, v->hwtail, v->swtail); return rc; } +/** + * reverse_memcmp - Compare two areas of memory, in reverse order + * @cs: One area of memory + * @ct: Another area of memory + * @count: The size of the area. + * + * Cut'n'paste from original memcmp (see lib/string.c) + * with minimal modifications + */ +static int reverse_memcmp(const void *cs, const void *ct, size_t count) +{ + const unsigned char *su1, *su2; + int res = 0; + + for (su1 = cs + count - 1, su2 = ct + count - 1; count > 0; + --su1, --su2, count--) { + res = *su1 - *su2; + if (res) + break; + } + return res; +} + +static int wil_rx_crypto_check(struct wil6210_priv *wil, struct sk_buff *skb) +{ + struct vring_rx_desc *d = wil_skb_rxdesc(skb); + int cid = wil_rxdesc_cid(d); + int tid = wil_rxdesc_tid(d); + int key_id = wil_rxdesc_key_id(d); + int mc = wil_rxdesc_mcast(d); + struct wil_sta_info *s = &wil->sta[cid]; + struct wil_tid_crypto_rx *c = mc ? &s->group_crypto_rx : + &s->tid_crypto_rx[tid]; + struct wil_tid_crypto_rx_single *cc = &c->key_id[key_id]; + const u8 *pn = (u8 *)&d->mac.pn_15_0; + + if (!cc->key_set) { + wil_err_ratelimited(wil, + "Key missing. CID %d TID %d MCast %d KEY_ID %d\n", + cid, tid, mc, key_id); + return -EINVAL; + } + + if (reverse_memcmp(pn, cc->pn, IEEE80211_GCMP_PN_LEN) <= 0) { + wil_err_ratelimited(wil, + "Replay attack. CID %d TID %d MCast %d KEY_ID %d PN %6phN last %6phN\n", + cid, tid, mc, key_id, pn, cc->pn); + return -EINVAL; + } + memcpy(cc->pn, pn, IEEE80211_GCMP_PN_LEN); + + return 0; +} + /* * Pass Rx packet to the netif. Update statistics. * Called in softirq context (NAPI poll). @@ -560,6 +718,7 @@ void wil_netif_rx_any(struct sk_buff *skb, struct net_device *ndev) unsigned int len = skb->len; struct vring_rx_desc *d = wil_skb_rxdesc(skb); int cid = wil_rxdesc_cid(d); /* always 0..7, no need to check */ + int security = wil_rxdesc_security(d); struct ethhdr *eth = (void *)skb->data; /* here looking for DA, not A1, thus Rxdesc's 'mcast' indication * is not suitable, need to look at data @@ -585,6 +744,13 @@ void wil_netif_rx_any(struct sk_buff *skb, struct net_device *ndev) skb_orphan(skb); + if (security && (wil_rx_crypto_check(wil, skb) != 0)) { + rc = GRO_DROP; + dev_kfree_skb(skb); + stats->rx_replay++; + goto stats; + } + if (wdev->iftype == NL80211_IFTYPE_AP && !wil->ap_isolate) { if (mcast) { /* send multicast frames both to higher layers in @@ -626,6 +792,7 @@ void wil_netif_rx_any(struct sk_buff *skb, struct net_device *ndev) wil_dbg_txrx(wil, "Rx complete %d bytes => %s\n", len, gro_res_str[rc]); } +stats: /* statistics. rc set to GRO_NORMAL for AP bridging */ if (unlikely(rc == GRO_DROP)) { ndev->stats.rx_dropped++; @@ -656,7 +823,7 @@ void wil_rx_handle(struct wil6210_priv *wil, int *quota) wil_err(wil, "Rx IRQ while Rx not yet initialized\n"); return; } - wil_dbg_txrx(wil, "%s()\n", __func__); + wil_dbg_txrx(wil, "rx_handle\n"); while ((*quota > 0) && (NULL != (skb = wil_vring_reap_rx(wil, v)))) { (*quota)--; @@ -674,18 +841,34 @@ void wil_rx_handle(struct wil6210_priv *wil, int *quota) wil_rx_refill(wil, v->size); } +static void wil_rx_buf_len_init(struct wil6210_priv *wil) +{ + wil->rx_buf_len = rx_large_buf ? + WIL_MAX_ETH_MTU : TXRX_BUF_LEN_DEFAULT - WIL_MAX_MPDU_OVERHEAD; + if (mtu_max > wil->rx_buf_len) { + /* do not allow RX buffers to be smaller than mtu_max, for + * backward compatibility (mtu_max parameter was also used + * to support receiving large packets) + */ + wil_info(wil, "Override RX buffer to mtu_max(%d)\n", mtu_max); + wil->rx_buf_len = mtu_max; + } +} + int wil_rx_init(struct wil6210_priv *wil, u16 size) { struct vring *vring = &wil->vring_rx; int rc; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "rx_init\n"); if (vring->va) { wil_err(wil, "Rx ring already allocated\n"); return -EINVAL; } + wil_rx_buf_len_init(wil); + vring->size = size; rc = wil_vring_alloc(wil, vring); if (rc) @@ -710,12 +893,27 @@ void wil_rx_fini(struct wil6210_priv *wil) { struct vring *vring = &wil->vring_rx; - wil_dbg_misc(wil, "%s()\n", __func__); + wil_dbg_misc(wil, "rx_fini\n"); if (vring->va) wil_vring_free(wil, vring, 0); } +static inline void wil_tx_data_init(struct vring_tx_data *txdata) +{ + spin_lock_bh(&txdata->lock); + txdata->dot1x_open = 0; + txdata->enabled = 0; + txdata->idle = 0; + txdata->last_idle = 0; + txdata->begin = 0; + txdata->agg_wsize = 0; + txdata->agg_timeout = 0; + txdata->agg_amsdu = 0; + txdata->addba_in_progress = false; + spin_unlock_bh(&txdata->lock); +} + int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, int cid, int tid) { @@ -741,14 +939,15 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, }, }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_vring_cfg_done_event cmd; } __packed reply; struct vring *vring = &wil->vring_tx[id]; struct vring_tx_data *txdata = &wil->vring_tx_data[id]; - wil_dbg_misc(wil, "%s() max_mpdu_size %d\n", __func__, + wil_dbg_misc(wil, "vring_init_tx: max_mpdu_size %d\n", cmd.vring_cfg.tx_sw_ring.max_mpdu_size); + lockdep_assert_held(&wil->mutex); if (vring->va) { wil_err(wil, "Tx ring [%d] already allocated\n", id); @@ -756,8 +955,7 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, goto out; } - memset(txdata, 0, sizeof(*txdata)); - spin_lock_init(&txdata->lock); + wil_tx_data_init(txdata); vring->size = size; rc = wil_vring_alloc(wil, vring); if (rc) @@ -781,17 +979,25 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, rc = -EINVAL; goto out_free; } - vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr); + spin_lock_bh(&txdata->lock); + vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr); txdata->enabled = 1; + spin_unlock_bh(&txdata->lock); + if (txdata->dot1x_open && (agg_wsize >= 0)) wil_addba_tx_request(wil, id, agg_wsize); return 0; out_free: + spin_lock_bh(&txdata->lock); txdata->dot1x_open = false; txdata->enabled = 0; + spin_unlock_bh(&txdata->lock); wil_vring_free(wil, vring, 1); + wil->vring2cid_tid[id][0] = WIL6210_MAX_CID; + wil->vring2cid_tid[id][1] = 0; + out: return rc; @@ -813,14 +1019,15 @@ int wil_vring_init_bcast(struct wil6210_priv *wil, int id, int size) }, }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_vring_cfg_done_event cmd; } __packed reply; struct vring *vring = &wil->vring_tx[id]; struct vring_tx_data *txdata = &wil->vring_tx_data[id]; - wil_dbg_misc(wil, "%s() max_mpdu_size %d\n", __func__, + wil_dbg_misc(wil, "vring_init_bcast: max_mpdu_size %d\n", cmd.vring_cfg.tx_sw_ring.max_mpdu_size); + lockdep_assert_held(&wil->mutex); if (vring->va) { wil_err(wil, "Tx ring [%d] already allocated\n", id); @@ -828,8 +1035,7 @@ int wil_vring_init_bcast(struct wil6210_priv *wil, int id, int size) goto out; } - memset(txdata, 0, sizeof(*txdata)); - spin_lock_init(&txdata->lock); + wil_tx_data_init(txdata); vring->size = size; rc = wil_vring_alloc(wil, vring); if (rc) @@ -853,14 +1059,18 @@ int wil_vring_init_bcast(struct wil6210_priv *wil, int id, int size) rc = -EINVAL; goto out_free; } - vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr); + spin_lock_bh(&txdata->lock); + vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr); txdata->enabled = 1; + spin_unlock_bh(&txdata->lock); return 0; out_free: + spin_lock_bh(&txdata->lock); txdata->enabled = 0; txdata->dot1x_open = false; + spin_unlock_bh(&txdata->lock); wil_vring_free(wil, vring, 1); out: @@ -872,23 +1082,29 @@ void wil_vring_fini_tx(struct wil6210_priv *wil, int id) struct vring *vring = &wil->vring_tx[id]; struct vring_tx_data *txdata = &wil->vring_tx_data[id]; - WARN_ON(!mutex_is_locked(&wil->mutex)); + lockdep_assert_held(&wil->mutex); if (!vring->va) return; - wil_dbg_misc(wil, "%s() id=%d\n", __func__, id); + wil_dbg_misc(wil, "vring_fini_tx: id=%d\n", id); spin_lock_bh(&txdata->lock); txdata->dot1x_open = false; txdata->enabled = 0; /* no Tx can be in progress or start anew */ spin_unlock_bh(&txdata->lock); + /* napi_synchronize waits for completion of the current NAPI but will + * not prevent the next NAPI run. + * Add a memory barrier to guarantee that txdata->enabled is zeroed + * before napi_synchronize so that the next scheduled NAPI will not + * handle this vring + */ + wmb(); /* make sure NAPI won't touch this vring */ if (test_bit(wil_status_napi_en, wil->status)) napi_synchronize(&wil->napi_tx); wil_vring_free(wil, vring, 1); - memset(txdata, 0, sizeof(*txdata)); } static struct vring *wil_find_tx_ucast(struct wil6210_priv *wil, @@ -908,13 +1124,16 @@ static struct vring *wil_find_tx_ucast(struct wil6210_priv *wil, continue; if (wil->vring2cid_tid[i][0] == cid) { struct vring *v = &wil->vring_tx[i]; + struct vring_tx_data *txdata = &wil->vring_tx_data[i]; - wil_dbg_txrx(wil, "%s(%pM) -> [%d]\n", - __func__, eth->h_dest, i); - if (v->va) { + wil_dbg_txrx(wil, "find_tx_ucast: (%pM) -> [%d]\n", + eth->h_dest, i); + if (v->va && txdata->enabled) { return v; } else { - wil_dbg_txrx(wil, "vring[%d] not valid\n", i); + wil_dbg_txrx(wil, + "find_tx_ucast: vring[%d] not valid\n", + i); return NULL; } } @@ -932,6 +1151,7 @@ static struct vring *wil_find_tx_vring_sta(struct wil6210_priv *wil, struct vring *v; int i; u8 cid; + struct vring_tx_data *txdata; /* In the STA mode, it is expected to have only 1 VRING * for the AP we connected to. @@ -939,7 +1159,8 @@ static struct vring *wil_find_tx_vring_sta(struct wil6210_priv *wil, */ for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) { v = &wil->vring_tx[i]; - if (!v->va) + txdata = &wil->vring_tx_data[i]; + if (!v->va || !txdata->enabled) continue; cid = wil->vring2cid_tid[i][0]; @@ -975,12 +1196,14 @@ static struct vring *wil_find_tx_bcast_1(struct wil6210_priv *wil, struct sk_buff *skb) { struct vring *v; + struct vring_tx_data *txdata; int i = wil->bcast_vring; if (i < 0) return NULL; v = &wil->vring_tx[i]; - if (!v->va) + txdata = &wil->vring_tx_data[i]; + if (!v->va || !txdata->enabled) return NULL; if (!wil->vring_tx_data[i].dot1x_open && (skb->protocol != cpu_to_be16(ETH_P_PAE))) @@ -1007,11 +1230,13 @@ static struct vring *wil_find_tx_bcast_2(struct wil6210_priv *wil, u8 cid; struct ethhdr *eth = (void *)skb->data; char *src = eth->h_source; + struct vring_tx_data *txdata; /* find 1-st vring eligible for data */ for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) { v = &wil->vring_tx[i]; - if (!v->va) + txdata = &wil->vring_tx_data[i]; + if (!v->va || !txdata->enabled) continue; cid = wil->vring2cid_tid[i][0]; @@ -1064,17 +1289,6 @@ found: return v; } -static struct vring *wil_find_tx_bcast(struct wil6210_priv *wil, - struct sk_buff *skb) -{ - struct wireless_dev *wdev = wil->wdev; - - if (wdev->iftype != NL80211_IFTYPE_AP) - return wil_find_tx_bcast_2(wil, skb); - - return wil_find_tx_bcast_1(wil, skb); -} - static int wil_tx_desc_map(struct vring_tx_desc *d, dma_addr_t pa, u32 len, int vring_index) { @@ -1244,8 +1458,8 @@ static int __wil_tx_vring_tso(struct wil6210_priv *wil, struct vring *vring, int gso_type; int rc = -EINVAL; - wil_dbg_txrx(wil, "%s() %d bytes to vring %d\n", - __func__, skb->len, vring_index); + wil_dbg_txrx(wil, "tx_vring_tso: %d bytes to vring %d\n", skb->len, + vring_index); if (unlikely(!txdata->enabled)) return -EINVAL; @@ -1452,13 +1666,20 @@ static int __wil_tx_vring_tso(struct wil6210_priv *wil, struct vring *vring, /* performance monitoring */ used = wil_vring_used_tx(vring); - if (wil_val_in_range(vring_idle_trsh, + if (wil_val_in_range(wil->vring_idle_trsh, used, used + descs_used)) { txdata->idle += get_cycles() - txdata->last_idle; wil_dbg_txrx(wil, "Ring[%2d] not idle %d -> %d\n", vring_index, used, used + descs_used); } + /* Make sure to advance the head only after descriptor update is done. + * This will prevent a race condition where the completion thread + * will see the DU bit set from previous run and will handle the + * skb before it was completed. + */ + wmb(); + /* advance swhead */ wil_vring_advance_head(vring, descs_used); wil_dbg_txrx(wil, "TSO: Tx swhead %d -> %d\n", swhead, vring->swhead); @@ -1475,7 +1696,7 @@ mem_error: while (descs_used > 0) { struct wil_ctx *ctx; - i = (swhead + descs_used) % vring->size; + i = (swhead + descs_used - 1) % vring->size; d = (struct vring_tx_desc *)&vring->va[i].tx; _desc = &vring->va[i].tx; *d = *_desc; @@ -1507,8 +1728,8 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, bool mcast = (vring_index == wil->bcast_vring); uint len = skb_headlen(skb); - wil_dbg_txrx(wil, "%s() %d bytes to vring %d\n", - __func__, skb->len, vring_index); + wil_dbg_txrx(wil, "tx_vring: %d bytes to vring %d\n", skb->len, + vring_index); if (unlikely(!txdata->enabled)) return -EINVAL; @@ -1592,13 +1813,20 @@ static int __wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, /* performance monitoring */ used = wil_vring_used_tx(vring); - if (wil_val_in_range(vring_idle_trsh, + if (wil_val_in_range(wil->vring_idle_trsh, used, used + nr_frags + 1)) { txdata->idle += get_cycles() - txdata->last_idle; wil_dbg_txrx(wil, "Ring[%2d] not idle %d -> %d\n", vring_index, used, used + nr_frags + 1); } + /* Make sure to advance the head only after descriptor update is done. + * This will prevent a race condition where the completion thread + * will see the DU bit set from previous run and will handle the + * skb before it was completed. + */ + wmb(); + /* advance swhead */ wil_vring_advance_head(vring, nr_frags + 1); wil_dbg_txrx(wil, "Tx[%2d] swhead %d -> %d\n", vring_index, swhead, @@ -1641,6 +1869,15 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, spin_lock(&txdata->lock); + if (test_bit(wil_status_suspending, wil->status) || + test_bit(wil_status_suspended, wil->status) || + test_bit(wil_status_resuming, wil->status)) { + wil_dbg_txrx(wil, + "suspend/resume in progress. drop packet\n"); + spin_unlock(&txdata->lock); + return -EINVAL; + } + rc = (skb_is_gso(skb) ? __wil_tx_vring_tso : __wil_tx_vring) (wil, vring, skb); @@ -1649,6 +1886,94 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, return rc; } +/** + * Check status of tx vrings and stop/wake net queues if needed + * + * This function does one of two checks: + * In case check_stop is true, will check if net queues need to be stopped. If + * the conditions for stopping are met, netif_tx_stop_all_queues() is called. + * In case check_stop is false, will check if net queues need to be waked. If + * the conditions for waking are met, netif_tx_wake_all_queues() is called. + * vring is the vring which is currently being modified by either adding + * descriptors (tx) into it or removing descriptors (tx complete) from it. Can + * be null when irrelevant (e.g. connect/disconnect events). + * + * The implementation is to stop net queues if modified vring has low + * descriptor availability. Wake if all vrings are not in low descriptor + * availability and modified vring has high descriptor availability. + */ +static inline void __wil_update_net_queues(struct wil6210_priv *wil, + struct vring *vring, + bool check_stop) +{ + int i; + + if (vring) + wil_dbg_txrx(wil, "vring %d, check_stop=%d, stopped=%d", + (int)(vring - wil->vring_tx), check_stop, + wil->net_queue_stopped); + else + wil_dbg_txrx(wil, "check_stop=%d, stopped=%d", + check_stop, wil->net_queue_stopped); + + if (check_stop == wil->net_queue_stopped) + /* net queues already in desired state */ + return; + + if (check_stop) { + if (!vring || unlikely(wil_vring_avail_low(vring))) { + /* not enough room in the vring */ + netif_tx_stop_all_queues(wil_to_ndev(wil)); + wil->net_queue_stopped = true; + wil_dbg_txrx(wil, "netif_tx_stop called\n"); + } + return; + } + + /* Do not wake the queues in suspend flow */ + if (test_bit(wil_status_suspending, wil->status) || + test_bit(wil_status_suspended, wil->status)) + return; + + /* check wake */ + for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) { + struct vring *cur_vring = &wil->vring_tx[i]; + struct vring_tx_data *txdata = &wil->vring_tx_data[i]; + + if (!cur_vring->va || !txdata->enabled || cur_vring == vring) + continue; + + if (wil_vring_avail_low(cur_vring)) { + wil_dbg_txrx(wil, "vring %d full, can't wake\n", + (int)(cur_vring - wil->vring_tx)); + return; + } + } + + if (!vring || wil_vring_avail_high(vring)) { + /* enough room in the vring */ + wil_dbg_txrx(wil, "calling netif_tx_wake\n"); + netif_tx_wake_all_queues(wil_to_ndev(wil)); + wil->net_queue_stopped = false; + } +} + +void wil_update_net_queues(struct wil6210_priv *wil, struct vring *vring, + bool check_stop) +{ + spin_lock(&wil->net_queue_lock); + __wil_update_net_queues(wil, vring, check_stop); + spin_unlock(&wil->net_queue_lock); +} + +void wil_update_net_queues_bh(struct wil6210_priv *wil, struct vring *vring, + bool check_stop) +{ + spin_lock_bh(&wil->net_queue_lock); + __wil_update_net_queues(wil, vring, check_stop); + spin_unlock_bh(&wil->net_queue_lock); +} + netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct wil6210_priv *wil = ndev_to_wil(ndev); @@ -1658,7 +1983,7 @@ netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) static bool pr_once_fw; int rc; - wil_dbg_txrx(wil, "%s()\n", __func__); + wil_dbg_txrx(wil, "start_xmit\n"); if (unlikely(!test_bit(wil_status_fwready, wil->status))) { if (!pr_once_fw) { wil_err(wil, "FW not ready\n"); @@ -1667,7 +1992,7 @@ netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) goto drop; } if (unlikely(!test_bit(wil_status_fwconnected, wil->status))) { - wil_err_ratelimited(wil, "FW not connected\n"); + wil_dbg_ratelimited(wil, "FW not connected, packet dropped\n"); goto drop; } if (unlikely(wil->wdev->iftype == NL80211_IFTYPE_MONITOR)) { @@ -1677,12 +2002,26 @@ netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) pr_once_fw = false; /* find vring */ - if (wil->wdev->iftype == NL80211_IFTYPE_STATION) { - /* in STA mode (ESS), all to same VRING */ + if (wil->wdev->iftype == NL80211_IFTYPE_STATION && !wil->pbss) { + /* in STA mode (ESS), all to same VRING (to AP) */ vring = wil_find_tx_vring_sta(wil, skb); - } else { /* direct communication, find matching VRING */ - vring = bcast ? wil_find_tx_bcast(wil, skb) : - wil_find_tx_ucast(wil, skb); + } else if (bcast) { + if (wil->pbss) + /* in pbss, no bcast VRING - duplicate skb in + * all stations VRINGs + */ + vring = wil_find_tx_bcast_2(wil, skb); + else if (wil->wdev->iftype == NL80211_IFTYPE_AP) + /* AP has a dedicated bcast VRING */ + vring = wil_find_tx_bcast_1(wil, skb); + else + /* unexpected combination, fallback to duplicating + * the skb in all stations VRINGs + */ + vring = wil_find_tx_bcast_2(wil, skb); + } else { + /* unicast, find specific VRING by dest. address */ + vring = wil_find_tx_ucast(wil, skb); } if (unlikely(!vring)) { wil_dbg_txrx(wil, "No Tx VRING found for %pM\n", eth->h_dest); @@ -1691,14 +2030,10 @@ netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) /* set up vring entry */ rc = wil_tx_vring(wil, vring, skb); - /* do we still have enough room in the vring? */ - if (unlikely(wil_vring_avail_tx(vring) < wil_vring_wmark_low(vring))) { - netif_tx_stop_all_queues(wil_to_ndev(wil)); - wil_dbg_txrx(wil, "netif_tx_stop : ring full\n"); - } - switch (rc) { case 0: + /* shall we stop net queues? */ + wil_update_net_queues_bh(wil, vring, true); /* statistics will be updated on the tx_complete */ dev_kfree_skb_any(skb); return NETDEV_TX_OK; @@ -1760,7 +2095,7 @@ int wil_tx_complete(struct wil6210_priv *wil, int ringid) return 0; } - wil_dbg_txrx(wil, "%s(%d)\n", __func__, ringid); + wil_dbg_txrx(wil, "tx_complete: (%d)\n", ringid); used_before_complete = wil_vring_used_tx(vring); @@ -1822,6 +2157,12 @@ int wil_tx_complete(struct wil6210_priv *wil, int ringid) wil_consume_skb(skb, d->dma.error == 0); } memset(ctx, 0, sizeof(*ctx)); + /* Make sure the ctx is zeroed before updating the tail + * to prevent a case where wil_tx_vring will see + * this descriptor as used and handle it before ctx zero + * is completed. + */ + wmb(); /* There is no need to touch HW descriptor: * - ststus bit TX_DMA_STATUS_DU is set by design, * so hardware will not try to process this desc., @@ -1834,17 +2175,16 @@ int wil_tx_complete(struct wil6210_priv *wil, int ringid) /* performance monitoring */ used_new = wil_vring_used_tx(vring); - if (wil_val_in_range(vring_idle_trsh, + if (wil_val_in_range(wil->vring_idle_trsh, used_new, used_before_complete)) { wil_dbg_txrx(wil, "Ring[%2d] idle %d -> %d\n", ringid, used_before_complete, used_new); txdata->last_idle = get_cycles(); } - if (wil_vring_avail_tx(vring) > wil_vring_wmark_high(vring)) { - wil_dbg_txrx(wil, "netif_tx_wake : ring not full\n"); - netif_tx_wake_all_queues(wil_to_ndev(wil)); - } + /* shall we wake net queues? */ + if (done) + wil_update_net_queues(wil, vring, false); return done; } diff --git a/drivers/net/wireless/ath/wil6210/txrx.h b/drivers/net/wireless/ath/wil6210/txrx.h index ee7c7b4b9a17..fcdffaa8251b 100644 --- a/drivers/net/wireless/ath/wil6210/txrx.h +++ b/drivers/net/wireless/ath/wil6210/txrx.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2016 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -480,6 +480,16 @@ static inline int wil_rxdesc_ext_subtype(struct vring_rx_desc *d) return WIL_GET_BITS(d->mac.d0, 28, 31); } +static inline int wil_rxdesc_key_id(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d1, 4, 5); +} + +static inline int wil_rxdesc_security(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d1, 7, 7); +} + static inline int wil_rxdesc_ds_bits(struct vring_rx_desc *d) { return WIL_GET_BITS(d->mac.d1, 8, 9); diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h index ade5f3b8274b..0df216689d20 100644 --- a/drivers/net/wireless/ath/wil6210/wil6210.h +++ b/drivers/net/wireless/ath/wil6210/wil6210.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,25 +17,36 @@ #ifndef __WIL6210_H__ #define __WIL6210_H__ +#include <linux/etherdevice.h> #include <linux/netdevice.h> #include <linux/wireless.h> #include <net/cfg80211.h> #include <linux/timex.h> #include <linux/types.h> +#include "wmi.h" #include "wil_platform.h" +#include "ftm.h" extern bool no_fw_recovery; extern unsigned int mtu_max; extern unsigned short rx_ring_overflow_thrsh; extern int agg_wsize; -extern u32 vring_idle_trsh; extern bool rx_align_2; +extern bool rx_large_buf; extern bool debug_fw; +extern bool disable_ap_sme; #define WIL_NAME "wil6210" -#define WIL_FW_NAME "wil6210.fw" /* code */ -#define WIL_FW2_NAME "wil6210.brd" /* board & radio parameters */ +#define WIL_FW_NAME_DEFAULT "wil6210.fw" +#define WIL_FW_NAME_FTM_DEFAULT "wil6210_ftm.fw" + +#define WIL_FW_NAME_SPARROW_PLUS "wil6210_sparrow_plus.fw" +#define WIL_FW_NAME_FTM_SPARROW_PLUS "wil6210_sparrow_plus_ftm.fw" + +#define WIL_BOARD_FILE_NAME "wil6210.brd" /* board & radio parameters */ + +#define WIL_DEFAULT_BUS_REQUEST_KBPS 128000 /* ~1Gbps */ #define WIL_MAX_BUS_REQUEST_KBPS 800000 /* ~6.1Gbps */ /** @@ -47,11 +58,12 @@ static inline u32 WIL_GET_BITS(u32 x, int b0, int b1) return (x >> b0) & ((1 << (b1 - b0 + 1)) - 1); } -#define WIL6210_MEM_SIZE (2*1024*1024UL) +#define WIL6210_MIN_MEM_SIZE (2 * 1024 * 1024UL) +#define WIL6210_MAX_MEM_SIZE (4 * 1024 * 1024UL) #define WIL_TX_Q_LEN_DEFAULT (4000) #define WIL_RX_RING_SIZE_ORDER_DEFAULT (10) -#define WIL_TX_RING_SIZE_ORDER_DEFAULT (10) +#define WIL_TX_RING_SIZE_ORDER_DEFAULT (12) #define WIL_BCAST_RING_SIZE_ORDER_DEFAULT (7) #define WIL_BCAST_MCS0_LIMIT (1024) /* limit for MCS0 frame size */ /* limit ring size in range [32..32k] */ @@ -71,6 +83,20 @@ static inline u32 WIL_GET_BITS(u32 x, int b0, int b1) */ #define WIL_MAX_MPDU_OVERHEAD (62) +struct wil_suspend_stats { + unsigned long successful_suspends; + unsigned long failed_suspends; + unsigned long successful_resumes; + unsigned long failed_resumes; + unsigned long rejected_by_device; + unsigned long rejected_by_host; + unsigned long long total_suspend_time; + unsigned long long min_suspend_time; + unsigned long long max_suspend_time; + ktime_t collection_start; + ktime_t suspend_start_time; +}; + /* Calculate MAC buffer size for the firmware. It includes all overhead, * as it will go over the air, and need to be 8 byte aligned */ @@ -92,9 +118,13 @@ static inline u32 wil_mtu2macbuf(u32 mtu) #define WIL6210_FW_RECOVERY_RETRIES (5) /* try to recover this many times */ #define WIL6210_FW_RECOVERY_TO msecs_to_jiffies(5000) #define WIL6210_SCAN_TO msecs_to_jiffies(10000) +#define WIL6210_DISCONNECT_TO_MS (2000) #define WIL6210_RX_HIGH_TRSH_INIT (0) #define WIL6210_RX_HIGH_TRSH_DEFAULT \ (1 << (WIL_RX_RING_SIZE_ORDER_DEFAULT - 3)) +#define WIL_MAX_DMG_AID 254 /* for DMG only 1-254 allowed (see + * 802.11REVmc/D5.0, section 9.4.1.8) + */ /* Hardware definitions begin */ /* @@ -130,6 +160,11 @@ struct RGF_ICR { /* registers - FW addresses */ #define RGF_USER_USAGE_1 (0x880004) #define RGF_USER_USAGE_6 (0x880018) + #define BIT_USER_OOB_MODE BIT(31) + #define BIT_USER_OOB_R2_MODE BIT(30) +#define RGF_USER_USAGE_8 (0x880020) + #define BIT_USER_PREVENT_DEEP_SLEEP BIT(0) + #define BIT_USER_SUPPORT_T_POWER_ON_0 BIT(1) #define RGF_USER_HW_MACHINE_STATE (0x8801dc) #define HW_MACHINE_BOOT_DONE (0x3fffffd) #define RGF_USER_USER_CPU_0 (0x8801e0) @@ -139,6 +174,10 @@ struct RGF_ICR { #define RGF_USER_USER_SCRATCH_PAD (0x8802bc) #define RGF_USER_BL (0x880A3C) /* Boot Loader */ #define RGF_USER_FW_REV_ID (0x880a8c) /* chip revision */ +#define RGF_USER_FW_CALIB_RESULT (0x880a90) /* b0-7:result + * b8-15:signature + */ + #define CALIB_RESULT_SIGNATURE (0x11) #define RGF_USER_CLKS_CTL_0 (0x880abc) #define BIT_USER_CLKS_CAR_AHB_SW_SEL BIT(1) /* ref clk/PLL */ #define BIT_USER_CLKS_RST_PWGD BIT(11) /* reset on "power good" */ @@ -156,6 +195,9 @@ struct RGF_ICR { #define RGF_USER_SPARROW_M_4 (0x880c50) /* Sparrow */ #define BIT_SPARROW_M_4_SEL_SLEEP_OR_REF BIT(2) +#define RGF_DMA_PEDI_DIF (0x8812C4) /* struct RGF_PEDI_DIF */ + #define BIT_DMA_WR_CMD_ATTR_NO_SNOOP BIT(4) + #define BIT_DMA_WR_CMD_ATTR_RELAXED_ORDERING BIT(5) #define RGF_DMA_EP_TX_ICR (0x881bb4) /* struct RGF_ICR */ #define BIT_DMA_EP_TX_ICR_TX_DONE BIT(0) #define BIT_DMA_EP_TX_ICR_TX_DONE_N(n) BIT(n+1) /* n = [0..23] */ @@ -165,6 +207,7 @@ struct RGF_ICR { #define RGF_DMA_EP_MISC_ICR (0x881bec) /* struct RGF_ICR */ #define BIT_DMA_EP_MISC_ICR_RX_HTRSH BIT(0) #define BIT_DMA_EP_MISC_ICR_TX_NO_ACT BIT(1) + #define BIT_DMA_EP_MISC_ICR_HALP BIT(27) #define BIT_DMA_EP_MISC_ICR_FW_INT(n) BIT(28+n) /* n = [0..3] */ /* Legacy interrupt moderation control (before Sparrow v2)*/ @@ -232,6 +275,7 @@ struct RGF_ICR { #define BIT_DMA_PSEUDO_CAUSE_MISC BIT(2) #define RGF_HP_CTRL (0x88265c) +#define RGF_PAL_UNIT_ICR (0x88266c) /* struct RGF_ICR */ #define RGF_PCIE_LOS_COUNTER_CTL (0x882dc4) /* MAC timer, usec, for packet lifetime */ @@ -244,7 +288,12 @@ struct RGF_ICR { #define BIT_CAF_OSC_DIG_XTAL_STABLE BIT(0) #define RGF_USER_JTAG_DEV_ID (0x880b34) /* device ID */ - #define JTAG_DEV_ID_SPARROW_B0 (0x2632072f) + #define JTAG_DEV_ID_SPARROW (0x2632072f) + +#define RGF_USER_REVISION_ID (0x88afe4) +#define RGF_USER_REVISION_ID_MASK (3) + #define REVISION_ID_SPARROW_B0 (0x0) + #define REVISION_ID_SPARROW_D0 (0x3) /* crash codes for FW/Ucode stored here */ #define RGF_FW_ASSERT_CODE (0x91f020) @@ -252,7 +301,8 @@ struct RGF_ICR { enum { HW_VER_UNKNOWN, - HW_VER_SPARROW_B0, /* JTAG_DEV_ID_SPARROW_B0 */ + HW_VER_SPARROW_B0, /* REVISION_ID_SPARROW_B0 */ + HW_VER_SPARROW_D0, /* REVISION_ID_SPARROW_D0 */ }; /* popular locations */ @@ -265,16 +315,19 @@ enum { #define ISR_MISC_MBOX_EVT BIT_DMA_EP_MISC_ICR_FW_INT(1) #define ISR_MISC_FW_ERROR BIT_DMA_EP_MISC_ICR_FW_INT(3) +#define WIL_DATA_COMPLETION_TO_MS 200 + /* Hardware definitions end */ struct fw_map { u32 from; /* linker address - from, inclusive */ u32 to; /* linker address - to, exclusive */ u32 host; /* PCI/Host address - BAR0 + 0x880000 */ const char *name; /* for debugfs */ + bool fw; /* true if FW mapping, false if UCODE mapping */ }; /* array size should be in sync with actual definition in the wmi.c */ -extern const struct fw_map fw_mapping[8]; +extern const struct fw_map fw_mapping[10]; /** * mk_cidxtid - construct @cidxtid field @@ -333,29 +386,11 @@ struct wil6210_mbox_hdr { /* max. value for wil6210_mbox_hdr.len */ #define MAX_MBOXITEM_SIZE (240) -/** - * struct wil6210_mbox_hdr_wmi - WMI header - * - * @mid: MAC ID - * 00 - default, created by FW - * 01..0f - WiFi ports, driver to create - * 10..fe - debug - * ff - broadcast - * @id: command/event ID - * @timestamp: FW fills for events, free-running msec timer - */ -struct wil6210_mbox_hdr_wmi { - u8 mid; - u8 reserved; - __le16 id; - __le32 timestamp; -} __packed; - struct pending_wmi_event { struct list_head list; struct { struct wil6210_mbox_hdr hdr; - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; u8 data[0]; } __packed event; }; @@ -410,6 +445,9 @@ enum { /* for wil6210_priv.status */ wil_status_irqen, /* FIXME: interrupts enabled - for debug */ wil_status_napi_en, /* NAPI enabled protected by wil->mutex */ wil_status_resetting, /* reset in progress */ + wil_status_suspending, /* suspend in progress */ + wil_status_suspended, /* suspend completed, device is suspended */ + wil_status_resuming, /* resume in progress */ wil_status_last /* keep last */ }; @@ -454,6 +492,33 @@ struct wil_tid_ampdu_rx { bool first_time; /* is it 1-st time this buffer used? */ }; +/** + * struct wil_tid_crypto_rx_single - TID crypto information (Rx). + * + * @pn: GCMP PN for the session + * @key_set: valid key present + */ +struct wil_tid_crypto_rx_single { + u8 pn[IEEE80211_GCMP_PN_LEN]; + bool key_set; +}; + +struct wil_tid_crypto_rx { + struct wil_tid_crypto_rx_single key_id[4]; +}; + +struct wil_p2p_info { + struct ieee80211_channel listen_chan; + u8 discovery_started; + u8 p2p_dev_started; + u64 cookie; + struct wireless_dev *pending_listen_wdev; + unsigned int listen_duration; + struct timer_list discovery_timer; /* listen/search duration */ + struct work_struct discovery_expired_work; /* listen/search expire */ + struct work_struct delayed_listen_work; /* listen after scan done */ +}; + enum wil_sta_status { wil_sta_unused = 0, wil_sta_conn_pending = 1, @@ -473,6 +538,7 @@ struct wil_net_stats { unsigned long rx_non_data_frame; unsigned long rx_short_frame; unsigned long rx_large_frame; + unsigned long rx_replay; u16 last_mcs_rx; u64 rx_per_mcs[WIL_MCS_MAX + 1]; }; @@ -494,6 +560,10 @@ struct wil_sta_info { spinlock_t tid_rx_lock; /* guarding tid_rx array */ unsigned long tid_rx_timer_expired[BITS_TO_LONGS(WIL_STA_TID_NUM)]; unsigned long tid_rx_stop_requested[BITS_TO_LONGS(WIL_STA_TID_NUM)]; + struct wil_tid_crypto_rx tid_crypto_rx[WIL_STA_TID_NUM]; + struct wil_tid_crypto_rx group_crypto_rx; + u8 aid; /* 1-254; 0 if unknown/not reported */ + bool fst_link_loss; }; enum { @@ -506,24 +576,6 @@ enum { hw_capability_last }; -struct wil_back_rx { - struct list_head list; - /* request params, converted to CPU byte order - what we asked for */ - u8 cidxtid; - u8 dialog_token; - u16 ba_param_set; - u16 ba_timeout; - u16 ba_seq_ctrl; -}; - -struct wil_back_tx { - struct list_head list; - /* request params, converted to CPU byte order - what we asked for */ - u8 ringid; - u8 agg_wsize; - u16 agg_timeout; -}; - struct wil_probe_client_req { struct list_head list; u64 cookie; @@ -541,15 +593,54 @@ struct pmc_ctx { int descriptor_size; }; +struct wil_halp { + struct mutex lock; /* protect halp ref_cnt */ + unsigned int ref_cnt; + struct completion comp; +}; + +struct wil_blob_wrapper { + struct wil6210_priv *wil; + struct debugfs_blob_wrapper blob; +}; + +#define WIL_LED_MAX_ID (2) +#define WIL_LED_INVALID_ID (0xF) +#define WIL_LED_BLINK_ON_SLOW_MS (300) +#define WIL_LED_BLINK_OFF_SLOW_MS (300) +#define WIL_LED_BLINK_ON_MED_MS (200) +#define WIL_LED_BLINK_OFF_MED_MS (200) +#define WIL_LED_BLINK_ON_FAST_MS (100) +#define WIL_LED_BLINK_OFF_FAST_MS (100) +enum { + WIL_LED_TIME_SLOW = 0, + WIL_LED_TIME_MED, + WIL_LED_TIME_FAST, + WIL_LED_TIME_LAST, +}; + +struct blink_on_off_time { + u32 on_ms; + u32 off_ms; +}; + +extern struct blink_on_off_time led_blink_time[WIL_LED_TIME_LAST]; +extern u8 led_id; +extern u8 led_polarity; + struct wil6210_priv { struct pci_dev *pdev; + u32 bar_size; struct wireless_dev *wdev; void __iomem *csr; DECLARE_BITMAP(status, wil_status_last); - u32 fw_version; + u8 fw_version[ETHTOOL_FWVERS_LEN]; u32 hw_version; + u8 chip_revision; const char *hw_name; + const char *wil_fw_name; DECLARE_BITMAP(hw_capabilities, hw_capability_last); + DECLARE_BITMAP(fw_capabilities, WMI_FW_CAPABILITY_MAX); u8 n_mids; /* number of additional MIDs as reported by FW */ u32 recovery_count; /* num of FW recovery attempts in a short time */ u32 recovery_state; /* FW recovery state machine */ @@ -562,6 +653,8 @@ struct wil6210_priv { u16 channel; /* relevant in AP mode */ int sinfo_gen; u32 ap_isolate; /* no intra-BSS communication */ + struct cfg80211_bss *bss; /* connected bss, relevant in STA mode */ + int locally_generated_disc; /* relevant in STA mode */ /* interrupt moderation */ u32 tx_max_burst_duration; u32 tx_interframe_timeout; @@ -581,12 +674,10 @@ struct wil6210_priv { struct workqueue_struct *wmi_wq; /* for deferred calls */ struct work_struct wmi_event_worker; struct workqueue_struct *wq_service; - struct work_struct connect_worker; struct work_struct disconnect_worker; struct work_struct fw_error_worker; /* for FW error recovery */ struct timer_list connect_timer; struct timer_list scan_timer; /* detect scan timeout */ - int pending_connect_cid; struct list_head pending_wmi_ev; /* * protect pending_wmi_ev @@ -594,26 +685,24 @@ struct wil6210_priv { * - consumed in thread by wmi_event_worker */ spinlock_t wmi_ev_lock; + spinlock_t net_queue_lock; /* guarding stop/wake netif queue */ + int net_queue_stopped; /* netif_tx_stop_all_queues invoked */ struct napi_struct napi_rx; struct napi_struct napi_tx; - /* BACK */ - struct list_head back_rx_pending; - struct mutex back_rx_mutex; /* protect @back_rx_pending */ - struct work_struct back_rx_worker; - struct list_head back_tx_pending; - struct mutex back_tx_mutex; /* protect @back_tx_pending */ - struct work_struct back_tx_worker; /* keep alive */ struct list_head probe_client_pending; struct mutex probe_client_mutex; /* protect @probe_client_pending */ struct work_struct probe_client_worker; /* DMA related */ struct vring vring_rx; + unsigned int rx_buf_len; struct vring vring_tx[WIL6210_MAX_TX_RINGS]; struct vring_tx_data vring_tx_data[WIL6210_MAX_TX_RINGS]; u8 vring2cid_tid[WIL6210_MAX_TX_RINGS][2]; /* [0] - CID, [1] - TID */ struct wil_sta_info sta[WIL6210_MAX_CID]; int bcast_vring; + u32 vring_idle_trsh; /* HW fetches up to 16 descriptors at once */ + bool use_extended_dma_addr; /* indicates whether we are using 48 bits */ /* scan */ struct cfg80211_scan_request *scan_request; @@ -622,12 +711,53 @@ struct wil6210_priv { atomic_t isr_count_rx, isr_count_tx; /* debugfs */ struct dentry *debug; - struct debugfs_blob_wrapper blobs[ARRAY_SIZE(fw_mapping)]; + struct wil_blob_wrapper blobs[ARRAY_SIZE(fw_mapping)]; + u8 discovery_mode; + u8 abft_len; + u8 wakeup_trigger; + struct wil_suspend_stats suspend_stats; void *platform_handle; struct wil_platform_ops platform_ops; + bool keep_radio_on_during_sleep; struct pmc_ctx pmc; + + bool pbss; + + struct wil_p2p_info p2p; + + /* P2P_DEVICE vif */ + struct wireless_dev *p2p_wdev; + struct mutex p2p_wdev_mutex; /* protect @p2p_wdev and @scan_request */ + struct wireless_dev *radio_wdev; + + /* High Access Latency Policy voting */ + struct wil_halp halp; + + enum wmi_ps_profile_type ps_profile; + + struct wil_ftm_priv ftm; + bool tt_data_set; + struct wmi_tt_data tt_data; + struct { + bool enabled; + short omni; + short direct; + } snr_thresh; + + int fw_calib_result; + +#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP + struct notifier_block pm_notify; +#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ + + bool suspend_resp_rcvd; + bool suspend_resp_comp; + u32 bus_request_kbps; + u32 bus_request_kbps_pre_suspend; }; #define wil_to_wiphy(i) (i->wdev->wiphy) @@ -641,11 +771,13 @@ struct wil6210_priv { __printf(2, 3) void wil_dbg_trace(struct wil6210_priv *wil, const char *fmt, ...); __printf(2, 3) -void wil_err(struct wil6210_priv *wil, const char *fmt, ...); +void __wil_err(struct wil6210_priv *wil, const char *fmt, ...); +__printf(2, 3) +void __wil_err_ratelimited(struct wil6210_priv *wil, const char *fmt, ...); __printf(2, 3) -void wil_err_ratelimited(struct wil6210_priv *wil, const char *fmt, ...); +void __wil_info(struct wil6210_priv *wil, const char *fmt, ...); __printf(2, 3) -void wil_info(struct wil6210_priv *wil, const char *fmt, ...); +void wil_dbg_ratelimited(const struct wil6210_priv *wil, const char *fmt, ...); #define wil_dbg(wil, fmt, arg...) do { \ netdev_dbg(wil_to_ndev(wil), fmt, ##arg); \ wil_dbg_trace(wil, fmt, ##arg); \ @@ -656,6 +788,10 @@ void wil_info(struct wil6210_priv *wil, const char *fmt, ...); #define wil_dbg_wmi(wil, fmt, arg...) wil_dbg(wil, "DBG[ WMI]" fmt, ##arg) #define wil_dbg_misc(wil, fmt, arg...) wil_dbg(wil, "DBG[MISC]" fmt, ##arg) #define wil_dbg_pm(wil, fmt, arg...) wil_dbg(wil, "DBG[ PM ]" fmt, ##arg) +#define wil_err(wil, fmt, arg...) __wil_err(wil, "%s: " fmt, __func__, ##arg) +#define wil_info(wil, fmt, arg...) __wil_info(wil, "%s: " fmt, __func__, ##arg) +#define wil_err_ratelimited(wil, fmt, arg...) \ + __wil_err_ratelimited(wil, "%s: " fmt, __func__, ##arg) /* target operations */ /* register read */ @@ -695,6 +831,12 @@ static inline void wil_c(struct wil6210_priv *wil, u32 reg, u32 val) print_hex_dump_debug("DBG[ WMI]" prefix_str,\ prefix_type, rowsize, \ groupsize, buf, len, ascii) + +#define wil_hex_dump_misc(prefix_str, prefix_type, rowsize, \ + groupsize, buf, len, ascii) \ + print_hex_dump_debug("DBG[MISC]" prefix_str,\ + prefix_type, rowsize, \ + groupsize, buf, len, ascii) #else /* defined(CONFIG_DYNAMIC_DEBUG) */ static inline void wil_hex_dump_txrx(const char *prefix_str, int prefix_type, int rowsize, @@ -707,6 +849,12 @@ void wil_hex_dump_wmi(const char *prefix_str, int prefix_type, int rowsize, int groupsize, const void *buf, size_t len, bool ascii) { } + +static inline +void wil_hex_dump_misc(const char *prefix_str, int prefix_type, int rowsize, + int groupsize, const void *buf, size_t len, bool ascii) +{ +} #endif /* defined(CONFIG_DYNAMIC_DEBUG) */ void wil_memcpy_fromio_32(void *dst, const volatile void __iomem *src, @@ -720,17 +868,22 @@ int wil_if_add(struct wil6210_priv *wil); void wil_if_remove(struct wil6210_priv *wil); int wil_priv_init(struct wil6210_priv *wil); void wil_priv_deinit(struct wil6210_priv *wil); +int wil_ps_update(struct wil6210_priv *wil, + enum wmi_ps_profile_type ps_profile); int wil_reset(struct wil6210_priv *wil, bool no_fw); void wil_fw_error_recovery(struct wil6210_priv *wil); void wil_set_recovery_state(struct wil6210_priv *wil, int state); +bool wil_is_recovery_blocked(struct wil6210_priv *wil); int wil_up(struct wil6210_priv *wil); int __wil_up(struct wil6210_priv *wil); int wil_down(struct wil6210_priv *wil); int __wil_down(struct wil6210_priv *wil); +void wil_refresh_fw_capabilities(struct wil6210_priv *wil); void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r); int wil_find_cid(struct wil6210_priv *wil, const u8 *mac); void wil_set_ethtoolops(struct net_device *ndev); +void __iomem *wmi_buffer_block(struct wil6210_priv *wil, __le32 ptr, u32 size); void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr); void __iomem *wmi_addr(struct wil6210_priv *wil, u32 ptr); int wmi_read_hdr(struct wil6210_priv *wil, __le32 ptr, @@ -753,23 +906,26 @@ int wmi_add_cipher_key(struct wil6210_priv *wil, u8 key_index, int wmi_echo(struct wil6210_priv *wil); int wmi_set_ie(struct wil6210_priv *wil, u8 type, u16 ie_len, const void *ie); int wmi_rx_chain_add(struct wil6210_priv *wil, struct vring *vring); -int wmi_p2p_cfg(struct wil6210_priv *wil, int channel); int wmi_rxon(struct wil6210_priv *wil, bool on); int wmi_get_temperature(struct wil6210_priv *wil, u32 *t_m, u32 *t_r); -int wmi_disconnect_sta(struct wil6210_priv *wil, const u8 *mac, u16 reason); +int wmi_disconnect_sta(struct wil6210_priv *wil, const u8 *mac, + u16 reason, bool full_disconnect, bool del_sta); int wmi_addba(struct wil6210_priv *wil, u8 ringid, u8 size, u16 timeout); int wmi_delba_tx(struct wil6210_priv *wil, u8 ringid, u16 reason); int wmi_delba_rx(struct wil6210_priv *wil, u8 cidxtid, u16 reason); int wmi_addba_rx_resp(struct wil6210_priv *wil, u8 cid, u8 tid, u8 token, u16 status, bool amsdu, u16 agg_wsize, u16 timeout); +int wmi_ps_dev_profile_cfg(struct wil6210_priv *wil, + enum wmi_ps_profile_type ps_profile); +int wmi_set_mgmt_retry(struct wil6210_priv *wil, u8 retry_short); +int wmi_get_mgmt_retry(struct wil6210_priv *wil, u8 *retry_short); +int wmi_new_sta(struct wil6210_priv *wil, const u8 *mac, u8 aid); +int wmi_set_tt_cfg(struct wil6210_priv *wil, struct wmi_tt_data *tt_data); +int wmi_get_tt_cfg(struct wil6210_priv *wil, struct wmi_tt_data *tt_data); int wil_addba_rx_request(struct wil6210_priv *wil, u8 cidxtid, u8 dialog_token, __le16 ba_param_set, __le16 ba_timeout, __le16 ba_seq_ctrl); -void wil_back_rx_worker(struct work_struct *work); -void wil_back_rx_flush(struct wil6210_priv *wil); int wil_addba_tx_request(struct wil6210_priv *wil, u8 ringid, u16 wsize); -void wil_back_tx_worker(struct work_struct *work); -void wil_back_tx_flush(struct wil6210_priv *wil); void wil6210_clear_irq(struct wil6210_priv *wil); int wil6210_init_irq(struct wil6210_priv *wil, int irq, bool use_msi); @@ -779,22 +935,59 @@ void wil_unmask_irq(struct wil6210_priv *wil); void wil_configure_interrupt_moderation(struct wil6210_priv *wil); void wil_disable_irq(struct wil6210_priv *wil); void wil_enable_irq(struct wil6210_priv *wil); +void wil6210_mask_halp(struct wil6210_priv *wil); + +/* P2P */ +bool wil_p2p_is_social_scan(struct cfg80211_scan_request *request); +void wil_p2p_discovery_timer_fn(ulong x); +int wil_p2p_search(struct wil6210_priv *wil, + struct cfg80211_scan_request *request); +int wil_p2p_listen(struct wil6210_priv *wil, struct wireless_dev *wdev, + unsigned int duration, struct ieee80211_channel *chan, + u64 *cookie); +u8 wil_p2p_stop_discovery(struct wil6210_priv *wil); +int wil_p2p_cancel_listen(struct wil6210_priv *wil, u64 cookie); +void wil_p2p_listen_expired(struct work_struct *work); +void wil_p2p_search_expired(struct work_struct *work); +void wil_p2p_stop_radio_operations(struct wil6210_priv *wil); +void wil_p2p_delayed_listen_work(struct work_struct *work); + +/* WMI for P2P */ +int wmi_p2p_cfg(struct wil6210_priv *wil, int channel, int bi); +int wmi_start_listen(struct wil6210_priv *wil); +int wmi_start_search(struct wil6210_priv *wil); +int wmi_stop_discovery(struct wil6210_priv *wil); + int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, u64 *cookie); +#if defined(CONFIG_WIL6210_DEBUGFS) int wil6210_debugfs_init(struct wil6210_priv *wil); void wil6210_debugfs_remove(struct wil6210_priv *wil); +#else +static inline int wil6210_debugfs_init(struct wil6210_priv *wil) { return 0; } +static inline void wil6210_debugfs_remove(struct wil6210_priv *wil) {} +#endif +int wil6210_sysfs_init(struct wil6210_priv *wil); +void wil6210_sysfs_remove(struct wil6210_priv *wil); int wil_cid_fill_sinfo(struct wil6210_priv *wil, int cid, struct station_info *sinfo); struct wireless_dev *wil_cfg80211_init(struct device *dev); void wil_wdev_free(struct wil6210_priv *wil); +void wil_p2p_wdev_free(struct wil6210_priv *wil); int wmi_set_mac_address(struct wil6210_priv *wil, void *addr); int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, - u8 chan, u8 hidden_ssid); + u8 chan, u8 hidden_ssid, u8 is_go); int wmi_pcp_stop(struct wil6210_priv *wil); +int wmi_led_cfg(struct wil6210_priv *wil, bool enable); +int wmi_aoa_meas(struct wil6210_priv *wil, const void *mac_addr, u8 chan, + u8 type); +int wmi_abort_scan(struct wil6210_priv *wil); +void wil_abort_scan(struct wil6210_priv *wil, bool sync); +void wil6210_bus_request(struct wil6210_priv *wil, u32 kbps); void wil6210_disconnect(struct wil6210_priv *wil, const u8 *bssid, u16 reason_code, bool from_event); void wil_probe_client_flush(struct wil6210_priv *wil); @@ -807,10 +1000,15 @@ void wil_rx_fini(struct wil6210_priv *wil); int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, int cid, int tid); void wil_vring_fini_tx(struct wil6210_priv *wil, int id); +int wil_tx_init(struct wil6210_priv *wil, int cid); int wil_vring_init_bcast(struct wil6210_priv *wil, int id, int size); int wil_bcast_init(struct wil6210_priv *wil); void wil_bcast_fini(struct wil6210_priv *wil); +void wil_update_net_queues(struct wil6210_priv *wil, struct vring *vring, + bool should_stop); +void wil_update_net_queues_bh(struct wil6210_priv *wil, struct vring *vring, + bool check_stop); netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev); int wil_tx_complete(struct wil6210_priv *wil, int ringid); void wil6210_unmask_irq_tx(struct wil6210_priv *wil); @@ -822,12 +1020,44 @@ void wil6210_unmask_irq_rx(struct wil6210_priv *wil); int wil_iftype_nl2wmi(enum nl80211_iftype type); int wil_ioctl(struct wil6210_priv *wil, void __user *data, int cmd); -int wil_request_firmware(struct wil6210_priv *wil, const char *name); +int wil_request_firmware(struct wil6210_priv *wil, const char *name, + bool load); +bool wil_fw_verify_file_exists(struct wil6210_priv *wil, const char *name); int wil_can_suspend(struct wil6210_priv *wil, bool is_runtime); int wil_suspend(struct wil6210_priv *wil, bool is_runtime); int wil_resume(struct wil6210_priv *wil, bool is_runtime); +bool wil_is_wmi_idle(struct wil6210_priv *wil); +int wmi_resume(struct wil6210_priv *wil); +int wmi_suspend(struct wil6210_priv *wil); +bool wil_is_tx_idle(struct wil6210_priv *wil); +bool wil_is_rx_idle(struct wil6210_priv *wil); +int wil_fw_copy_crash_dump(struct wil6210_priv *wil, void *dest, u32 size); void wil_fw_core_dump(struct wil6210_priv *wil); +void wil_halp_vote(struct wil6210_priv *wil); +void wil_halp_unvote(struct wil6210_priv *wil); +void wil6210_set_halp(struct wil6210_priv *wil); +void wil6210_clear_halp(struct wil6210_priv *wil); + +void wil_ftm_init(struct wil6210_priv *wil); +void wil_ftm_deinit(struct wil6210_priv *wil); +void wil_ftm_stop_operations(struct wil6210_priv *wil); +void wil_aoa_cfg80211_meas_result(struct wil6210_priv *wil, + struct wil_aoa_meas_result *result); + +void wil_ftm_evt_session_ended(struct wil6210_priv *wil, + struct wmi_tof_session_end_event *evt); +void wil_ftm_evt_per_dest_res(struct wil6210_priv *wil, + struct wmi_tof_ftm_per_dest_res_event *evt); +void wil_aoa_evt_meas(struct wil6210_priv *wil, + struct wmi_aoa_meas_event *evt, + int len); +/* link loss */ +int wmi_link_maintain_cfg_write(struct wil6210_priv *wil, + const u8 *addr, + bool fst_link_loss); + +int wmi_set_snr_thresh(struct wil6210_priv *wil, short omni, short direct); #endif /* __WIL6210_H__ */ diff --git a/drivers/net/wireless/ath/wil6210/wil_crash_dump.c b/drivers/net/wireless/ath/wil6210/wil_crash_dump.c index 7e70934990ae..e53cf0cf7031 100644 --- a/drivers/net/wireless/ath/wil6210/wil_crash_dump.c +++ b/drivers/net/wireless/ath/wil6210/wil_crash_dump.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Qualcomm Atheros, Inc. + * Copyright (c) 2015,2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -36,6 +36,9 @@ static int wil_fw_get_crash_dump_bounds(struct wil6210_priv *wil, for (i = 1; i < ARRAY_SIZE(fw_mapping); i++) { map = &fw_mapping[i]; + if (!map->fw) + continue; + if (map->host < host_min) host_min = map->host; @@ -51,8 +54,7 @@ static int wil_fw_get_crash_dump_bounds(struct wil6210_priv *wil, return 0; } -static int wil_fw_copy_crash_dump(struct wil6210_priv *wil, void *dest, - u32 size) +int wil_fw_copy_crash_dump(struct wil6210_priv *wil, void *dest, u32 size) { int i; const struct fw_map *map; @@ -60,13 +62,13 @@ static int wil_fw_copy_crash_dump(struct wil6210_priv *wil, void *dest, u32 host_min, dump_size, offset, len; if (wil_fw_get_crash_dump_bounds(wil, &dump_size, &host_min)) { - wil_err(wil, "%s: fail to obtain crash dump size\n", __func__); + wil_err(wil, "fail to obtain crash dump size\n"); return -EINVAL; } if (dump_size > size) { - wil_err(wil, "%s: not enough space for dump. Need %d have %d\n", - __func__, dump_size, size); + wil_err(wil, "not enough space for dump. Need %d have %d\n", + dump_size, size); return -EINVAL; } @@ -74,12 +76,16 @@ static int wil_fw_copy_crash_dump(struct wil6210_priv *wil, void *dest, for (i = 0; i < ARRAY_SIZE(fw_mapping); i++) { map = &fw_mapping[i]; + if (!map->fw) + continue; + data = (void * __force)wil->csr + HOSTADDR(map->host); len = map->to - map->from; offset = map->host - host_min; - wil_dbg_misc(wil, "%s() - dump %s, size %d, offset %d\n", - __func__, fw_mapping[i].name, len, offset); + wil_dbg_misc(wil, + "fw_copy_crash_dump: - dump %s, size %d, offset %d\n", + fw_mapping[i].name, len, offset); wil_memcpy_fromio_32((void * __force)(dest + offset), (const void __iomem * __force)data, len); @@ -94,7 +100,7 @@ void wil_fw_core_dump(struct wil6210_priv *wil) u32 fw_dump_size; if (wil_fw_get_crash_dump_bounds(wil, &fw_dump_size, NULL)) { - wil_err(wil, "%s: fail to get fw dump size\n", __func__); + wil_err(wil, "fail to get fw dump size\n"); return; } @@ -110,6 +116,5 @@ void wil_fw_core_dump(struct wil6210_priv *wil) * after 5 min */ dev_coredumpv(wil_to_dev(wil), fw_dump_data, fw_dump_size, GFP_KERNEL); - wil_info(wil, "%s: fw core dumped, size %d bytes\n", __func__, - fw_dump_size); + wil_info(wil, "fw core dumped, size %d bytes\n", fw_dump_size); } diff --git a/drivers/net/wireless/ath/wil6210/wil_platform.c b/drivers/net/wireless/ath/wil6210/wil_platform.c index 2e831bf20117..263740a9b980 100644 --- a/drivers/net/wireless/ath/wil6210/wil_platform.c +++ b/drivers/net/wireless/ath/wil6210/wil_platform.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Qualcomm Atheros, Inc. + * Copyright (c) 2014-2016 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,14 +16,16 @@ #include <linux/device.h> #include "wil_platform.h" +#include "msm_11ad.h" int __init wil_platform_modinit(void) { - return 0; + return msm_11ad_modinit(); } void wil_platform_modexit(void) { + msm_11ad_modexit(); } /** @@ -33,9 +35,10 @@ void wil_platform_modexit(void) * It returns a handle which is used with the rest of the API * */ -void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops) +void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops, + const struct wil_platform_rops *rops, void *wil_handle) { - void *handle = ops; /* to return some non-NULL for 'void' impl. */ + void *handle; if (!ops) { dev_err(dev, @@ -43,7 +46,7 @@ void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops) return NULL; } - /* platform specific init functions should be called here */ + handle = msm_11ad_dev_init(dev, ops, rops, wil_handle); return handle; } diff --git a/drivers/net/wireless/ath/wil6210/wil_platform.h b/drivers/net/wireless/ath/wil6210/wil_platform.h index d7fa19b7886d..621005b0c5ca 100644 --- a/drivers/net/wireless/ath/wil6210/wil_platform.h +++ b/drivers/net/wireless/ath/wil6210/wil_platform.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Qualcomm Atheros, Inc. + * Copyright (c) 2014-2016 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,17 +19,58 @@ struct device; +enum wil_platform_event { + WIL_PLATFORM_EVT_FW_CRASH = 0, + WIL_PLATFORM_EVT_PRE_RESET = 1, + WIL_PLATFORM_EVT_FW_RDY = 2, + WIL_PLATFORM_EVT_PRE_SUSPEND = 3, + WIL_PLATFORM_EVT_POST_SUSPEND = 4, +}; + /** - * struct wil_platform_ops - wil platform module callbacks + * struct wil_platform_ops - wil platform module calls from this + * driver to platform driver */ struct wil_platform_ops { int (*bus_request)(void *handle, uint32_t kbps /* KBytes/Sec */); - int (*suspend)(void *handle); - int (*resume)(void *handle); + int (*suspend)(void *handle, bool keep_device_power); + int (*resume)(void *handle, bool device_powered_on); void (*uninit)(void *handle); + int (*notify)(void *handle, enum wil_platform_event evt); + bool (*keep_radio_on_during_sleep)(void *handle); }; -void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops); +/** + * struct wil_platform_rops - wil platform module callbacks from + * platform driver to this driver + * @ramdump: store a ramdump from the wil firmware. The platform + * driver may add additional data to the ramdump to + * generate the final crash dump. + * @fw_recovery: start a firmware recovery process. Called as + * part of a crash recovery process which may include other + * related platform subsystems. + */ +struct wil_platform_rops { + int (*ramdump)(void *wil_handle, void *buf, uint32_t size); + int (*fw_recovery)(void *wil_handle); +}; + +/** + * wil_platform_init - initialize the platform driver + * + * @dev - pointer to the wil6210 device + * @ops - structure with platform driver operations. Platform + * driver will fill this structure with function pointers. + * @rops - structure with callbacks from platform driver to + * this driver. The platform driver copies the structure to + * its own storage. Can be NULL if this driver does not + * support crash recovery. + * @wil_handle - context for this driver that will be passed + * when platform driver invokes one of the callbacks in + * rops. May be NULL if rops is NULL. + */ +void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops, + const struct wil_platform_rops *rops, void *wil_handle); int __init wil_platform_modinit(void); void wil_platform_modexit(void); diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c index 6ed26baca0e5..27b2f6219ea0 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.c +++ b/drivers/net/wireless/ath/wil6210/wmi.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -22,16 +22,24 @@ #include "txrx.h" #include "wmi.h" #include "trace.h" +#include "ftm.h" static uint max_assoc_sta = WIL6210_MAX_CID; -module_param(max_assoc_sta, uint, S_IRUGO | S_IWUSR); +module_param(max_assoc_sta, uint, 0644); MODULE_PARM_DESC(max_assoc_sta, " Max number of stations associated to the AP"); int agg_wsize; /* = 0; */ -module_param(agg_wsize, int, S_IRUGO | S_IWUSR); +module_param(agg_wsize, int, 0644); MODULE_PARM_DESC(agg_wsize, " Window size for Tx Block Ack after connect;" " 0 - use default; < 0 - don't auto-establish"); +u8 led_id = WIL_LED_INVALID_ID; +module_param(led_id, byte, 0444); +MODULE_PARM_DESC(led_id, + " 60G device led enablement. Set the led ID (0-2) to enable"); + +#define WIL_WAIT_FOR_SUSPEND_RESUME_COMP 200 + /** * WMI event receiving - theory of operations * @@ -79,23 +87,41 @@ MODULE_PARM_DESC(agg_wsize, " Window size for Tx Block Ack after connect;" * array size should be in sync with the declaration in the wil6210.h */ const struct fw_map fw_mapping[] = { - {0x000000, 0x040000, 0x8c0000, "fw_code"}, /* FW code RAM 256k */ - {0x800000, 0x808000, 0x900000, "fw_data"}, /* FW data RAM 32k */ - {0x840000, 0x860000, 0x908000, "fw_peri"}, /* periph. data RAM 128k */ - {0x880000, 0x88a000, 0x880000, "rgf"}, /* various RGF 40k */ - {0x88a000, 0x88b000, 0x88a000, "AGC_tbl"}, /* AGC table 4k */ - {0x88b000, 0x88c000, 0x88b000, "rgf_ext"}, /* Pcie_ext_rgf 4k */ - {0x88c000, 0x88c200, 0x88c000, "mac_rgf_ext"}, /* mac_ext_rgf 512b */ - {0x8c0000, 0x949000, 0x8c0000, "upper"}, /* upper area 548k */ - /* - * 920000..930000 ucode code RAM - * 930000..932000 ucode data RAM - * 932000..949000 back-door debug data + /* FW code RAM 256k */ + {0x000000, 0x040000, 0x8c0000, "fw_code", true}, + /* FW data RAM 32k */ + {0x800000, 0x808000, 0x900000, "fw_data", true}, + /* periph data 128k */ + {0x840000, 0x860000, 0x908000, "fw_peri", true}, + /* various RGF 40k */ + {0x880000, 0x88a000, 0x880000, "rgf", true}, + /* AGC table 4k */ + {0x88a000, 0x88b000, 0x88a000, "AGC_tbl", true}, + /* Pcie_ext_rgf 4k */ + {0x88b000, 0x88c000, 0x88b000, "rgf_ext", true}, + /* mac_ext_rgf 512b */ + {0x88c000, 0x88c200, 0x88c000, "mac_rgf_ext", true}, + /* upper area 548k */ + {0x8c0000, 0x949000, 0x8c0000, "upper", true}, + /* UCODE areas - accessible by debugfs blobs but not by + * wmi_addr_remap. UCODE areas MUST be added AFTER FW areas! */ + /* ucode code RAM 128k */ + {0x000000, 0x020000, 0x920000, "uc_code", false}, + /* ucode data RAM 16k */ + {0x800000, 0x804000, 0x940000, "uc_data", false}, }; +struct blink_on_off_time led_blink_time[] = { + {WIL_LED_BLINK_ON_SLOW_MS, WIL_LED_BLINK_OFF_SLOW_MS}, + {WIL_LED_BLINK_ON_MED_MS, WIL_LED_BLINK_OFF_MED_MS}, + {WIL_LED_BLINK_ON_FAST_MS, WIL_LED_BLINK_OFF_FAST_MS}, +}; + +u8 led_polarity = LED_POLARITY_LOW_ACTIVE; + /** - * return AHB address for given firmware/ucode internal (linker) address + * return AHB address for given firmware internal (linker) address * @x - internal address * If address have no valid AHB mapping, return 0 */ @@ -104,7 +130,8 @@ static u32 wmi_addr_remap(u32 x) uint i; for (i = 0; i < ARRAY_SIZE(fw_mapping); i++) { - if ((x >= fw_mapping[i].from) && (x < fw_mapping[i].to)) + if (fw_mapping[i].fw && + ((x >= fw_mapping[i].from) && (x < fw_mapping[i].to))) return x + fw_mapping[i].host - fw_mapping[i].from; } @@ -114,13 +141,15 @@ static u32 wmi_addr_remap(u32 x) /** * Check address validity for WMI buffer; remap if needed * @ptr - internal (linker) fw/ucode address + * @size - if non zero, validate the block does not + * exceed the device memory (bar) * * Valid buffer should be DWORD aligned * * return address for accessing buffer from the host; * if buffer is not valid, return NULL. */ -void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr_) +void __iomem *wmi_buffer_block(struct wil6210_priv *wil, __le32 ptr_, u32 size) { u32 off; u32 ptr = le32_to_cpu(ptr_); @@ -133,12 +162,19 @@ void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr_) return NULL; off = HOSTADDR(ptr); - if (off > WIL6210_MEM_SIZE - 4) + if (off > wil->bar_size - 4) + return NULL; + if (size && ((off + size > wil->bar_size) || (off + size < off))) return NULL; return wil->csr + off; } +void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr_) +{ + return wmi_buffer_block(wil, ptr_, 0); +} + /** * Check address validity */ @@ -153,7 +189,7 @@ void __iomem *wmi_addr(struct wil6210_priv *wil, u32 ptr) return NULL; off = HOSTADDR(ptr); - if (off > WIL6210_MEM_SIZE - 4) + if (off > wil->bar_size - 4) return NULL; return wil->csr + off; @@ -176,7 +212,7 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) { struct { struct wil6210_mbox_hdr hdr; - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; } __packed cmd = { .hdr = { .type = WIL_MBOX_HDR_TYPE_WMI, @@ -185,7 +221,7 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) }, .wmi = { .mid = 0, - .id = cpu_to_le16(cmdid), + .command_id = cpu_to_le16(cmdid), }, }; struct wil6210_mbox_ring *r = &wil->mbox_ctl.tx; @@ -194,8 +230,9 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) void __iomem *dst; void __iomem *head = wmi_addr(wil, r->head); uint retry; + int rc = 0; - if (sizeof(cmd) + len > r->entry_size) { + if (len > r->entry_size - sizeof(cmd)) { wil_err(wil, "WMI size too large: %d bytes, max is %d\n", (int)(sizeof(cmd) + len), r->entry_size); return -ERANGE; @@ -208,10 +245,23 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) return -EAGAIN; } + /* Allow sending only suspend / resume commands during susepnd flow */ + if ((test_bit(wil_status_suspending, wil->status) || + test_bit(wil_status_suspended, wil->status) || + test_bit(wil_status_resuming, wil->status)) && + ((cmdid != WMI_TRAFFIC_SUSPEND_CMDID) && + (cmdid != WMI_TRAFFIC_RESUME_CMDID))) { + wil_err(wil, "WMI: reject send_command during suspend\n"); + return -EINVAL; + } + if (!head) { wil_err(wil, "WMI head is garbage: 0x%08x\n", r->head); return -EINVAL; } + + wil_halp_vote(wil); + /* read Tx head till it is not busy */ for (retry = 5; retry > 0; retry--) { wil_memcpy_fromio_32(&d_head, head, sizeof(d_head)); @@ -221,13 +271,19 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) } if (d_head.sync != 0) { wil_err(wil, "WMI head busy\n"); - return -EBUSY; + rc = -EBUSY; + goto out; } /* next head */ next_head = r->base + ((r->head - r->base + sizeof(d_head)) % r->size); wil_dbg_wmi(wil, "Head 0x%08x -> 0x%08x\n", r->head, next_head); /* wait till FW finish with previous command */ for (retry = 5; retry > 0; retry--) { + if (!test_bit(wil_status_fwready, wil->status)) { + wil_err(wil, "WMI: cannot send command while FW not ready\n"); + rc = -EAGAIN; + goto out; + } r->tail = wil_r(wil, RGF_MBOX + offsetof(struct wil6210_mbox_ctl, tx.tail)); if (next_head != r->tail) @@ -236,13 +292,15 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) } if (next_head == r->tail) { wil_err(wil, "WMI ring full\n"); - return -EBUSY; + rc = -EBUSY; + goto out; } dst = wmi_buffer(wil, d_head.addr); if (!dst) { wil_err(wil, "invalid WMI buffer: 0x%08x\n", le32_to_cpu(d_head.addr)); - return -EINVAL; + rc = -EAGAIN; + goto out; } cmd.hdr.seq = cpu_to_le16(++wil->wmi_seq); /* set command */ @@ -265,7 +323,9 @@ static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) wil_w(wil, RGF_USER_USER_ICR + offsetof(struct RGF_ICR, ICS), SW_INT_MBOX); - return 0; +out: + wil_halp_unvote(wil); + return rc; } int wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) @@ -285,15 +345,20 @@ static void wmi_evt_ready(struct wil6210_priv *wil, int id, void *d, int len) struct wireless_dev *wdev = wil->wdev; struct wmi_ready_event *evt = d; - wil->fw_version = le32_to_cpu(evt->sw_version); wil->n_mids = evt->numof_additional_mids; - wil_info(wil, "FW ver. %d; MAC %pM; %d MID's\n", wil->fw_version, + wil_info(wil, "FW ver. %s(SW %d); MAC %pM; %d MID's\n", + wil->fw_version, le32_to_cpu(evt->sw_version), evt->mac, wil->n_mids); /* ignore MAC address, we already have it from the boot loader */ - snprintf(wdev->wiphy->fw_version, sizeof(wdev->wiphy->fw_version), - "%d", wil->fw_version); + strlcpy(wdev->wiphy->fw_version, wil->fw_version, + sizeof(wdev->wiphy->fw_version)); + if (len > offsetof(struct wmi_ready_event, rfc_read_calib_result)) { + wil_dbg_wmi(wil, "rfc calibration result %d\n", + evt->rfc_read_calib_result); + wil->fw_calib_result = evt->rfc_read_calib_result; + } wil_set_recovery_state(wil, fw_recovery_idle); set_bit(wil_status_fwready, wil->status); /* let the reset sequence continue */ @@ -313,7 +378,7 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len) s32 signal; __le16 fc; u32 d_len; - u16 d_status; + s16 snr; if (flen < 0) { wil_err(wil, "MGMT Rx: short event, len %d\n", len); @@ -331,14 +396,17 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len) ch_no = data->info.channel + 1; freq = ieee80211_channel_to_frequency(ch_no, IEEE80211_BAND_60GHZ); channel = ieee80211_get_channel(wiphy, freq); - signal = data->info.sqi; - d_status = le16_to_cpu(data->info.status); + if (test_bit(WMI_FW_CAPABILITY_RSSI_REPORTING, wil->fw_capabilities)) + signal = 100 * data->info.rssi; + else + signal = data->info.sqi; + snr = le16_to_cpu(data->info.snr); /* 1/4 dB units */ fc = rx_mgmt_frame->frame_control; - wil_dbg_wmi(wil, "MGMT Rx: channel %d MCS %d SNR %d SQI %d%%\n", - data->info.channel, data->info.mcs, data->info.snr, + wil_dbg_wmi(wil, "MGMT Rx: channel %d MCS %d RSSI %d SQI %d%%\n", + data->info.channel, data->info.mcs, data->info.rssi, data->info.sqi); - wil_dbg_wmi(wil, "status 0x%04x len %d fc 0x%04x\n", d_status, d_len, + wil_dbg_wmi(wil, "snr %ddB len %d fc 0x%04x\n", snr / 4, d_len, le16_to_cpu(fc)); wil_dbg_wmi(wil, "qid %d mid %d cid %d\n", data->info.qid, data->info.mid, data->info.cid); @@ -364,6 +432,13 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len) wil_hex_dump_wmi("IE ", DUMP_PREFIX_OFFSET, 16, 1, ie_buf, ie_len, true); + wil_dbg_wmi(wil, "Capability info : 0x%04x\n", cap); + + if (wil->snr_thresh.enabled && snr < wil->snr_thresh.omni) { + wil_dbg_wmi(wil, "snr below threshold. dropping\n"); + return; + } + bss = cfg80211_inform_bss_frame(wiphy, channel, rx_mgmt_frame, d_len, signal, GFP_KERNEL); if (bss) { @@ -374,8 +449,10 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len) wil_err(wil, "cfg80211_inform_bss_frame() failed\n"); } } else { - cfg80211_rx_mgmt(wil->wdev, freq, signal, + mutex_lock(&wil->p2p_wdev_mutex); + cfg80211_rx_mgmt(wil->radio_wdev, freq, signal, (void *)rx_mgmt_frame, d_len, 0); + mutex_unlock(&wil->p2p_wdev_mutex); } } @@ -393,20 +470,30 @@ static void wmi_evt_tx_mgmt(struct wil6210_priv *wil, int id, void *d, int len) static void wmi_evt_scan_complete(struct wil6210_priv *wil, int id, void *d, int len) { + mutex_lock(&wil->p2p_wdev_mutex); if (wil->scan_request) { struct wmi_scan_complete_event *data = d; - bool aborted = (data->status != WMI_SCAN_SUCCESS); + int status = le32_to_cpu(data->status); + bool aborted = (status != WMI_SCAN_SUCCESS) && + (status != WMI_SCAN_ABORT_REJECTED); - wil_dbg_wmi(wil, "SCAN_COMPLETE(0x%08x)\n", data->status); + wil_dbg_wmi(wil, "SCAN_COMPLETE(0x%08x)\n", status); wil_dbg_misc(wil, "Complete scan_request 0x%p aborted %d\n", wil->scan_request, aborted); del_timer_sync(&wil->scan_timer); cfg80211_scan_done(wil->scan_request, aborted); + wil->radio_wdev = wil->wdev; wil->scan_request = NULL; + wake_up_interruptible(&wil->wq); + if (wil->p2p.pending_listen_wdev) { + wil_dbg_misc(wil, "Scheduling delayed listen\n"); + schedule_work(&wil->p2p.delayed_listen_work); + } } else { wil_err(wil, "SCAN_COMPLETE while not scanning\n"); } + mutex_unlock(&wil->p2p_wdev_mutex); } static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len) @@ -422,6 +509,7 @@ static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len) const size_t assoc_req_ie_offset = sizeof(u16) * 2; /* capinfo(u16) + status_code(u16) + associd(u16) + IEs */ const size_t assoc_resp_ie_offset = sizeof(u16) * 3; + int rc; if (len < sizeof(*evt)) { wil_err(wil, "Connect event too short : %d bytes\n", len); @@ -441,8 +529,8 @@ static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len) } ch = evt->channel + 1; - wil_dbg_wmi(wil, "Connect %pM channel [%d] cid %d\n", - evt->bssid, ch, evt->cid); + wil_info(wil, "Connect %pM channel [%d] cid %d aid %d\n", + evt->bssid, ch, evt->cid, evt->aid); wil_hex_dump_wmi("connect AI : ", DUMP_PREFIX_OFFSET, 16, 1, evt->assoc_info, len - sizeof(*evt), true); @@ -464,20 +552,80 @@ static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len) assoc_resp_ielen = 0; } + if (test_bit(wil_status_resetting, wil->status) || + !test_bit(wil_status_fwready, wil->status)) { + wil_err(wil, "status_resetting, cancel connect event, CID %d\n", + evt->cid); + /* no need for cleanup, wil_reset will do that */ + return; + } + + mutex_lock(&wil->mutex); + if ((wdev->iftype == NL80211_IFTYPE_STATION) || (wdev->iftype == NL80211_IFTYPE_P2P_CLIENT)) { if (!test_bit(wil_status_fwconnecting, wil->status)) { wil_err(wil, "Not in connecting state\n"); + mutex_unlock(&wil->mutex); return; } del_timer_sync(&wil->connect_timer); - cfg80211_connect_result(ndev, evt->bssid, - assoc_req_ie, assoc_req_ielen, - assoc_resp_ie, assoc_resp_ielen, - WLAN_STATUS_SUCCESS, GFP_KERNEL); + } else if ((wdev->iftype == NL80211_IFTYPE_AP) || + (wdev->iftype == NL80211_IFTYPE_P2P_GO)) { + if (wil->sta[evt->cid].status != wil_sta_unused) { + wil_err(wil, "AP: Invalid status %d for CID %d\n", + wil->sta[evt->cid].status, evt->cid); + mutex_unlock(&wil->mutex); + return; + } + } + + /* FIXME FW can transmit only ucast frames to peer */ + /* FIXME real ring_id instead of hard coded 0 */ + ether_addr_copy(wil->sta[evt->cid].addr, evt->bssid); + wil->sta[evt->cid].status = wil_sta_conn_pending; + rc = wil_tx_init(wil, evt->cid); + if (rc) { + wil_err(wil, "config tx vring failed for CID %d, rc (%d)\n", + evt->cid, rc); + wmi_disconnect_sta(wil, wil->sta[evt->cid].addr, + WLAN_REASON_UNSPECIFIED, false, false); + } else { + wil_info(wil, "successful connection to CID %d\n", evt->cid); + } + + if ((wdev->iftype == NL80211_IFTYPE_STATION) || + (wdev->iftype == NL80211_IFTYPE_P2P_CLIENT)) { + if (rc) { + netif_carrier_off(ndev); + wil6210_bus_request(wil, WIL_DEFAULT_BUS_REQUEST_KBPS); + wil_err(wil, "cfg80211_connect_result with failure\n"); + cfg80211_connect_result(ndev, evt->bssid, NULL, 0, + NULL, 0, + WLAN_STATUS_UNSPECIFIED_FAILURE, + GFP_KERNEL); + goto out; + } else { + struct wiphy *wiphy = wil_to_wiphy(wil); + + cfg80211_ref_bss(wiphy, wil->bss); + cfg80211_connect_bss(ndev, evt->bssid, wil->bss, + assoc_req_ie, assoc_req_ielen, + assoc_resp_ie, assoc_resp_ielen, + WLAN_STATUS_SUCCESS, GFP_KERNEL, + NL80211_TIMEOUT_UNSPECIFIED); + } + wil->bss = NULL; } else if ((wdev->iftype == NL80211_IFTYPE_AP) || (wdev->iftype == NL80211_IFTYPE_P2P_GO)) { + if (rc) { + if (disable_ap_sme) + /* notify new_sta has failed */ + cfg80211_del_sta(ndev, evt->bssid, GFP_KERNEL); + goto out; + } + memset(&sinfo, 0, sizeof(sinfo)); sinfo.generation = wil->sinfo_gen++; @@ -488,17 +636,22 @@ static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len) } cfg80211_new_sta(ndev, evt->bssid, &sinfo, GFP_KERNEL); + } else { + wil_err(wil, "unhandled iftype %d for CID %d\n", wdev->iftype, + evt->cid); + goto out; } - clear_bit(wil_status_fwconnecting, wil->status); - set_bit(wil_status_fwconnected, wil->status); - /* FIXME FW can transmit only ucast frames to peer */ - /* FIXME real ring_id instead of hard coded 0 */ - ether_addr_copy(wil->sta[evt->cid].addr, evt->bssid); - wil->sta[evt->cid].status = wil_sta_conn_pending; + wil->sta[evt->cid].status = wil_sta_connected; + wil->sta[evt->cid].aid = evt->aid; + set_bit(wil_status_fwconnected, wil->status); + wil_update_net_queues_bh(wil, NULL, false); - wil->pending_connect_cid = evt->cid; - queue_work(wil->wq_service, &wil->connect_worker); +out: + if (rc) + wil->sta[evt->cid].status = wil_sta_unused; + clear_bit(wil_status_fwconnecting, wil->status); + mutex_unlock(&wil->mutex); } static void wmi_evt_disconnect(struct wil6210_priv *wil, int id, @@ -507,11 +660,18 @@ static void wmi_evt_disconnect(struct wil6210_priv *wil, int id, struct wmi_disconnect_event *evt = d; u16 reason_code = le16_to_cpu(evt->protocol_reason_status); - wil_dbg_wmi(wil, "Disconnect %pM reason [proto %d wmi %d]\n", - evt->bssid, reason_code, evt->disconnect_reason); + wil_info(wil, "Disconnect %pM reason [proto %d wmi %d]\n", + evt->bssid, reason_code, evt->disconnect_reason); wil->sinfo_gen++; + if (test_bit(wil_status_resetting, wil->status) || + !test_bit(wil_status_fwready, wil->status)) { + wil_err(wil, "status_resetting, cancel disconnect event\n"); + /* no need for cleanup, wil_reset will do that */ + return; + } + mutex_lock(&wil->mutex); wil6210_disconnect(wil, evt->bssid, reason_code, true); mutex_unlock(&wil->mutex); @@ -575,6 +735,7 @@ static void wmi_evt_vring_en(struct wil6210_priv *wil, int id, void *d, int len) { struct wmi_vring_en_event *evt = d; u8 vri = evt->vring_index; + struct wireless_dev *wdev = wil_to_wdev(wil); wil_dbg_wmi(wil, "Enable vring %d\n", vri); @@ -582,7 +743,12 @@ static void wmi_evt_vring_en(struct wil6210_priv *wil, int id, void *d, int len) wil_err(wil, "Enable for invalid vring %d\n", vri); return; } - wil->vring_tx_data[vri].dot1x_open = true; + + if (wdev->iftype != NL80211_IFTYPE_AP || !disable_ap_sme) + /* in AP mode with disable_ap_sme, this is done by + * wil_cfg80211_change_station() + */ + wil->vring_tx_data[vri].dot1x_open = true; if (vri == wil->bcast_vring) /* no BA for bcast */ return; if (agg_wsize >= 0) @@ -592,7 +758,7 @@ static void wmi_evt_vring_en(struct wil6210_priv *wil, int id, void *d, int len) static void wmi_evt_ba_status(struct wil6210_priv *wil, int id, void *d, int len) { - struct wmi_vring_ba_status_event *evt = d; + struct wmi_ba_status_event *evt = d; struct vring_tx_data *txdata; wil_dbg_wmi(wil, "BACK[%d] %s {%d} timeout %d AMSDU%s\n", @@ -678,6 +844,30 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) spin_unlock_bh(&sta->tid_rx_lock); } +static void wmi_evt_aoa_meas(struct wil6210_priv *wil, int id, + void *d, int len) +{ + struct wmi_aoa_meas_event *evt = d; + + wil_aoa_evt_meas(wil, evt, len); +} + +static void wmi_evt_ftm_session_ended(struct wil6210_priv *wil, int id, + void *d, int len) +{ + struct wmi_tof_session_end_event *evt = d; + + wil_ftm_evt_session_ended(wil, evt); +} + +static void wmi_evt_per_dest_res(struct wil6210_priv *wil, int id, + void *d, int len) +{ + struct wmi_tof_ftm_per_dest_res_event *evt = d; + + wil_ftm_evt_per_dest_res(wil, evt); +} + /** * Some events are ignored for purpose; and need not be interpreted as * "unhandled events" @@ -705,6 +895,13 @@ static const struct { {WMI_DELBA_EVENTID, wmi_evt_delba}, {WMI_VRING_EN_EVENTID, wmi_evt_vring_en}, {WMI_DATA_PORT_OPEN_EVENTID, wmi_evt_ignore}, + {WMI_AOA_MEAS_EVENTID, wmi_evt_aoa_meas}, + {WMI_TOF_SESSION_END_EVENTID, wmi_evt_ftm_session_ended}, + {WMI_TOF_GET_CAPABILITIES_EVENTID, wmi_evt_ignore}, + {WMI_TOF_SET_LCR_EVENTID, wmi_evt_ignore}, + {WMI_TOF_SET_LCI_EVENTID, wmi_evt_ignore}, + {WMI_TOF_FTM_PER_DEST_RES_EVENTID, wmi_evt_per_dest_res}, + {WMI_TOF_CHANNEL_INFO_EVENTID, wmi_evt_ignore}, }; /* @@ -723,15 +920,22 @@ void wmi_recv_cmd(struct wil6210_priv *wil) void __iomem *src; ulong flags; unsigned n; + unsigned int num_immed_reply = 0; if (!test_bit(wil_status_mbox_ready, wil->status)) { wil_err(wil, "Reset in progress. Cannot handle WMI event\n"); return; } + if (test_bit(wil_status_suspended, wil->status)) { + wil_err(wil, "suspended. cannot handle WMI event\n"); + return; + } + for (n = 0;; n++) { u16 len; bool q; + bool immed_reply = false; r->head = wil_r(wil, RGF_MBOX + offsetof(struct wil6210_mbox_ctl, rx.head)); @@ -776,10 +980,33 @@ void wmi_recv_cmd(struct wil6210_priv *wil) offsetof(struct wil6210_mbox_ring_desc, sync), 0); /* indicate */ if ((hdr.type == WIL_MBOX_HDR_TYPE_WMI) && - (len >= sizeof(struct wil6210_mbox_hdr_wmi))) { - struct wil6210_mbox_hdr_wmi *wmi = &evt->event.wmi; - u16 id = le16_to_cpu(wmi->id); - u32 tstamp = le32_to_cpu(wmi->timestamp); + (len >= sizeof(struct wmi_cmd_hdr))) { + struct wmi_cmd_hdr *wmi = &evt->event.wmi; + u16 id = le16_to_cpu(wmi->command_id); + u32 tstamp = le32_to_cpu(wmi->fw_timestamp); + if (test_bit(wil_status_resuming, wil->status)) { + if (id == WMI_TRAFFIC_RESUME_EVENTID) + clear_bit(wil_status_resuming, + wil->status); + else + wil_err(wil, + "WMI evt %d while resuming\n", + id); + } + spin_lock_irqsave(&wil->wmi_ev_lock, flags); + if (wil->reply_id && wil->reply_id == id) { + if (wil->reply_buf) { + memcpy(wil->reply_buf, wmi, + min(len, wil->reply_size)); + immed_reply = true; + } + if (id == WMI_TRAFFIC_SUSPEND_EVENTID) { + wil_dbg_wmi(wil, + "set suspend_resp_rcvd\n"); + wil->suspend_resp_rcvd = true; + } + } + spin_unlock_irqrestore(&wil->wmi_ev_lock, flags); wil_dbg_wmi(wil, "WMI event 0x%04x MID %d @%d msec\n", id, wmi->mid, tstamp); @@ -795,15 +1022,24 @@ void wmi_recv_cmd(struct wil6210_priv *wil) wil_w(wil, RGF_MBOX + offsetof(struct wil6210_mbox_ctl, rx.tail), r->tail); - /* add to the pending list */ - spin_lock_irqsave(&wil->wmi_ev_lock, flags); - list_add_tail(&evt->list, &wil->pending_wmi_ev); - spin_unlock_irqrestore(&wil->wmi_ev_lock, flags); - q = queue_work(wil->wmi_wq, &wil->wmi_event_worker); - wil_dbg_wmi(wil, "queue_work -> %d\n", q); + if (immed_reply) { + wil_dbg_wmi(wil, "recv_cmd: Complete WMI 0x%04x\n", + wil->reply_id); + kfree(evt); + num_immed_reply++; + complete(&wil->wmi_call); + } else { + /* add to the pending list */ + spin_lock_irqsave(&wil->wmi_ev_lock, flags); + list_add_tail(&evt->list, &wil->pending_wmi_ev); + spin_unlock_irqrestore(&wil->wmi_ev_lock, flags); + q = queue_work(wil->wmi_wq, &wil->wmi_event_worker); + wil_dbg_wmi(wil, "queue_work -> %d\n", q); + } } /* normally, 1 event per IRQ should be processed */ - wil_dbg_wmi(wil, "%s -> %d events queued\n", __func__, n); + wil_dbg_wmi(wil, "recv_cmd: -> %d events queued, %d completed\n", + n - num_immed_reply, num_immed_reply); } int wmi_call(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len, @@ -814,13 +1050,17 @@ int wmi_call(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len, mutex_lock(&wil->wmi_mutex); + spin_lock(&wil->wmi_ev_lock); + wil->reply_id = reply_id; + wil->reply_buf = reply; + wil->reply_size = reply_size; + reinit_completion(&wil->wmi_call); + spin_unlock(&wil->wmi_ev_lock); + rc = __wmi_send(wil, cmdid, buf, len); if (rc) goto out; - wil->reply_id = reply_id; - wil->reply_buf = reply; - wil->reply_size = reply_size; remain = wait_for_completion_timeout(&wil->wmi_call, msecs_to_jiffies(to_msec)); if (0 == remain) { @@ -833,10 +1073,14 @@ int wmi_call(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len, cmdid, reply_id, to_msec - jiffies_to_msecs(remain)); } + +out: + spin_lock(&wil->wmi_ev_lock); wil->reply_id = 0; wil->reply_buf = NULL; wil->reply_size = 0; - out: + spin_unlock(&wil->wmi_ev_lock); + mutex_unlock(&wil->wmi_mutex); return rc; @@ -863,8 +1107,62 @@ int wmi_set_mac_address(struct wil6210_priv *wil, void *addr) return wmi_send(wil, WMI_SET_MAC_ADDRESS_CMDID, &cmd, sizeof(cmd)); } +int wmi_led_cfg(struct wil6210_priv *wil, bool enable) +{ + int rc = 0; + struct wmi_led_cfg_cmd cmd = { + .led_mode = enable, + .id = led_id, + .slow_blink_cfg.blink_on = + cpu_to_le32(led_blink_time[WIL_LED_TIME_SLOW].on_ms), + .slow_blink_cfg.blink_off = + cpu_to_le32(led_blink_time[WIL_LED_TIME_SLOW].off_ms), + .medium_blink_cfg.blink_on = + cpu_to_le32(led_blink_time[WIL_LED_TIME_MED].on_ms), + .medium_blink_cfg.blink_off = + cpu_to_le32(led_blink_time[WIL_LED_TIME_MED].off_ms), + .fast_blink_cfg.blink_on = + cpu_to_le32(led_blink_time[WIL_LED_TIME_FAST].on_ms), + .fast_blink_cfg.blink_off = + cpu_to_le32(led_blink_time[WIL_LED_TIME_FAST].off_ms), + .led_polarity = led_polarity, + }; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_led_cfg_done_event evt; + } __packed reply; + + if (led_id == WIL_LED_INVALID_ID) + goto out; + + if (led_id > WIL_LED_MAX_ID) { + wil_err(wil, "Invalid led id %d\n", led_id); + rc = -EINVAL; + goto out; + } + + wil_dbg_wmi(wil, + "%s led %d\n", + enable ? "enabling" : "disabling", led_id); + + rc = wmi_call(wil, WMI_LED_CFG_CMDID, &cmd, sizeof(cmd), + WMI_LED_CFG_DONE_EVENTID, &reply, sizeof(reply), + 100); + if (rc) + goto out; + + if (reply.evt.status) { + wil_err(wil, "led %d cfg failed with status %d\n", + led_id, le32_to_cpu(reply.evt.status)); + rc = -EINVAL; + } + +out: + return rc; +} + int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, - u8 chan, u8 hidden_ssid) + u8 chan, u8 hidden_ssid, u8 is_go) { int rc; @@ -875,9 +1173,12 @@ int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, .channel = chan - 1, .pcp_max_assoc_sta = max_assoc_sta, .hidden_ssid = hidden_ssid, + .is_go = is_go, + .disable_ap_sme = disable_ap_sme, + .abft_len = wil->abft_len, }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_pcp_started_event evt; } __packed reply; @@ -892,6 +1193,13 @@ int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, cmd.pcp_max_assoc_sta = WIL6210_MAX_CID; } + if (disable_ap_sme && + !test_bit(WMI_FW_CAPABILITY_DISABLE_AP_SME, + wil->fw_capabilities)) { + wil_err(wil, "disable_ap_sme not supported by FW\n"); + return -EOPNOTSUPP; + } + /* * Processing time may be huge, in case of secure AP it takes about * 3500ms for FW to start AP @@ -904,11 +1212,21 @@ int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, if (reply.evt.status != WMI_FW_STATUS_SUCCESS) rc = -EINVAL; + if (wmi_nettype != WMI_NETTYPE_P2P) + /* Don't fail due to error in the led configuration */ + wmi_led_cfg(wil, true); + return rc; } int wmi_pcp_stop(struct wil6210_priv *wil) { + int rc; + + rc = wmi_led_cfg(wil, false); + if (rc) + return rc; + return wmi_call(wil, WMI_PCP_STOP_CMDID, NULL, 0, WMI_PCP_STOPPED_EVENTID, NULL, 0, 20); } @@ -931,7 +1249,7 @@ int wmi_get_ssid(struct wil6210_priv *wil, u8 *ssid_len, void *ssid) { int rc; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_set_ssid_cmd cmd; } __packed reply; int len; /* reply.cmd.ssid_len in CPU order */ @@ -964,7 +1282,7 @@ int wmi_get_channel(struct wil6210_priv *wil, int *channel) { int rc; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_set_pcp_channel_cmd cmd; } __packed reply; @@ -981,14 +1299,86 @@ int wmi_get_channel(struct wil6210_priv *wil, int *channel) return 0; } -int wmi_p2p_cfg(struct wil6210_priv *wil, int channel) +int wmi_p2p_cfg(struct wil6210_priv *wil, int channel, int bi) { + int rc; struct wmi_p2p_cfg_cmd cmd = { - .discovery_mode = WMI_DISCOVERY_MODE_NON_OFFLOAD, + .discovery_mode = WMI_DISCOVERY_MODE_PEER2PEER, + .bcon_interval = cpu_to_le16(bi), .channel = channel - 1, }; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_p2p_cfg_done_event evt; + } __packed reply; + + wil_dbg_wmi(wil, "sending WMI_P2P_CFG_CMDID\n"); + + rc = wmi_call(wil, WMI_P2P_CFG_CMDID, &cmd, sizeof(cmd), + WMI_P2P_CFG_DONE_EVENTID, &reply, sizeof(reply), 300); + if (!rc && reply.evt.status != WMI_FW_STATUS_SUCCESS) { + wil_err(wil, "P2P_CFG failed. status %d\n", reply.evt.status); + rc = -EINVAL; + } - return wmi_send(wil, WMI_P2P_CFG_CMDID, &cmd, sizeof(cmd)); + return rc; +} + +int wmi_start_listen(struct wil6210_priv *wil) +{ + int rc; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_listen_started_event evt; + } __packed reply; + + wil_dbg_wmi(wil, "sending WMI_START_LISTEN_CMDID\n"); + + rc = wmi_call(wil, WMI_START_LISTEN_CMDID, NULL, 0, + WMI_LISTEN_STARTED_EVENTID, &reply, sizeof(reply), 300); + if (!rc && reply.evt.status != WMI_FW_STATUS_SUCCESS) { + wil_err(wil, "device failed to start listen. status %d\n", + reply.evt.status); + rc = -EINVAL; + } + + return rc; +} + +int wmi_start_search(struct wil6210_priv *wil) +{ + int rc; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_search_started_event evt; + } __packed reply; + + wil_dbg_wmi(wil, "sending WMI_START_SEARCH_CMDID\n"); + + rc = wmi_call(wil, WMI_START_SEARCH_CMDID, NULL, 0, + WMI_SEARCH_STARTED_EVENTID, &reply, sizeof(reply), 300); + if (!rc && reply.evt.status != WMI_FW_STATUS_SUCCESS) { + wil_err(wil, "device failed to start search. status %d\n", + reply.evt.status); + rc = -EINVAL; + } + + return rc; +} + +int wmi_stop_discovery(struct wil6210_priv *wil) +{ + int rc; + + wil_dbg_wmi(wil, "sending WMI_DISCOVERY_STOP_CMDID\n"); + + rc = wmi_call(wil, WMI_DISCOVERY_STOP_CMDID, NULL, 0, + WMI_DISCOVERY_STOPPED_EVENTID, NULL, 0, 100); + + if (rc) + wil_err(wil, "Failed to stop discovery\n"); + + return rc; } int wmi_del_cipher_key(struct wil6210_priv *wil, u8 key_index, @@ -1035,8 +1425,14 @@ int wmi_set_ie(struct wil6210_priv *wil, u8 type, u16 ie_len, const void *ie) }; int rc; u16 len = sizeof(struct wmi_set_appie_cmd) + ie_len; - struct wmi_set_appie_cmd *cmd = kzalloc(len, GFP_KERNEL); + struct wmi_set_appie_cmd *cmd; + + if (len < ie_len) { + rc = -EINVAL; + goto out; + } + cmd = kzalloc(len, GFP_KERNEL); if (!cmd) { rc = -ENOMEM; goto out; @@ -1072,11 +1468,11 @@ int wmi_rxon(struct wil6210_priv *wil, bool on) { int rc; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_listen_started_event evt; } __packed reply; - wil_info(wil, "%s(%s)\n", __func__, on ? "on" : "off"); + wil_info(wil, "(%s)\n", on ? "on" : "off"); if (on) { rc = wmi_call(wil, WMI_START_LISTEN_CMDID, NULL, 0, @@ -1099,7 +1495,8 @@ int wmi_rx_chain_add(struct wil6210_priv *wil, struct vring *vring) struct wmi_cfg_rx_chain_cmd cmd = { .action = WMI_RX_CHAIN_ADD, .rx_sw_ring = { - .max_mpdu_size = cpu_to_le16(wil_mtu2macbuf(mtu_max)), + .max_mpdu_size = cpu_to_le16( + wil_mtu2macbuf(wil->rx_buf_len)), .ring_mem_base = cpu_to_le64(vring->pa), .ring_size = cpu_to_le16(vring->size), }, @@ -1109,7 +1506,7 @@ int wmi_rx_chain_add(struct wil6210_priv *wil, struct vring *vring) .host_thrsh = cpu_to_le16(rx_ring_overflow_thrsh), }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_cfg_rx_chain_done_event evt; } __packed evt; int rc; @@ -1163,7 +1560,7 @@ int wmi_get_temperature(struct wil6210_priv *wil, u32 *t_bb, u32 *t_rf) .measure_mode = cpu_to_le32(TEMPERATURE_MEASURE_NOW), }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_temp_sense_done_event evt; } __packed reply; @@ -1180,43 +1577,56 @@ int wmi_get_temperature(struct wil6210_priv *wil, u32 *t_bb, u32 *t_rf) return 0; } -int wmi_disconnect_sta(struct wil6210_priv *wil, const u8 *mac, u16 reason) +int wmi_disconnect_sta(struct wil6210_priv *wil, const u8 *mac, + u16 reason, bool full_disconnect, bool del_sta) { int rc; u16 reason_code; - struct wmi_disconnect_sta_cmd cmd = { + struct wmi_disconnect_sta_cmd disc_sta_cmd = { + .disconnect_reason = cpu_to_le16(reason), + }; + struct wmi_del_sta_cmd del_sta_cmd = { .disconnect_reason = cpu_to_le16(reason), }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_disconnect_event evt; } __packed reply; - ether_addr_copy(cmd.dst_mac, mac); - - wil_dbg_wmi(wil, "%s(%pM, reason %d)\n", __func__, mac, reason); + wil_dbg_wmi(wil, "disconnect_sta: (%pM, reason %d)\n", mac, reason); - rc = wmi_call(wil, WMI_DISCONNECT_STA_CMDID, &cmd, sizeof(cmd), - WMI_DISCONNECT_EVENTID, &reply, sizeof(reply), 1000); + wil->locally_generated_disc = true; + if (del_sta) { + ether_addr_copy(del_sta_cmd.dst_mac, mac); + rc = wmi_call(wil, WMI_DEL_STA_CMDID, &del_sta_cmd, + sizeof(del_sta_cmd), WMI_DISCONNECT_EVENTID, + &reply, sizeof(reply), 1000); + } else { + ether_addr_copy(disc_sta_cmd.dst_mac, mac); + rc = wmi_call(wil, WMI_DISCONNECT_STA_CMDID, &disc_sta_cmd, + sizeof(disc_sta_cmd), WMI_DISCONNECT_EVENTID, + &reply, sizeof(reply), 1000); + } /* failure to disconnect in reasonable time treated as FW error */ if (rc) { wil_fw_error_recovery(wil); return rc; } - /* call event handler manually after processing wmi_call, - * to avoid deadlock - disconnect event handler acquires wil->mutex - * while it is already held here - */ - reason_code = le16_to_cpu(reply.evt.protocol_reason_status); + if (full_disconnect) { + /* call event handler manually after processing wmi_call, + * to avoid deadlock - disconnect event handler acquires + * wil->mutex while it is already held here + */ + reason_code = le16_to_cpu(reply.evt.protocol_reason_status); - wil_dbg_wmi(wil, "Disconnect %pM reason [proto %d wmi %d]\n", - reply.evt.bssid, reason_code, - reply.evt.disconnect_reason); - - wil->sinfo_gen++; - wil6210_disconnect(wil, reply.evt.bssid, reason_code, true); + wil_dbg_wmi(wil, "Disconnect %pM reason [proto %d wmi %d]\n", + reply.evt.bssid, reason_code, + reply.evt.disconnect_reason); + wil->sinfo_gen++; + wil6210_disconnect(wil, reply.evt.bssid, reason_code, true); + } return 0; } @@ -1229,8 +1639,8 @@ int wmi_addba(struct wil6210_priv *wil, u8 ringid, u8 size, u16 timeout) .amsdu = 0, }; - wil_dbg_wmi(wil, "%s(ring %d size %d timeout %d)\n", __func__, - ringid, size, timeout); + wil_dbg_wmi(wil, "addba: (ring %d size %d timeout %d)\n", ringid, size, + timeout); return wmi_send(wil, WMI_VRING_BA_EN_CMDID, &cmd, sizeof(cmd)); } @@ -1242,8 +1652,7 @@ int wmi_delba_tx(struct wil6210_priv *wil, u8 ringid, u16 reason) .reason = cpu_to_le16(reason), }; - wil_dbg_wmi(wil, "%s(ring %d reason %d)\n", __func__, - ringid, reason); + wil_dbg_wmi(wil, "delba_tx: (ring %d reason %d)\n", ringid, reason); return wmi_send(wil, WMI_VRING_BA_DIS_CMDID, &cmd, sizeof(cmd)); } @@ -1255,8 +1664,8 @@ int wmi_delba_rx(struct wil6210_priv *wil, u8 cidxtid, u16 reason) .reason = cpu_to_le16(reason), }; - wil_dbg_wmi(wil, "%s(CID %d TID %d reason %d)\n", __func__, - cidxtid & 0xf, (cidxtid >> 4) & 0xf, reason); + wil_dbg_wmi(wil, "delba_rx: (CID %d TID %d reason %d)\n", cidxtid & 0xf, + (cidxtid >> 4) & 0xf, reason); return wmi_send(wil, WMI_RCP_DELBA_CMDID, &cmd, sizeof(cmd)); } @@ -1279,7 +1688,7 @@ int wmi_addba_rx_resp(struct wil6210_priv *wil, u8 cid, u8 tid, u8 token, .ba_timeout = cpu_to_le16(timeout), }; struct { - struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cmd_hdr wmi; struct wmi_rcp_addba_resp_sent_event evt; } __packed reply; @@ -1302,16 +1711,340 @@ int wmi_addba_rx_resp(struct wil6210_priv *wil, u8 cid, u8 tid, u8 token, return rc; } +int wmi_ps_dev_profile_cfg(struct wil6210_priv *wil, + enum wmi_ps_profile_type ps_profile) +{ + int rc; + struct wmi_ps_dev_profile_cfg_cmd cmd = { + .ps_profile = ps_profile, + }; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_ps_dev_profile_cfg_event evt; + } __packed reply; + u32 status; + + wil_dbg_wmi(wil, "Setting ps dev profile %d\n", ps_profile); + + reply.evt.status = cpu_to_le32(WMI_PS_CFG_CMD_STATUS_ERROR); + + rc = wmi_call(wil, WMI_PS_DEV_PROFILE_CFG_CMDID, &cmd, sizeof(cmd), + WMI_PS_DEV_PROFILE_CFG_EVENTID, &reply, sizeof(reply), + 100); + if (rc) + return rc; + + status = le32_to_cpu(reply.evt.status); + + if (status != WMI_PS_CFG_CMD_STATUS_SUCCESS) { + wil_err(wil, "ps dev profile cfg failed with status %d\n", + status); + rc = -EINVAL; + } + + return rc; +} + +int wmi_set_mgmt_retry(struct wil6210_priv *wil, u8 retry_short) +{ + int rc; + struct wmi_set_mgmt_retry_limit_cmd cmd = { + .mgmt_retry_limit = retry_short, + }; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_set_mgmt_retry_limit_event evt; + } __packed reply; + + wil_dbg_wmi(wil, "Setting mgmt retry short %d\n", retry_short); + + if (!test_bit(WMI_FW_CAPABILITY_MGMT_RETRY_LIMIT, wil->fw_capabilities)) + return -ENOTSUPP; + + reply.evt.status = WMI_FW_STATUS_FAILURE; + + rc = wmi_call(wil, WMI_SET_MGMT_RETRY_LIMIT_CMDID, &cmd, sizeof(cmd), + WMI_SET_MGMT_RETRY_LIMIT_EVENTID, &reply, sizeof(reply), + 100); + if (rc) + return rc; + + if (reply.evt.status != WMI_FW_STATUS_SUCCESS) { + wil_err(wil, "set mgmt retry limit failed with status %d\n", + reply.evt.status); + rc = -EINVAL; + } + + return rc; +} + +int wmi_get_mgmt_retry(struct wil6210_priv *wil, u8 *retry_short) +{ + int rc; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_get_mgmt_retry_limit_event evt; + } __packed reply; + + wil_dbg_wmi(wil, "getting mgmt retry short\n"); + + if (!test_bit(WMI_FW_CAPABILITY_MGMT_RETRY_LIMIT, wil->fw_capabilities)) + return -ENOTSUPP; + + reply.evt.mgmt_retry_limit = 0; + rc = wmi_call(wil, WMI_GET_MGMT_RETRY_LIMIT_CMDID, NULL, 0, + WMI_GET_MGMT_RETRY_LIMIT_EVENTID, &reply, sizeof(reply), + 100); + if (rc) + return rc; + + if (retry_short) + *retry_short = reply.evt.mgmt_retry_limit; + + return 0; +} + +int wmi_abort_scan(struct wil6210_priv *wil) +{ + int rc; + + wil_dbg_wmi(wil, "sending WMI_ABORT_SCAN_CMDID\n"); + + rc = wmi_send(wil, WMI_ABORT_SCAN_CMDID, NULL, 0); + if (rc) + wil_err(wil, "Failed to abort scan (%d)\n", rc); + + return rc; +} + +int wmi_new_sta(struct wil6210_priv *wil, const u8 *mac, u8 aid) +{ + int rc; + struct wmi_new_sta_cmd cmd = { + .aid = aid, + }; + + wil_dbg_wmi(wil, "new sta %pM, aid %d\n", mac, aid); + + ether_addr_copy(cmd.dst_mac, mac); + + rc = wmi_send(wil, WMI_NEW_STA_CMDID, &cmd, sizeof(cmd)); + if (rc) + wil_err(wil, "Failed to send new sta (%d)\n", rc); + + return rc; +} + +int wmi_set_tt_cfg(struct wil6210_priv *wil, struct wmi_tt_data *tt_data) +{ + int rc; + struct wmi_set_thermal_throttling_cfg_cmd cmd = { + .tt_data = *tt_data, + }; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_set_thermal_throttling_cfg_event evt; + } __packed reply; + + if (!test_bit(WMI_FW_CAPABILITY_THERMAL_THROTTLING, + wil->fw_capabilities)) + return -EOPNOTSUPP; + + memset(&reply, 0, sizeof(reply)); + rc = wmi_call(wil, WMI_SET_THERMAL_THROTTLING_CFG_CMDID, &cmd, + sizeof(cmd), WMI_SET_THERMAL_THROTTLING_CFG_EVENTID, + &reply, sizeof(reply), 100); + if (rc) { + wil_err(wil, "failed to set thermal throttling\n"); + return rc; + } + if (reply.evt.status) { + wil_err(wil, "set thermal throttling failed, error %d\n", + reply.evt.status); + return -EIO; + } + + wil->tt_data = *tt_data; + wil->tt_data_set = true; + + return 0; +} + +int wmi_get_tt_cfg(struct wil6210_priv *wil, struct wmi_tt_data *tt_data) +{ + int rc; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_get_thermal_throttling_cfg_event evt; + } __packed reply; + + if (!test_bit(WMI_FW_CAPABILITY_THERMAL_THROTTLING, + wil->fw_capabilities)) + return -EOPNOTSUPP; + + rc = wmi_call(wil, WMI_GET_THERMAL_THROTTLING_CFG_CMDID, NULL, 0, + WMI_GET_THERMAL_THROTTLING_CFG_EVENTID, &reply, + sizeof(reply), 100); + if (rc) { + wil_err(wil, "failed to get thermal throttling\n"); + return rc; + } + + if (tt_data) + *tt_data = reply.evt.tt_data; + + return 0; +} + void wmi_event_flush(struct wil6210_priv *wil) { + ulong flags; struct pending_wmi_event *evt, *t; - wil_dbg_wmi(wil, "%s()\n", __func__); + wil_dbg_wmi(wil, "event_flush\n"); + + spin_lock_irqsave(&wil->wmi_ev_lock, flags); list_for_each_entry_safe(evt, t, &wil->pending_wmi_ev, list) { list_del(&evt->list); kfree(evt); } + + spin_unlock_irqrestore(&wil->wmi_ev_lock, flags); +} + +int wmi_link_maintain_cfg_write(struct wil6210_priv *wil, + const u8 *addr, + bool fst_link_loss) +{ + int rc; + int cid = wil_find_cid(wil, addr); + u32 cfg_type; + struct wmi_link_maintain_cfg_write_cmd cmd; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_link_maintain_cfg_write_done_event evt; + } __packed reply; + + if (cid < 0) + return cid; + + switch (wil->wdev->iftype) { + case NL80211_IFTYPE_STATION: + cfg_type = fst_link_loss ? + WMI_LINK_MAINTAIN_CFG_TYPE_DEFAULT_FST_STA : + WMI_LINK_MAINTAIN_CFG_TYPE_DEFAULT_NORMAL_STA; + break; + case NL80211_IFTYPE_AP: + cfg_type = fst_link_loss ? + WMI_LINK_MAINTAIN_CFG_TYPE_DEFAULT_FST_AP : + WMI_LINK_MAINTAIN_CFG_TYPE_DEFAULT_NORMAL_AP; + break; + default: + wil_err(wil, "Unsupported for iftype %d", wil->wdev->iftype); + return -EINVAL; + } + + wil_dbg_misc(wil, "Setting cid:%d with cfg_type:%d\n", cid, cfg_type); + + cmd.cfg_type = cpu_to_le32(cfg_type); + cmd.cid = cpu_to_le32(cid); + + reply.evt.status = cpu_to_le32(WMI_FW_STATUS_FAILURE); + + rc = wmi_call(wil, WMI_LINK_MAINTAIN_CFG_WRITE_CMDID, &cmd, sizeof(cmd), + WMI_LINK_MAINTAIN_CFG_WRITE_DONE_EVENTID, &reply, + sizeof(reply), 250); + if (rc) { + wil_err(wil, "Failed to %s FST link loss", + fst_link_loss ? "enable" : "disable"); + } else if (reply.evt.status == WMI_FW_STATUS_SUCCESS) { + wil->sta[cid].fst_link_loss = fst_link_loss; + } else { + wil_err(wil, "WMI_LINK_MAINTAIN_CFG_WRITE_CMDID returned status %d", + reply.evt.status); + rc = -EINVAL; + } + return rc; +} + +int wmi_suspend(struct wil6210_priv *wil) +{ + int rc; + struct wmi_traffic_suspend_cmd cmd = { + .wakeup_trigger = wil->wakeup_trigger, + }; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_traffic_suspend_event evt; + } __packed reply; + u32 suspend_to = WIL_WAIT_FOR_SUSPEND_RESUME_COMP; + + wil->suspend_resp_rcvd = false; + wil->suspend_resp_comp = false; + + reply.evt.status = WMI_TRAFFIC_SUSPEND_REJECTED; + + rc = wmi_call(wil, WMI_TRAFFIC_SUSPEND_CMDID, &cmd, sizeof(cmd), + WMI_TRAFFIC_SUSPEND_EVENTID, &reply, sizeof(reply), + suspend_to); + if (rc) { + wil_err(wil, "wmi_call for suspend req failed, rc=%d\n", rc); + if (rc == -ETIME) + /* wmi_call TO */ + wil->suspend_stats.rejected_by_device++; + else + wil->suspend_stats.rejected_by_host++; + goto out; + } + + wil_dbg_wmi(wil, "waiting for suspend_response_completed\n"); + + rc = wait_event_interruptible_timeout(wil->wq, + wil->suspend_resp_comp, + msecs_to_jiffies(suspend_to)); + if (rc == 0) { + wil_err(wil, "TO waiting for suspend_response_completed\n"); + if (wil->suspend_resp_rcvd) + /* Device responded but we TO due to another reason */ + wil->suspend_stats.rejected_by_host++; + else + wil->suspend_stats.rejected_by_device++; + rc = -EBUSY; + goto out; + } + + wil_dbg_wmi(wil, "suspend_response_completed rcvd\n"); + if (reply.evt.status == WMI_TRAFFIC_SUSPEND_REJECTED) { + wil_dbg_pm(wil, "device rejected the suspend\n"); + wil->suspend_stats.rejected_by_device++; + } + rc = reply.evt.status; + +out: + wil->suspend_resp_rcvd = false; + wil->suspend_resp_comp = false; + + return rc; +} + +int wmi_resume(struct wil6210_priv *wil) +{ + int rc; + struct { + struct wmi_cmd_hdr wmi; + struct wmi_traffic_resume_event evt; + } __packed reply; + + reply.evt.status = WMI_TRAFFIC_RESUME_FAILED; + + rc = wmi_call(wil, WMI_TRAFFIC_RESUME_CMDID, NULL, 0, + WMI_TRAFFIC_RESUME_EVENTID, &reply, sizeof(reply), + WIL_WAIT_FOR_SUSPEND_RESUME_COMP); + if (rc) + return rc; + + return reply.evt.status; } static bool wmi_evt_call_handler(struct wil6210_priv *wil, int id, @@ -1335,23 +2068,20 @@ static void wmi_event_handle(struct wil6210_priv *wil, u16 len = le16_to_cpu(hdr->len); if ((hdr->type == WIL_MBOX_HDR_TYPE_WMI) && - (len >= sizeof(struct wil6210_mbox_hdr_wmi))) { - struct wil6210_mbox_hdr_wmi *wmi = (void *)(&hdr[1]); + (len >= sizeof(struct wmi_cmd_hdr))) { + struct wmi_cmd_hdr *wmi = (void *)(&hdr[1]); void *evt_data = (void *)(&wmi[1]); - u16 id = le16_to_cpu(wmi->id); + u16 id = le16_to_cpu(wmi->command_id); wil_dbg_wmi(wil, "Handle WMI 0x%04x (reply_id 0x%04x)\n", id, wil->reply_id); /* check if someone waits for this event */ if (wil->reply_id && wil->reply_id == id) { - if (wil->reply_buf) { - memcpy(wil->reply_buf, wmi, - min(len, wil->reply_size)); - } else { - wmi_evt_call_handler(wil, id, evt_data, - len - sizeof(*wmi)); - } - wil_dbg_wmi(wil, "Complete WMI 0x%04x\n", id); + WARN_ON(wil->reply_buf); + wmi_evt_call_handler(wil, id, evt_data, + len - sizeof(*wmi)); + wil_dbg_wmi(wil, "event_handle: Complete WMI 0x%04x\n", + id); complete(&wil->wmi_call); return; } @@ -1398,11 +2128,73 @@ void wmi_event_worker(struct work_struct *work) struct pending_wmi_event *evt; struct list_head *lh; - wil_dbg_wmi(wil, "Start %s\n", __func__); + wil_dbg_wmi(wil, "event_worker: Start\n"); while ((lh = next_wmi_ev(wil)) != NULL) { evt = list_entry(lh, struct pending_wmi_event, list); wmi_event_handle(wil, &evt->event.hdr); kfree(evt); } - wil_dbg_wmi(wil, "Finished %s\n", __func__); + wil_dbg_wmi(wil, "event_worker: Finished\n"); +} + +bool wil_is_wmi_idle(struct wil6210_priv *wil) +{ + ulong flags; + struct wil6210_mbox_ring *r = &wil->mbox_ctl.rx; + bool rc = false; + + spin_lock_irqsave(&wil->wmi_ev_lock, flags); + + /* Check if there are pending WMI events in the events queue */ + if (!list_empty(&wil->pending_wmi_ev)) { + wil_dbg_pm(wil, "Pending WMI events in queue\n"); + goto out; + } + + /* Check if there is a pending WMI call */ + if (wil->reply_id) { + wil_dbg_pm(wil, "Pending WMI call\n"); + goto out; + } + + /* Check if there are pending RX events in mbox */ + r->head = wil_r(wil, RGF_MBOX + + offsetof(struct wil6210_mbox_ctl, rx.head)); + if (r->tail != r->head) + wil_dbg_pm(wil, "Pending WMI mbox events\n"); + else + rc = true; + +out: + spin_unlock_irqrestore(&wil->wmi_ev_lock, flags); + return rc; +} + +int wmi_set_snr_thresh(struct wil6210_priv *wil, short omni, short direct) +{ + int rc; + struct wmi_set_connect_snr_thr_cmd cmd = { + .enable = true, + .omni_snr_thr = cpu_to_le16(omni), + .direct_snr_thr = cpu_to_le16(direct), + }; + + if (!test_bit(WMI_FW_CAPABILITY_CONNECT_SNR_THR, wil->fw_capabilities)) + return -ENOTSUPP; + + if (omni == 0 && direct == 0) + cmd.enable = false; + + wil_dbg_wmi(wil, "%s snr thresh omni=%d, direct=%d (1/4 dB units)\n", + cmd.enable ? "enable" : "disable", omni, direct); + + rc = wmi_send(wil, WMI_SET_CONNECT_SNR_THR_CMDID, &cmd, sizeof(cmd)); + if (rc) + return rc; + + wil->snr_thresh.enabled = cmd.enable; + wil->snr_thresh.omni = omni; + wil->snr_thresh.direct = direct; + + return 0; } diff --git a/drivers/net/wireless/ath/wil6210/wmi.h b/drivers/net/wireless/ath/wil6210/wmi.h index 6e90e78f1554..35752885320e 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.h +++ b/drivers/net/wireless/ath/wil6210/wmi.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2012-2015 Qualcomm Atheros, Inc. - * Copyright (c) 2006-2012 Wilocity . + * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. + * Copyright (c) 2006-2012 Wilocity * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,187 +17,271 @@ /* * This file contains the definitions of the WMI protocol specified in the - * Wireless Module Interface (WMI) for the Wilocity - * MARLON 60 Gigabit wireless solution. + * Wireless Module Interface (WMI) for the Qualcomm + * 60 GHz wireless solution. * It includes definitions of all the commands and events. * Commands are messages from the host to the WM. * Events are messages from the WM to the host. + * + * This is an automatically generated file. */ #ifndef __WILOCITY_WMI_H__ #define __WILOCITY_WMI_H__ /* General */ -#define WILOCITY_MAX_ASSOC_STA (8) -#define WILOCITY_DEFAULT_ASSOC_STA (1) -#define WMI_MAC_LEN (6) -#define WMI_PROX_RANGE_NUM (3) -#define WMI_MAX_LOSS_DMG_BEACONS (32) +#define WMI_MAX_ASSOC_STA (8) +#define WMI_DEFAULT_ASSOC_STA (1) +#define WMI_MAC_LEN (6) +#define WMI_PROX_RANGE_NUM (3) +#define WMI_MAX_LOSS_DMG_BEACONS (20) +#define MAX_NUM_OF_SECTORS (128) +#define WMI_SCHED_MAX_ALLOCS_PER_CMD (4) +#define WMI_RF_DTYPE_LENGTH (3) +#define WMI_RF_ETYPE_LENGTH (3) +#define WMI_RF_RX2TX_LENGTH (3) +#define WMI_RF_ETYPE_VAL_PER_RANGE (5) + +/* Mailbox interface + * used for commands and events + */ +enum wmi_mid { + MID_DEFAULT = 0x00, + FIRST_DBG_MID_ID = 0x10, + LAST_DBG_MID_ID = 0xFE, + MID_BROADCAST = 0xFF, +}; + +/* FW capability IDs + * Each ID maps to a bit in a 32-bit bitmask value provided by the FW to + * the host + */ +enum wmi_fw_capability { + WMI_FW_CAPABILITY_FTM = 0, + WMI_FW_CAPABILITY_PS_CONFIG = 1, + WMI_FW_CAPABILITY_RF_SECTORS = 2, + WMI_FW_CAPABILITY_MGMT_RETRY_LIMIT = 3, + WMI_FW_CAPABILITY_DISABLE_AP_SME = 4, + WMI_FW_CAPABILITY_WMI_ONLY = 5, + WMI_FW_CAPABILITY_THERMAL_THROTTLING = 7, + WMI_FW_CAPABILITY_D3_SUSPEND = 8, + WMI_FW_CAPABILITY_LONG_RANGE = 9, + WMI_FW_CAPABILITY_FIXED_SCHEDULING = 10, + WMI_FW_CAPABILITY_MULTI_DIRECTED_OMNIS = 11, + WMI_FW_CAPABILITY_RSSI_REPORTING = 12, + WMI_FW_CAPABILITY_SET_SILENT_RSSI_TABLE = 13, + WMI_FW_CAPABILITY_LO_POWER_CALIB_FROM_OTP = 14, + WMI_FW_CAPABILITY_CONNECT_SNR_THR = 16, + WMI_FW_CAPABILITY_MAX, +}; + +/* WMI_CMD_HDR */ +struct wmi_cmd_hdr { + u8 mid; + u8 reserved; + __le16 command_id; + __le32 fw_timestamp; +} __packed; /* List of Commands */ enum wmi_command_id { - WMI_CONNECT_CMDID = 0x0001, - WMI_DISCONNECT_CMDID = 0x0003, - WMI_DISCONNECT_STA_CMDID = 0x0004, - WMI_START_SCAN_CMDID = 0x0007, - WMI_SET_BSS_FILTER_CMDID = 0x0009, - WMI_SET_PROBED_SSID_CMDID = 0x000a, - WMI_SET_LISTEN_INT_CMDID = 0x000b, - WMI_BCON_CTRL_CMDID = 0x000f, - WMI_ADD_CIPHER_KEY_CMDID = 0x0016, - WMI_DELETE_CIPHER_KEY_CMDID = 0x0017, - WMI_SET_APPIE_CMDID = 0x003f, - WMI_SET_WSC_STATUS_CMDID = 0x0041, - WMI_PXMT_RANGE_CFG_CMDID = 0x0042, - WMI_PXMT_SNR2_RANGE_CFG_CMDID = 0x0043, -/* WMI_FAST_MEM_ACC_MODE_CMDID = 0x0300, */ - WMI_MEM_READ_CMDID = 0x0800, - WMI_MEM_WR_CMDID = 0x0801, - WMI_ECHO_CMDID = 0x0803, - WMI_DEEP_ECHO_CMDID = 0x0804, - WMI_CONFIG_MAC_CMDID = 0x0805, - WMI_CONFIG_PHY_DEBUG_CMDID = 0x0806, - WMI_ADD_DEBUG_TX_PCKT_CMDID = 0x0808, - WMI_PHY_GET_STATISTICS_CMDID = 0x0809, - WMI_FS_TUNE_CMDID = 0x080a, - WMI_CORR_MEASURE_CMDID = 0x080b, - WMI_READ_RSSI_CMDID = 0x080c, - WMI_TEMP_SENSE_CMDID = 0x080e, - WMI_DC_CALIB_CMDID = 0x080f, - WMI_SEND_TONE_CMDID = 0x0810, - WMI_IQ_TX_CALIB_CMDID = 0x0811, - WMI_IQ_RX_CALIB_CMDID = 0x0812, - WMI_SET_UCODE_IDLE_CMDID = 0x0813, - WMI_SET_WORK_MODE_CMDID = 0x0815, - WMI_LO_LEAKAGE_CALIB_CMDID = 0x0816, - WMI_MARLON_R_READ_CMDID = 0x0818, - WMI_MARLON_R_WRITE_CMDID = 0x0819, - WMI_MARLON_R_TXRX_SEL_CMDID = 0x081a, - MAC_IO_STATIC_PARAMS_CMDID = 0x081b, - MAC_IO_DYNAMIC_PARAMS_CMDID = 0x081c, - WMI_SILENT_RSSI_CALIB_CMDID = 0x081d, - WMI_RF_RX_TEST_CMDID = 0x081e, - WMI_CFG_RX_CHAIN_CMDID = 0x0820, - WMI_VRING_CFG_CMDID = 0x0821, - WMI_BCAST_VRING_CFG_CMDID = 0x0822, - WMI_VRING_BA_EN_CMDID = 0x0823, - WMI_VRING_BA_DIS_CMDID = 0x0824, - WMI_RCP_ADDBA_RESP_CMDID = 0x0825, - WMI_RCP_DELBA_CMDID = 0x0826, - WMI_SET_SSID_CMDID = 0x0827, - WMI_GET_SSID_CMDID = 0x0828, - WMI_SET_PCP_CHANNEL_CMDID = 0x0829, - WMI_GET_PCP_CHANNEL_CMDID = 0x082a, - WMI_SW_TX_REQ_CMDID = 0x082b, - WMI_READ_MAC_RXQ_CMDID = 0x0830, - WMI_READ_MAC_TXQ_CMDID = 0x0831, - WMI_WRITE_MAC_RXQ_CMDID = 0x0832, - WMI_WRITE_MAC_TXQ_CMDID = 0x0833, - WMI_WRITE_MAC_XQ_FIELD_CMDID = 0x0834, - WMI_MLME_PUSH_CMDID = 0x0835, - WMI_BEAMFORMING_MGMT_CMDID = 0x0836, - WMI_BF_TXSS_MGMT_CMDID = 0x0837, - WMI_BF_SM_MGMT_CMDID = 0x0838, - WMI_BF_RXSS_MGMT_CMDID = 0x0839, - WMI_BF_TRIG_CMDID = 0x083A, - WMI_SET_SECTORS_CMDID = 0x0849, - WMI_MAINTAIN_PAUSE_CMDID = 0x0850, - WMI_MAINTAIN_RESUME_CMDID = 0x0851, - WMI_RS_MGMT_CMDID = 0x0852, - WMI_RF_MGMT_CMDID = 0x0853, - WMI_THERMAL_THROTTLING_CTRL_CMDID = 0x0854, - WMI_THERMAL_THROTTLING_GET_STATUS_CMDID = 0x0855, + WMI_CONNECT_CMDID = 0x01, + WMI_DISCONNECT_CMDID = 0x03, + WMI_DISCONNECT_STA_CMDID = 0x04, + WMI_START_SCAN_CMDID = 0x07, + WMI_SET_BSS_FILTER_CMDID = 0x09, + WMI_SET_PROBED_SSID_CMDID = 0x0A, + /* deprecated */ + WMI_SET_LISTEN_INT_CMDID = 0x0B, + WMI_BCON_CTRL_CMDID = 0x0F, + WMI_ADD_CIPHER_KEY_CMDID = 0x16, + WMI_DELETE_CIPHER_KEY_CMDID = 0x17, + WMI_PCP_CONF_CMDID = 0x18, + WMI_SET_APPIE_CMDID = 0x3F, + WMI_SET_WSC_STATUS_CMDID = 0x41, + WMI_PXMT_RANGE_CFG_CMDID = 0x42, + WMI_PXMT_SNR2_RANGE_CFG_CMDID = 0x43, + WMI_MEM_READ_CMDID = 0x800, + WMI_MEM_WR_CMDID = 0x801, + WMI_ECHO_CMDID = 0x803, + WMI_DEEP_ECHO_CMDID = 0x804, + WMI_CONFIG_MAC_CMDID = 0x805, + /* deprecated */ + WMI_CONFIG_PHY_DEBUG_CMDID = 0x806, + WMI_ADD_DEBUG_TX_PCKT_CMDID = 0x808, + WMI_PHY_GET_STATISTICS_CMDID = 0x809, + /* deprecated */ + WMI_FS_TUNE_CMDID = 0x80A, + /* deprecated */ + WMI_CORR_MEASURE_CMDID = 0x80B, + WMI_READ_RSSI_CMDID = 0x80C, + WMI_TEMP_SENSE_CMDID = 0x80E, + WMI_DC_CALIB_CMDID = 0x80F, + /* deprecated */ + WMI_SEND_TONE_CMDID = 0x810, + /* deprecated */ + WMI_IQ_TX_CALIB_CMDID = 0x811, + /* deprecated */ + WMI_IQ_RX_CALIB_CMDID = 0x812, + WMI_SET_WORK_MODE_CMDID = 0x815, + WMI_LO_LEAKAGE_CALIB_CMDID = 0x816, + WMI_LO_POWER_CALIB_FROM_OTP_CMDID = 0x817, + WMI_SILENT_RSSI_CALIB_CMDID = 0x81D, + /* deprecated */ + WMI_RF_RX_TEST_CMDID = 0x81E, + WMI_CFG_RX_CHAIN_CMDID = 0x820, + WMI_VRING_CFG_CMDID = 0x821, + WMI_BCAST_VRING_CFG_CMDID = 0x822, + WMI_VRING_BA_EN_CMDID = 0x823, + WMI_VRING_BA_DIS_CMDID = 0x824, + WMI_RCP_ADDBA_RESP_CMDID = 0x825, + WMI_RCP_DELBA_CMDID = 0x826, + WMI_SET_SSID_CMDID = 0x827, + WMI_GET_SSID_CMDID = 0x828, + WMI_SET_PCP_CHANNEL_CMDID = 0x829, + WMI_GET_PCP_CHANNEL_CMDID = 0x82A, + WMI_SW_TX_REQ_CMDID = 0x82B, + WMI_MLME_PUSH_CMDID = 0x835, + WMI_BEAMFORMING_MGMT_CMDID = 0x836, + WMI_BF_TXSS_MGMT_CMDID = 0x837, + WMI_BF_SM_MGMT_CMDID = 0x838, + WMI_BF_RXSS_MGMT_CMDID = 0x839, + WMI_BF_TRIG_CMDID = 0x83A, + WMI_LINK_MAINTAIN_CFG_WRITE_CMDID = 0x842, + WMI_LINK_MAINTAIN_CFG_READ_CMDID = 0x843, + WMI_SET_SECTORS_CMDID = 0x849, + WMI_MAINTAIN_PAUSE_CMDID = 0x850, + WMI_MAINTAIN_RESUME_CMDID = 0x851, + WMI_RS_MGMT_CMDID = 0x852, + WMI_RF_MGMT_CMDID = 0x853, + WMI_RF_XPM_READ_CMDID = 0x856, + WMI_RF_XPM_WRITE_CMDID = 0x857, + WMI_LED_CFG_CMDID = 0x858, + WMI_SET_CONNECT_SNR_THR_CMDID = 0x85B, + WMI_SET_ACTIVE_SILENT_RSSI_TABLE_CMDID = 0x85C, + WMI_RF_PWR_ON_DELAY_CMDID = 0x85D, + WMI_SET_HIGH_POWER_TABLE_PARAMS_CMDID = 0x85E, /* Performance monitoring commands */ - WMI_BF_CTRL_CMDID = 0x0862, - WMI_NOTIFY_REQ_CMDID = 0x0863, - WMI_GET_STATUS_CMDID = 0x0864, - WMI_UNIT_TEST_CMDID = 0x0900, - WMI_HICCUP_CMDID = 0x0901, - WMI_FLASH_READ_CMDID = 0x0902, - WMI_FLASH_WRITE_CMDID = 0x0903, - WMI_SECURITY_UNIT_TEST_CMDID = 0x0904, - /*P2P*/ - WMI_P2P_CFG_CMDID = 0x0910, - WMI_PORT_ALLOCATE_CMDID = 0x0911, - WMI_PORT_DELETE_CMDID = 0x0912, - WMI_POWER_MGMT_CFG_CMDID = 0x0913, - WMI_START_LISTEN_CMDID = 0x0914, - WMI_START_SEARCH_CMDID = 0x0915, - WMI_DISCOVERY_START_CMDID = 0x0916, - WMI_DISCOVERY_STOP_CMDID = 0x0917, - WMI_PCP_START_CMDID = 0x0918, - WMI_PCP_STOP_CMDID = 0x0919, - WMI_GET_PCP_FACTOR_CMDID = 0x091b, - - WMI_SET_MAC_ADDRESS_CMDID = 0xf003, - WMI_ABORT_SCAN_CMDID = 0xf007, - WMI_SET_PMK_CMDID = 0xf028, - - WMI_SET_PROMISCUOUS_MODE_CMDID = 0xf041, - WMI_GET_PMK_CMDID = 0xf048, - WMI_SET_PASSPHRASE_CMDID = 0xf049, - WMI_SEND_ASSOC_RES_CMDID = 0xf04a, - WMI_SET_ASSOC_REQ_RELAY_CMDID = 0xf04b, - WMI_EAPOL_TX_CMDID = 0xf04c, - WMI_MAC_ADDR_REQ_CMDID = 0xf04d, - WMI_FW_VER_CMDID = 0xf04e, - WMI_PMC_CMDID = 0xf04f, + WMI_BF_CTRL_CMDID = 0x862, + WMI_NOTIFY_REQ_CMDID = 0x863, + WMI_GET_STATUS_CMDID = 0x864, + WMI_GET_RF_STATUS_CMDID = 0x866, + WMI_GET_BASEBAND_TYPE_CMDID = 0x867, + WMI_UNIT_TEST_CMDID = 0x900, + WMI_FLASH_READ_CMDID = 0x902, + WMI_FLASH_WRITE_CMDID = 0x903, + /* Power management */ + WMI_TRAFFIC_SUSPEND_CMDID = 0x904, + WMI_TRAFFIC_RESUME_CMDID = 0x905, + /* P2P */ + WMI_P2P_CFG_CMDID = 0x910, + WMI_PORT_ALLOCATE_CMDID = 0x911, + WMI_PORT_DELETE_CMDID = 0x912, + WMI_POWER_MGMT_CFG_CMDID = 0x913, + WMI_START_LISTEN_CMDID = 0x914, + WMI_START_SEARCH_CMDID = 0x915, + WMI_DISCOVERY_START_CMDID = 0x916, + WMI_DISCOVERY_STOP_CMDID = 0x917, + WMI_PCP_START_CMDID = 0x918, + WMI_PCP_STOP_CMDID = 0x919, + WMI_GET_PCP_FACTOR_CMDID = 0x91B, + /* Power Save Configuration Commands */ + WMI_PS_DEV_PROFILE_CFG_CMDID = 0x91C, + WMI_RS_CFG_CMDID = 0x921, + WMI_GET_DETAILED_RS_RES_CMDID = 0x922, + WMI_AOA_MEAS_CMDID = 0x923, + WMI_BRP_SET_ANT_LIMIT_CMDID = 0x924, + WMI_SET_MGMT_RETRY_LIMIT_CMDID = 0x930, + WMI_GET_MGMT_RETRY_LIMIT_CMDID = 0x931, + WMI_NEW_STA_CMDID = 0x935, + WMI_DEL_STA_CMDID = 0x936, + WMI_SET_THERMAL_THROTTLING_CFG_CMDID = 0x940, + WMI_GET_THERMAL_THROTTLING_CFG_CMDID = 0x941, + /* Read Power Save profile type */ + WMI_PS_DEV_PROFILE_CFG_READ_CMDID = 0x942, + WMI_TOF_SESSION_START_CMDID = 0x991, + WMI_TOF_GET_CAPABILITIES_CMDID = 0x992, + WMI_TOF_SET_LCR_CMDID = 0x993, + WMI_TOF_SET_LCI_CMDID = 0x994, + WMI_TOF_CFG_RESPONDER_CMDID = 0x996, + WMI_TOF_SET_TX_RX_OFFSET_CMDID = 0x997, + WMI_TOF_GET_TX_RX_OFFSET_CMDID = 0x998, + WMI_TOF_CHANNEL_INFO_CMDID = 0x999, + WMI_GET_RF_SECTOR_PARAMS_CMDID = 0x9A0, + WMI_SET_RF_SECTOR_PARAMS_CMDID = 0x9A1, + WMI_GET_SELECTED_RF_SECTOR_INDEX_CMDID = 0x9A2, + WMI_SET_SELECTED_RF_SECTOR_INDEX_CMDID = 0x9A3, + WMI_SET_RF_SECTOR_ON_CMDID = 0x9A4, + WMI_PRIO_TX_SECTORS_ORDER_CMDID = 0x9A5, + WMI_PRIO_TX_SECTORS_NUMBER_CMDID = 0x9A6, + WMI_PRIO_TX_SECTORS_SET_DEFAULT_CFG_CMDID = 0x9A7, + WMI_SCHEDULING_SCHEME_CMDID = 0xA01, + WMI_FIXED_SCHEDULING_CONFIG_CMDID = 0xA02, + WMI_ENABLE_FIXED_SCHEDULING_CMDID = 0xA03, + WMI_SET_MULTI_DIRECTED_OMNIS_CONFIG_CMDID = 0xA04, + WMI_SET_LONG_RANGE_CONFIG_CMDID = 0xA05, + WMI_SET_MAC_ADDRESS_CMDID = 0xF003, + WMI_ABORT_SCAN_CMDID = 0xF007, + WMI_SET_PROMISCUOUS_MODE_CMDID = 0xF041, + /* deprecated */ + WMI_GET_PMK_CMDID = 0xF048, + WMI_SET_PASSPHRASE_CMDID = 0xF049, + /* deprecated */ + WMI_SEND_ASSOC_RES_CMDID = 0xF04A, + /* deprecated */ + WMI_SET_ASSOC_REQ_RELAY_CMDID = 0xF04B, + WMI_MAC_ADDR_REQ_CMDID = 0xF04D, + WMI_FW_VER_CMDID = 0xF04E, + WMI_PMC_CMDID = 0xF04F, }; -/* - * Commands data structures - */ - -/* - * WMI_CONNECT_CMDID - */ +/* WMI_CONNECT_CMDID */ enum wmi_network_type { WMI_NETTYPE_INFRA = 0x01, WMI_NETTYPE_ADHOC = 0x02, WMI_NETTYPE_ADHOC_CREATOR = 0x04, WMI_NETTYPE_AP = 0x10, WMI_NETTYPE_P2P = 0x20, - WMI_NETTYPE_WBE = 0x40, /* PCIE over 60g */ + /* PCIE over 60g */ + WMI_NETTYPE_WBE = 0x40, }; enum wmi_dot11_auth_mode { - WMI_AUTH11_OPEN = 0x01, - WMI_AUTH11_SHARED = 0x02, - WMI_AUTH11_LEAP = 0x04, - WMI_AUTH11_WSC = 0x08, + WMI_AUTH11_OPEN = 0x01, + WMI_AUTH11_SHARED = 0x02, + WMI_AUTH11_LEAP = 0x04, + WMI_AUTH11_WSC = 0x08, }; enum wmi_auth_mode { - WMI_AUTH_NONE = 0x01, - WMI_AUTH_WPA = 0x02, - WMI_AUTH_WPA2 = 0x04, - WMI_AUTH_WPA_PSK = 0x08, - WMI_AUTH_WPA2_PSK = 0x10, - WMI_AUTH_WPA_CCKM = 0x20, - WMI_AUTH_WPA2_CCKM = 0x40, + WMI_AUTH_NONE = 0x01, + WMI_AUTH_WPA = 0x02, + WMI_AUTH_WPA2 = 0x04, + WMI_AUTH_WPA_PSK = 0x08, + WMI_AUTH_WPA2_PSK = 0x10, + WMI_AUTH_WPA_CCKM = 0x20, + WMI_AUTH_WPA2_CCKM = 0x40, }; enum wmi_crypto_type { - WMI_CRYPT_NONE = 0x01, - WMI_CRYPT_WEP = 0x02, - WMI_CRYPT_TKIP = 0x04, - WMI_CRYPT_AES = 0x08, - WMI_CRYPT_AES_GCMP = 0x20, + WMI_CRYPT_NONE = 0x01, + WMI_CRYPT_AES_GCMP = 0x20, }; enum wmi_connect_ctrl_flag_bits { - WMI_CONNECT_ASSOC_POLICY_USER = 0x0001, - WMI_CONNECT_SEND_REASSOC = 0x0002, - WMI_CONNECT_IGNORE_WPA_GROUP_CIPHER = 0x0004, - WMI_CONNECT_PROFILE_MATCH_DONE = 0x0008, - WMI_CONNECT_IGNORE_AAC_BEACON = 0x0010, - WMI_CONNECT_CSA_FOLLOW_BSS = 0x0020, - WMI_CONNECT_DO_WPA_OFFLOAD = 0x0040, - WMI_CONNECT_DO_NOT_DEAUTH = 0x0080, + WMI_CONNECT_ASSOC_POLICY_USER = 0x01, + WMI_CONNECT_SEND_REASSOC = 0x02, + WMI_CONNECT_IGNORE_WPA_GROUP_CIPHER = 0x04, + WMI_CONNECT_PROFILE_MATCH_DONE = 0x08, + WMI_CONNECT_IGNORE_AAC_BEACON = 0x10, + WMI_CONNECT_CSA_FOLLOW_BSS = 0x20, + WMI_CONNECT_DO_WPA_OFFLOAD = 0x40, + WMI_CONNECT_DO_NOT_DEAUTH = 0x80, }; -#define WMI_MAX_SSID_LEN (32) +#define WMI_MAX_SSID_LEN (32) +/* WMI_CONNECT_CMDID */ struct wmi_connect_cmd { u8 network_type; u8 dot11_auth_mode; @@ -216,31 +300,17 @@ struct wmi_connect_cmd { u8 reserved1[2]; } __packed; -/* - * WMI_DISCONNECT_STA_CMDID - */ +/* WMI_DISCONNECT_STA_CMDID */ struct wmi_disconnect_sta_cmd { u8 dst_mac[WMI_MAC_LEN]; __le16 disconnect_reason; } __packed; -/* - * WMI_SET_PMK_CMDID - */ - -#define WMI_MIN_KEY_INDEX (0) #define WMI_MAX_KEY_INDEX (3) #define WMI_MAX_KEY_LEN (32) #define WMI_PASSPHRASE_LEN (64) -#define WMI_PMK_LEN (32) - -struct wmi_set_pmk_cmd { - u8 pmk[WMI_PMK_LEN]; -} __packed; -/* - * WMI_SET_PASSPHRASE_CMDID - */ +/* WMI_SET_PASSPHRASE_CMDID */ struct wmi_set_passphrase_cmd { u8 ssid[WMI_MAX_SSID_LEN]; u8 passphrase[WMI_PASSPHRASE_LEN]; @@ -248,36 +318,34 @@ struct wmi_set_passphrase_cmd { u8 passphrase_len; } __packed; -/* - * WMI_ADD_CIPHER_KEY_CMDID - */ +/* WMI_ADD_CIPHER_KEY_CMDID */ enum wmi_key_usage { - WMI_KEY_USE_PAIRWISE = 0, - WMI_KEY_USE_RX_GROUP = 1, - WMI_KEY_USE_TX_GROUP = 2, + WMI_KEY_USE_PAIRWISE = 0x00, + WMI_KEY_USE_RX_GROUP = 0x01, + WMI_KEY_USE_TX_GROUP = 0x02, }; struct wmi_add_cipher_key_cmd { u8 key_index; u8 key_type; - u8 key_usage; /* enum wmi_key_usage */ + /* enum wmi_key_usage */ + u8 key_usage; u8 key_len; - u8 key_rsc[8]; /* key replay sequence counter */ + /* key replay sequence counter */ + u8 key_rsc[8]; u8 key[WMI_MAX_KEY_LEN]; - u8 key_op_ctrl; /* Additional Key Control information */ + /* Additional Key Control information */ + u8 key_op_ctrl; u8 mac[WMI_MAC_LEN]; } __packed; -/* - * WMI_DELETE_CIPHER_KEY_CMDID - */ +/* WMI_DELETE_CIPHER_KEY_CMDID */ struct wmi_delete_cipher_key_cmd { u8 key_index; u8 mac[WMI_MAC_LEN]; } __packed; -/* - * WMI_START_SCAN_CMDID +/* WMI_START_SCAN_CMDID * * Start L1 scan operation * @@ -286,146 +354,126 @@ struct wmi_delete_cipher_key_cmd { * - WMI_SCAN_COMPLETE_EVENTID */ enum wmi_scan_type { - WMI_LONG_SCAN = 0, - WMI_SHORT_SCAN = 1, - WMI_PBC_SCAN = 2, - WMI_DIRECT_SCAN = 3, - WMI_ACTIVE_SCAN = 4, + WMI_ACTIVE_SCAN = 0x00, + WMI_SHORT_SCAN = 0x01, + WMI_PASSIVE_SCAN = 0x02, + WMI_DIRECT_SCAN = 0x03, + WMI_LONG_SCAN = 0x04, }; +/* WMI_START_SCAN_CMDID */ struct wmi_start_scan_cmd { - u8 direct_scan_mac_addr[6]; - u8 reserved[2]; - __le32 home_dwell_time; /* Max duration in the home channel(ms) */ - __le32 force_scan_interval; /* Time interval between scans (ms)*/ - u8 scan_type; /* wmi_scan_type */ - u8 num_channels; /* how many channels follow */ + u8 direct_scan_mac_addr[WMI_MAC_LEN]; + /* run scan with discovery beacon. Relevant for ACTIVE scan only. */ + u8 discovery_mode; + u8 reserved; + /* Max duration in the home channel(ms) */ + __le32 dwell_time; + /* Time interval between scans (ms) */ + __le32 force_scan_interval; + /* enum wmi_scan_type */ + u8 scan_type; + /* how many channels follow */ + u8 num_channels; + /* channels ID's: + * 0 - 58320 MHz + * 1 - 60480 MHz + * 2 - 62640 MHz + */ struct { u8 channel; u8 reserved; - } channel_list[0]; /* channels ID's */ - /* 0 - 58320 MHz */ - /* 1 - 60480 MHz */ - /* 2 - 62640 MHz */ + } channel_list[0]; } __packed; -/* - * WMI_SET_PROBED_SSID_CMDID - */ +/* WMI_SET_PROBED_SSID_CMDID */ #define MAX_PROBED_SSID_INDEX (3) enum wmi_ssid_flag { - WMI_SSID_FLAG_DISABLE = 0, /* disables entry */ - WMI_SSID_FLAG_SPECIFIC = 1, /* probes specified ssid */ - WMI_SSID_FLAG_ANY = 2, /* probes for any ssid */ + /* disables entry */ + WMI_SSID_FLAG_DISABLE = 0x00, + /* probes specified ssid */ + WMI_SSID_FLAG_SPECIFIC = 0x01, + /* probes for any ssid */ + WMI_SSID_FLAG_ANY = 0x02, }; struct wmi_probed_ssid_cmd { - u8 entry_index; /* 0 to MAX_PROBED_SSID_INDEX */ - u8 flag; /* enum wmi_ssid_flag */ + /* 0 to MAX_PROBED_SSID_INDEX */ + u8 entry_index; + /* enum wmi_ssid_flag */ + u8 flag; u8 ssid_len; u8 ssid[WMI_MAX_SSID_LEN]; } __packed; -/* - * WMI_SET_APPIE_CMDID +/* WMI_SET_APPIE_CMDID * Add Application specified IE to a management frame */ -#define WMI_MAX_IE_LEN (1024) +#define WMI_MAX_IE_LEN (1024) -/* - * Frame Types - */ +/* Frame Types */ enum wmi_mgmt_frame_type { - WMI_FRAME_BEACON = 0, - WMI_FRAME_PROBE_REQ = 1, - WMI_FRAME_PROBE_RESP = 2, - WMI_FRAME_ASSOC_REQ = 3, - WMI_FRAME_ASSOC_RESP = 4, - WMI_NUM_MGMT_FRAME, + WMI_FRAME_BEACON = 0x00, + WMI_FRAME_PROBE_REQ = 0x01, + WMI_FRAME_PROBE_RESP = 0x02, + WMI_FRAME_ASSOC_REQ = 0x03, + WMI_FRAME_ASSOC_RESP = 0x04, + WMI_NUM_MGMT_FRAME = 0x05, }; struct wmi_set_appie_cmd { - u8 mgmt_frm_type; /* enum wmi_mgmt_frame_type */ + /* enum wmi_mgmt_frame_type */ + u8 mgmt_frm_type; u8 reserved; - __le16 ie_len; /* Length of the IE to be added to MGMT frame */ + /* Length of the IE to be added to MGMT frame */ + __le16 ie_len; u8 ie_info[0]; } __packed; -/* - * WMI_PXMT_RANGE_CFG_CMDID - */ +/* WMI_PXMT_RANGE_CFG_CMDID */ struct wmi_pxmt_range_cfg_cmd { u8 dst_mac[WMI_MAC_LEN]; __le16 range; } __packed; -/* - * WMI_PXMT_SNR2_RANGE_CFG_CMDID - */ +/* WMI_PXMT_SNR2_RANGE_CFG_CMDID */ struct wmi_pxmt_snr2_range_cfg_cmd { - s8 snr2range_arr[WMI_PROX_RANGE_NUM-1]; + s8 snr2range_arr[2]; } __packed; -/* - * WMI_RF_MGMT_CMDID - */ +/* WMI_RF_MGMT_CMDID */ enum wmi_rf_mgmt_type { - WMI_RF_MGMT_W_DISABLE = 0, - WMI_RF_MGMT_W_ENABLE = 1, - WMI_RF_MGMT_GET_STATUS = 2, + WMI_RF_MGMT_W_DISABLE = 0x00, + WMI_RF_MGMT_W_ENABLE = 0x01, + WMI_RF_MGMT_GET_STATUS = 0x02, }; +/* WMI_RF_MGMT_CMDID */ struct wmi_rf_mgmt_cmd { __le32 rf_mgmt_type; } __packed; -/* - * WMI_THERMAL_THROTTLING_CTRL_CMDID - */ -#define THERMAL_THROTTLING_USE_DEFAULT_MAX_TXOP_LENGTH (0xFFFFFFFF) - -struct wmi_thermal_throttling_ctrl_cmd { - __le32 time_on_usec; - __le32 time_off_usec; - __le32 max_txop_length_usec; -} __packed; - -/* - * WMI_RF_RX_TEST_CMDID - */ -struct wmi_rf_rx_test_cmd { - __le32 sector; -} __packed; - -/* - * WMI_CORR_MEASURE_CMDID - */ +/* WMI_CORR_MEASURE_CMDID */ struct wmi_corr_measure_cmd { - s32 freq_mhz; + __le32 freq_mhz; __le32 length_samples; __le32 iterations; } __packed; -/* - * WMI_SET_SSID_CMDID - */ +/* WMI_SET_SSID_CMDID */ struct wmi_set_ssid_cmd { __le32 ssid_len; u8 ssid[WMI_MAX_SSID_LEN]; } __packed; -/* - * WMI_SET_PCP_CHANNEL_CMDID - */ +/* WMI_SET_PCP_CHANNEL_CMDID */ struct wmi_set_pcp_channel_cmd { u8 channel; u8 reserved[3]; } __packed; -/* - * WMI_BCON_CTRL_CMDID - */ +/* WMI_BCON_CTRL_CMDID */ struct wmi_bcon_ctrl_cmd { __le16 bcon_interval; __le16 frag_num; @@ -434,214 +482,221 @@ struct wmi_bcon_ctrl_cmd { u8 pcp_max_assoc_sta; u8 disable_sec_offload; u8 disable_sec; + u8 hidden_ssid; + u8 is_go; + u8 reserved[2]; } __packed; -/******* P2P ***********/ - -/* - * WMI_PORT_ALLOCATE_CMDID - */ +/* WMI_PORT_ALLOCATE_CMDID */ enum wmi_port_role { - WMI_PORT_STA = 0, - WMI_PORT_PCP = 1, - WMI_PORT_AP = 2, - WMI_PORT_P2P_DEV = 3, - WMI_PORT_P2P_CLIENT = 4, - WMI_PORT_P2P_GO = 5, + WMI_PORT_STA = 0x00, + WMI_PORT_PCP = 0x01, + WMI_PORT_AP = 0x02, + WMI_PORT_P2P_DEV = 0x03, + WMI_PORT_P2P_CLIENT = 0x04, + WMI_PORT_P2P_GO = 0x05, }; +/* WMI_PORT_ALLOCATE_CMDID */ struct wmi_port_allocate_cmd { u8 mac[WMI_MAC_LEN]; u8 port_role; u8 mid; } __packed; -/* - * WMI_PORT_DELETE_CMDID - */ -struct wmi_delete_port_cmd { +/* WMI_PORT_DELETE_CMDID */ +struct wmi_port_delete_cmd { u8 mid; u8 reserved[3]; } __packed; -/* - * WMI_P2P_CFG_CMDID - */ +/* WMI_TRAFFIC_SUSPEND_CMD wakeup trigger bit mask values */ +enum wmi_wakeup_trigger { + WMI_WAKEUP_TRIGGER_UCAST = 0x01, + WMI_WAKEUP_TRIGGER_BCAST = 0x02, +}; + +/* WMI_TRAFFIC_SUSPEND_CMDID */ +struct wmi_traffic_suspend_cmd { + /* Bit vector: bit[0] - wake on Unicast, bit[1] - wake on Broadcast */ + u8 wakeup_trigger; +} __packed; + +/* WMI_P2P_CFG_CMDID */ enum wmi_discovery_mode { - WMI_DISCOVERY_MODE_NON_OFFLOAD = 0, - WMI_DISCOVERY_MODE_OFFLOAD = 1, - WMI_DISCOVERY_MODE_PEER2PEER = 2, + WMI_DISCOVERY_MODE_NON_OFFLOAD = 0x00, + WMI_DISCOVERY_MODE_OFFLOAD = 0x01, + WMI_DISCOVERY_MODE_PEER2PEER = 0x02, }; struct wmi_p2p_cfg_cmd { - u8 discovery_mode; /* wmi_discovery_mode */ + /* enum wmi_discovery_mode */ + u8 discovery_mode; u8 channel; - __le16 bcon_interval; /* base to listen/search duration calculation */ + /* base to listen/search duration calculation */ + __le16 bcon_interval; } __packed; -/* - * WMI_POWER_MGMT_CFG_CMDID - */ +/* WMI_POWER_MGMT_CFG_CMDID */ enum wmi_power_source_type { - WMI_POWER_SOURCE_BATTERY = 0, - WMI_POWER_SOURCE_OTHER = 1, + WMI_POWER_SOURCE_BATTERY = 0x00, + WMI_POWER_SOURCE_OTHER = 0x01, }; struct wmi_power_mgmt_cfg_cmd { - u8 power_source; /* wmi_power_source_type */ + /* enum wmi_power_source_type */ + u8 power_source; u8 reserved[3]; } __packed; -/* - * WMI_PCP_START_CMDID - */ - -enum wmi_hidden_ssid { - WMI_HIDDEN_SSID_DISABLED = 0, - WMI_HIDDEN_SSID_SEND_EMPTY = 1, - WMI_HIDDEN_SSID_CLEAR = 2, -}; - +/* WMI_PCP_START_CMDID */ struct wmi_pcp_start_cmd { __le16 bcon_interval; u8 pcp_max_assoc_sta; u8 hidden_ssid; - u8 reserved0[8]; + u8 is_go; + u8 reserved0[5]; + /* A-BFT length override if non-0 */ + u8 abft_len; + u8 disable_ap_sme; u8 network_type; u8 channel; u8 disable_sec_offload; u8 disable_sec; } __packed; -/* - * WMI_SW_TX_REQ_CMDID - */ +/* WMI_SW_TX_REQ_CMDID */ struct wmi_sw_tx_req_cmd { u8 dst_mac[WMI_MAC_LEN]; __le16 len; u8 payload[0]; } __packed; -/* - * WMI_VRING_CFG_CMDID - */ - struct wmi_sw_ring_cfg { __le64 ring_mem_base; __le16 ring_size; __le16 max_mpdu_size; } __packed; +/* wmi_vring_cfg_schd */ struct wmi_vring_cfg_schd { __le16 priority; __le16 timeslot_us; } __packed; enum wmi_vring_cfg_encap_trans_type { - WMI_VRING_ENC_TYPE_802_3 = 0, - WMI_VRING_ENC_TYPE_NATIVE_WIFI = 1, + WMI_VRING_ENC_TYPE_802_3 = 0x00, + WMI_VRING_ENC_TYPE_NATIVE_WIFI = 0x01, }; enum wmi_vring_cfg_ds_cfg { - WMI_VRING_DS_PBSS = 0, - WMI_VRING_DS_STATION = 1, - WMI_VRING_DS_AP = 2, - WMI_VRING_DS_ADDR4 = 3, + WMI_VRING_DS_PBSS = 0x00, + WMI_VRING_DS_STATION = 0x01, + WMI_VRING_DS_AP = 0x02, + WMI_VRING_DS_ADDR4 = 0x03, }; enum wmi_vring_cfg_nwifi_ds_trans_type { - WMI_NWIFI_TX_TRANS_MODE_NO = 0, - WMI_NWIFI_TX_TRANS_MODE_AP2PBSS = 1, - WMI_NWIFI_TX_TRANS_MODE_STA2PBSS = 2, + WMI_NWIFI_TX_TRANS_MODE_NO = 0x00, + WMI_NWIFI_TX_TRANS_MODE_AP2PBSS = 0x01, + WMI_NWIFI_TX_TRANS_MODE_STA2PBSS = 0x02, }; enum wmi_vring_cfg_schd_params_priority { - WMI_SCH_PRIO_REGULAR = 0, - WMI_SCH_PRIO_HIGH = 1, + WMI_SCH_PRIO_REGULAR = 0x00, + WMI_SCH_PRIO_HIGH = 0x01, }; -#define CIDXTID_CID_POS (0) -#define CIDXTID_CID_LEN (4) -#define CIDXTID_CID_MSK (0xF) -#define CIDXTID_TID_POS (4) -#define CIDXTID_TID_LEN (4) -#define CIDXTID_TID_MSK (0xF0) +#define CIDXTID_CID_POS (0) +#define CIDXTID_CID_LEN (4) +#define CIDXTID_CID_MSK (0xF) +#define CIDXTID_TID_POS (4) +#define CIDXTID_TID_LEN (4) +#define CIDXTID_TID_MSK (0xF0) +#define VRING_CFG_MAC_CTRL_LIFETIME_EN_POS (0) +#define VRING_CFG_MAC_CTRL_LIFETIME_EN_LEN (1) +#define VRING_CFG_MAC_CTRL_LIFETIME_EN_MSK (0x1) +#define VRING_CFG_MAC_CTRL_AGGR_EN_POS (1) +#define VRING_CFG_MAC_CTRL_AGGR_EN_LEN (1) +#define VRING_CFG_MAC_CTRL_AGGR_EN_MSK (0x2) +#define VRING_CFG_TO_RESOLUTION_VALUE_POS (0) +#define VRING_CFG_TO_RESOLUTION_VALUE_LEN (6) +#define VRING_CFG_TO_RESOLUTION_VALUE_MSK (0x3F) struct wmi_vring_cfg { struct wmi_sw_ring_cfg tx_sw_ring; - u8 ringid; /* 0-23 vrings */ - + /* 0-23 vrings */ + u8 ringid; u8 cidxtid; - u8 encap_trans_type; - u8 ds_cfg; /* 802.3 DS cfg */ + /* 802.3 DS cfg */ + u8 ds_cfg; u8 nwifi_ds_trans_type; - - #define VRING_CFG_MAC_CTRL_LIFETIME_EN_POS (0) - #define VRING_CFG_MAC_CTRL_LIFETIME_EN_LEN (1) - #define VRING_CFG_MAC_CTRL_LIFETIME_EN_MSK (0x1) - #define VRING_CFG_MAC_CTRL_AGGR_EN_POS (1) - #define VRING_CFG_MAC_CTRL_AGGR_EN_LEN (1) - #define VRING_CFG_MAC_CTRL_AGGR_EN_MSK (0x2) u8 mac_ctrl; - - #define VRING_CFG_TO_RESOLUTION_VALUE_POS (0) - #define VRING_CFG_TO_RESOLUTION_VALUE_LEN (6) - #define VRING_CFG_TO_RESOLUTION_VALUE_MSK (0x3F) u8 to_resolution; u8 agg_max_wsize; struct wmi_vring_cfg_schd schd_params; } __packed; enum wmi_vring_cfg_cmd_action { - WMI_VRING_CMD_ADD = 0, - WMI_VRING_CMD_MODIFY = 1, - WMI_VRING_CMD_DELETE = 2, + WMI_VRING_CMD_ADD = 0x00, + WMI_VRING_CMD_MODIFY = 0x01, + WMI_VRING_CMD_DELETE = 0x02, }; +/* WMI_VRING_CFG_CMDID */ struct wmi_vring_cfg_cmd { __le32 action; struct wmi_vring_cfg vring_cfg; } __packed; -/* - * WMI_BCAST_VRING_CFG_CMDID - */ struct wmi_bcast_vring_cfg { struct wmi_sw_ring_cfg tx_sw_ring; - u8 ringid; /* 0-23 vrings */ + /* 0-23 vrings */ + u8 ringid; u8 encap_trans_type; - u8 ds_cfg; /* 802.3 DS cfg */ + /* 802.3 DS cfg */ + u8 ds_cfg; u8 nwifi_ds_trans_type; } __packed; +/* WMI_BCAST_VRING_CFG_CMDID */ struct wmi_bcast_vring_cfg_cmd { __le32 action; struct wmi_bcast_vring_cfg vring_cfg; } __packed; -/* - * WMI_VRING_BA_EN_CMDID - */ +/* WMI_LO_POWER_CALIB_FROM_OTP_CMDID */ +struct wmi_lo_power_calib_from_otp_cmd { + /* index to read from OTP. zero based */ + u8 index; + u8 reserved[3]; +} __packed; + +/* WMI_LO_POWER_CALIB_FROM_OTP_EVENTID */ +struct wmi_lo_power_calib_from_otp_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_VRING_BA_EN_CMDID */ struct wmi_vring_ba_en_cmd { u8 ringid; u8 agg_max_wsize; __le16 ba_timeout; u8 amsdu; + u8 reserved[3]; } __packed; -/* - * WMI_VRING_BA_DIS_CMDID - */ +/* WMI_VRING_BA_DIS_CMDID */ struct wmi_vring_ba_dis_cmd { u8 ringid; u8 reserved; __le16 reason; } __packed; -/* - * WMI_NOTIFY_REQ_CMDID - */ +/* WMI_NOTIFY_REQ_CMDID */ struct wmi_notify_req_cmd { u8 cid; u8 year; @@ -654,102 +709,118 @@ struct wmi_notify_req_cmd { u8 miliseconds; } __packed; -/* - * WMI_CFG_RX_CHAIN_CMDID - */ +/* WMI_CFG_RX_CHAIN_CMDID */ enum wmi_sniffer_cfg_mode { - WMI_SNIFFER_OFF = 0, - WMI_SNIFFER_ON = 1, + WMI_SNIFFER_OFF = 0x00, + WMI_SNIFFER_ON = 0x01, }; +/* WMI_SILENT_RSSI_TABLE */ +enum wmi_silent_rssi_table { + RF_TEMPERATURE_CALIB_DEFAULT_DB = 0x00, + RF_TEMPERATURE_CALIB_HIGH_POWER_DB = 0x01, +}; + +/* WMI_SILENT_RSSI_STATUS */ +enum wmi_silent_rssi_status { + SILENT_RSSI_SUCCESS = 0x00, + SILENT_RSSI_FAILURE = 0x01, +}; + +/* WMI_SET_ACTIVE_SILENT_RSSI_TABLE_CMDID */ +struct wmi_set_active_silent_rssi_table_cmd { + /* enum wmi_silent_rssi_table */ + __le32 table; +} __packed; + enum wmi_sniffer_cfg_phy_info_mode { - WMI_SNIFFER_PHY_INFO_DISABLED = 0, - WMI_SNIFFER_PHY_INFO_ENABLED = 1, + WMI_SNIFFER_PHY_INFO_DISABLED = 0x00, + WMI_SNIFFER_PHY_INFO_ENABLED = 0x01, }; enum wmi_sniffer_cfg_phy_support { - WMI_SNIFFER_CP = 0, - WMI_SNIFFER_DP = 1, - WMI_SNIFFER_BOTH_PHYS = 2, + WMI_SNIFFER_CP = 0x00, + WMI_SNIFFER_DP = 0x01, + WMI_SNIFFER_BOTH_PHYS = 0x02, }; +/* wmi_sniffer_cfg */ struct wmi_sniffer_cfg { - __le32 mode; /* enum wmi_sniffer_cfg_mode */ - __le32 phy_info_mode; /* enum wmi_sniffer_cfg_phy_info_mode */ - __le32 phy_support; /* enum wmi_sniffer_cfg_phy_support */ + /* enum wmi_sniffer_cfg_mode */ + __le32 mode; + /* enum wmi_sniffer_cfg_phy_info_mode */ + __le32 phy_info_mode; + /* enum wmi_sniffer_cfg_phy_support */ + __le32 phy_support; u8 channel; u8 reserved[3]; } __packed; enum wmi_cfg_rx_chain_cmd_action { - WMI_RX_CHAIN_ADD = 0, - WMI_RX_CHAIN_DEL = 1, + WMI_RX_CHAIN_ADD = 0x00, + WMI_RX_CHAIN_DEL = 0x01, }; enum wmi_cfg_rx_chain_cmd_decap_trans_type { - WMI_DECAP_TYPE_802_3 = 0, - WMI_DECAP_TYPE_NATIVE_WIFI = 1, - WMI_DECAP_TYPE_NONE = 2, + WMI_DECAP_TYPE_802_3 = 0x00, + WMI_DECAP_TYPE_NATIVE_WIFI = 0x01, + WMI_DECAP_TYPE_NONE = 0x02, }; enum wmi_cfg_rx_chain_cmd_nwifi_ds_trans_type { - WMI_NWIFI_RX_TRANS_MODE_NO = 0, - WMI_NWIFI_RX_TRANS_MODE_PBSS2AP = 1, - WMI_NWIFI_RX_TRANS_MODE_PBSS2STA = 2, + WMI_NWIFI_RX_TRANS_MODE_NO = 0x00, + WMI_NWIFI_RX_TRANS_MODE_PBSS2AP = 0x01, + WMI_NWIFI_RX_TRANS_MODE_PBSS2STA = 0x02, }; enum wmi_cfg_rx_chain_cmd_reorder_type { - WMI_RX_HW_REORDER = 0, - WMI_RX_SW_REORDER = 1, + WMI_RX_HW_REORDER = 0x00, + WMI_RX_SW_REORDER = 0x01, }; +#define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_POS (0) +#define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_LEN (1) +#define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_MSK (0x1) +#define L2_802_3_OFFLOAD_CTRL_SNAP_KEEP_POS (1) +#define L2_802_3_OFFLOAD_CTRL_SNAP_KEEP_LEN (1) +#define L2_802_3_OFFLOAD_CTRL_SNAP_KEEP_MSK (0x2) +#define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_POS (0) +#define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_LEN (1) +#define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_MSK (0x1) +#define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_POS (1) +#define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_LEN (1) +#define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_MSK (0x2) +#define L3_L4_CTRL_IPV4_CHECKSUM_EN_POS (0) +#define L3_L4_CTRL_IPV4_CHECKSUM_EN_LEN (1) +#define L3_L4_CTRL_IPV4_CHECKSUM_EN_MSK (0x1) +#define L3_L4_CTRL_TCPIP_CHECKSUM_EN_POS (1) +#define L3_L4_CTRL_TCPIP_CHECKSUM_EN_LEN (1) +#define L3_L4_CTRL_TCPIP_CHECKSUM_EN_MSK (0x2) +#define RING_CTRL_OVERRIDE_PREFETCH_THRSH_POS (0) +#define RING_CTRL_OVERRIDE_PREFETCH_THRSH_LEN (1) +#define RING_CTRL_OVERRIDE_PREFETCH_THRSH_MSK (0x1) +#define RING_CTRL_OVERRIDE_WB_THRSH_POS (1) +#define RING_CTRL_OVERRIDE_WB_THRSH_LEN (1) +#define RING_CTRL_OVERRIDE_WB_THRSH_MSK (0x2) +#define RING_CTRL_OVERRIDE_ITR_THRSH_POS (2) +#define RING_CTRL_OVERRIDE_ITR_THRSH_LEN (1) +#define RING_CTRL_OVERRIDE_ITR_THRSH_MSK (0x4) +#define RING_CTRL_OVERRIDE_HOST_THRSH_POS (3) +#define RING_CTRL_OVERRIDE_HOST_THRSH_LEN (1) +#define RING_CTRL_OVERRIDE_HOST_THRSH_MSK (0x8) + +/* WMI_CFG_RX_CHAIN_CMDID */ struct wmi_cfg_rx_chain_cmd { __le32 action; struct wmi_sw_ring_cfg rx_sw_ring; u8 mid; u8 decap_trans_type; - - #define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_POS (0) - #define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_LEN (1) - #define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_MSK (0x1) - #define L2_802_3_OFFLOAD_CTRL_SNAP_KEEP_POS (1) - #define L2_802_3_OFFLOAD_CTRL_SNAP_KEEP_LEN (1) - #define L2_802_3_OFFLOAD_CTRL_SNAP_KEEP_MSK (0x2) u8 l2_802_3_offload_ctrl; - - #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_POS (0) - #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_LEN (1) - #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_MSK (0x1) - #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_POS (1) - #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_LEN (1) - #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_MSK (0x2) u8 l2_nwifi_offload_ctrl; - u8 vlan_id; u8 nwifi_ds_trans_type; - - #define L3_L4_CTRL_IPV4_CHECKSUM_EN_POS (0) - #define L3_L4_CTRL_IPV4_CHECKSUM_EN_LEN (1) - #define L3_L4_CTRL_IPV4_CHECKSUM_EN_MSK (0x1) - #define L3_L4_CTRL_TCPIP_CHECKSUM_EN_POS (1) - #define L3_L4_CTRL_TCPIP_CHECKSUM_EN_LEN (1) - #define L3_L4_CTRL_TCPIP_CHECKSUM_EN_MSK (0x2) u8 l3_l4_ctrl; - - #define RING_CTRL_OVERRIDE_PREFETCH_THRSH_POS (0) - #define RING_CTRL_OVERRIDE_PREFETCH_THRSH_LEN (1) - #define RING_CTRL_OVERRIDE_PREFETCH_THRSH_MSK (0x1) - #define RING_CTRL_OVERRIDE_WB_THRSH_POS (1) - #define RING_CTRL_OVERRIDE_WB_THRSH_LEN (1) - #define RING_CTRL_OVERRIDE_WB_THRSH_MSK (0x2) - #define RING_CTRL_OVERRIDE_ITR_THRSH_POS (2) - #define RING_CTRL_OVERRIDE_ITR_THRSH_LEN (1) - #define RING_CTRL_OVERRIDE_ITR_THRSH_MSK (0x4) - #define RING_CTRL_OVERRIDE_HOST_THRSH_POS (3) - #define RING_CTRL_OVERRIDE_HOST_THRSH_LEN (1) - #define RING_CTRL_OVERRIDE_HOST_THRSH_MSK (0x8) u8 ring_ctrl; - __le16 prefetch_thrsh; __le16 wb_thrsh; __le32 itr_value; @@ -757,31 +828,27 @@ struct wmi_cfg_rx_chain_cmd { u8 reorder_type; u8 reserved; struct wmi_sniffer_cfg sniffer_cfg; + __le16 max_rx_pl_per_desc; } __packed; -/* - * WMI_RCP_ADDBA_RESP_CMDID - */ +/* WMI_RCP_ADDBA_RESP_CMDID */ struct wmi_rcp_addba_resp_cmd { u8 cidxtid; u8 dialog_token; __le16 status_code; - __le16 ba_param_set; /* ieee80211_ba_parameterset field to send */ + /* ieee80211_ba_parameterset field to send */ + __le16 ba_param_set; __le16 ba_timeout; } __packed; -/* - * WMI_RCP_DELBA_CMDID - */ +/* WMI_RCP_DELBA_CMDID */ struct wmi_rcp_delba_cmd { u8 cidxtid; u8 reserved; __le16 reason; } __packed; -/* - * WMI_RCP_ADDBA_REQ_CMDID - */ +/* WMI_RCP_ADDBA_REQ_CMDID */ struct wmi_rcp_addba_req_cmd { u8 cidxtid; u8 dialog_token; @@ -792,32 +859,16 @@ struct wmi_rcp_addba_req_cmd { __le16 ba_seq_ctrl; } __packed; -/* - * WMI_SET_MAC_ADDRESS_CMDID - */ +/* WMI_SET_MAC_ADDRESS_CMDID */ struct wmi_set_mac_address_cmd { u8 mac[WMI_MAC_LEN]; u8 reserved[2]; } __packed; -/* -* WMI_EAPOL_TX_CMDID -*/ -struct wmi_eapol_tx_cmd { - u8 dst_mac[WMI_MAC_LEN]; - __le16 eapol_len; - u8 eapol[0]; -} __packed; - -/* - * WMI_ECHO_CMDID - * +/* WMI_ECHO_CMDID * Check FW is alive - * * WMI_DEEP_ECHO_CMDID - * * Check FW and ucode are alive - * * Returned event: WMI_ECHO_RSP_EVENTID * same event for both commands */ @@ -825,161 +876,506 @@ struct wmi_echo_cmd { __le32 value; } __packed; -/* - * WMI_TEMP_SENSE_CMDID +/* WMI_RF_PWR_ON_DELAY_CMDID + * set FW time parameters used through RF resetting + * RF reset consists of bringing its power down for a period of time, then + * bringing the power up + * Returned event: WMI_RF_PWR_ON_DELAY_RSP_EVENTID + */ +struct wmi_rf_pwr_on_delay_cmd { + /* time in usec the FW waits after bringing the RF PWR down, + * set 0 for default + */ + __le16 down_delay_usec; + /* time in usec the FW waits after bringing the RF PWR up, + * set 0 for default + */ + __le16 up_delay_usec; +} __packed; + +/* \WMI_SET_HIGH_POWER_TABLE_PARAMS_CMDID + * This API controls the Tx and Rx gain over temperature. + * It controls the Tx D-type, Rx D-type and Rx E-type amplifiers. + * It also controls the Tx gain index, by controlling the Rx to Tx gain index + * offset. + * The control is divided by 3 temperature values to 4 temperature ranges. + * Each parameter uses its own temperature values. + * Returned event: WMI_SET_HIGH_POWER_TABLE_PARAMS_EVENTID + */ +struct wmi_set_high_power_table_params_cmd { + /* Temperature range for Tx D-type parameters */ + u8 tx_dtype_temp[WMI_RF_DTYPE_LENGTH]; + u8 reserved0; + /* Tx D-type values to be used for each temperature range */ + __le32 tx_dtype_conf[WMI_RF_DTYPE_LENGTH + 1]; + /* Temperature range for Rx D-type parameters */ + u8 rx_dtype_temp[WMI_RF_DTYPE_LENGTH]; + u8 reserved1; + /* Rx D-type values to be used for each temperature range */ + __le32 rx_dtype_conf[WMI_RF_DTYPE_LENGTH + 1]; + /* Temperature range for Rx E-type parameters */ + u8 rx_etype_temp[WMI_RF_ETYPE_LENGTH]; + u8 reserved2; + /* Rx E-type values to be used for each temperature range. + * The last 4 values of any range are the first 4 values of the next + * range and so on + */ + __le32 rx_etype_conf[WMI_RF_ETYPE_VAL_PER_RANGE + WMI_RF_ETYPE_LENGTH]; + /* Temperature range for rx_2_tx_offs parameters */ + u8 rx_2_tx_temp[WMI_RF_RX2TX_LENGTH]; + u8 reserved3; + /* Rx to Tx gain index offset */ + s8 rx_2_tx_offs[WMI_RF_RX2TX_LENGTH + 1]; +} __packed; + +/* CMD: WMI_RF_XPM_READ_CMDID */ +struct wmi_rf_xpm_read_cmd { + u8 rf_id; + u8 reserved[3]; + /* XPM bit start address in range [0,8191]bits - rounded by FW to + * multiple of 8bits + */ + __le32 xpm_bit_address; + __le32 num_bytes; +} __packed; + +/* CMD: WMI_RF_XPM_WRITE_CMDID */ +struct wmi_rf_xpm_write_cmd { + u8 rf_id; + u8 reserved0[3]; + /* XPM bit start address in range [0,8191]bits - rounded by FW to + * multiple of 8bits + */ + __le32 xpm_bit_address; + __le32 num_bytes; + /* boolean flag indicating whether FW should verify the write + * operation + */ + u8 verify; + u8 reserved1[3]; + /* actual size=num_bytes */ + u8 data_bytes[0]; +} __packed; + +/* WMI_TEMP_SENSE_CMDID * * Measure MAC and radio temperatures + * + * Possible modes for temperature measurement */ - -/* Possible modes for temperature measurement */ enum wmi_temperature_measure_mode { - TEMPERATURE_USE_OLD_VALUE = 0x1, - TEMPERATURE_MEASURE_NOW = 0x2, + TEMPERATURE_USE_OLD_VALUE = 0x01, + TEMPERATURE_MEASURE_NOW = 0x02, }; +/* WMI_TEMP_SENSE_CMDID */ struct wmi_temp_sense_cmd { __le32 measure_baseband_en; __le32 measure_rf_en; __le32 measure_mode; } __packed; -/* - * WMI_PMC_CMDID - */ -enum wmi_pmc_op_e { - WMI_PMC_ALLOCATE = 0, - WMI_PMC_RELEASE = 1, +enum wmi_pmc_op { + WMI_PMC_ALLOCATE = 0x00, + WMI_PMC_RELEASE = 0x01, }; +/* WMI_PMC_CMDID */ struct wmi_pmc_cmd { - u8 op; /* enum wmi_pmc_cmd_op_type */ + /* enum wmi_pmc_cmd_op_type */ + u8 op; u8 reserved; __le16 ring_size; __le64 mem_base; } __packed; -/* - * WMI Events +enum wmi_aoa_meas_type { + WMI_AOA_PHASE_MEAS = 0x00, + WMI_AOA_PHASE_AMP_MEAS = 0x01, +}; + +/* WMI_AOA_MEAS_CMDID */ +struct wmi_aoa_meas_cmd { + u8 mac_addr[WMI_MAC_LEN]; + /* channels IDs: + * 0 - 58320 MHz + * 1 - 60480 MHz + * 2 - 62640 MHz + */ + u8 channel; + /* enum wmi_aoa_meas_type */ + u8 aoa_meas_type; + __le32 meas_rf_mask; +} __packed; + +/* WMI_SET_MGMT_RETRY_LIMIT_CMDID */ +struct wmi_set_mgmt_retry_limit_cmd { + /* MAC retransmit limit for mgmt frames */ + u8 mgmt_retry_limit; + /* alignment to 32b */ + u8 reserved[3]; +} __packed; + +/* Zones: HIGH, MAX, CRITICAL */ +#define WMI_NUM_OF_TT_ZONES (3) + +struct wmi_tt_zone_limits { + /* Above this temperature this zone is active */ + u8 temperature_high; + /* Below this temperature the adjacent lower zone is active */ + u8 temperature_low; + u8 reserved[2]; +} __packed; + +/* Struct used for both configuration and status commands of thermal + * throttling */ +struct wmi_tt_data { + /* Enable/Disable TT algorithm for baseband */ + u8 bb_enabled; + u8 reserved0[3]; + /* Define zones for baseband */ + struct wmi_tt_zone_limits bb_zones[WMI_NUM_OF_TT_ZONES]; + /* Enable/Disable TT algorithm for radio */ + u8 rf_enabled; + u8 reserved1[3]; + /* Define zones for all radio chips */ + struct wmi_tt_zone_limits rf_zones[WMI_NUM_OF_TT_ZONES]; +} __packed; -/* +/* WMI_SET_THERMAL_THROTTLING_CFG_CMDID */ +struct wmi_set_thermal_throttling_cfg_cmd { + /* Command data */ + struct wmi_tt_data tt_data; +} __packed; + +/* WMI_NEW_STA_CMDID */ +struct wmi_new_sta_cmd { + u8 dst_mac[WMI_MAC_LEN]; + u8 aid; +} __packed; + +/* WMI_DEL_STA_CMDID */ +struct wmi_del_sta_cmd { + u8 dst_mac[WMI_MAC_LEN]; + __le16 disconnect_reason; +} __packed; + +enum wmi_tof_burst_duration { + WMI_TOF_BURST_DURATION_250_USEC = 2, + WMI_TOF_BURST_DURATION_500_USEC = 3, + WMI_TOF_BURST_DURATION_1_MSEC = 4, + WMI_TOF_BURST_DURATION_2_MSEC = 5, + WMI_TOF_BURST_DURATION_4_MSEC = 6, + WMI_TOF_BURST_DURATION_8_MSEC = 7, + WMI_TOF_BURST_DURATION_16_MSEC = 8, + WMI_TOF_BURST_DURATION_32_MSEC = 9, + WMI_TOF_BURST_DURATION_64_MSEC = 10, + WMI_TOF_BURST_DURATION_128_MSEC = 11, + WMI_TOF_BURST_DURATION_NO_PREFERENCES = 15, +}; + +enum wmi_tof_session_start_flags { + WMI_TOF_SESSION_START_FLAG_SECURED = 0x1, + WMI_TOF_SESSION_START_FLAG_ASAP = 0x2, + WMI_TOF_SESSION_START_FLAG_LCI_REQ = 0x4, + WMI_TOF_SESSION_START_FLAG_LCR_REQ = 0x8, +}; + +/* WMI_TOF_SESSION_START_CMDID */ +struct wmi_ftm_dest_info { + u8 channel; + /* wmi_tof_session_start_flags_e */ + u8 flags; + u8 initial_token; + u8 num_of_ftm_per_burst; + u8 num_of_bursts_exp; + /* wmi_tof_burst_duration_e */ + u8 burst_duration; + /* Burst Period indicate interval between two consecutive burst + * instances, in units of 100 ms + */ + __le16 burst_period; + u8 dst_mac[WMI_MAC_LEN]; + u8 reserved; + u8 num_burst_per_aoa_meas; +} __packed; + +/* WMI_TOF_SESSION_START_CMDID */ +struct wmi_tof_session_start_cmd { + __le32 session_id; + u8 reserved1; + u8 aoa_type; + __le16 num_of_dest; + u8 reserved[4]; + struct wmi_ftm_dest_info ftm_dest_info[0]; +} __packed; + +/* WMI_TOF_CFG_RESPONDER_CMDID */ +struct wmi_tof_cfg_responder_cmd { + u8 enable; + u8 reserved[3]; +} __packed; + +enum wmi_tof_channel_info_report_type { + WMI_TOF_CHANNEL_INFO_TYPE_CIR = 0x1, + WMI_TOF_CHANNEL_INFO_TYPE_RSSI = 0x2, + WMI_TOF_CHANNEL_INFO_TYPE_SNR = 0x4, + WMI_TOF_CHANNEL_INFO_TYPE_DEBUG_DATA = 0x8, + WMI_TOF_CHANNEL_INFO_TYPE_VENDOR_SPECIFIC = 0x10, +}; + +/* WMI_TOF_CHANNEL_INFO_CMDID */ +struct wmi_tof_channel_info_cmd { + /* wmi_tof_channel_info_report_type_e */ + __le32 channel_info_report_request; +} __packed; + +/* WMI_TOF_SET_TX_RX_OFFSET_CMDID */ +struct wmi_tof_set_tx_rx_offset_cmd { + /* TX delay offset */ + __le32 tx_offset; + /* RX delay offset */ + __le32 rx_offset; + /* Mask to define which RFs to configure. 0 means all RFs */ + __le32 rf_mask; + /* Offset to strongest tap of CIR */ + __le32 precursor; +} __packed; + +/* WMI_TOF_GET_TX_RX_OFFSET_CMDID */ +struct wmi_tof_get_tx_rx_offset_cmd { + /* rf index to read offsets from */ + u8 rf_index; + u8 reserved[3]; +} __packed; + +/* WMI_FIXED_SCHEDULING_CONFIG_CMDID */ +struct wmi_map_mcs_to_schd_params { + u8 mcs; + /* time in usec from start slot to start tx flow - default 15 */ + u8 time_in_usec_before_initiate_tx; + /* RD enable - if yes consider RD according to STA mcs */ + u8 rd_enabled; + u8 reserved; + /* time in usec from start slot to stop vring */ + __le16 time_in_usec_to_stop_vring; + /* timeout to force flush from start of slot */ + __le16 flush_to_in_usec; + /* per mcs the mac buffer limit size in bytes */ + __le32 mac_buff_size_in_bytes; +} __packed; + +/* WMI_FIXED_SCHEDULING_CONFIG_COMPLETE_EVENTID */ +struct wmi_fixed_scheduling_config_complete_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +#define WMI_NUM_MCS (13) + +/* WMI_FIXED_SCHEDULING_CONFIG_CMDID */ +struct wmi_fixed_scheduling_config_cmd { + /* defaults in the SAS table */ + struct wmi_map_mcs_to_schd_params mcs_to_schd_params_map[WMI_NUM_MCS]; + /* default 150 uSec */ + __le16 max_sta_rd_ppdu_duration_in_usec; + /* default 300 uSec */ + __le16 max_sta_grant_ppdu_duration_in_usec; + /* default 1000 uSec */ + __le16 assoc_slot_duration_in_usec; + /* default 360 uSec */ + __le16 virtual_slot_duration_in_usec; + /* each this field value slots start with grant frame to the station + * - default 2 + */ + u8 number_of_ap_slots_for_initiate_grant; + u8 reserved[3]; +} __packed; + +/* WMI_ENABLE_FIXED_SCHEDULING_CMDID */ +struct wmi_enable_fixed_scheduling_cmd { + __le32 reserved; +} __packed; + +/* WMI_ENABLE_FIXED_SCHEDULING_COMPLETE_EVENTID */ +struct wmi_enable_fixed_scheduling_complete_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_SET_MULTI_DIRECTED_OMNIS_CONFIG_CMDID */ +struct wmi_set_multi_directed_omnis_config_cmd { + /* number of directed omnis at destination AP */ + u8 dest_ap_num_directed_omnis; + u8 reserved[3]; +} __packed; + +/* WMI_SET_MULTI_DIRECTED_OMNIS_CONFIG_EVENTID */ +struct wmi_set_multi_directed_omnis_config_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_SET_LONG_RANGE_CONFIG_CMDID */ +struct wmi_set_long_range_config_cmd { + __le32 reserved; +} __packed; + +/* WMI_SET_LONG_RANGE_CONFIG_COMPLETE_EVENTID */ +struct wmi_set_long_range_config_complete_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI Events * List of Events (target to host) */ enum wmi_event_id { - WMI_READY_EVENTID = 0x1001, - WMI_CONNECT_EVENTID = 0x1002, - WMI_DISCONNECT_EVENTID = 0x1003, - WMI_SCAN_COMPLETE_EVENTID = 0x100a, - WMI_REPORT_STATISTICS_EVENTID = 0x100b, - WMI_RD_MEM_RSP_EVENTID = 0x1800, - WMI_FW_READY_EVENTID = 0x1801, - WMI_EXIT_FAST_MEM_ACC_MODE_EVENTID = 0x0200, - WMI_ECHO_RSP_EVENTID = 0x1803, - WMI_FS_TUNE_DONE_EVENTID = 0x180a, - WMI_CORR_MEASURE_EVENTID = 0x180b, - WMI_READ_RSSI_EVENTID = 0x180c, - WMI_TEMP_SENSE_DONE_EVENTID = 0x180e, - WMI_DC_CALIB_DONE_EVENTID = 0x180f, - WMI_IQ_TX_CALIB_DONE_EVENTID = 0x1811, - WMI_IQ_RX_CALIB_DONE_EVENTID = 0x1812, - WMI_SET_WORK_MODE_DONE_EVENTID = 0x1815, - WMI_LO_LEAKAGE_CALIB_DONE_EVENTID = 0x1816, - WMI_MARLON_R_READ_DONE_EVENTID = 0x1818, - WMI_MARLON_R_WRITE_DONE_EVENTID = 0x1819, - WMI_MARLON_R_TXRX_SEL_DONE_EVENTID = 0x181a, - WMI_SILENT_RSSI_CALIB_DONE_EVENTID = 0x181d, - WMI_RF_RX_TEST_DONE_EVENTID = 0x181e, - WMI_CFG_RX_CHAIN_DONE_EVENTID = 0x1820, - WMI_VRING_CFG_DONE_EVENTID = 0x1821, - WMI_BA_STATUS_EVENTID = 0x1823, - WMI_RCP_ADDBA_REQ_EVENTID = 0x1824, - WMI_RCP_ADDBA_RESP_SENT_EVENTID = 0x1825, - WMI_DELBA_EVENTID = 0x1826, - WMI_GET_SSID_EVENTID = 0x1828, - WMI_GET_PCP_CHANNEL_EVENTID = 0x182a, - WMI_SW_TX_COMPLETE_EVENTID = 0x182b, - - WMI_READ_MAC_RXQ_EVENTID = 0x1830, - WMI_READ_MAC_TXQ_EVENTID = 0x1831, - WMI_WRITE_MAC_RXQ_EVENTID = 0x1832, - WMI_WRITE_MAC_TXQ_EVENTID = 0x1833, - WMI_WRITE_MAC_XQ_FIELD_EVENTID = 0x1834, - - WMI_BEAMFORMING_MGMT_DONE_EVENTID = 0x1836, - WMI_BF_TXSS_MGMT_DONE_EVENTID = 0x1837, - WMI_BF_RXSS_MGMT_DONE_EVENTID = 0x1839, - WMI_RS_MGMT_DONE_EVENTID = 0x1852, - WMI_RF_MGMT_STATUS_EVENTID = 0x1853, - WMI_THERMAL_THROTTLING_STATUS_EVENTID = 0x1855, - WMI_BF_SM_MGMT_DONE_EVENTID = 0x1838, - WMI_RX_MGMT_PACKET_EVENTID = 0x1840, - WMI_TX_MGMT_PACKET_EVENTID = 0x1841, - + WMI_READY_EVENTID = 0x1001, + WMI_CONNECT_EVENTID = 0x1002, + WMI_DISCONNECT_EVENTID = 0x1003, + WMI_SCAN_COMPLETE_EVENTID = 0x100A, + WMI_REPORT_STATISTICS_EVENTID = 0x100B, + WMI_RD_MEM_RSP_EVENTID = 0x1800, + WMI_FW_READY_EVENTID = 0x1801, + WMI_EXIT_FAST_MEM_ACC_MODE_EVENTID = 0x200, + WMI_ECHO_RSP_EVENTID = 0x1803, + /* deprecated */ + WMI_FS_TUNE_DONE_EVENTID = 0x180A, + /* deprecated */ + WMI_CORR_MEASURE_EVENTID = 0x180B, + WMI_READ_RSSI_EVENTID = 0x180C, + WMI_TEMP_SENSE_DONE_EVENTID = 0x180E, + WMI_DC_CALIB_DONE_EVENTID = 0x180F, + /* deprecated */ + WMI_IQ_TX_CALIB_DONE_EVENTID = 0x1811, + /* deprecated */ + WMI_IQ_RX_CALIB_DONE_EVENTID = 0x1812, + WMI_SET_WORK_MODE_DONE_EVENTID = 0x1815, + WMI_LO_LEAKAGE_CALIB_DONE_EVENTID = 0x1816, + WMI_LO_POWER_CALIB_FROM_OTP_EVENTID = 0x1817, + WMI_SILENT_RSSI_CALIB_DONE_EVENTID = 0x181D, + /* deprecated */ + WMI_RF_RX_TEST_DONE_EVENTID = 0x181E, + WMI_CFG_RX_CHAIN_DONE_EVENTID = 0x1820, + WMI_VRING_CFG_DONE_EVENTID = 0x1821, + WMI_BA_STATUS_EVENTID = 0x1823, + WMI_RCP_ADDBA_REQ_EVENTID = 0x1824, + WMI_RCP_ADDBA_RESP_SENT_EVENTID = 0x1825, + WMI_DELBA_EVENTID = 0x1826, + WMI_GET_SSID_EVENTID = 0x1828, + WMI_GET_PCP_CHANNEL_EVENTID = 0x182A, + WMI_SW_TX_COMPLETE_EVENTID = 0x182B, + WMI_BEAMFORMING_MGMT_DONE_EVENTID = 0x1836, + WMI_BF_TXSS_MGMT_DONE_EVENTID = 0x1837, + WMI_BF_RXSS_MGMT_DONE_EVENTID = 0x1839, + WMI_RS_MGMT_DONE_EVENTID = 0x1852, + WMI_RF_MGMT_STATUS_EVENTID = 0x1853, + WMI_BF_SM_MGMT_DONE_EVENTID = 0x1838, + WMI_RX_MGMT_PACKET_EVENTID = 0x1840, + WMI_TX_MGMT_PACKET_EVENTID = 0x1841, + WMI_LINK_MAINTAIN_CFG_WRITE_DONE_EVENTID = 0x1842, + WMI_LINK_MAINTAIN_CFG_READ_DONE_EVENTID = 0x1843, + WMI_RF_XPM_READ_RESULT_EVENTID = 0x1856, + WMI_RF_XPM_WRITE_RESULT_EVENTID = 0x1857, + WMI_LED_CFG_DONE_EVENTID = 0x1858, + WMI_SET_SILENT_RSSI_TABLE_DONE_EVENTID = 0x185C, + WMI_RF_PWR_ON_DELAY_RSP_EVENTID = 0x185D, + WMI_SET_HIGH_POWER_TABLE_PARAMS_EVENTID = 0x185E, /* Performance monitoring events */ - WMI_DATA_PORT_OPEN_EVENTID = 0x1860, - WMI_WBE_LINK_DOWN_EVENTID = 0x1861, - - WMI_BF_CTRL_DONE_EVENTID = 0x1862, - WMI_NOTIFY_REQ_DONE_EVENTID = 0x1863, - WMI_GET_STATUS_DONE_EVENTID = 0x1864, - WMI_VRING_EN_EVENTID = 0x1865, - - WMI_UNIT_TEST_EVENTID = 0x1900, - WMI_FLASH_READ_DONE_EVENTID = 0x1902, - WMI_FLASH_WRITE_DONE_EVENTID = 0x1903, - /*P2P*/ - WMI_P2P_CFG_DONE_EVENTID = 0x1910, - WMI_PORT_ALLOCATED_EVENTID = 0x1911, - WMI_PORT_DELETED_EVENTID = 0x1912, - WMI_LISTEN_STARTED_EVENTID = 0x1914, - WMI_SEARCH_STARTED_EVENTID = 0x1915, - WMI_DISCOVERY_STARTED_EVENTID = 0x1916, - WMI_DISCOVERY_STOPPED_EVENTID = 0x1917, - WMI_PCP_STARTED_EVENTID = 0x1918, - WMI_PCP_STOPPED_EVENTID = 0x1919, - WMI_PCP_FACTOR_EVENTID = 0x191a, - WMI_SET_CHANNEL_EVENTID = 0x9000, - WMI_ASSOC_REQ_EVENTID = 0x9001, - WMI_EAPOL_RX_EVENTID = 0x9002, - WMI_MAC_ADDR_RESP_EVENTID = 0x9003, - WMI_FW_VER_EVENTID = 0x9004, + WMI_DATA_PORT_OPEN_EVENTID = 0x1860, + WMI_WBE_LINK_DOWN_EVENTID = 0x1861, + WMI_BF_CTRL_DONE_EVENTID = 0x1862, + WMI_NOTIFY_REQ_DONE_EVENTID = 0x1863, + WMI_GET_STATUS_DONE_EVENTID = 0x1864, + WMI_VRING_EN_EVENTID = 0x1865, + WMI_GET_RF_STATUS_EVENTID = 0x1866, + WMI_GET_BASEBAND_TYPE_EVENTID = 0x1867, + WMI_UNIT_TEST_EVENTID = 0x1900, + WMI_FLASH_READ_DONE_EVENTID = 0x1902, + WMI_FLASH_WRITE_DONE_EVENTID = 0x1903, + /* Power management */ + WMI_TRAFFIC_SUSPEND_EVENTID = 0x1904, + WMI_TRAFFIC_RESUME_EVENTID = 0x1905, + /* P2P */ + WMI_P2P_CFG_DONE_EVENTID = 0x1910, + WMI_PORT_ALLOCATED_EVENTID = 0x1911, + WMI_PORT_DELETED_EVENTID = 0x1912, + WMI_LISTEN_STARTED_EVENTID = 0x1914, + WMI_SEARCH_STARTED_EVENTID = 0x1915, + WMI_DISCOVERY_STARTED_EVENTID = 0x1916, + WMI_DISCOVERY_STOPPED_EVENTID = 0x1917, + WMI_PCP_STARTED_EVENTID = 0x1918, + WMI_PCP_STOPPED_EVENTID = 0x1919, + WMI_PCP_FACTOR_EVENTID = 0x191A, + /* Power Save Configuration Events */ + WMI_PS_DEV_PROFILE_CFG_EVENTID = 0x191C, + WMI_RS_CFG_DONE_EVENTID = 0x1921, + WMI_GET_DETAILED_RS_RES_EVENTID = 0x1922, + WMI_AOA_MEAS_EVENTID = 0x1923, + WMI_BRP_SET_ANT_LIMIT_EVENTID = 0x1924, + WMI_SET_MGMT_RETRY_LIMIT_EVENTID = 0x1930, + WMI_GET_MGMT_RETRY_LIMIT_EVENTID = 0x1931, + WMI_SET_THERMAL_THROTTLING_CFG_EVENTID = 0x1940, + WMI_GET_THERMAL_THROTTLING_CFG_EVENTID = 0x1941, + /* return the Power Save profile */ + WMI_PS_DEV_PROFILE_CFG_READ_EVENTID = 0x1942, + WMI_TOF_SESSION_END_EVENTID = 0x1991, + WMI_TOF_GET_CAPABILITIES_EVENTID = 0x1992, + WMI_TOF_SET_LCR_EVENTID = 0x1993, + WMI_TOF_SET_LCI_EVENTID = 0x1994, + WMI_TOF_FTM_PER_DEST_RES_EVENTID = 0x1995, + WMI_TOF_CFG_RESPONDER_EVENTID = 0x1996, + WMI_TOF_SET_TX_RX_OFFSET_EVENTID = 0x1997, + WMI_TOF_GET_TX_RX_OFFSET_EVENTID = 0x1998, + WMI_TOF_CHANNEL_INFO_EVENTID = 0x1999, + WMI_GET_RF_SECTOR_PARAMS_DONE_EVENTID = 0x19A0, + WMI_SET_RF_SECTOR_PARAMS_DONE_EVENTID = 0x19A1, + WMI_GET_SELECTED_RF_SECTOR_INDEX_DONE_EVENTID = 0x19A2, + WMI_SET_SELECTED_RF_SECTOR_INDEX_DONE_EVENTID = 0x19A3, + WMI_SET_RF_SECTOR_ON_DONE_EVENTID = 0x19A4, + WMI_PRIO_TX_SECTORS_ORDER_EVENTID = 0x19A5, + WMI_PRIO_TX_SECTORS_NUMBER_EVENTID = 0x19A6, + WMI_PRIO_TX_SECTORS_SET_DEFAULT_CFG_EVENTID = 0x19A7, + WMI_SCHEDULING_SCHEME_EVENTID = 0x1A01, + WMI_FIXED_SCHEDULING_CONFIG_COMPLETE_EVENTID = 0x1A02, + WMI_ENABLE_FIXED_SCHEDULING_COMPLETE_EVENTID = 0x1A03, + WMI_SET_MULTI_DIRECTED_OMNIS_CONFIG_EVENTID = 0x1A04, + WMI_SET_LONG_RANGE_CONFIG_COMPLETE_EVENTID = 0x1A05, + WMI_SET_CHANNEL_EVENTID = 0x9000, + WMI_ASSOC_REQ_EVENTID = 0x9001, + WMI_EAPOL_RX_EVENTID = 0x9002, + WMI_MAC_ADDR_RESP_EVENTID = 0x9003, + WMI_FW_VER_EVENTID = 0x9004, + WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENTID = 0x9005, + WMI_COMMAND_NOT_SUPPORTED_EVENTID = 0xFFFF, }; -/* - * Events data structures - */ - +/* Events data structures */ enum wmi_fw_status { - WMI_FW_STATUS_SUCCESS, - WMI_FW_STATUS_FAILURE, + WMI_FW_STATUS_SUCCESS = 0x00, + WMI_FW_STATUS_FAILURE = 0x01, }; -/* - * WMI_RF_MGMT_STATUS_EVENTID - */ +/* WMI_RF_MGMT_STATUS_EVENTID */ enum wmi_rf_status { - WMI_RF_ENABLED = 0, - WMI_RF_DISABLED_HW = 1, - WMI_RF_DISABLED_SW = 2, - WMI_RF_DISABLED_HW_SW = 3, + WMI_RF_ENABLED = 0x00, + WMI_RF_DISABLED_HW = 0x01, + WMI_RF_DISABLED_SW = 0x02, + WMI_RF_DISABLED_HW_SW = 0x03, }; +/* WMI_RF_MGMT_STATUS_EVENTID */ struct wmi_rf_mgmt_status_event { __le32 rf_status; } __packed; -/* - * WMI_THERMAL_THROTTLING_STATUS_EVENTID - */ -struct wmi_thermal_throttling_status_event { - __le32 time_on_usec; - __le32 time_off_usec; - __le32 max_txop_length_usec; -} __packed; - -/* - * WMI_GET_STATUS_DONE_EVENTID - */ +/* WMI_GET_STATUS_DONE_EVENTID */ struct wmi_get_status_done_event { __le32 is_associated; u8 cid; @@ -995,19 +1391,93 @@ struct wmi_get_status_done_event { __le32 is_secured; } __packed; -/* - * WMI_FW_VER_EVENTID - */ +/* WMI_FW_VER_EVENTID */ struct wmi_fw_ver_event { - u8 major; - u8 minor; - __le16 subminor; - __le16 build; + /* FW image version */ + __le32 fw_major; + __le32 fw_minor; + __le32 fw_subminor; + __le32 fw_build; + /* FW image build time stamp */ + __le32 hour; + __le32 minute; + __le32 second; + __le32 day; + __le32 month; + __le32 year; + /* Boot Loader image version */ + __le32 bl_major; + __le32 bl_minor; + __le32 bl_subminor; + __le32 bl_build; + /* The number of entries in the FW capabilities array */ + u8 fw_capabilities_len; + u8 reserved[3]; + /* FW capabilities info + * Must be the last member of the struct + */ + __le32 fw_capabilities[0]; } __packed; -/* -* WMI_MAC_ADDR_RESP_EVENTID -*/ +/* WMI_GET_RF_STATUS_EVENTID */ +enum rf_type { + RF_UNKNOWN = 0x00, + RF_MARLON = 0x01, + RF_SPARROW = 0x02, +}; + +/* WMI_GET_RF_STATUS_EVENTID */ +enum board_file_rf_type { + BF_RF_MARLON = 0x00, + BF_RF_SPARROW = 0x01, +}; + +/* WMI_GET_RF_STATUS_EVENTID */ +enum rf_status { + RF_OK = 0x00, + RF_NO_COMM = 0x01, + RF_WRONG_BOARD_FILE = 0x02, +}; + +/* WMI_GET_RF_STATUS_EVENTID */ +struct wmi_get_rf_status_event { + /* enum rf_type */ + __le32 rf_type; + /* attached RFs bit vector */ + __le32 attached_rf_vector; + /* enabled RFs bit vector */ + __le32 enabled_rf_vector; + /* enum rf_status, refers to enabled RFs */ + u8 rf_status[32]; + /* enum board file RF type */ + __le32 board_file_rf_type; + /* board file platform type */ + __le32 board_file_platform_type; + /* board file version */ + __le32 board_file_version; + /* enabled XIFs bit vector */ + __le32 enabled_xif_vector; + __le32 reserved; +} __packed; + +/* WMI_GET_BASEBAND_TYPE_EVENTID */ +enum baseband_type { + BASEBAND_UNKNOWN = 0x00, + BASEBAND_SPARROW_M_A0 = 0x03, + BASEBAND_SPARROW_M_A1 = 0x04, + BASEBAND_SPARROW_M_B0 = 0x05, + BASEBAND_SPARROW_M_C0 = 0x06, + BASEBAND_SPARROW_M_D0 = 0x07, + BASEBAND_TALYN_M_A0 = 0x08, +}; + +/* WMI_GET_BASEBAND_TYPE_EVENTID */ +struct wmi_get_baseband_type_event { + /* enum baseband_type */ + __le32 baseband_type; +} __packed; + +/* WMI_MAC_ADDR_RESP_EVENTID */ struct wmi_mac_addr_resp_event { u8 mac[WMI_MAC_LEN]; u8 auth_mode; @@ -1015,44 +1485,44 @@ struct wmi_mac_addr_resp_event { __le32 offload_mode; } __packed; -/* -* WMI_EAPOL_RX_EVENTID -*/ +/* WMI_EAPOL_RX_EVENTID */ struct wmi_eapol_rx_event { u8 src_mac[WMI_MAC_LEN]; __le16 eapol_len; u8 eapol[0]; } __packed; -/* -* WMI_READY_EVENTID -*/ +/* WMI_READY_EVENTID */ enum wmi_phy_capability { - WMI_11A_CAPABILITY = 1, - WMI_11G_CAPABILITY = 2, - WMI_11AG_CAPABILITY = 3, - WMI_11NA_CAPABILITY = 4, - WMI_11NG_CAPABILITY = 5, - WMI_11NAG_CAPABILITY = 6, - WMI_11AD_CAPABILITY = 7, - WMI_11N_CAPABILITY_OFFSET = WMI_11NA_CAPABILITY - WMI_11A_CAPABILITY, + WMI_11A_CAPABILITY = 0x01, + WMI_11G_CAPABILITY = 0x02, + WMI_11AG_CAPABILITY = 0x03, + WMI_11NA_CAPABILITY = 0x04, + WMI_11NG_CAPABILITY = 0x05, + WMI_11NAG_CAPABILITY = 0x06, + WMI_11AD_CAPABILITY = 0x07, + WMI_11N_CAPABILITY_OFFSET = 0x03, }; struct wmi_ready_event { __le32 sw_version; __le32 abi_version; u8 mac[WMI_MAC_LEN]; - u8 phy_capability; /* enum wmi_phy_capability */ + /* enum wmi_phy_capability */ + u8 phy_capability; u8 numof_additional_mids; + /* rfc read calibration result. 5..15 */ + u8 rfc_read_calib_result; + u8 reserved[3]; } __packed; -/* - * WMI_NOTIFY_REQ_DONE_EVENTID - */ +/* WMI_NOTIFY_REQ_DONE_EVENTID */ struct wmi_notify_req_done_event { - __le32 status; /* beamforming status, 0: fail; 1: OK; 2: retrying */ + /* beamforming status, 0: fail; 1: OK; 2: retrying */ + __le32 status; __le64 tsf; - __le32 snr_val; + s8 rssi; + u8 reserved0[3]; __le32 tx_tpt; __le32 tx_goodput; __le32 rx_goodput; @@ -1066,9 +1536,7 @@ struct wmi_notify_req_done_event { u8 reserved[3]; } __packed; -/* - * WMI_CONNECT_EVENTID - */ +/* WMI_CONNECT_EVENTID */ struct wmi_connect_event { u8 channel; u8 reserved0; @@ -1081,69 +1549,106 @@ struct wmi_connect_event { u8 assoc_req_len; u8 assoc_resp_len; u8 cid; - u8 reserved2[3]; + u8 aid; + u8 reserved2[2]; + /* not in use */ u8 assoc_info[0]; } __packed; -/* - * WMI_DISCONNECT_EVENTID - */ +/* disconnect_reason */ enum wmi_disconnect_reason { - WMI_DIS_REASON_NO_NETWORK_AVAIL = 1, - WMI_DIS_REASON_LOST_LINK = 2, /* bmiss */ - WMI_DIS_REASON_DISCONNECT_CMD = 3, - WMI_DIS_REASON_BSS_DISCONNECTED = 4, - WMI_DIS_REASON_AUTH_FAILED = 5, - WMI_DIS_REASON_ASSOC_FAILED = 6, - WMI_DIS_REASON_NO_RESOURCES_AVAIL = 7, - WMI_DIS_REASON_CSERV_DISCONNECT = 8, - WMI_DIS_REASON_INVALID_PROFILE = 10, - WMI_DIS_REASON_DOT11H_CHANNEL_SWITCH = 11, - WMI_DIS_REASON_PROFILE_MISMATCH = 12, - WMI_DIS_REASON_CONNECTION_EVICTED = 13, - WMI_DIS_REASON_IBSS_MERGE = 14, + WMI_DIS_REASON_NO_NETWORK_AVAIL = 0x01, + /* bmiss */ + WMI_DIS_REASON_LOST_LINK = 0x02, + WMI_DIS_REASON_DISCONNECT_CMD = 0x03, + WMI_DIS_REASON_BSS_DISCONNECTED = 0x04, + WMI_DIS_REASON_AUTH_FAILED = 0x05, + WMI_DIS_REASON_ASSOC_FAILED = 0x06, + WMI_DIS_REASON_NO_RESOURCES_AVAIL = 0x07, + WMI_DIS_REASON_CSERV_DISCONNECT = 0x08, + WMI_DIS_REASON_INVALID_PROFILE = 0x0A, + WMI_DIS_REASON_DOT11H_CHANNEL_SWITCH = 0x0B, + WMI_DIS_REASON_PROFILE_MISMATCH = 0x0C, + WMI_DIS_REASON_CONNECTION_EVICTED = 0x0D, + WMI_DIS_REASON_IBSS_MERGE = 0x0E, }; +/* WMI_DISCONNECT_EVENTID */ struct wmi_disconnect_event { - __le16 protocol_reason_status; /* reason code, see 802.11 spec. */ - u8 bssid[WMI_MAC_LEN]; /* set if known */ - u8 disconnect_reason; /* see wmi_disconnect_reason */ - u8 assoc_resp_len; /* not used */ - u8 assoc_info[0]; /* not used */ + /* reason code, see 802.11 spec. */ + __le16 protocol_reason_status; + /* set if known */ + u8 bssid[WMI_MAC_LEN]; + /* see enum wmi_disconnect_reason */ + u8 disconnect_reason; + /* last assoc req may passed to host - not in used */ + u8 assoc_resp_len; + /* last assoc req may passed to host - not in used */ + u8 assoc_info[0]; } __packed; -/* - * WMI_SCAN_COMPLETE_EVENTID - */ +/* WMI_SCAN_COMPLETE_EVENTID */ enum scan_status { - WMI_SCAN_SUCCESS = 0, - WMI_SCAN_FAILED = 1, - WMI_SCAN_ABORTED = 2, - WMI_SCAN_REJECTED = 3, + WMI_SCAN_SUCCESS = 0x00, + WMI_SCAN_FAILED = 0x01, + WMI_SCAN_ABORTED = 0x02, + WMI_SCAN_REJECTED = 0x03, + WMI_SCAN_ABORT_REJECTED = 0x04, }; struct wmi_scan_complete_event { - __le32 status; /* scan_status */ + /* enum scan_status */ + __le32 status; } __packed; -/* - * WMI_BA_STATUS_EVENTID - */ +/* WMI_ACS_PASSIVE_SCAN_COMPLETE_EVENT */ +enum wmi_acs_info_bitmask { + WMI_ACS_INFO_BITMASK_BEACON_FOUND = 0x01, + WMI_ACS_INFO_BITMASK_BUSY_TIME = 0x02, + WMI_ACS_INFO_BITMASK_TX_TIME = 0x04, + WMI_ACS_INFO_BITMASK_RX_TIME = 0x08, + WMI_ACS_INFO_BITMASK_NOISE = 0x10, +}; + +struct scan_acs_info { + u8 channel; + u8 beacon_found; + /* msec */ + __le16 busy_time; + __le16 tx_time; + __le16 rx_time; + u8 noise; + u8 reserved[3]; +} __packed; + +struct wmi_acs_passive_scan_complete_event { + __le32 dwell_time; + /* valid fields within channel info according to + * their appearance in struct order + */ + __le16 filled; + u8 num_scanned_channels; + u8 reserved; + struct scan_acs_info scan_info_list[0]; +} __packed; + +/* WMI_BA_STATUS_EVENTID */ enum wmi_vring_ba_status { - WMI_BA_AGREED = 0, - WMI_BA_NON_AGREED = 1, + WMI_BA_AGREED = 0x00, + WMI_BA_NON_AGREED = 0x01, /* BA_EN in middle of teardown flow */ - WMI_BA_TD_WIP = 2, + WMI_BA_TD_WIP = 0x02, /* BA_DIS or BA_EN in middle of BA SETUP flow */ - WMI_BA_SETUP_WIP = 3, + WMI_BA_SETUP_WIP = 0x03, /* BA_EN when the BA session is already active */ - WMI_BA_SESSION_ACTIVE = 4, + WMI_BA_SESSION_ACTIVE = 0x04, /* BA_DIS when the BA session is not active */ - WMI_BA_SESSION_NOT_ACTIVE = 5, + WMI_BA_SESSION_NOT_ACTIVE = 0x05, }; -struct wmi_vring_ba_status_event { - __le16 status; /* enum wmi_vring_ba_status */ +struct wmi_ba_status_event { + /* enum wmi_vring_ba_status */ + __le16 status; u8 reserved[2]; u8 ringid; u8 agg_wsize; @@ -1151,18 +1656,14 @@ struct wmi_vring_ba_status_event { u8 amsdu; } __packed; -/* - * WMI_DELBA_EVENTID - */ +/* WMI_DELBA_EVENTID */ struct wmi_delba_event { u8 cidxtid; u8 from_initiator; __le16 reason; } __packed; -/* - * WMI_VRING_CFG_DONE_EVENTID - */ +/* WMI_VRING_CFG_DONE_EVENTID */ struct wmi_vring_cfg_done_event { u8 ringid; u8 status; @@ -1170,215 +1671,1195 @@ struct wmi_vring_cfg_done_event { __le32 tx_vring_tail_ptr; } __packed; -/* - * WMI_RCP_ADDBA_RESP_SENT_EVENTID - */ +/* WMI_RCP_ADDBA_RESP_SENT_EVENTID */ struct wmi_rcp_addba_resp_sent_event { u8 cidxtid; u8 reserved; __le16 status; } __packed; -/* - * WMI_RCP_ADDBA_REQ_EVENTID - */ +/* WMI_RCP_ADDBA_REQ_EVENTID */ struct wmi_rcp_addba_req_event { u8 cidxtid; u8 dialog_token; - __le16 ba_param_set; /* ieee80211_ba_parameterset as it received */ + /* ieee80211_ba_parameterset as it received */ + __le16 ba_param_set; __le16 ba_timeout; - __le16 ba_seq_ctrl; /* ieee80211_ba_seqstrl field as it received */ + /* ieee80211_ba_seqstrl field as it received */ + __le16 ba_seq_ctrl; } __packed; -/* - * WMI_CFG_RX_CHAIN_DONE_EVENTID - */ +/* WMI_CFG_RX_CHAIN_DONE_EVENTID */ enum wmi_cfg_rx_chain_done_event_status { - WMI_CFG_RX_CHAIN_SUCCESS = 1, + WMI_CFG_RX_CHAIN_SUCCESS = 0x01, }; struct wmi_cfg_rx_chain_done_event { - __le32 rx_ring_tail_ptr; /* Rx V-Ring Tail pointer */ + /* V-Ring Tail pointer */ + __le32 rx_ring_tail_ptr; __le32 status; } __packed; -/* - * WMI_WBE_LINK_DOWN_EVENTID - */ +/* WMI_WBE_LINK_DOWN_EVENTID */ enum wmi_wbe_link_down_event_reason { - WMI_WBE_REASON_USER_REQUEST = 0, - WMI_WBE_REASON_RX_DISASSOC = 1, - WMI_WBE_REASON_BAD_PHY_LINK = 2, + WMI_WBE_REASON_USER_REQUEST = 0x00, + WMI_WBE_REASON_RX_DISASSOC = 0x01, + WMI_WBE_REASON_BAD_PHY_LINK = 0x02, }; +/* WMI_WBE_LINK_DOWN_EVENTID */ struct wmi_wbe_link_down_event { u8 cid; u8 reserved[3]; __le32 reason; } __packed; -/* - * WMI_DATA_PORT_OPEN_EVENTID - */ +/* WMI_DATA_PORT_OPEN_EVENTID */ struct wmi_data_port_open_event { u8 cid; u8 reserved[3]; } __packed; -/* - * WMI_VRING_EN_EVENTID - */ +/* WMI_VRING_EN_EVENTID */ struct wmi_vring_en_event { u8 vring_index; u8 reserved[3]; } __packed; -/* - * WMI_GET_PCP_CHANNEL_EVENTID - */ +/* WMI_GET_PCP_CHANNEL_EVENTID */ struct wmi_get_pcp_channel_event { u8 channel; u8 reserved[3]; } __packed; -/* - * WMI_P2P_CFG_DONE_EVENTID - */ +/* WMI_P2P_CFG_DONE_EVENTID */ struct wmi_p2p_cfg_done_event { - u8 status; /* wmi_fw_status */ + /* wmi_fw_status */ + u8 status; u8 reserved[3]; } __packed; -/* -* WMI_PORT_ALLOCATED_EVENTID -*/ +/* WMI_PORT_ALLOCATED_EVENTID */ struct wmi_port_allocated_event { - u8 status; /* wmi_fw_status */ + /* wmi_fw_status */ + u8 status; u8 reserved[3]; } __packed; -/* -* WMI_PORT_DELETED_EVENTID -*/ +/* WMI_PORT_DELETED_EVENTID */ struct wmi_port_deleted_event { - u8 status; /* wmi_fw_status */ + /* wmi_fw_status */ + u8 status; u8 reserved[3]; } __packed; -/* - * WMI_LISTEN_STARTED_EVENTID - */ +/* WMI_LISTEN_STARTED_EVENTID */ struct wmi_listen_started_event { - u8 status; /* wmi_fw_status */ + /* wmi_fw_status */ + u8 status; u8 reserved[3]; } __packed; -/* - * WMI_SEARCH_STARTED_EVENTID - */ +/* WMI_SEARCH_STARTED_EVENTID */ struct wmi_search_started_event { - u8 status; /* wmi_fw_status */ + /* wmi_fw_status */ + u8 status; u8 reserved[3]; } __packed; -/* - * WMI_PCP_STARTED_EVENTID - */ +/* WMI_PCP_STARTED_EVENTID */ struct wmi_pcp_started_event { - u8 status; /* wmi_fw_status */ + /* wmi_fw_status */ + u8 status; u8 reserved[3]; } __packed; -/* - * WMI_PCP_FACTOR_EVENTID - */ +/* WMI_PCP_FACTOR_EVENTID */ struct wmi_pcp_factor_event { __le32 pcp_factor; } __packed; -/* - * WMI_SW_TX_COMPLETE_EVENTID - */ enum wmi_sw_tx_status { - WMI_TX_SW_STATUS_SUCCESS = 0, - WMI_TX_SW_STATUS_FAILED_NO_RESOURCES = 1, - WMI_TX_SW_STATUS_FAILED_TX = 2, + WMI_TX_SW_STATUS_SUCCESS = 0x00, + WMI_TX_SW_STATUS_FAILED_NO_RESOURCES = 0x01, + WMI_TX_SW_STATUS_FAILED_TX = 0x02, }; +/* WMI_SW_TX_COMPLETE_EVENTID */ struct wmi_sw_tx_complete_event { - u8 status; /* enum wmi_sw_tx_status */ + /* enum wmi_sw_tx_status */ + u8 status; u8 reserved[3]; } __packed; -/* - * WMI_CORR_MEASURE_EVENTID - */ +/* WMI_CORR_MEASURE_EVENTID - deprecated */ struct wmi_corr_measure_event { - s32 i; - s32 q; - s32 image_i; - s32 image_q; + /* signed */ + __le32 i; + /* signed */ + __le32 q; + /* signed */ + __le32 image_i; + /* signed */ + __le32 image_q; } __packed; -/* - * WMI_READ_RSSI_EVENTID - */ +/* WMI_READ_RSSI_EVENTID */ struct wmi_read_rssi_event { __le32 ina_rssi_adc_dbm; } __packed; -/* - * WMI_GET_SSID_EVENTID - */ +/* WMI_GET_SSID_EVENTID */ struct wmi_get_ssid_event { __le32 ssid_len; u8 ssid[WMI_MAX_SSID_LEN]; } __packed; -/* - * WMI_RX_MGMT_PACKET_EVENTID - */ +/* wmi_rx_mgmt_info */ struct wmi_rx_mgmt_info { u8 mcs; - s8 snr; + s8 rssi; u8 range; u8 sqi; __le16 stype; - __le16 status; + __le16 snr; __le32 len; + /* Not resolved when == 0xFFFFFFFF == > Broadcast to all MIDS */ u8 qid; + /* Not resolved when == 0xFFFFFFFF == > Broadcast to all MIDS */ u8 mid; u8 cid; - u8 channel; /* From Radio MNGR */ + /* From Radio MNGR */ + u8 channel; } __packed; -/* - * WMI_TX_MGMT_PACKET_EVENTID - */ +/* EVENT: WMI_RF_XPM_READ_RESULT_EVENTID */ +struct wmi_rf_xpm_read_result_event { + /* enum wmi_fw_status_e - success=0 or fail=1 */ + u8 status; + u8 reserved[3]; + /* requested num_bytes of data */ + u8 data_bytes[0]; +} __packed; + +/* EVENT: WMI_RF_XPM_WRITE_RESULT_EVENTID */ +struct wmi_rf_xpm_write_result_event { + /* enum wmi_fw_status_e - success=0 or fail=1 */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_TX_MGMT_PACKET_EVENTID */ struct wmi_tx_mgmt_packet_event { u8 payload[0]; } __packed; +/* WMI_RX_MGMT_PACKET_EVENTID */ struct wmi_rx_mgmt_packet_event { struct wmi_rx_mgmt_info info; u8 payload[0]; } __packed; -/* - * WMI_ECHO_RSP_EVENTID - */ -struct wmi_echo_event { +/* WMI_ECHO_RSP_EVENTID */ +struct wmi_echo_rsp_event { __le32 echoed_value; } __packed; -/* - * WMI_TEMP_SENSE_DONE_EVENTID +/* WMI_RF_PWR_ON_DELAY_RSP_EVENTID */ +struct wmi_rf_pwr_on_delay_rsp_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_SET_HIGH_POWER_TABLE_PARAMS_EVENTID */ +struct wmi_set_high_power_table_params_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_TEMP_SENSE_DONE_EVENTID * * Measure MAC and radio temperatures */ struct wmi_temp_sense_done_event { + /* Temperature times 1000 (actual temperature will be achieved by + * dividing the value by 1000) + */ __le32 baseband_t1000; + /* Temperature times 1000 (actual temperature will be achieved by + * dividing the value by 1000) + */ __le32 rf_t1000; } __packed; +#define WMI_SCAN_DWELL_TIME_MS (100) +#define WMI_SURVEY_TIMEOUT_MS (10000) + +enum wmi_hidden_ssid { + WMI_HIDDEN_SSID_DISABLED = 0x00, + WMI_HIDDEN_SSID_SEND_EMPTY = 0x10, + WMI_HIDDEN_SSID_CLEAR = 0xFE, +}; + +/* WMI_LED_CFG_CMDID + * + * Configure LED On\Off\Blinking operation + * + * Returned events: + * - WMI_LED_CFG_DONE_EVENTID + */ +enum led_mode { + LED_DISABLE = 0x00, + LED_ENABLE = 0x01, +}; + +/* The names of the led as + * described on HW schemes. + */ +enum wmi_led_id { + WMI_LED_WLAN = 0x00, + WMI_LED_WPAN = 0x01, + WMI_LED_WWAN = 0x02, +}; + +/* Led polarity mode. */ +enum wmi_led_polarity { + LED_POLARITY_HIGH_ACTIVE = 0x00, + LED_POLARITY_LOW_ACTIVE = 0x01, +}; + +/* Combination of on and off + * creates the blinking period + */ +struct wmi_led_blink_mode { + __le32 blink_on; + __le32 blink_off; +} __packed; + +/* WMI_LED_CFG_CMDID */ +struct wmi_led_cfg_cmd { + /* enum led_mode_e */ + u8 led_mode; + /* enum wmi_led_id_e */ + u8 id; + /* slow speed blinking combination */ + struct wmi_led_blink_mode slow_blink_cfg; + /* medium speed blinking combination */ + struct wmi_led_blink_mode medium_blink_cfg; + /* high speed blinking combination */ + struct wmi_led_blink_mode fast_blink_cfg; + /* polarity of the led */ + u8 led_polarity; + /* reserved */ + u8 reserved; +} __packed; + +/* \WMI_SET_CONNECT_SNR_THR_CMDID */ +struct wmi_set_connect_snr_thr_cmd { + u8 enable; + u8 reserved; + /* 1/4 Db units */ + __le16 omni_snr_thr; + /* 1/4 Db units */ + __le16 direct_snr_thr; +} __packed; + +/* WMI_LED_CFG_DONE_EVENTID */ +struct wmi_led_cfg_done_event { + /* led config status */ + __le32 status; +} __packed; + +/* Rate search parameters configuration per connection */ +struct wmi_rs_cfg { + /* The maximal allowed PER for each MCS + * MCS will be considered as failed if PER during RS is higher + */ + u8 per_threshold[WMI_NUM_MCS]; + /* Number of MPDUs for each MCS + * this is the minimal statistic required to make an educated + * decision + */ + u8 min_frame_cnt[WMI_NUM_MCS]; + /* stop threshold [0-100] */ + u8 stop_th; + /* MCS1 stop threshold [0-100] */ + u8 mcs1_fail_th; + u8 max_back_failure_th; + /* Debug feature for disabling internal RS trigger (which is + * currently triggered by BF Done) + */ + u8 dbg_disable_internal_trigger; + __le32 back_failure_mask; + __le32 mcs_en_vec; +} __packed; + +/* Slot types */ +enum wmi_sched_scheme_slot_type { + WMI_SCHED_SLOT_SP = 0x0, + WMI_SCHED_SLOT_CBAP = 0x1, + WMI_SCHED_SLOT_IDLE = 0x2, + WMI_SCHED_SLOT_ANNOUNCE_NO_ACK = 0x3, + WMI_SCHED_SLOT_DISCOVERY = 0x4, +}; + +enum wmi_sched_scheme_slot_flags { + WMI_SCHED_SCHEME_SLOT_PERIODIC = 0x1, +}; + +struct wmi_sched_scheme_slot { + /* in microsecond */ + __le32 tbtt_offset; + /* wmi_sched_scheme_slot_flags */ + u8 flags; + /* wmi_sched_scheme_slot_type */ + u8 type; + /* in microsecond */ + __le16 duration; + /* frame_exchange_sequence_duration */ + __le16 tx_op; + /* time in microseconds between two consecutive slots + * relevant only if flag WMI_SCHED_SCHEME_SLOT_PERIODIC set + */ + __le16 period; + /* relevant only if flag WMI_SCHED_SCHEME_SLOT_PERIODIC set + * number of times to repeat allocation + */ + u8 num_of_blocks; + /* relevant only if flag WMI_SCHED_SCHEME_SLOT_PERIODIC set + * every idle_period allocation will be idle + */ + u8 idle_period; + u8 src_aid; + u8 dest_aid; + __le32 reserved; +} __packed; + +enum wmi_sched_scheme_flags { + /* should not be set when clearing scheduling scheme */ + WMI_SCHED_SCHEME_ENABLE = 0x01, + WMI_SCHED_PROTECTED_SP = 0x02, + /* should be set only on first WMI fragment of scheme */ + WMI_SCHED_FIRST = 0x04, + /* should be set only on last WMI fragment of scheme */ + WMI_SCHED_LAST = 0x08, + WMI_SCHED_IMMEDIATE_START = 0x10, +}; + +enum wmi_sched_scheme_advertisment { + /* ESE is not advertised at all, STA has to be configured with WMI + * also + */ + WMI_ADVERTISE_ESE_DISABLED = 0x0, + WMI_ADVERTISE_ESE_IN_BEACON = 0x1, + WMI_ADVERTISE_ESE_IN_ANNOUNCE_FRAME = 0x2, +}; + +/* WMI_SCHEDULING_SCHEME_CMD */ +struct wmi_scheduling_scheme_cmd { + u8 serial_num; + /* wmi_sched_scheme_advertisment */ + u8 ese_advertisment; + /* wmi_sched_scheme_flags */ + __le16 flags; + u8 num_allocs; + u8 reserved[3]; + __le64 start_tbtt; + /* allocations list */ + struct wmi_sched_scheme_slot allocs[WMI_SCHED_MAX_ALLOCS_PER_CMD]; +} __packed; + +enum wmi_sched_scheme_failure_type { + WMI_SCHED_SCHEME_FAILURE_NO_ERROR = 0x00, + WMI_SCHED_SCHEME_FAILURE_OLD_START_TSF_ERR = 0x01, +}; + +/* WMI_SCHEDULING_SCHEME_EVENTID */ +struct wmi_scheduling_scheme_event { + /* wmi_fw_status_e */ + u8 status; + /* serial number given in command */ + u8 serial_num; + /* wmi_sched_scheme_failure_type */ + u8 failure_type; + /* alignment to 32b */ + u8 reserved[1]; +} __packed; + +/* WMI_RS_CFG_CMDID */ +struct wmi_rs_cfg_cmd { + /* connection id */ + u8 cid; + /* enable or disable rate search */ + u8 rs_enable; + /* rate search configuration */ + struct wmi_rs_cfg rs_cfg; +} __packed; + +/* WMI_RS_CFG_DONE_EVENTID */ +struct wmi_rs_cfg_done_event { + u8 cid; + /* enum wmi_fw_status */ + u8 status; + u8 reserved[2]; +} __packed; + +/* WMI_GET_DETAILED_RS_RES_CMDID */ +struct wmi_get_detailed_rs_res_cmd { + /* connection id */ + u8 cid; + u8 reserved[3]; +} __packed; + +/* RS results status */ +enum wmi_rs_results_status { + WMI_RS_RES_VALID = 0x00, + WMI_RS_RES_INVALID = 0x01, +}; + +/* Rate search results */ +struct wmi_rs_results { + /* number of sent MPDUs */ + u8 num_of_tx_pkt[WMI_NUM_MCS]; + /* number of non-acked MPDUs */ + u8 num_of_non_acked_pkt[WMI_NUM_MCS]; + /* RS timestamp */ + __le32 tsf; + /* RS selected MCS */ + u8 mcs; +} __packed; + +/* WMI_GET_DETAILED_RS_RES_EVENTID */ +struct wmi_get_detailed_rs_res_event { + u8 cid; + /* enum wmi_rs_results_status */ + u8 status; + /* detailed rs results */ + struct wmi_rs_results rs_results; + u8 reserved[3]; +} __packed; + +/* BRP antenna limit mode */ +enum wmi_brp_ant_limit_mode { + /* Disable BRP force antenna limit */ + WMI_BRP_ANT_LIMIT_MODE_DISABLE = 0x00, + /* Define maximal antennas limit. Only effective antennas will be + * actually used + */ + WMI_BRP_ANT_LIMIT_MODE_EFFECTIVE = 0x01, + /* Force a specific number of antennas */ + WMI_BRP_ANT_LIMIT_MODE_FORCE = 0x02, + /* number of BRP antenna limit modes */ + WMI_BRP_ANT_LIMIT_MODES_NUM = 0x03, +}; + +/* WMI_BRP_SET_ANT_LIMIT_CMDID */ +struct wmi_brp_set_ant_limit_cmd { + /* connection id */ + u8 cid; + /* enum wmi_brp_ant_limit_mode */ + u8 limit_mode; + /* antenna limit count, 1-27 + * disable_mode - ignored + * effective_mode - upper limit to number of antennas to be used + * force_mode - exact number of antennas to be used + */ + u8 ant_limit; + u8 reserved; +} __packed; + +/* WMI_BRP_SET_ANT_LIMIT_EVENTID */ +struct wmi_brp_set_ant_limit_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* broadcast connection ID */ +#define WMI_LINK_MAINTAIN_CFG_CID_BROADCAST (0xFFFFFFFF) + +/* Types wmi_link_maintain_cfg presets for WMI_LINK_MAINTAIN_CFG_WRITE_CMD */ +enum wmi_link_maintain_cfg_type { + /* AP/PCP default normal (non-FST) configuration settings */ + WMI_LINK_MAINTAIN_CFG_TYPE_DEFAULT_NORMAL_AP = 0x00, + /* AP/PCP default FST configuration settings */ + WMI_LINK_MAINTAIN_CFG_TYPE_DEFAULT_FST_AP = 0x01, + /* STA default normal (non-FST) configuration settings */ + WMI_LINK_MAINTAIN_CFG_TYPE_DEFAULT_NORMAL_STA = 0x02, + /* STA default FST configuration settings */ + WMI_LINK_MAINTAIN_CFG_TYPE_DEFAULT_FST_STA = 0x03, + /* custom configuration settings */ + WMI_LINK_MAINTAIN_CFG_TYPE_CUSTOM = 0x04, + /* number of defined configuration types */ + WMI_LINK_MAINTAIN_CFG_TYPES_NUM = 0x05, +}; + +/* Response status codes for WMI_LINK_MAINTAIN_CFG_WRITE/READ commands */ +enum wmi_link_maintain_cfg_response_status { + /* WMI_LINK_MAINTAIN_CFG_WRITE/READ command successfully accomplished + */ + WMI_LINK_MAINTAIN_CFG_RESPONSE_STATUS_OK = 0x00, + /* ERROR due to bad argument in WMI_LINK_MAINTAIN_CFG_WRITE/READ + * command request + */ + WMI_LINK_MAINTAIN_CFG_RESPONSE_STATUS_BAD_ARGUMENT = 0x01, +}; + +/* Link Loss and Keep Alive configuration */ +struct wmi_link_maintain_cfg { + /* link_loss_enable_detectors_vec */ + __le32 link_loss_enable_detectors_vec; + /* detectors check period usec */ + __le32 check_link_loss_period_usec; + /* max allowed tx ageing */ + __le32 tx_ageing_threshold_usec; + /* keep alive period for high SNR */ + __le32 keep_alive_period_usec_high_snr; + /* keep alive period for low SNR */ + __le32 keep_alive_period_usec_low_snr; + /* lower snr limit for keep alive period update */ + __le32 keep_alive_snr_threshold_low_db; + /* upper snr limit for keep alive period update */ + __le32 keep_alive_snr_threshold_high_db; + /* num of successive bad bcons causing link-loss */ + __le32 bad_beacons_num_threshold; + /* SNR limit for bad_beacons_detector */ + __le32 bad_beacons_snr_threshold_db; +} __packed; + +/* WMI_LINK_MAINTAIN_CFG_WRITE_CMDID */ +struct wmi_link_maintain_cfg_write_cmd { + /* enum wmi_link_maintain_cfg_type_e - type of requested default + * configuration to be applied + */ + __le32 cfg_type; + /* requested connection ID or WMI_LINK_MAINTAIN_CFG_CID_BROADCAST */ + __le32 cid; + /* custom configuration settings to be applied (relevant only if + * cfg_type==WMI_LINK_MAINTAIN_CFG_TYPE_CUSTOM) + */ + struct wmi_link_maintain_cfg lm_cfg; +} __packed; + +/* WMI_LINK_MAINTAIN_CFG_READ_CMDID */ +struct wmi_link_maintain_cfg_read_cmd { + /* connection ID which configuration settings are requested */ + __le32 cid; +} __packed; + +/* WMI_LINK_MAINTAIN_CFG_WRITE_DONE_EVENTID */ +struct wmi_link_maintain_cfg_write_done_event { + /* requested connection ID */ + __le32 cid; + /* wmi_link_maintain_cfg_response_status_e - write status */ + __le32 status; +} __packed; + +/* \WMI_LINK_MAINTAIN_CFG_READ_DONE_EVENT */ +struct wmi_link_maintain_cfg_read_done_event { + /* requested connection ID */ + __le32 cid; + /* wmi_link_maintain_cfg_response_status_e - read status */ + __le32 status; + /* Retrieved configuration settings */ + struct wmi_link_maintain_cfg lm_cfg; +} __packed; + +enum wmi_traffic_suspend_status { + WMI_TRAFFIC_SUSPEND_APPROVED = 0x0, + WMI_TRAFFIC_SUSPEND_REJECTED = 0x1, +}; + +/* WMI_TRAFFIC_SUSPEND_EVENTID */ +struct wmi_traffic_suspend_event { + /* enum wmi_traffic_suspend_status_e */ + u8 status; +} __packed; + +enum wmi_traffic_resume_status { + WMI_TRAFFIC_RESUME_SUCCESS = 0x0, + WMI_TRAFFIC_RESUME_FAILED = 0x1, +}; + +/* WMI_TRAFFIC_RESUME_EVENTID */ +struct wmi_traffic_resume_event { + /* enum wmi_traffic_resume_status_e */ + u8 status; +} __packed; + +/* Power Save command completion status codes */ +enum wmi_ps_cfg_cmd_status { + WMI_PS_CFG_CMD_STATUS_SUCCESS = 0x00, + WMI_PS_CFG_CMD_STATUS_BAD_PARAM = 0x01, + /* other error */ + WMI_PS_CFG_CMD_STATUS_ERROR = 0x02, +}; + +/* Device Power Save Profiles */ +enum wmi_ps_profile_type { + WMI_PS_PROFILE_TYPE_DEFAULT = 0x00, + WMI_PS_PROFILE_TYPE_PS_DISABLED = 0x01, + WMI_PS_PROFILE_TYPE_MAX_PS = 0x02, + WMI_PS_PROFILE_TYPE_LOW_LATENCY_PS = 0x03, +}; + +/* WMI_PS_DEV_PROFILE_CFG_READ_CMDID */ +struct wmi_ps_dev_profile_cfg_read_cmd { + /* reserved */ + __le32 reserved; +} __packed; + +/* WMI_PS_DEV_PROFILE_CFG_READ_EVENTID */ +struct wmi_ps_dev_profile_cfg_read_event { + /* wmi_ps_profile_type_e */ + u8 ps_profile; + u8 reserved[3]; +} __packed; + +/* WMI_PS_DEV_PROFILE_CFG_CMDID + * + * Power save profile to be used by the device + * + * Returned event: + * - WMI_PS_DEV_PROFILE_CFG_EVENTID + */ +struct wmi_ps_dev_profile_cfg_cmd { + /* wmi_ps_profile_type_e */ + u8 ps_profile; + u8 reserved[3]; +} __packed; + +/* WMI_PS_DEV_PROFILE_CFG_EVENTID */ +struct wmi_ps_dev_profile_cfg_event { + /* wmi_ps_cfg_cmd_status_e */ + __le32 status; +} __packed; + +enum wmi_ps_level { + WMI_PS_LEVEL_DEEP_SLEEP = 0x00, + WMI_PS_LEVEL_SHALLOW_SLEEP = 0x01, + /* awake = all PS mechanisms are disabled */ + WMI_PS_LEVEL_AWAKE = 0x02, +}; + +enum wmi_ps_deep_sleep_clk_level { + /* 33k */ + WMI_PS_DEEP_SLEEP_CLK_LEVEL_RTC = 0x00, + /* 10k */ + WMI_PS_DEEP_SLEEP_CLK_LEVEL_OSC = 0x01, + /* @RTC Low latency */ + WMI_PS_DEEP_SLEEP_CLK_LEVEL_RTC_LT = 0x02, + WMI_PS_DEEP_SLEEP_CLK_LEVEL_XTAL = 0x03, + WMI_PS_DEEP_SLEEP_CLK_LEVEL_SYSCLK = 0x04, + /* Not Applicable */ + WMI_PS_DEEP_SLEEP_CLK_LEVEL_N_A = 0xFF, +}; + +/* Response by the FW to a D3 entry request */ +enum wmi_ps_d3_resp_policy { + WMI_PS_D3_RESP_POLICY_DEFAULT = 0x00, + /* debug -D3 req is always denied */ + WMI_PS_D3_RESP_POLICY_DENIED = 0x01, + /* debug -D3 req is always approved */ + WMI_PS_D3_RESP_POLICY_APPROVED = 0x02, +}; + +#define WMI_AOA_MAX_DATA_SIZE (128) + +enum wmi_aoa_meas_status { + WMI_AOA_MEAS_SUCCESS = 0x00, + WMI_AOA_MEAS_PEER_INCAPABLE = 0x01, + WMI_AOA_MEAS_FAILURE = 0x02, +}; + +/* WMI_AOA_MEAS_EVENTID */ +struct wmi_aoa_meas_event { + u8 mac_addr[WMI_MAC_LEN]; + /* channels IDs: + * 0 - 58320 MHz + * 1 - 60480 MHz + * 2 - 62640 MHz + */ + u8 channel; + /* enum wmi_aoa_meas_type */ + u8 aoa_meas_type; + /* Measurments are from RFs, defined by the mask */ + __le32 meas_rf_mask; + /* enum wmi_aoa_meas_status */ + u8 meas_status; + u8 reserved; + /* Length of meas_data in bytes */ + __le16 length; + u8 meas_data[WMI_AOA_MAX_DATA_SIZE]; +} __packed; + +/* WMI_SET_MGMT_RETRY_LIMIT_EVENTID */ +struct wmi_set_mgmt_retry_limit_event { + /* enum wmi_fw_status */ + u8 status; + /* alignment to 32b */ + u8 reserved[3]; +} __packed; + +/* WMI_GET_MGMT_RETRY_LIMIT_EVENTID */ +struct wmi_get_mgmt_retry_limit_event { + /* MAC retransmit limit for mgmt frames */ + u8 mgmt_retry_limit; + /* alignment to 32b */ + u8 reserved[3]; +} __packed; + +/* WMI_TOF_GET_CAPABILITIES_EVENTID */ +struct wmi_tof_get_capabilities_event { + u8 ftm_capability; + /* maximum supported number of destination to start TOF */ + u8 max_num_of_dest; + /* maximum supported number of measurements per burst */ + u8 max_num_of_meas_per_burst; + u8 reserved; + /* maximum supported multi bursts */ + __le16 max_multi_bursts_sessions; + /* maximum supported FTM burst duration , wmi_tof_burst_duration_e */ + __le16 max_ftm_burst_duration; + /* AOA supported types */ + __le32 aoa_supported_types; +} __packed; + +/* WMI_SET_THERMAL_THROTTLING_CFG_EVENTID */ +struct wmi_set_thermal_throttling_cfg_event { + /* wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_GET_THERMAL_THROTTLING_CFG_EVENTID */ +struct wmi_get_thermal_throttling_cfg_event { + /* Status data */ + struct wmi_tt_data tt_data; +} __packed; + +enum wmi_tof_session_end_status { + WMI_TOF_SESSION_END_NO_ERROR = 0x00, + WMI_TOF_SESSION_END_FAIL = 0x01, + WMI_TOF_SESSION_END_PARAMS_ERROR = 0x02, + WMI_TOF_SESSION_END_ABORTED = 0x03, +}; + +/* WMI_TOF_SESSION_END_EVENTID */ +struct wmi_tof_session_end_event { + /* FTM session ID */ + __le32 session_id; + /* wmi_tof_session_end_status_e */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_TOF_SET_LCI_EVENTID */ +struct wmi_tof_set_lci_event { + /* enum wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_TOF_SET_LCR_EVENTID */ +struct wmi_tof_set_lcr_event { + /* enum wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* Responder FTM Results */ +struct wmi_responder_ftm_res { + u8 t1[6]; + u8 t2[6]; + u8 t3[6]; + u8 t4[6]; + __le16 tod_err; + __le16 toa_err; + __le16 tod_err_initiator; + __le16 toa_err_initiator; +} __packed; + +enum wmi_tof_ftm_per_dest_res_status { + WMI_PER_DEST_RES_NO_ERROR = 0x00, + WMI_PER_DEST_RES_TX_RX_FAIL = 0x01, + WMI_PER_DEST_RES_PARAM_DONT_MATCH = 0x02, +}; + +enum wmi_tof_ftm_per_dest_res_flags { + WMI_PER_DEST_RES_REQ_START = 0x01, + WMI_PER_DEST_RES_BURST_REPORT_END = 0x02, + WMI_PER_DEST_RES_REQ_END = 0x04, + WMI_PER_DEST_RES_PARAM_UPDATE = 0x08, +}; + +/* WMI_TOF_FTM_PER_DEST_RES_EVENTID */ +struct wmi_tof_ftm_per_dest_res_event { + /* FTM session ID */ + __le32 session_id; + /* destination MAC address */ + u8 dst_mac[WMI_MAC_LEN]; + /* wmi_tof_ftm_per_dest_res_flags_e */ + u8 flags; + /* wmi_tof_ftm_per_dest_res_status_e */ + u8 status; + /* responder ASAP */ + u8 responder_asap; + /* responder number of FTM per burst */ + u8 responder_num_ftm_per_burst; + /* responder number of FTM burst exponent */ + u8 responder_num_ftm_bursts_exp; + /* responder burst duration ,wmi_tof_burst_duration_e */ + u8 responder_burst_duration; + /* responder burst period, indicate interval between two consecutive + * burst instances, in units of 100 ms + */ + __le16 responder_burst_period; + /* receive burst counter */ + __le16 bursts_cnt; + /* tsf of responder start burst */ + __le32 tsf_sync; + /* actual received ftm per burst */ + u8 actual_ftm_per_burst; + /* Measurments are from RFs, defined by the mask */ + __le32 meas_rf_mask; + u8 reserved0[3]; + struct wmi_responder_ftm_res responder_ftm_res[0]; +} __packed; + +/* WMI_TOF_CFG_RESPONDER_EVENTID */ +struct wmi_tof_cfg_responder_event { + /* enum wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +enum wmi_tof_channel_info_type { + WMI_TOF_CHANNEL_INFO_AOA = 0x00, + WMI_TOF_CHANNEL_INFO_LCI = 0x01, + WMI_TOF_CHANNEL_INFO_LCR = 0x02, + WMI_TOF_CHANNEL_INFO_VENDOR_SPECIFIC = 0x03, + WMI_TOF_CHANNEL_INFO_CIR = 0x04, + WMI_TOF_CHANNEL_INFO_RSSI = 0x05, + WMI_TOF_CHANNEL_INFO_SNR = 0x06, + WMI_TOF_CHANNEL_INFO_DEBUG = 0x07, +}; + +/* WMI_TOF_CHANNEL_INFO_EVENTID */ +struct wmi_tof_channel_info_event { + /* FTM session ID */ + __le32 session_id; + /* destination MAC address */ + u8 dst_mac[WMI_MAC_LEN]; + /* wmi_tof_channel_info_type_e */ + u8 type; + /* data report length */ + u8 len; + /* data report payload */ + u8 report[0]; +} __packed; + +/* WMI_TOF_SET_TX_RX_OFFSET_EVENTID */ +struct wmi_tof_set_tx_rx_offset_event { + /* enum wmi_fw_status */ + u8 status; + u8 reserved[3]; +} __packed; + +/* WMI_TOF_GET_TX_RX_OFFSET_EVENTID */ +struct wmi_tof_get_tx_rx_offset_event { + /* enum wmi_fw_status */ + u8 status; + /* RF index used to read the offsets */ + u8 rf_index; + u8 reserved1[2]; + /* TX delay offset */ + __le32 tx_offset; + /* RX delay offset */ + __le32 rx_offset; + /* Offset to strongest tap of CIR */ + __le32 precursor; +} __packed; + +/* Result status codes for WMI commands */ +enum wmi_rf_sector_status { + WMI_RF_SECTOR_STATUS_SUCCESS = 0x00, + WMI_RF_SECTOR_STATUS_BAD_PARAMETERS_ERROR = 0x01, + WMI_RF_SECTOR_STATUS_BUSY_ERROR = 0x02, + WMI_RF_SECTOR_STATUS_NOT_SUPPORTED_ERROR = 0x03, +}; + +/* Types of the RF sector (TX,RX) */ +enum wmi_rf_sector_type { + WMI_RF_SECTOR_TYPE_RX = 0x00, + WMI_RF_SECTOR_TYPE_TX = 0x01, +}; + +/* Content of RF Sector (six 32-bits registers) */ +struct wmi_rf_sector_info { + /* Phase values for RF Chains[15-0] (2bits per RF chain) */ + __le32 psh_hi; + /* Phase values for RF Chains[31-16] (2bits per RF chain) */ + __le32 psh_lo; + /* ETYPE Bit0 for all RF chains[31-0] - bit0 of Edge amplifier gain + * index + */ + __le32 etype0; + /* ETYPE Bit1 for all RF chains[31-0] - bit1 of Edge amplifier gain + * index + */ + __le32 etype1; + /* ETYPE Bit2 for all RF chains[31-0] - bit2 of Edge amplifier gain + * index + */ + __le32 etype2; + /* D-Type values (3bits each) for 8 Distribution amplifiers + X16 + * switch bits + */ + __le32 dtype_swch_off; +} __packed; + +#define WMI_INVALID_RF_SECTOR_INDEX (0xFFFF) +#define WMI_MAX_RF_MODULES_NUM (8) + +/* WMI_GET_RF_SECTOR_PARAMS_CMD */ +struct wmi_get_rf_sector_params_cmd { + /* Sector number to be retrieved */ + __le16 sector_idx; + /* enum wmi_rf_sector_type - type of requested RF sector */ + u8 sector_type; + /* bitmask vector specifying destination RF modules */ + u8 rf_modules_vec; +} __packed; + +/* \WMI_GET_RF_SECTOR_PARAMS_DONE_EVENT */ +struct wmi_get_rf_sector_params_done_event { + /* result status of WMI_GET_RF_SECTOR_PARAMS_CMD (enum + * wmi_rf_sector_status) + */ + u8 status; + /* align next field to U64 boundary */ + u8 reserved[7]; + /* TSF timestamp when RF sectors where retrieved */ + __le64 tsf; + /* Content of RF sector retrieved from each RF module */ + struct wmi_rf_sector_info sectors_info[WMI_MAX_RF_MODULES_NUM]; +} __packed; + +/* WMI_SET_RF_SECTOR_PARAMS_CMD */ +struct wmi_set_rf_sector_params_cmd { + /* Sector number to be retrieved */ + __le16 sector_idx; + /* enum wmi_rf_sector_type - type of requested RF sector */ + u8 sector_type; + /* bitmask vector specifying destination RF modules */ + u8 rf_modules_vec; + /* Content of RF sector to be written to each RF module */ + struct wmi_rf_sector_info sectors_info[WMI_MAX_RF_MODULES_NUM]; +} __packed; + +/* \WMI_SET_RF_SECTOR_PARAMS_DONE_EVENT */ +struct wmi_set_rf_sector_params_done_event { + /* result status of WMI_SET_RF_SECTOR_PARAMS_CMD (enum + * wmi_rf_sector_status) + */ + u8 status; +} __packed; + +/* WMI_GET_SELECTED_RF_SECTOR_INDEX_CMD - Get RF sector index selected by + * TXSS/BRP for communication with specified CID + */ +struct wmi_get_selected_rf_sector_index_cmd { + /* Connection/Station ID in [0:7] range */ + u8 cid; + /* type of requested RF sector (enum wmi_rf_sector_type) */ + u8 sector_type; + /* align to U32 boundary */ + u8 reserved[2]; +} __packed; + +/* \WMI_GET_SELECTED_RF_SECTOR_INDEX_DONE_EVENT - Returns retrieved RF sector + * index selected by TXSS/BRP for communication with specified CID + */ +struct wmi_get_selected_rf_sector_index_done_event { + /* Retrieved sector index selected in TXSS (for TX sector request) or + * BRP (for RX sector request) + */ + __le16 sector_idx; + /* result status of WMI_GET_SELECTED_RF_SECTOR_INDEX_CMD (enum + * wmi_rf_sector_status) + */ + u8 status; + /* align next field to U64 boundary */ + u8 reserved[5]; + /* TSF timestamp when result was retrieved */ + __le64 tsf; +} __packed; + +/* WMI_SET_SELECTED_RF_SECTOR_INDEX_CMD - Force RF sector index for + * communication with specified CID. Assumes that TXSS/BRP is disabled by + * other command + */ +struct wmi_set_selected_rf_sector_index_cmd { + /* Connection/Station ID in [0:7] range */ + u8 cid; + /* type of requested RF sector (enum wmi_rf_sector_type) */ + u8 sector_type; + /* Forced sector index */ + __le16 sector_idx; +} __packed; + +/* \WMI_SET_SELECTED_RF_SECTOR_INDEX_DONE_EVENT - Success/Fail status for + * WMI_SET_SELECTED_RF_SECTOR_INDEX_CMD + */ +struct wmi_set_selected_rf_sector_index_done_event { + /* result status of WMI_SET_SELECTED_RF_SECTOR_INDEX_CMD (enum + * wmi_rf_sector_status) + */ + u8 status; + /* align to U32 boundary */ + u8 reserved[3]; +} __packed; + +/* WMI_SET_RF_SECTOR_ON_CMD - Activates specified sector for specified rf + * modules + */ +struct wmi_set_rf_sector_on_cmd { + /* Sector index to be activated */ + __le16 sector_idx; + /* type of requested RF sector (enum wmi_rf_sector_type) */ + u8 sector_type; + /* bitmask vector specifying destination RF modules */ + u8 rf_modules_vec; +} __packed; + +/* \WMI_SET_RF_SECTOR_ON_DONE_EVENT - Success/Fail status for + * WMI_SET_RF_SECTOR_ON_CMD + */ +struct wmi_set_rf_sector_on_done_event { + /* result status of WMI_SET_RF_SECTOR_ON_CMD (enum + * wmi_rf_sector_status) + */ + u8 status; + /* align to U32 boundary */ + u8 reserved[3]; +} __packed; + +enum wmi_sector_sweep_type { + WMI_SECTOR_SWEEP_TYPE_TXSS = 0x00, + WMI_SECTOR_SWEEP_TYPE_BCON = 0x01, + WMI_SECTOR_SWEEP_TYPE_TXSS_AND_BCON = 0x02, + WMI_SECTOR_SWEEP_TYPE_NUM = 0x03, +}; + +/* WMI_PRIO_TX_SECTORS_ORDER_CMDID + * + * Set the order of TX sectors in TXSS and/or Beacon(AP). + * + * Returned event: + * - WMI_PRIO_TX_SECTORS_ORDER_EVENTID + */ +struct wmi_prio_tx_sectors_order_cmd { + /* tx sectors order to be applied, 0xFF for end of array */ + u8 tx_sectors_priority_array[MAX_NUM_OF_SECTORS]; + /* enum wmi_sector_sweep_type, TXSS and/or Beacon */ + u8 sector_sweep_type; + /* needed only for TXSS configuration */ + u8 cid; + /* alignment to 32b */ + u8 reserved[2]; +} __packed; + +/* completion status codes */ +enum wmi_prio_tx_sectors_cmd_status { + WMI_PRIO_TX_SECT_CMD_STATUS_SUCCESS = 0x00, + WMI_PRIO_TX_SECT_CMD_STATUS_BAD_PARAM = 0x01, + /* other error */ + WMI_PRIO_TX_SECT_CMD_STATUS_ERROR = 0x02, +}; + +/* WMI_PRIO_TX_SECTORS_ORDER_EVENTID */ +struct wmi_prio_tx_sectors_order_event { + /* enum wmi_prio_tx_sectors_cmd_status */ + u8 status; + /* alignment to 32b */ + u8 reserved[3]; +} __packed; + +struct wmi_prio_tx_sectors_num_cmd { + /* [0-128], 0 = No changes */ + u8 beacon_number_of_sectors; + /* [0-128], 0 = No changes */ + u8 txss_number_of_sectors; + /* [0-8] needed only for TXSS configuration */ + u8 cid; +} __packed; + +/* WMI_PRIO_TX_SECTORS_NUMBER_CMDID + * + * Set the number of active sectors in TXSS and/or Beacon. + * + * Returned event: + * - WMI_PRIO_TX_SECTORS_NUMBER_EVENTID + */ +struct wmi_prio_tx_sectors_number_cmd { + struct wmi_prio_tx_sectors_num_cmd active_sectors_num; + /* alignment to 32b */ + u8 reserved; +} __packed; + +/* WMI_PRIO_TX_SECTORS_NUMBER_EVENTID */ +struct wmi_prio_tx_sectors_number_event { + /* enum wmi_prio_tx_sectors_cmd_status */ + u8 status; + /* alignment to 32b */ + u8 reserved[3]; +} __packed; + +/* WMI_PRIO_TX_SECTORS_SET_DEFAULT_CFG_CMDID + * + * Set default sectors order and number (hard coded in board file) + * in TXSS and/or Beacon. + * + * Returned event: + * - WMI_PRIO_TX_SECTORS_SET_DEFAULT_CFG_EVENTID + */ +struct wmi_prio_tx_sectors_set_default_cfg_cmd { + /* enum wmi_sector_sweep_type, TXSS and/or Beacon */ + u8 sector_sweep_type; + /* needed only for TXSS configuration */ + u8 cid; + /* alignment to 32b */ + u8 reserved[2]; +} __packed; + +/* WMI_PRIO_TX_SECTORS_SET_DEFAULT_CFG_EVENTID */ +struct wmi_prio_tx_sectors_set_default_cfg_event { + /* enum wmi_prio_tx_sectors_cmd_status */ + u8 status; + /* alignment to 32b */ + u8 reserved[3]; +} __packed; + +/* WMI_SET_SILENT_RSSI_TABLE_DONE_EVENTID */ +struct wmi_set_silent_rssi_table_done_event { + /* enum wmi_silent_rssi_status */ + __le32 status; + /* enum wmi_silent_rssi_table */ + __le32 table; +} __packed; + +/* \WMI_COMMAND_NOT_SUPPORTED_EVENTID */ +struct wmi_command_not_supported_event { + /* device id */ + u8 mid; + u8 reserved0; + __le16 command_id; + /* for UT command only, otherwise reserved */ + __le16 command_subtype; + __le16 reserved1; +} __packed; + #endif /* __WILOCITY_WMI_H__ */ diff --git a/drivers/net/wireless/cnss/Kconfig b/drivers/net/wireless/cnss/Kconfig new file mode 100644 index 000000000000..051b709f53f0 --- /dev/null +++ b/drivers/net/wireless/cnss/Kconfig @@ -0,0 +1,88 @@ +config CNSS + tristate "CNSS driver for wifi module" + select CNSS_UTILS + select CRYPTO + select CRYPTO_HASH + select CRYPTO_BLKCIPHER + ---help--- + This module adds support for the CNSS connectivity subsystem used + for wifi devices based on the QCA AR6320 chipset. + This driver also adds support to integrate WLAN module to subsystem + restart framework. + +config CNSS_ASYNC + bool "Enable/disable cnss pci platform driver asynchronous probe" + depends on CNSS || CNSS2 + ---help--- + If enabled, CNSS PCI platform driver would do asynchronous probe. + Using asynchronous probe will allow CNSS PCI platform driver to + probe in parallel with other device drivers and will help to + reduce kernel boot time. + +config CNSS_MAC_BUG + bool "Enable/disable 0-4K memory initialization for QCA6174" + depends on CNSS + ---help--- + If enabled, 0-4K memory is reserved for QCA6174 to address + a MAC HW bug. MAC would do an invalid pointer fetch based on + the data, that was read from 0 to 4K. So fill it with zero's; + to an address for which PCIe root complex would honor the read + without any errors. + +config CLD_DEBUG + bool "Enable/disable CLD debug features" + help + WLAN CLD driver uses this config to enable certain debug features. + Some of the debug features may affect performance or may compromise + on security. + + Say N, if you are building a release kernel for production use. + Only say Y, if you are building a kernel with debug support. + +config CLD_HL_SDIO_CORE + tristate "Qualcomm CORE driver for QCA6174 with SDIO interface" + select WIRELESS_EXT + select WEXT_PRIV + select WEXT_CORE + select WEXT_SPY + select NL80211_TESTMODE + depends on MMC + +config CLD_LL_CORE + tristate "Qualcomm core WLAN driver for QCA6174 chipset" + select NL80211_TESTMODE + select WEXT_CORE + select WEXT_PRIV + select WEXT_SPY + select WIRELESS_EXT + select CRYPTO + select CRYPTO_HASH + select CRYPTO_BLKCIPHER + ---help--- + This section contains the necessary modules needed to enable the + core WLAN driver for Qualcomm QCA6174 chipset. + Select Y to compile the driver in order to have WLAN functionality + support. + +config CNSS_SECURE_FW + bool "Enable/Disable Memory Allocation for Secure Firmware Feature" + depends on CNSS + ---help--- + CLD Driver can use this for holding local copy of firmware + binaries which is used for sha crypto computation. + The Memory Allocation is done only if this Config Parameter is + enabled + +config BUS_AUTO_SUSPEND + bool "Enable/Disable Runtime PM support for PCIe based WLAN Drivers" + depends on CNSS || CNSS2 + depends on PCI + ---help--- + Runtime Power Management is supported for PCIe based WLAN Drivers. + The features enable cld wlan driver to suspend pcie bus when APPS + is awake based on the driver inactivity with the Firmware. + The Feature uses runtime power management framework from kernel to + track bus access clients and to synchronize the driver activity + during system pm. + This config flag controls the feature per target based. The feature + requires CNSS driver support. diff --git a/drivers/net/wireless/cnss/Makefile b/drivers/net/wireless/cnss/Makefile new file mode 100644 index 000000000000..86bd27ae3f45 --- /dev/null +++ b/drivers/net/wireless/cnss/Makefile @@ -0,0 +1,5 @@ +# Makefile for CNSS platform driver + +obj-$(CONFIG_CNSS) += cnss_pci.o +obj-$(CONFIG_CNSS) += cnss_sdio.o +obj-$(CONFIG_CNSS) += cnss_common.o diff --git a/drivers/net/wireless/cnss/cnss_common.c b/drivers/net/wireless/cnss/cnss_common.c new file mode 100644 index 000000000000..0b73f37afd94 --- /dev/null +++ b/drivers/net/wireless/cnss/cnss_common.c @@ -0,0 +1,511 @@ +/* Copyright (c) 2015-2017, 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/export.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/pm_wakeup.h> +#include <linux/sched.h> +#include <linux/suspend.h> +#include <linux/mutex.h> +#include <linux/rwsem.h> +#include <net/cnss.h> +#include "cnss_common.h" +#include <net/cfg80211.h> + +#define AR6320_REV1_VERSION 0x5000000 +#define AR6320_REV1_1_VERSION 0x5000001 +#define AR6320_REV1_3_VERSION 0x5000003 +#define AR6320_REV2_1_VERSION 0x5010000 +#define AR6320_REV3_VERSION 0x5020000 +#define AR6320_REV3_2_VERSION 0x5030000 +#define AR900B_DEV_VERSION 0x1000000 +#define QCA9377_REV1_1_VERSION 0x5020001 + +static struct cnss_fw_files FW_FILES_QCA6174_FW_1_1 = { + "qwlan11.bin", "bdwlan11.bin", "otp11.bin", "utf11.bin", + "utfbd11.bin", "epping11.bin", "evicted11.bin"}; +static struct cnss_fw_files FW_FILES_QCA6174_FW_2_0 = { + "qwlan20.bin", "bdwlan20.bin", "otp20.bin", "utf20.bin", + "utfbd20.bin", "epping20.bin", "evicted20.bin"}; +static struct cnss_fw_files FW_FILES_QCA6174_FW_1_3 = { + "qwlan13.bin", "bdwlan13.bin", "otp13.bin", "utf13.bin", + "utfbd13.bin", "epping13.bin", "evicted13.bin"}; +static struct cnss_fw_files FW_FILES_QCA6174_FW_3_0 = { + "qwlan30.bin", "bdwlan30.bin", "otp30.bin", "utf30.bin", + "utfbd30.bin", "epping30.bin", "evicted30.bin"}; +static struct cnss_fw_files FW_FILES_DEFAULT = { + "qwlan.bin", "bdwlan.bin", "otp.bin", "utf.bin", + "utfbd.bin", "epping.bin", "evicted.bin"}; + +enum cnss_dev_bus_type { + CNSS_BUS_NONE = -1, + CNSS_BUS_PCI, + CNSS_BUS_SDIO +}; + +static DEFINE_MUTEX(unsafe_channel_list_lock); +static DEFINE_MUTEX(dfs_nol_info_lock); + +static struct cnss_unsafe_channel_list { + u16 unsafe_ch_count; + u16 unsafe_ch_list[CNSS_MAX_CH_NUM]; +} unsafe_channel_list; + +static struct cnss_dfs_nol_info { + void *dfs_nol_info; + u16 dfs_nol_info_len; +} dfs_nol_info; + +static enum cnss_cc_src cnss_cc_source = CNSS_SOURCE_CORE; + +int cnss_set_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 ch_count) +{ + struct cnss_unsafe_channel_list *unsafe_list; + + mutex_lock(&unsafe_channel_list_lock); + if ((!unsafe_ch_list) || (!ch_count) || (ch_count > CNSS_MAX_CH_NUM)) { + mutex_unlock(&unsafe_channel_list_lock); + return -EINVAL; + } + + unsafe_list = &unsafe_channel_list; + unsafe_channel_list.unsafe_ch_count = ch_count; + + memcpy( + (char *)unsafe_list->unsafe_ch_list, + (char *)unsafe_ch_list, ch_count * sizeof(u16)); + mutex_unlock(&unsafe_channel_list_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_set_wlan_unsafe_channel); + +int cnss_get_wlan_unsafe_channel( + u16 *unsafe_ch_list, + u16 *ch_count, u16 buf_len) +{ + struct cnss_unsafe_channel_list *unsafe_list; + + mutex_lock(&unsafe_channel_list_lock); + if (!unsafe_ch_list || !ch_count) { + mutex_unlock(&unsafe_channel_list_lock); + return -EINVAL; + } + + unsafe_list = &unsafe_channel_list; + if (buf_len < (unsafe_list->unsafe_ch_count * sizeof(u16))) { + mutex_unlock(&unsafe_channel_list_lock); + return -ENOMEM; + } + + *ch_count = unsafe_list->unsafe_ch_count; + memcpy( + (char *)unsafe_ch_list, + (char *)unsafe_list->unsafe_ch_list, + unsafe_list->unsafe_ch_count * sizeof(u16)); + mutex_unlock(&unsafe_channel_list_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_get_wlan_unsafe_channel); + +int cnss_wlan_set_dfs_nol(const void *info, u16 info_len) +{ + void *temp; + struct cnss_dfs_nol_info *dfs_info; + + mutex_lock(&dfs_nol_info_lock); + if (!info || !info_len) { + mutex_unlock(&dfs_nol_info_lock); + return -EINVAL; + } + + temp = kmalloc(info_len, GFP_KERNEL); + if (!temp) { + mutex_unlock(&dfs_nol_info_lock); + return -ENOMEM; + } + + memcpy(temp, info, info_len); + dfs_info = &dfs_nol_info; + kfree(dfs_info->dfs_nol_info); + + dfs_info->dfs_nol_info = temp; + dfs_info->dfs_nol_info_len = info_len; + mutex_unlock(&dfs_nol_info_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_wlan_set_dfs_nol); + +int cnss_wlan_get_dfs_nol(void *info, u16 info_len) +{ + int len; + struct cnss_dfs_nol_info *dfs_info; + + mutex_lock(&dfs_nol_info_lock); + if (!info || !info_len) { + mutex_unlock(&dfs_nol_info_lock); + return -EINVAL; + } + + dfs_info = &dfs_nol_info; + + if (dfs_info->dfs_nol_info == NULL || dfs_info->dfs_nol_info_len == 0) { + mutex_unlock(&dfs_nol_info_lock); + return -ENOENT; + } + + len = min(info_len, dfs_info->dfs_nol_info_len); + + memcpy(info, dfs_info->dfs_nol_info, len); + mutex_unlock(&dfs_nol_info_lock); + + return len; +} +EXPORT_SYMBOL(cnss_wlan_get_dfs_nol); + +void cnss_init_work(struct work_struct *work, work_func_t func) +{ + INIT_WORK(work, func); +} +EXPORT_SYMBOL(cnss_init_work); + +void cnss_flush_work(void *work) +{ + struct work_struct *cnss_work = work; + + cancel_work_sync(cnss_work); +} +EXPORT_SYMBOL(cnss_flush_work); + +void cnss_flush_delayed_work(void *dwork) +{ + struct delayed_work *cnss_dwork = dwork; + + cancel_delayed_work_sync(cnss_dwork); +} +EXPORT_SYMBOL(cnss_flush_delayed_work); + +void cnss_pm_wake_lock_init(struct wakeup_source *ws, const char *name) +{ + wakeup_source_init(ws, name); +} +EXPORT_SYMBOL(cnss_pm_wake_lock_init); + +void cnss_pm_wake_lock(struct wakeup_source *ws) +{ + __pm_stay_awake(ws); +} +EXPORT_SYMBOL(cnss_pm_wake_lock); + +void cnss_pm_wake_lock_timeout(struct wakeup_source *ws, ulong msec) +{ + __pm_wakeup_event(ws, msec); +} +EXPORT_SYMBOL(cnss_pm_wake_lock_timeout); + +void cnss_pm_wake_lock_release(struct wakeup_source *ws) +{ + __pm_relax(ws); +} +EXPORT_SYMBOL(cnss_pm_wake_lock_release); + +void cnss_pm_wake_lock_destroy(struct wakeup_source *ws) +{ + wakeup_source_trash(ws); +} +EXPORT_SYMBOL(cnss_pm_wake_lock_destroy); + +void cnss_get_monotonic_boottime(struct timespec *ts) +{ + get_monotonic_boottime(ts); +} +EXPORT_SYMBOL(cnss_get_monotonic_boottime); + +void cnss_get_boottime(struct timespec *ts) +{ + ktime_get_ts(ts); +} +EXPORT_SYMBOL(cnss_get_boottime); + +void cnss_init_delayed_work(struct delayed_work *work, work_func_t func) +{ + INIT_DELAYED_WORK(work, func); +} +EXPORT_SYMBOL(cnss_init_delayed_work); + +int cnss_vendor_cmd_reply(struct sk_buff *skb) +{ + return cfg80211_vendor_cmd_reply(skb); +} +EXPORT_SYMBOL(cnss_vendor_cmd_reply); + +int cnss_set_cpus_allowed_ptr(struct task_struct *task, ulong cpu) +{ + return set_cpus_allowed_ptr(task, cpumask_of(cpu)); +} +EXPORT_SYMBOL(cnss_set_cpus_allowed_ptr); + +/* wlan prop driver cannot invoke show_stack + * function directly, so to invoke this function it + * call wcnss_dump_stack function + */ +void cnss_dump_stack(struct task_struct *task) +{ + show_stack(task, NULL); +} +EXPORT_SYMBOL(cnss_dump_stack); + +enum cnss_dev_bus_type cnss_get_dev_bus_type(struct device *dev) +{ + if (!dev) + return CNSS_BUS_NONE; + + if (!dev->bus) + return CNSS_BUS_NONE; + + if (memcmp(dev->bus->name, "sdio", 4) == 0) + return CNSS_BUS_SDIO; + else if (memcmp(dev->bus->name, "pci", 3) == 0) + return CNSS_BUS_PCI; + else + return CNSS_BUS_NONE; +} + +int cnss_common_request_bus_bandwidth(struct device *dev, int bandwidth) +{ + int ret; + + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_SDIO: + ret = cnss_sdio_request_bus_bandwidth(bandwidth); + break; + case CNSS_BUS_PCI: + ret = cnss_pci_request_bus_bandwidth(bandwidth); + break; + default: + pr_debug("%s: Invalid device type\n", __func__); + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(cnss_common_request_bus_bandwidth); + +void *cnss_common_get_virt_ramdump_mem(struct device *dev, unsigned long *size) +{ + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_SDIO: + return cnss_sdio_get_virt_ramdump_mem(size); + case CNSS_BUS_PCI: + return cnss_pci_get_virt_ramdump_mem(size); + default: + pr_debug("%s: Invalid device type\n", __func__); + return NULL; + } +} +EXPORT_SYMBOL(cnss_common_get_virt_ramdump_mem); + +void cnss_common_device_self_recovery(struct device *dev) +{ + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_SDIO: + cnss_sdio_device_self_recovery(); + break; + case CNSS_BUS_PCI: + cnss_pci_device_self_recovery(); + break; + default: + pr_debug("%s: Invalid device type\n", __func__); + break; + } +} +EXPORT_SYMBOL(cnss_common_device_self_recovery); + +void cnss_common_schedule_recovery_work(struct device *dev) +{ + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_SDIO: + cnss_sdio_schedule_recovery_work(); + break; + case CNSS_BUS_PCI: + cnss_pci_schedule_recovery_work(); + break; + default: + pr_debug("%s: Invalid device type\n", __func__); + break; + } +} +EXPORT_SYMBOL(cnss_common_schedule_recovery_work); + +void cnss_common_device_crashed(struct device *dev) +{ + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_SDIO: + cnss_sdio_device_crashed(); + break; + case CNSS_BUS_PCI: + cnss_pci_device_crashed(); + break; + default: + pr_debug("%s: Invalid device type\n", __func__); + break; + } +} +EXPORT_SYMBOL(cnss_common_device_crashed); + +u8 *cnss_common_get_wlan_mac_address(struct device *dev, uint32_t *num) +{ + u8 *ret; + + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_SDIO: + ret = cnss_sdio_get_wlan_mac_address(num); + break; + case CNSS_BUS_PCI: + ret = cnss_pci_get_wlan_mac_address(num); + break; + default: + pr_debug("%s: Invalid device type\n", __func__); + ret = NULL; + break; + } + return ret; +} +EXPORT_SYMBOL(cnss_common_get_wlan_mac_address); + +int cnss_common_set_wlan_mac_address( + struct device *dev, const u8 *in, uint32_t len) +{ + int ret; + + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_SDIO: + ret = cnss_sdio_set_wlan_mac_address(in, len); + break; + case CNSS_BUS_PCI: + ret = cnss_pcie_set_wlan_mac_address(in, len); + break; + default: + pr_debug("%s: Invalid device type\n", __func__); + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(cnss_common_set_wlan_mac_address); + +int cnss_power_up(struct device *dev) +{ + int ret; + + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_PCI: + ret = cnss_pcie_power_up(dev); + break; + case CNSS_BUS_SDIO: + ret = cnss_sdio_power_up(dev); + break; + default: + pr_err("%s: Invalid Bus Type\n", __func__); + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(cnss_power_up); + +int cnss_power_down(struct device *dev) +{ + int ret; + + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_PCI: + ret = cnss_pcie_power_down(dev); + break; + case CNSS_BUS_SDIO: + ret = cnss_sdio_power_down(dev); + break; + default: + pr_err("%s: Invalid Bus Type\n", __func__); + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(cnss_power_down); + +void cnss_get_qca9377_fw_files(struct cnss_fw_files *pfw_files, + u32 size, u32 tufello_dual_fw) +{ + if (tufello_dual_fw) + memcpy(pfw_files, &FW_FILES_DEFAULT, sizeof(*pfw_files)); + else + memcpy(pfw_files, &FW_FILES_QCA6174_FW_3_0, sizeof(*pfw_files)); +} +EXPORT_SYMBOL(cnss_get_qca9377_fw_files); + +int cnss_get_fw_files_for_target(struct cnss_fw_files *pfw_files, + u32 target_type, u32 target_version) +{ + if (!pfw_files) + return -ENODEV; + + switch (target_version) { + case AR6320_REV1_VERSION: + case AR6320_REV1_1_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_1_1, sizeof(*pfw_files)); + break; + case AR6320_REV1_3_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_1_3, sizeof(*pfw_files)); + break; + case AR6320_REV2_1_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_2_0, sizeof(*pfw_files)); + break; + case AR6320_REV3_VERSION: + case AR6320_REV3_2_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_3_0, sizeof(*pfw_files)); + break; + default: + memcpy(pfw_files, &FW_FILES_DEFAULT, sizeof(*pfw_files)); + pr_err("%s default version 0x%X 0x%X", __func__, + target_type, target_version); + break; + } + return 0; +} +EXPORT_SYMBOL(cnss_get_fw_files_for_target); + +void cnss_set_cc_source(enum cnss_cc_src cc_source) +{ + cnss_cc_source = cc_source; +} +EXPORT_SYMBOL(cnss_set_cc_source); + +enum cnss_cc_src cnss_get_cc_source(void) +{ + return cnss_cc_source; +} +EXPORT_SYMBOL(cnss_get_cc_source); + +const char *cnss_wlan_get_evicted_data_file(void) +{ + return FW_FILES_QCA6174_FW_3_0.evicted_data; +} diff --git a/drivers/net/wireless/cnss/cnss_common.h b/drivers/net/wireless/cnss/cnss_common.h new file mode 100644 index 000000000000..07ef9844b6db --- /dev/null +++ b/drivers/net/wireless/cnss/cnss_common.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2016, 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. + */ + +#ifndef _NET_CNSS_COMMON_H_ +#define _NET_CNSS_COMMON_H_ + +#define MAX_FIRMWARE_SIZE (1 * 1024 * 1024) +/* max 20mhz channel count */ +#define CNSS_MAX_CH_NUM 45 + +int cnss_pci_request_bus_bandwidth(int bandwidth); +int cnss_sdio_request_bus_bandwidth(int bandwidth); + +void cnss_sdio_device_crashed(void); +void cnss_pci_device_crashed(void); + +void cnss_pci_device_self_recovery(void); +void cnss_sdio_device_self_recovery(void); + +void *cnss_pci_get_virt_ramdump_mem(unsigned long *size); +void *cnss_sdio_get_virt_ramdump_mem(unsigned long *size); + +void cnss_sdio_schedule_recovery_work(void); +void cnss_pci_schedule_recovery_work(void); + +int cnss_pcie_set_wlan_mac_address(const u8 *in, uint32_t len); +int cnss_sdio_set_wlan_mac_address(const u8 *in, uint32_t len); + +u8 *cnss_pci_get_wlan_mac_address(uint32_t *num); +u8 *cnss_sdio_get_wlan_mac_address(uint32_t *num); +int cnss_sdio_power_up(struct device *dev); +int cnss_sdio_power_down(struct device *dev); +int cnss_pcie_power_up(struct device *dev); +int cnss_pcie_power_down(struct device *dev); +const char *cnss_wlan_get_evicted_data_file(void); +#endif /* _NET_CNSS_COMMON_H_ */ diff --git a/drivers/net/wireless/cnss/cnss_pci.c b/drivers/net/wireless/cnss/cnss_pci.c new file mode 100644 index 000000000000..8de6ff46941f --- /dev/null +++ b/drivers/net/wireless/cnss/cnss_pci.c @@ -0,0 +1,3851 @@ +/* Copyright (c) 2013-2018, 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 <asm/dma-iommu.h> +#include <linux/iommu.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/pm.h> +#include <linux/pm_wakeup.h> +#include <linux/sched.h> +#include <linux/pm_qos.h> +#include <linux/pm_runtime.h> +#include <linux/esoc_client.h> +#include <linux/pinctrl/consumer.h> +#include <linux/firmware.h> +#include <linux/dma-mapping.h> +#include <linux/msm-bus.h> +#include <linux/msm-bus-board.h> +#include <linux/spinlock.h> +#include <linux/suspend.h> +#include <linux/rwsem.h> +#include <linux/crypto.h> +#include <linux/scatterlist.h> +#include <linux/log2.h> +#include <linux/etherdevice.h> +#include <linux/msm_pcie.h> +#include <soc/qcom/subsystem_restart.h> +#include <soc/qcom/subsystem_notif.h> +#include <soc/qcom/ramdump.h> +#include <net/cfg80211.h> +#include <soc/qcom/memory_dump.h> +#include <net/cnss.h> +#include "cnss_common.h" + +#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC +#include <net/cnss_prealloc.h> +#endif + +#define subsys_to_drv(d) container_of(d, struct cnss_data, subsys_desc) + +#define VREG_ON 1 +#define VREG_OFF 0 +#define WLAN_EN_HIGH 1 +#define WLAN_EN_LOW 0 +#define PCIE_LINK_UP 1 +#define PCIE_LINK_DOWN 0 +#define WLAN_BOOTSTRAP_HIGH 1 +#define WLAN_BOOTSTRAP_LOW 0 +#define CNSS_DUMP_FORMAT_VER 0x11 +#define CNSS_DUMP_MAGIC_VER_V2 0x42445953 +#define CNSS_DUMP_NAME "CNSS_WLAN" + +#define QCA6174_VENDOR_ID (0x168C) +#define QCA6174_DEVICE_ID (0x003E) +#define BEELINER_DEVICE_ID (0x0040) +#define QCA6174_REV_ID_OFFSET (0x08) +#define QCA6174_FW_1_1 (0x11) +#define QCA6174_FW_1_3 (0x13) +#define QCA6174_FW_2_0 (0x20) +#define QCA6174_FW_3_0 (0x30) +#define QCA6174_FW_3_2 (0x32) +#define BEELINER_FW (0x00) + +#define QCA6180_VENDOR_ID (0x168C) +#define QCA6180_DEVICE_ID (0x0041) +#define QCA6180_REV_ID_OFFSET (0x08) + +#define WLAN_EN_VREG_NAME "vdd-wlan-en" +#define WLAN_VREG_NAME "vdd-wlan" +#define WLAN_VREG_IO_NAME "vdd-wlan-io" +#define WLAN_VREG_XTAL_NAME "vdd-wlan-xtal" +#define WLAN_VREG_XTAL_AON_NAME "vdd-wlan-xtal-aon" +#define WLAN_VREG_CORE_NAME "vdd-wlan-core" +#define WLAN_VREG_SP2T_NAME "vdd-wlan-sp2t" +#define WLAN_SWREG_NAME "wlan-soc-swreg" +#define WLAN_ANT_SWITCH_NAME "wlan-ant-switch" +#define WLAN_EN_GPIO_NAME "wlan-en-gpio" +#define WLAN_BOOTSTRAP_GPIO_NAME "wlan-bootstrap-gpio" +#define PM_OPTIONS 0 +#define PM_OPTIONS_SUSPEND_LINK_DOWN \ + (MSM_PCIE_CONFIG_NO_CFG_RESTORE | MSM_PCIE_CONFIG_LINKDOWN) +#define PM_OPTIONS_RESUME_LINK_DOWN \ + (MSM_PCIE_CONFIG_NO_CFG_RESTORE) + +#define SOC_SWREG_VOLT_MAX 1200000 +#define SOC_SWREG_VOLT_MIN 1200000 +#define WLAN_ANT_SWITCH_VOLT_MAX 2700000 +#define WLAN_ANT_SWITCH_VOLT_MIN 2700000 +#define WLAN_ANT_SWITCH_CURR 20000 +#define WLAN_VREG_IO_MAX 1800000 +#define WLAN_VREG_IO_MIN 1800000 +#define WLAN_VREG_XTAL_MAX 1800000 +#define WLAN_VREG_XTAL_MIN 1800000 +#define WLAN_VREG_CORE_MAX 1300000 +#define WLAN_VREG_CORE_MIN 1300000 +#define WLAN_VREG_SP2T_MAX 2700000 +#define WLAN_VREG_SP2T_MIN 2700000 + +#define POWER_ON_DELAY 2 +#define WLAN_VREG_IO_DELAY_MIN 100 +#define WLAN_VREG_IO_DELAY_MAX 1000 +#define WLAN_ENABLE_DELAY 10 +#define WLAN_RECOVERY_DELAY 1 +#define PCIE_ENABLE_DELAY 100 +#define WLAN_BOOTSTRAP_DELAY 10 +#define EVICT_BIN_MAX_SIZE (512*1024) +#define CNSS_PINCTRL_STATE_ACTIVE "default" + +static DEFINE_SPINLOCK(pci_link_down_lock); + +#define FW_NAME_FIXED_LEN (6) +#define MAX_NUM_OF_SEGMENTS (16) +#define MAX_INDEX_FILE_SIZE (512) +#define FW_FILENAME_LENGTH (13) +#define TYPE_LENGTH (4) +#define PER_FILE_DATA (21) +#define MAX_IMAGE_SIZE (2*1024*1024) +#define FW_IMAGE_FTM (0x01) +#define FW_IMAGE_MISSION (0x02) +#define FW_IMAGE_BDATA (0x03) +#define FW_IMAGE_PRINT (0x04) + +#define SEG_METADATA (0x01) +#define SEG_NON_PAGED (0x02) +#define SEG_LOCKED_PAGE (0x03) +#define SEG_UNLOCKED_PAGE (0x04) +#define SEG_NON_SECURE_DATA (0x05) + +#define BMI_TEST_SETUP (0x09) + +struct cnss_wlan_gpio_info { + char *name; + u32 num; + bool state; + bool init; + bool prop; + struct pinctrl *pinctrl; + struct pinctrl_state *gpio_state_default; +}; + +struct cnss_wlan_vreg_info { + struct regulator *wlan_en_reg; + struct regulator *wlan_reg; + struct regulator *soc_swreg; + struct regulator *ant_switch; + struct regulator *wlan_reg_io; + struct regulator *wlan_reg_xtal; + struct regulator *wlan_reg_xtal_aon; + struct regulator *wlan_reg_core; + struct regulator *wlan_reg_sp2t; + bool state; +}; + +struct segment_memory { + dma_addr_t dma_region; + void *cpu_region; + u32 size; +}; + +/* FW image descriptor lists */ +struct image_desc_hdr { + u8 image_id; + u8 reserved[3]; + u32 segments_cnt; +}; + +struct segment_desc { + u8 segment_id; + u8 segment_idx; + u8 flags[2]; + u32 addr_count; + u32 addr_low; + u32 addr_high; +}; + +struct region_desc { + u32 addr_low; + u32 addr_high; + u32 size; + u32 reserved; +}; + +struct index_file { + u32 type; + u32 segment_idx; + u8 file_name[13]; +}; + +struct cnss_dual_wifi { + bool is_dual_wifi_enabled; +}; + +/** + * struct wlan_mac_addr - Structure to hold WLAN MAC Address + * @mac_addr: MAC address + */ +#define MAX_NO_OF_MAC_ADDR 4 +struct cnss_wlan_mac_addr { + u8 mac_addr[MAX_NO_OF_MAC_ADDR][ETH_ALEN]; + uint32_t no_of_mac_addr_set; +}; + +/* device_info is expected to be fully populated after cnss_config is invoked. + * The function pointer callbacks are expected to be non null as well. + */ +static struct cnss_data { + struct platform_device *pldev; + struct subsys_device *subsys; + struct subsys_desc subsysdesc; + struct cnss_wlan_mac_addr wlan_mac_addr; + bool is_wlan_mac_set; + bool ramdump_dynamic; + struct ramdump_device *ramdump_dev; + unsigned long ramdump_size; + void *ramdump_addr; + phys_addr_t ramdump_phys; + struct msm_dump_data dump_data; + struct cnss_wlan_driver *driver; + struct pci_dev *pdev; + const struct pci_device_id *id; + struct dma_iommu_mapping *smmu_mapping; + dma_addr_t smmu_iova_start; + size_t smmu_iova_len; + struct cnss_wlan_vreg_info vreg_info; + bool wlan_en_vreg_support; + struct cnss_wlan_gpio_info gpio_info; + bool pcie_link_state; + bool pcie_link_down_ind; + bool pci_register_again; + bool notify_modem_status; + struct pci_saved_state *saved_state; + u16 revision_id; + bool recovery_in_progress; + atomic_t fw_available; + struct codeswap_codeseg_info *cnss_seg_info; + /* Virtual Address of the DMA page */ + void *codeseg_cpuaddr[CODESWAP_MAX_CODESEGS]; + struct cnss_fw_files fw_files; + struct pm_qos_request qos_request; + void *modem_notify_handler; + int modem_current_status; + struct msm_bus_scale_pdata *bus_scale_table; + uint32_t bus_client; + int current_bandwidth_vote; + void *subsys_handle; + struct esoc_desc *esoc_desc; + struct cnss_platform_cap cap; + struct msm_pcie_register_event event_reg; + struct wakeup_source ws; + uint32_t recovery_count; + enum cnss_driver_status driver_status; +#ifdef CONFIG_CNSS_SECURE_FW + void *fw_mem; +#endif + u32 device_id; + int fw_image_setup; + uint32_t bmi_test; + void *fw_cpu; + dma_addr_t fw_dma; + u32 fw_dma_size; + u32 fw_seg_count; + struct segment_memory fw_seg_mem[MAX_NUM_OF_SEGMENTS]; + /* Firmware setup complete lock */ + struct mutex fw_setup_stat_lock; + void *bdata_cpu; + dma_addr_t bdata_dma; + u32 bdata_dma_size; + u32 bdata_seg_count; + struct segment_memory bdata_seg_mem[MAX_NUM_OF_SEGMENTS]; + int wlan_bootstrap_gpio; + atomic_t auto_suspended; + bool monitor_wake_intr; + struct cnss_dual_wifi dual_wifi_info; +} *penv; + +static unsigned int pcie_link_down_panic; +module_param(pcie_link_down_panic, uint, S_IRUSR | S_IWUSR); +MODULE_PARM_DESC(pcie_link_down_panic, + "Trigger kernel panic when PCIe link down is detected"); + +static void cnss_put_wlan_enable_gpio(void) +{ + struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + + if (penv->wlan_en_vreg_support) + regulator_put(vreg_info->wlan_en_reg); + else + gpio_free(gpio_info->num); +} + +static int cnss_wlan_vreg_on(struct cnss_wlan_vreg_info *vreg_info) +{ + int ret; + + if (vreg_info->wlan_reg_core) { + ret = regulator_enable(vreg_info->wlan_reg_core); + if (ret) { + pr_err("%s: regulator enable failed for wlan_reg_core\n", + __func__); + goto error_enable_reg_core; + } + } + + if (vreg_info->wlan_reg_io) { + ret = regulator_enable(vreg_info->wlan_reg_io); + if (ret) { + pr_err("%s: regulator enable failed for wlan_reg_io\n", + __func__); + goto error_enable_reg_io; + } + + usleep_range(WLAN_VREG_IO_DELAY_MIN, WLAN_VREG_IO_DELAY_MAX); + } + + if (vreg_info->wlan_reg_xtal_aon) { + ret = regulator_enable(vreg_info->wlan_reg_xtal_aon); + if (ret) { + pr_err("%s: wlan_reg_xtal_aon enable failed\n", + __func__); + goto error_enable_reg_xtal_aon; + } + } + + if (vreg_info->wlan_reg_xtal) { + ret = regulator_enable(vreg_info->wlan_reg_xtal); + if (ret) { + pr_err("%s: regulator enable failed for wlan_reg_xtal\n", + __func__); + goto error_enable_reg_xtal; + } + } + + ret = regulator_enable(vreg_info->wlan_reg); + if (ret) { + pr_err("%s: regulator enable failed for WLAN power\n", + __func__); + goto error_enable; + } + + if (vreg_info->wlan_reg_sp2t) { + ret = regulator_enable(vreg_info->wlan_reg_sp2t); + if (ret) { + pr_err("%s: regulator enable failed for wlan_reg_sp2t\n", + __func__); + goto error_enable_reg_sp2t; + } + } + + if (vreg_info->ant_switch) { + ret = regulator_enable(vreg_info->ant_switch); + if (ret) { + pr_err("%s: regulator enable failed for ant_switch\n", + __func__); + goto error_enable_ant_switch; + } + } + + if (vreg_info->soc_swreg) { + ret = regulator_enable(vreg_info->soc_swreg); + if (ret) { + pr_err("%s: regulator enable failed for external soc-swreg\n", + __func__); + goto error_enable_soc_swreg; + } + } + + return ret; + +error_enable_soc_swreg: + if (vreg_info->ant_switch) + regulator_disable(vreg_info->ant_switch); +error_enable_ant_switch: + if (vreg_info->wlan_reg_sp2t) + regulator_disable(vreg_info->wlan_reg_sp2t); +error_enable_reg_sp2t: + regulator_disable(vreg_info->wlan_reg); +error_enable: + if (vreg_info->wlan_reg_xtal) + regulator_disable(vreg_info->wlan_reg_xtal); +error_enable_reg_xtal: + if (vreg_info->wlan_reg_xtal_aon) + regulator_disable(vreg_info->wlan_reg_xtal_aon); +error_enable_reg_xtal_aon: + if (vreg_info->wlan_reg_io) + regulator_disable(vreg_info->wlan_reg_io); +error_enable_reg_io: + if (vreg_info->wlan_reg_core) + regulator_disable(vreg_info->wlan_reg_core); +error_enable_reg_core: + return ret; +} + +static int cnss_wlan_vreg_off(struct cnss_wlan_vreg_info *vreg_info) +{ + int ret; + + if (vreg_info->soc_swreg) { + ret = regulator_disable(vreg_info->soc_swreg); + if (ret) { + pr_err("%s: regulator disable failed for external soc-swreg\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->ant_switch) { + ret = regulator_disable(vreg_info->ant_switch); + if (ret) { + pr_err("%s: regulator disable failed for ant_switch\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->wlan_reg_sp2t) { + ret = regulator_disable(vreg_info->wlan_reg_sp2t); + if (ret) { + pr_err("%s: regulator disable failed for wlan_reg_sp2t\n", + __func__); + goto error_disable; + } + } + + ret = regulator_disable(vreg_info->wlan_reg); + if (ret) { + pr_err("%s: regulator disable failed for WLAN power\n", + __func__); + goto error_disable; + } + + if (vreg_info->wlan_reg_xtal) { + ret = regulator_disable(vreg_info->wlan_reg_xtal); + if (ret) { + pr_err("%s: regulator disable failed for wlan_reg_xtal\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->wlan_reg_xtal_aon) { + ret = regulator_disable(vreg_info->wlan_reg_xtal_aon); + if (ret) { + pr_err("%s: wlan_reg_xtal_aon disable failed\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->wlan_reg_io) { + ret = regulator_disable(vreg_info->wlan_reg_io); + if (ret) { + pr_err("%s: regulator disable failed for wlan_reg_io\n", + __func__); + goto error_disable; + } + } + + if (vreg_info->wlan_reg_core) { + ret = regulator_disable(vreg_info->wlan_reg_core); + if (ret) { + pr_err("%s: regulator disable failed for wlan_reg_core\n", + __func__); + goto error_disable; + } + } + +error_disable: + return ret; +} + +static int cnss_wlan_vreg_set(struct cnss_wlan_vreg_info *vreg_info, bool state) +{ + int ret = 0; + + if (vreg_info->state == state) { + pr_debug("Already wlan vreg state is %s\n", + state ? "enabled" : "disabled"); + goto out; + } + + if (state) + ret = cnss_wlan_vreg_on(vreg_info); + else + ret = cnss_wlan_vreg_off(vreg_info); + + if (ret) + goto out; + + pr_debug("%s: wlan vreg is now %s\n", __func__, + state ? "enabled" : "disabled"); + vreg_info->state = state; + +out: + return ret; +} + +static int cnss_wlan_gpio_init(struct cnss_wlan_gpio_info *info) +{ + int ret = 0; + + ret = gpio_request(info->num, info->name); + + if (ret) { + pr_err("can't get gpio %s ret %d\n", info->name, ret); + goto err_gpio_req; + } + + ret = gpio_direction_output(info->num, info->init); + + if (ret) { + pr_err("can't set gpio direction %s ret %d\n", info->name, ret); + goto err_gpio_dir; + } + info->state = info->init; + + return ret; + +err_gpio_dir: + gpio_free(info->num); + +err_gpio_req: + + return ret; +} + +static int cnss_wlan_bootstrap_gpio_init(void) +{ + int ret = 0; + + ret = gpio_request(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_GPIO_NAME); + if (ret) { + pr_err("%s: Can't get GPIO %s, ret = %d\n", + __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); + goto out; + } + + ret = gpio_direction_output(penv->wlan_bootstrap_gpio, + WLAN_BOOTSTRAP_HIGH); + if (ret) { + pr_err("%s: Can't set GPIO %s direction, ret = %d\n", + __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); + gpio_free(penv->wlan_bootstrap_gpio); + goto out; + } + + msleep(WLAN_BOOTSTRAP_DELAY); +out: + return ret; +} + +static void cnss_wlan_gpio_set(struct cnss_wlan_gpio_info *info, bool state) +{ +#ifndef CONFIG_MSM_GVM_QUIN + if (!info->prop) + return; + + if (info->state == state) { + pr_debug("Already %s gpio is %s\n", + info->name, state ? "high" : "low"); + return; + } + + if (state == WLAN_EN_LOW && penv->dual_wifi_info.is_dual_wifi_enabled) { + pr_debug("%s Dual WiFi enabled\n", __func__); + return; + } + + gpio_set_value(info->num, state); + info->state = state; + + pr_debug("%s: %s gpio is now %s\n", __func__, + info->name, info->state ? "enabled" : "disabled"); +#else + return; +#endif +} + +static int cnss_configure_wlan_en_gpio(bool state) +{ + int ret = 0; + struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + + if (penv->wlan_en_vreg_support) { + if (state) + ret = regulator_enable(vreg_info->wlan_en_reg); + else + ret = regulator_disable(vreg_info->wlan_en_reg); + } else { + cnss_wlan_gpio_set(gpio_info, state); + } + + msleep(WLAN_ENABLE_DELAY); + return ret; +} + +static int cnss_pinctrl_init(struct cnss_wlan_gpio_info *gpio_info, + struct platform_device *pdev) +{ + int ret; + gpio_info->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR_OR_NULL(gpio_info->pinctrl)) { + pr_err("%s: Failed to get pinctrl!\n", __func__); + return PTR_ERR(gpio_info->pinctrl); + } + + gpio_info->gpio_state_default = pinctrl_lookup_state(gpio_info->pinctrl, + CNSS_PINCTRL_STATE_ACTIVE); + if (IS_ERR_OR_NULL(gpio_info->gpio_state_default)) { + pr_err("%s: Can not get active pin state!\n", __func__); + return PTR_ERR(gpio_info->gpio_state_default); + } + + ret = pinctrl_select_state(gpio_info->pinctrl, + gpio_info->gpio_state_default); + + return ret; +} + +static void cnss_disable_xtal_ldo(struct platform_device *pdev) +{ + struct cnss_wlan_vreg_info *info = &penv->vreg_info; + + if (info->wlan_reg_xtal) { + regulator_disable(info->wlan_reg_xtal); + regulator_put(info->wlan_reg_xtal); + } + + if (info->wlan_reg_xtal_aon) { + regulator_disable(info->wlan_reg_xtal_aon); + regulator_put(info->wlan_reg_xtal_aon); + } +} + +static int cnss_enable_xtal_ldo(struct platform_device *pdev) +{ + int ret = 0; + struct cnss_wlan_vreg_info *info = &penv->vreg_info; + + if (!of_get_property(pdev->dev.of_node, + WLAN_VREG_XTAL_AON_NAME "-supply", NULL)) + goto enable_xtal; + + info->wlan_reg_xtal_aon = regulator_get(&pdev->dev, + WLAN_VREG_XTAL_AON_NAME); + if (IS_ERR(info->wlan_reg_xtal_aon)) { + ret = PTR_ERR(info->wlan_reg_xtal_aon); + pr_err("%s: XTAL AON Regulator get failed err:%d\n", __func__, + ret); + return ret; + } + + ret = regulator_enable(info->wlan_reg_xtal_aon); + if (ret) { + pr_err("%s: VREG_XTAL_ON enable failed\n", __func__); + goto end; + } + +enable_xtal: + + if (!of_get_property(pdev->dev.of_node, + WLAN_VREG_XTAL_NAME "-supply", NULL)) + goto out_disable_xtal_aon; + + info->wlan_reg_xtal = regulator_get(&pdev->dev, WLAN_VREG_XTAL_NAME); + + if (IS_ERR(info->wlan_reg_xtal)) { + ret = PTR_ERR(info->wlan_reg_xtal); + pr_err("%s XTAL Regulator get failed err:%d\n", __func__, ret); + goto out_disable_xtal_aon; + } + + ret = regulator_set_voltage(info->wlan_reg_xtal, WLAN_VREG_XTAL_MIN, + WLAN_VREG_XTAL_MAX); + if (ret) { + pr_err("%s: Set wlan_vreg_xtal failed!\n", __func__); + goto out_put_xtal; + } + + ret = regulator_enable(info->wlan_reg_xtal); + if (ret) { + pr_err("%s: Enable wlan_vreg_xtal failed!\n", __func__); + goto out_put_xtal; + } + + return 0; + +out_put_xtal: + if (info->wlan_reg_xtal) + regulator_put(info->wlan_reg_xtal); + +out_disable_xtal_aon: + if (info->wlan_reg_xtal_aon) + regulator_disable(info->wlan_reg_xtal_aon); + +end: + if (info->wlan_reg_xtal_aon) + regulator_put(info->wlan_reg_xtal_aon); + + return ret; +} + +static int cnss_get_wlan_enable_gpio( + struct cnss_wlan_gpio_info *gpio_info, + struct platform_device *pdev) +{ + int ret = 0; + struct device *dev = &pdev->dev; + + if (!of_find_property(dev->of_node, gpio_info->name, NULL)) { + gpio_info->prop = false; + return -ENODEV; + } + + gpio_info->prop = true; + ret = of_get_named_gpio(dev->of_node, gpio_info->name, 0); + if (ret >= 0) { + gpio_info->num = ret; + } else { + if (ret == -EPROBE_DEFER) + pr_debug("get WLAN_EN GPIO probe defer\n"); + else + pr_err( + "can't get gpio %s ret %d", gpio_info->name, ret); + } + + ret = cnss_pinctrl_init(gpio_info, pdev); + if (ret) + pr_debug("%s: pinctrl init failed!\n", __func__); + + ret = cnss_wlan_gpio_init(gpio_info); + if (ret) + pr_err("gpio init failed\n"); + + return ret; +} + +static int cnss_get_wlan_bootstrap_gpio(struct platform_device *pdev) +{ + int ret = 0; + struct device_node *node = (&pdev->dev)->of_node; + + if (!of_find_property(node, WLAN_BOOTSTRAP_GPIO_NAME, NULL)) + return ret; + + penv->wlan_bootstrap_gpio = + of_get_named_gpio(node, WLAN_BOOTSTRAP_GPIO_NAME, 0); + if (penv->wlan_bootstrap_gpio > 0) { + ret = cnss_wlan_bootstrap_gpio_init(); + } else { + ret = penv->wlan_bootstrap_gpio; + pr_err( + "%s: Can't get GPIO %s, ret = %d", + __func__, WLAN_BOOTSTRAP_GPIO_NAME, ret); + } + + return ret; +} + +static int cnss_wlan_get_resources(struct platform_device *pdev) +{ + int ret = 0; + struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + struct device_node *node = pdev->dev.of_node; + + if (of_get_property(node, WLAN_VREG_CORE_NAME "-supply", NULL)) { + vreg_info->wlan_reg_core = regulator_get(&pdev->dev, + WLAN_VREG_CORE_NAME); + if (IS_ERR(vreg_info->wlan_reg_core)) { + ret = PTR_ERR(vreg_info->wlan_reg_core); + + if (ret == -EPROBE_DEFER) { + pr_err("%s: wlan_reg_core probe deferred!\n", + __func__); + } else { + pr_err("%s: Get wlan_reg_core failed!\n", + __func__); + } + goto err_reg_core_get; + } + + ret = regulator_set_voltage(vreg_info->wlan_reg_core, + WLAN_VREG_CORE_MIN, WLAN_VREG_CORE_MAX); + if (ret) { + pr_err("%s: Set wlan_reg_core failed!\n", __func__); + goto err_reg_core_set; + } + + ret = regulator_enable(vreg_info->wlan_reg_core); + if (ret) { + pr_err("%s: Enable wlan_reg_core failed!\n", __func__); + goto err_reg_core_enable; + } + } + + if (of_get_property(node, WLAN_VREG_IO_NAME "-supply", NULL)) { + vreg_info->wlan_reg_io = regulator_get(&pdev->dev, + WLAN_VREG_IO_NAME); + if (!IS_ERR(vreg_info->wlan_reg_io)) { + ret = regulator_set_voltage(vreg_info->wlan_reg_io, + WLAN_VREG_IO_MIN, WLAN_VREG_IO_MAX); + if (ret) { + pr_err("%s: Set wlan_vreg_io failed!\n", + __func__); + goto err_reg_io_set; + } + + ret = regulator_enable(vreg_info->wlan_reg_io); + if (ret) { + pr_err("%s: Enable wlan_vreg_io failed!\n", + __func__); + goto err_reg_io_enable; + } + + usleep_range(WLAN_VREG_IO_DELAY_MIN, + WLAN_VREG_IO_DELAY_MAX); + } + } + + if (cnss_enable_xtal_ldo(pdev)) + goto err_reg_xtal_enable; + + vreg_info->wlan_reg = regulator_get(&pdev->dev, WLAN_VREG_NAME); + + if (IS_ERR(vreg_info->wlan_reg)) { + if (PTR_ERR(vreg_info->wlan_reg) == -EPROBE_DEFER) + pr_err("%s: vreg probe defer\n", __func__); + else + pr_err("%s: vreg regulator get failed\n", __func__); + ret = PTR_ERR(vreg_info->wlan_reg); + goto err_reg_get; + } + + ret = regulator_enable(vreg_info->wlan_reg); + + if (ret) { + pr_err("%s: vreg initial vote failed\n", __func__); + goto err_reg_enable; + } + + if (of_get_property(node, WLAN_VREG_SP2T_NAME "-supply", NULL)) { + vreg_info->wlan_reg_sp2t = + regulator_get(&pdev->dev, WLAN_VREG_SP2T_NAME); + if (!IS_ERR(vreg_info->wlan_reg_sp2t)) { + ret = regulator_set_voltage(vreg_info->wlan_reg_sp2t, + WLAN_VREG_SP2T_MIN, WLAN_VREG_SP2T_MAX); + if (ret) { + pr_err("%s: Set wlan_vreg_sp2t failed!\n", + __func__); + goto err_reg_sp2t_set; + } + + ret = regulator_enable(vreg_info->wlan_reg_sp2t); + if (ret) { + pr_err("%s: Enable wlan_vreg_sp2t failed!\n", + __func__); + goto err_reg_sp2t_enable; + } + } + } + + if (of_get_property(node, WLAN_ANT_SWITCH_NAME "-supply", NULL)) { + vreg_info->ant_switch = + regulator_get(&pdev->dev, WLAN_ANT_SWITCH_NAME); + if (!IS_ERR(vreg_info->ant_switch)) { + ret = regulator_set_voltage(vreg_info->ant_switch, + WLAN_ANT_SWITCH_VOLT_MIN, + WLAN_ANT_SWITCH_VOLT_MAX); + if (ret < 0) { + pr_err("%s: Set ant_switch voltage failed!\n", + __func__); + goto err_ant_switch_set; + } + + ret = regulator_set_load(vreg_info->ant_switch, + WLAN_ANT_SWITCH_CURR); + if (ret < 0) { + pr_err("%s: Set ant_switch current failed!\n", + __func__); + goto err_ant_switch_set; + } + + ret = regulator_enable(vreg_info->ant_switch); + if (ret < 0) { + pr_err("%s: Enable ant_switch failed!\n", + __func__); + goto err_ant_switch_enable; + } + } + } + + if (of_find_property(node, "qcom,wlan-uart-access", NULL)) + penv->cap.cap_flag |= CNSS_HAS_UART_ACCESS; + + if (of_get_property(node, WLAN_SWREG_NAME "-supply", NULL)) { + vreg_info->soc_swreg = regulator_get(&pdev->dev, + WLAN_SWREG_NAME); + if (IS_ERR(vreg_info->soc_swreg)) { + pr_err("%s: soc-swreg node not found\n", + __func__); + goto err_reg_get2; + } + ret = regulator_set_voltage(vreg_info->soc_swreg, + SOC_SWREG_VOLT_MIN, SOC_SWREG_VOLT_MAX); + if (ret) { + pr_err("%s: vreg initial voltage set failed on soc-swreg\n", + __func__); + goto err_reg_set; + } + ret = regulator_enable(vreg_info->soc_swreg); + if (ret) { + pr_err("%s: vreg initial vote failed\n", __func__); + goto err_reg_enable2; + } + penv->cap.cap_flag |= CNSS_HAS_EXTERNAL_SWREG; + } + + penv->wlan_en_vreg_support = + of_property_read_bool(node, "qcom,wlan-en-vreg-support"); + if (penv->wlan_en_vreg_support) { + vreg_info->wlan_en_reg = + regulator_get(&pdev->dev, WLAN_EN_VREG_NAME); + if (IS_ERR(vreg_info->wlan_en_reg)) { + pr_err("%s:wlan_en vreg get failed\n", __func__); + ret = PTR_ERR(vreg_info->wlan_en_reg); + goto err_wlan_en_reg_get; + } + } + + if (!penv->wlan_en_vreg_support) { + ret = cnss_get_wlan_enable_gpio(gpio_info, pdev); + if (ret) { + pr_err( + "%s:Failed to config the WLAN_EN gpio\n", __func__); + goto err_gpio_wlan_en; + } + } + vreg_info->state = VREG_ON; + + ret = cnss_get_wlan_bootstrap_gpio(pdev); + if (ret) { + pr_err("%s: Failed to enable wlan bootstrap gpio\n", __func__); + goto err_gpio_wlan_bootstrap; + } + + return ret; + +err_gpio_wlan_bootstrap: + cnss_put_wlan_enable_gpio(); +err_gpio_wlan_en: +err_wlan_en_reg_get: + vreg_info->wlan_en_reg = NULL; + if (vreg_info->soc_swreg) + regulator_disable(vreg_info->soc_swreg); + vreg_info->state = VREG_OFF; + +err_reg_enable2: +err_reg_set: + if (vreg_info->soc_swreg) + regulator_put(vreg_info->soc_swreg); + +err_reg_get2: + if (vreg_info->ant_switch) + regulator_disable(vreg_info->ant_switch); + +err_ant_switch_enable: +err_ant_switch_set: + if (vreg_info->ant_switch) + regulator_put(vreg_info->ant_switch); + if (vreg_info->wlan_reg_sp2t) + regulator_disable(vreg_info->wlan_reg_sp2t); + +err_reg_sp2t_enable: +err_reg_sp2t_set: + if (vreg_info->wlan_reg_sp2t) + regulator_put(vreg_info->wlan_reg_sp2t); + regulator_disable(vreg_info->wlan_reg); + +err_reg_enable: + regulator_put(vreg_info->wlan_reg); +err_reg_get: + cnss_disable_xtal_ldo(pdev); + +err_reg_xtal_enable: + if (vreg_info->wlan_reg_io) + regulator_disable(vreg_info->wlan_reg_io); + +err_reg_io_enable: +err_reg_io_set: + if (vreg_info->wlan_reg_io) + regulator_put(vreg_info->wlan_reg_io); + if (vreg_info->wlan_reg_core) + regulator_disable(vreg_info->wlan_reg_core); + +err_reg_core_enable: +err_reg_core_set: + if (vreg_info->wlan_reg_core) + regulator_put(vreg_info->wlan_reg_core); + +err_reg_core_get: + return ret; +} + +static void cnss_wlan_release_resources(void) +{ + struct cnss_wlan_gpio_info *gpio_info = &penv->gpio_info; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + + if (penv->wlan_bootstrap_gpio > 0) + gpio_free(penv->wlan_bootstrap_gpio); + cnss_put_wlan_enable_gpio(); + gpio_info->state = WLAN_EN_LOW; + gpio_info->prop = false; + cnss_wlan_vreg_set(vreg_info, VREG_OFF); + if (vreg_info->soc_swreg) + regulator_put(vreg_info->soc_swreg); + if (vreg_info->ant_switch) + regulator_put(vreg_info->ant_switch); + if (vreg_info->wlan_reg_sp2t) + regulator_put(vreg_info->wlan_reg_sp2t); + regulator_put(vreg_info->wlan_reg); + if (vreg_info->wlan_reg_xtal) + regulator_put(vreg_info->wlan_reg_xtal); + if (vreg_info->wlan_reg_xtal_aon) + regulator_put(vreg_info->wlan_reg_xtal_aon); + if (vreg_info->wlan_reg_io) + regulator_put(vreg_info->wlan_reg_io); + if (vreg_info->wlan_reg_core) + regulator_put(vreg_info->wlan_reg_core); + vreg_info->state = VREG_OFF; +} + +static u8 cnss_get_pci_dev_bus_number(struct pci_dev *pdev) +{ + return pdev->bus->number; +} + +void cnss_setup_fw_files(u16 revision) +{ + switch (revision) { + + case QCA6174_FW_1_1: + strlcpy(penv->fw_files.image_file, "qwlan11.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan11.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp11.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf11.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd11.bin", + CNSS_MAX_FILE_NAME); + break; + + case QCA6174_FW_1_3: + strlcpy(penv->fw_files.image_file, "qwlan13.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan13.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp13.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf13.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd13.bin", + CNSS_MAX_FILE_NAME); + break; + + case QCA6174_FW_2_0: + strlcpy(penv->fw_files.image_file, "qwlan20.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan20.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp20.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf20.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd20.bin", + CNSS_MAX_FILE_NAME); + break; + + case QCA6174_FW_3_0: + case QCA6174_FW_3_2: + strlcpy(penv->fw_files.image_file, "qwlan30.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan30.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp30.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf30.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd30.bin", + CNSS_MAX_FILE_NAME); + break; + + default: + strlcpy(penv->fw_files.image_file, "qwlan.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.board_data, "bdwlan.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.otp_data, "otp.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_file, "utf.bin", + CNSS_MAX_FILE_NAME); + strlcpy(penv->fw_files.utf_board_data, "utfbd.bin", + CNSS_MAX_FILE_NAME); + break; + } +} + +int cnss_get_fw_files(struct cnss_fw_files *pfw_files) +{ + if (!penv || !pfw_files) + return -ENODEV; + + *pfw_files = penv->fw_files; + + return 0; +} +EXPORT_SYMBOL(cnss_get_fw_files); + +#ifdef CONFIG_CNSS_SECURE_FW +static void cnss_wlan_fw_mem_alloc(struct pci_dev *pdev) +{ + penv->fw_mem = devm_kzalloc(&pdev->dev, MAX_FIRMWARE_SIZE, GFP_KERNEL); + + if (!penv->fw_mem) + pr_debug("Memory not available for Secure FW\n"); +} +#else +static void cnss_wlan_fw_mem_alloc(struct pci_dev *pdev) +{ +} +#endif + +static int get_image_file(const u8 *index_info, u8 *file_name, + u32 *type, u32 *segment_idx) +{ + + if (!file_name || !index_info || !type) + return -EINVAL; + + memcpy(type, index_info, TYPE_LENGTH); + memcpy(segment_idx, index_info + TYPE_LENGTH, TYPE_LENGTH); + memcpy(file_name, index_info + TYPE_LENGTH + TYPE_LENGTH, + FW_FILENAME_LENGTH); + + pr_debug("%u: %u: %s", *type, *segment_idx, file_name); + + return PER_FILE_DATA; +} + +static void print_allocated_image_table(void) +{ + u32 seg = 0, count = 0; + u8 *dump_addr; + struct segment_memory *pseg_mem = penv->fw_seg_mem; + struct segment_memory *p_bdata_seg_mem = penv->bdata_seg_mem; + + pr_debug("%s: Dumping FW IMAGE\n", __func__); + while (seg++ < penv->fw_seg_count) { + dump_addr = (u8 *)pseg_mem->cpu_region + + sizeof(struct region_desc); + for (count = 0; count < pseg_mem->size - + sizeof(struct region_desc); count++) + pr_debug("%02x", dump_addr[count]); + + pseg_mem++; + } + + seg = 0; + pr_debug("%s: Dumping BOARD DATA\n", __func__); + while (seg++ < penv->bdata_seg_count) { + dump_addr = (u8 *)p_bdata_seg_mem->cpu_region + + sizeof(struct region_desc); + for (count = 0; count < p_bdata_seg_mem->size - + sizeof(struct region_desc); count++) + pr_debug("%02x ", dump_addr[count]); + + p_bdata_seg_mem++; + } +} + +static void free_allocated_image_table(void) +{ + struct device *dev = &penv->pdev->dev; + struct segment_memory *pseg_mem; + u32 seg = 0; + + /* free fw memroy */ + pseg_mem = penv->fw_seg_mem; + while (seg++ < penv->fw_seg_count) { + dma_free_coherent(dev, pseg_mem->size, + pseg_mem->cpu_region, pseg_mem->dma_region); + pseg_mem++; + } + if (penv->fw_cpu) + dma_free_coherent(dev, + sizeof(struct segment_desc) * MAX_NUM_OF_SEGMENTS, + penv->fw_cpu, penv->fw_dma); + penv->fw_seg_count = 0; + penv->fw_dma = 0; + penv->fw_cpu = NULL; + penv->fw_dma_size = 0; + + /* free bdata memory */ + seg = 0; + pseg_mem = penv->bdata_seg_mem; + while (seg++ < penv->bdata_seg_count) { + dma_free_coherent(dev, pseg_mem->size, + pseg_mem->cpu_region, pseg_mem->dma_region); + pseg_mem++; + } + if (penv->bdata_cpu) + dma_free_coherent(dev, + sizeof(struct segment_desc) * MAX_NUM_OF_SEGMENTS, + penv->bdata_cpu, penv->bdata_dma); + penv->bdata_seg_count = 0; + penv->bdata_dma = 0; + penv->bdata_cpu = NULL; + penv->bdata_dma_size = 0; +} + +static int cnss_setup_fw_image_table(int mode) +{ + struct image_desc_hdr *image_hdr; + struct segment_desc *pseg = NULL; + const struct firmware *fw_index, *fw_image; + struct device *dev = NULL; + char reserved[3] = ""; + u8 image_file[FW_FILENAME_LENGTH] = ""; + u8 index_file[FW_FILENAME_LENGTH] = ""; + u8 index_info[MAX_INDEX_FILE_SIZE] = ""; + size_t image_desc_size = 0, file_size = 0; + size_t index_pos = 0, image_pos = 0; + struct region_desc *reg_desc = NULL; + u32 type = 0; + u32 segment_idx = 0; + uintptr_t address; + int ret = 0; + dma_addr_t dma_addr; + void *vaddr = NULL; + dma_addr_t paddr; + struct segment_memory *pseg_mem; + u32 *pseg_count; + + if (!penv || !penv->pdev) { + pr_err("cnss: invalid penv or pdev or dev\n"); + ret = -EINVAL; + goto err; + } + dev = &penv->pdev->dev; + + /* meta data file has image details */ + switch (mode) { + case FW_IMAGE_FTM: + ret = scnprintf(index_file, FW_FILENAME_LENGTH, "qftm.bin"); + pseg_mem = penv->fw_seg_mem; + pseg_count = &penv->fw_seg_count; + break; + case FW_IMAGE_MISSION: + ret = scnprintf(index_file, FW_FILENAME_LENGTH, "qwlan.bin"); + pseg_mem = penv->fw_seg_mem; + pseg_count = &penv->fw_seg_count; + break; + case FW_IMAGE_BDATA: + ret = scnprintf(index_file, FW_FILENAME_LENGTH, "bdwlan.bin"); + pseg_mem = penv->bdata_seg_mem; + pseg_count = &penv->bdata_seg_count; + break; + default: + pr_err("%s: Unknown meta data file type 0x%x\n", + __func__, mode); + ret = -EINVAL; + } + if (ret < 0) + goto err; + + image_desc_size = sizeof(struct image_desc_hdr) + + sizeof(struct segment_desc) * MAX_NUM_OF_SEGMENTS; + + vaddr = dma_alloc_coherent(dev, image_desc_size, + &paddr, GFP_KERNEL); + + if (!vaddr) { + pr_err("cnss: image desc allocation failure\n"); + ret = -ENOMEM; + goto err; + } + + memset(vaddr, 0, image_desc_size); + + image_hdr = (struct image_desc_hdr *)vaddr; + image_hdr->image_id = mode; + memcpy(image_hdr->reserved, reserved, 3); + + pr_err("cnss: request meta data file %s\n", index_file); + ret = request_firmware(&fw_index, index_file, dev); + if (ret || !fw_index || !fw_index->data || !fw_index->size) { + pr_err("cnss: meta data file open failure %s\n", index_file); + goto err_free; + } + + if (fw_index->size > MAX_INDEX_FILE_SIZE) { + pr_err("cnss: meta data file has invalid size %s: %zu\n", + index_file, fw_index->size); + release_firmware(fw_index); + goto err_free; + } + + memcpy(index_info, fw_index->data, fw_index->size); + file_size = fw_index->size; + release_firmware(fw_index); + + while (file_size >= PER_FILE_DATA && image_pos < image_desc_size && + image_hdr->segments_cnt < MAX_NUM_OF_SEGMENTS) { + + ret = get_image_file(index_info + index_pos, + image_file, &type, &segment_idx); + if (ret == -EINVAL) + goto err_free; + + file_size -= ret; + index_pos += ret; + pseg = vaddr + image_pos + + sizeof(struct image_desc_hdr); + + switch (type) { + case SEG_METADATA: + case SEG_NON_PAGED: + case SEG_LOCKED_PAGE: + case SEG_UNLOCKED_PAGE: + case SEG_NON_SECURE_DATA: + + image_hdr->segments_cnt++; + pseg->segment_id = type; + pseg->segment_idx = (u8)(segment_idx & 0xff); + memcpy(pseg->flags, reserved, 2); + + ret = request_firmware(&fw_image, image_file, dev); + if (ret || !fw_image || !fw_image->data || + !fw_image->size) { + pr_err("cnss: image file read failed %s", + image_file); + goto err_free; + } + if (fw_image->size > MAX_IMAGE_SIZE) { + pr_err("cnss: %s: image file invalid size %zu\n", + image_file, fw_image->size); + release_firmware(fw_image); + ret = -EINVAL; + goto err_free; + } + reg_desc = dma_alloc_coherent(dev, + sizeof(struct region_desc) + fw_image->size, + &dma_addr, GFP_KERNEL); + if (!reg_desc) { + pr_err("cnss: region allocation failure\n"); + ret = -ENOMEM; + release_firmware(fw_image); + goto err_free; + } + address = (uintptr_t) dma_addr; + pseg->addr_low = address & 0xFFFFFFFF; + pseg->addr_high = 0x00; + /* one region for one image file */ + pseg->addr_count = 1; + memcpy((u8 *)reg_desc + sizeof(struct region_desc), + fw_image->data, fw_image->size); + address += sizeof(struct region_desc); + reg_desc->addr_low = address & 0xFFFFFFFF; + reg_desc->addr_high = 0x00; + reg_desc->reserved = 0; + reg_desc->size = fw_image->size; + + pseg_mem[*pseg_count].dma_region = dma_addr; + pseg_mem[*pseg_count].cpu_region = reg_desc; + pseg_mem[*pseg_count].size = + sizeof(struct region_desc) + fw_image->size; + + release_firmware(fw_image); + (*pseg_count)++; + break; + + default: + pr_err("cnss: Unknown segment %d", type); + ret = -EINVAL; + goto err_free; + } + image_pos += sizeof(struct segment_desc); + } + if (mode != FW_IMAGE_BDATA) { + penv->fw_cpu = vaddr; + penv->fw_dma = paddr; + penv->fw_dma_size = sizeof(struct image_desc_hdr) + + sizeof(struct segment_desc) * image_hdr->segments_cnt; + } else { + penv->bdata_cpu = vaddr; + penv->bdata_dma = paddr; + penv->bdata_dma_size = sizeof(struct image_desc_hdr) + + sizeof(struct segment_desc) * image_hdr->segments_cnt; + } + pr_info("%s: Mode %d: Image setup table built on host", __func__, mode); + + return file_size; + + +err_free: + free_allocated_image_table(); +err: + pr_err("cnss: image file setup failed %d\n", ret); + return ret; +} + +int cnss_get_fw_image(struct image_desc_info *image_desc_info) +{ + if (!image_desc_info || !penv || + !penv->fw_seg_count || !penv->bdata_seg_count) + return -EINVAL; + + mutex_lock(&penv->fw_setup_stat_lock); + image_desc_info->fw_addr = penv->fw_dma; + image_desc_info->fw_size = penv->fw_dma_size; + image_desc_info->bdata_addr = penv->bdata_dma; + image_desc_info->bdata_size = penv->bdata_dma_size; + mutex_unlock(&penv->fw_setup_stat_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_get_fw_image); + +static ssize_t wlan_setup_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", penv->revision_id); +} + +static DEVICE_ATTR(wlan_setup, S_IRUSR, + wlan_setup_show, NULL); + +static int cnss_wlan_is_codeswap_supported(u16 revision) +{ + switch (revision) { + case QCA6174_FW_3_0: + case QCA6174_FW_3_2: + return 0; + default: + return 1; + } +} + +static int cnss_smmu_init(struct device *dev) +{ + struct dma_iommu_mapping *mapping; + int atomic_ctx = 1; + int ret; + + mapping = arm_iommu_create_mapping(&platform_bus_type, + penv->smmu_iova_start, + penv->smmu_iova_len); + if (IS_ERR(mapping)) { + ret = PTR_ERR(mapping); + pr_err("%s: create mapping failed, err = %d\n", __func__, ret); + goto map_fail; + } + + ret = iommu_domain_set_attr(mapping->domain, + DOMAIN_ATTR_ATOMIC, + &atomic_ctx); + if (ret) { + pr_err("%s: set atomic_ctx attribute failed, err = %d\n", + __func__, ret); + goto set_attr_fail; + } + + ret = arm_iommu_attach_device(dev, mapping); + if (ret) { + pr_err("%s: attach device failed, err = %d\n", __func__, ret); + goto attach_fail; + } + + penv->smmu_mapping = mapping; + + return ret; + +attach_fail: +set_attr_fail: + arm_iommu_release_mapping(mapping); +map_fail: + return ret; +} + +static void cnss_smmu_remove(struct device *dev) +{ + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(penv->smmu_mapping); + + penv->smmu_mapping = NULL; +} + +#ifdef CONFIG_PCI_MSM +struct pci_saved_state *cnss_pci_store_saved_state(struct pci_dev *dev) +{ + return pci_store_saved_state(dev); +} + +#ifndef CONFIG_GHS_VMM +int cnss_msm_pcie_pm_control( + enum msm_pcie_pm_opt pm_opt, u32 bus_num, + struct pci_dev *pdev, u32 options) +{ + return msm_pcie_pm_control(pm_opt, bus_num, pdev, NULL, options); +} +#else +int cnss_msm_pcie_pm_control( + enum msm_pcie_pm_opt pm_opt, u32 bus_num, + struct pci_dev *pdev, u32 options) +{ + return 0; +} +#endif +int cnss_pci_load_and_free_saved_state( + struct pci_dev *dev, struct pci_saved_state **state) +{ + return pci_load_and_free_saved_state(dev, state); +} + +int cnss_msm_pcie_shadow_control(struct pci_dev *dev, bool enable) +{ + return msm_pcie_shadow_control(dev, enable); +} + +int cnss_msm_pcie_deregister_event(struct msm_pcie_register_event *reg) +{ + return msm_pcie_deregister_event(reg); +} + +int cnss_msm_pcie_recover_config(struct pci_dev *dev) +{ + return msm_pcie_recover_config(dev); +} + +int cnss_msm_pcie_register_event(struct msm_pcie_register_event *reg) +{ + return msm_pcie_register_event(reg); +} + +int cnss_msm_pcie_enumerate(u32 rc_idx) +{ + return msm_pcie_enumerate(rc_idx); +} +#else /* !defined CONFIG_PCI_MSM */ +struct pci_saved_state *cnss_pci_store_saved_state(struct pci_dev *dev) +{ + return NULL; +} + +int cnss_msm_pcie_pm_control( + enum msm_pcie_pm_opt pm_opt, u32 bus_num, + struct pci_dev *pdev, u32 options) +{ + return 0; +} + +int cnss_pci_load_and_free_saved_state( + struct pci_dev *dev, struct pci_saved_state **state) +{ + return 0; +} + +int cnss_msm_pcie_shadow_control(struct pci_dev *dev, bool enable) +{ + return 0; +} + +int cnss_msm_pcie_deregister_event(struct msm_pcie_register_event *reg) +{ + return 0; +} + +int cnss_msm_pcie_recover_config(struct pci_dev *dev) +{ + return 0; +} + +int cnss_msm_pcie_register_event(struct msm_pcie_register_event *reg) +{ + return 0; +} + +int cnss_msm_pcie_enumerate(u32 rc_idx) +{ + return 0; +} +#endif + +static int cnss_wlan_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret = 0; + struct cnss_wlan_vreg_info *vreg_info = &penv->vreg_info; + void *cpu_addr; + dma_addr_t dma_handle; + struct codeswap_codeseg_info *cnss_seg_info = NULL; + struct device *dev = &pdev->dev; + + penv->pdev = pdev; + penv->id = id; + atomic_set(&penv->fw_available, 0); + penv->device_id = pdev->device; + + if (penv->smmu_iova_len) { + ret = cnss_smmu_init(&pdev->dev); + if (ret) { + pr_err("%s: SMMU init failed, err = %d\n", + __func__, ret); + goto smmu_init_fail; + } + } + + if (penv->pci_register_again) { + pr_debug("%s: PCI re-registration complete\n", __func__); + penv->pci_register_again = false; + return 0; + } + + switch (pdev->device) { + case QCA6180_DEVICE_ID: + pci_read_config_word(pdev, QCA6180_REV_ID_OFFSET, + &penv->revision_id); + break; + + case QCA6174_DEVICE_ID: + pci_read_config_word(pdev, QCA6174_REV_ID_OFFSET, + &penv->revision_id); + cnss_setup_fw_files(penv->revision_id); + break; + + default: + pr_err("cnss: unknown device found %d\n", pdev->device); + ret = -EPROBE_DEFER; + goto err_unknown; + } + + + if (penv->pcie_link_state) { + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + + ret = cnss_msm_pcie_pm_control( + MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + if (ret) { + pr_err("Failed to shutdown PCIe link\n"); + goto err_pcie_suspend; + } + penv->pcie_link_state = PCIE_LINK_DOWN; + } + + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + ret = cnss_wlan_vreg_set(vreg_info, VREG_OFF); + + if (ret) { + pr_err("can't turn off wlan vreg\n"); + goto err_pcie_suspend; + } + + mutex_lock(&penv->fw_setup_stat_lock); + cnss_wlan_fw_mem_alloc(pdev); + mutex_unlock(&penv->fw_setup_stat_lock); + + ret = device_create_file(&penv->pldev->dev, &dev_attr_wlan_setup); + + if (ret) { + pr_err("Can't Create Device file\n"); + goto err_pcie_suspend; + } + + if (cnss_wlan_is_codeswap_supported(penv->revision_id)) { + pr_debug("Code-swap not enabled: %d\n", penv->revision_id); + goto err_pcie_suspend; + } + + cpu_addr = dma_alloc_coherent(dev, EVICT_BIN_MAX_SIZE, + &dma_handle, GFP_KERNEL); + if (!cpu_addr || !dma_handle) { + pr_err("cnss: Memory Alloc failed for codeswap feature\n"); + goto err_pcie_suspend; + } + + memset(cpu_addr, 0, EVICT_BIN_MAX_SIZE); + cnss_seg_info = devm_kzalloc(dev, sizeof(*cnss_seg_info), + GFP_KERNEL); + if (!cnss_seg_info) { + pr_err("Fail to allocate memory for cnss_seg_info\n"); + goto end_dma_alloc; + } + + memset(cnss_seg_info, 0, sizeof(*cnss_seg_info)); + cnss_seg_info->codeseg_busaddr[0] = (void *)dma_handle; + penv->codeseg_cpuaddr[0] = cpu_addr; + cnss_seg_info->codeseg_size = EVICT_BIN_MAX_SIZE; + cnss_seg_info->codeseg_total_bytes = EVICT_BIN_MAX_SIZE; + cnss_seg_info->num_codesegs = 1; + cnss_seg_info->codeseg_size_log2 = ilog2(EVICT_BIN_MAX_SIZE); + + penv->cnss_seg_info = cnss_seg_info; + pr_debug("%s: Successfully allocated memory for CODESWAP\n", __func__); + + return ret; + +end_dma_alloc: + dma_free_coherent(dev, EVICT_BIN_MAX_SIZE, cpu_addr, dma_handle); +err_unknown: +err_pcie_suspend: +smmu_init_fail: + return ret; +} + +static void cnss_wlan_pci_remove(struct pci_dev *pdev) +{ + struct device *dev; + + if (!penv) + return; + + dev = &penv->pldev->dev; + device_remove_file(dev, &dev_attr_wlan_setup); + + if (penv->smmu_mapping) + cnss_smmu_remove(&pdev->dev); +} + +static int cnss_wlan_pci_suspend(struct device *dev) +{ + int ret = 0; + struct cnss_wlan_driver *wdriver; + struct pci_dev *pdev = to_pci_dev(dev); + + pm_message_t state = { .event = PM_EVENT_SUSPEND }; + + if (!penv) + goto out; + + wdriver = penv->driver; + if (!wdriver) + goto out; + + if (wdriver->suspend) { + ret = wdriver->suspend(pdev, state); + + if (penv->pcie_link_state) { + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + } + } + penv->monitor_wake_intr = false; + +out: + return ret; +} + +static int cnss_wlan_pci_resume(struct device *dev) +{ + int ret = 0; + struct cnss_wlan_driver *wdriver; + struct pci_dev *pdev = to_pci_dev(dev); + + if (!penv) + goto out; + + wdriver = penv->driver; + if (!wdriver) + goto out; + + if (wdriver->resume && !penv->pcie_link_down_ind) { + if (penv->saved_state) + cnss_pci_load_and_free_saved_state( + pdev, &penv->saved_state); + pci_restore_state(pdev); + + ret = wdriver->resume(pdev); + } + +out: + return ret; +} + +static int cnss_wlan_runtime_suspend(struct device *dev) +{ + int ret = 0; + struct cnss_wlan_driver *wdrv; + + if (!penv) + return -EAGAIN; + + if (penv->pcie_link_down_ind) { + pr_debug("PCI link down recovery is in progress!\n"); + return -EAGAIN; + } + + pr_debug("cnss: runtime suspend start\n"); + + wdrv = penv->driver; + + if (wdrv && wdrv->runtime_ops && wdrv->runtime_ops->runtime_suspend) + ret = wdrv->runtime_ops->runtime_suspend(to_pci_dev(dev)); + + pr_info("cnss: runtime suspend status: %d\n", ret); + + return ret; + +} + +static int cnss_wlan_runtime_resume(struct device *dev) +{ + struct cnss_wlan_driver *wdrv; + int ret = 0; + + if (!penv) + return -EAGAIN; + + if (penv->pcie_link_down_ind) { + pr_debug("PCI link down recovery is in progress!\n"); + return -EAGAIN; + } + + pr_debug("cnss: runtime resume start\n"); + + wdrv = penv->driver; + + if (wdrv && wdrv->runtime_ops && wdrv->runtime_ops->runtime_resume) + ret = wdrv->runtime_ops->runtime_resume(to_pci_dev(dev)); + + pr_info("cnss: runtime resume status: %d\n", ret); + + return ret; +} + +static int cnss_wlan_runtime_idle(struct device *dev) +{ + pr_debug("cnss: runtime idle\n"); + + pm_request_autosuspend(dev); + + return -EBUSY; +} + +static DECLARE_RWSEM(cnss_pm_sem); + +static int cnss_pm_notify(struct notifier_block *b, + unsigned long event, void *p) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + down_write(&cnss_pm_sem); + break; + + case PM_POST_SUSPEND: + up_write(&cnss_pm_sem); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block cnss_pm_notifier = { + .notifier_call = cnss_pm_notify, +}; + +static DEFINE_PCI_DEVICE_TABLE(cnss_wlan_pci_id_table) = { + { QCA6174_VENDOR_ID, QCA6174_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID }, + { QCA6174_VENDOR_ID, BEELINER_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID }, + { QCA6180_VENDOR_ID, QCA6180_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cnss_wlan_pci_id_table); + +#ifdef CONFIG_PM +static const struct dev_pm_ops cnss_wlan_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cnss_wlan_pci_suspend, cnss_wlan_pci_resume) + SET_RUNTIME_PM_OPS(cnss_wlan_runtime_suspend, cnss_wlan_runtime_resume, + cnss_wlan_runtime_idle) +}; +#endif + +struct pci_driver cnss_wlan_pci_driver = { + .name = "cnss_wlan_pci", + .id_table = cnss_wlan_pci_id_table, + .probe = cnss_wlan_pci_probe, + .remove = cnss_wlan_pci_remove, +#ifdef CONFIG_PM + .driver = { + .pm = &cnss_wlan_pm_ops, + }, +#endif +}; + +static ssize_t fw_image_setup_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", penv->fw_image_setup); +} + +static ssize_t fw_image_setup_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + int ret; + + if (!penv) + return -ENODEV; + + mutex_lock(&penv->fw_setup_stat_lock); + pr_info("%s: Firmware setup in progress\n", __func__); + + if (kstrtoint(buf, 0, &val)) { + mutex_unlock(&penv->fw_setup_stat_lock); + return -EINVAL; + } + + if (val == FW_IMAGE_FTM || val == FW_IMAGE_MISSION + || val == FW_IMAGE_BDATA) { + pr_info("%s: fw image setup triggered %d\n", __func__, val); + ret = cnss_setup_fw_image_table(val); + if (ret != 0) { + pr_err("%s: Invalid parsing of FW image files %d", + __func__, ret); + mutex_unlock(&penv->fw_setup_stat_lock); + return -EINVAL; + } + penv->fw_image_setup = val; + } else if (val == FW_IMAGE_PRINT) { + print_allocated_image_table(); + } else if (val == BMI_TEST_SETUP) { + penv->bmi_test = val; + } + + pr_info("%s: Firmware setup completed\n", __func__); + mutex_unlock(&penv->fw_setup_stat_lock); + return count; +} + +static DEVICE_ATTR(fw_image_setup, S_IRUSR | S_IWUSR, + fw_image_setup_show, fw_image_setup_store); + +void cnss_pci_recovery_work_handler(struct work_struct *recovery) +{ + cnss_pci_device_self_recovery(); +} + +DECLARE_WORK(cnss_pci_recovery_work, cnss_pci_recovery_work_handler); + +void cnss_schedule_recovery_work(void) +{ + schedule_work(&cnss_pci_recovery_work); +} +EXPORT_SYMBOL(cnss_schedule_recovery_work); + +static inline void __cnss_disable_irq(void *data) +{ + struct pci_dev *pdev = data; + + disable_irq_nosync(pdev->irq); +} + +void cnss_pci_events_cb(struct msm_pcie_notify *notify) +{ + unsigned long flags; + + if (notify == NULL) + return; + + switch (notify->event) { + + case MSM_PCIE_EVENT_LINKDOWN: + if (pcie_link_down_panic) + panic("PCIe link is down!\n"); + + spin_lock_irqsave(&pci_link_down_lock, flags); + if (penv->pcie_link_down_ind) { + pr_debug("PCI link down recovery is in progress, ignore!\n"); + spin_unlock_irqrestore(&pci_link_down_lock, flags); + return; + } + penv->pcie_link_down_ind = true; + spin_unlock_irqrestore(&pci_link_down_lock, flags); + + pr_err("PCI link down, schedule recovery\n"); + __cnss_disable_irq(notify->user); + schedule_work(&cnss_pci_recovery_work); + break; + + case MSM_PCIE_EVENT_WAKEUP: + if (penv->monitor_wake_intr && + atomic_read(&penv->auto_suspended)) { + penv->monitor_wake_intr = false; + pm_request_resume(&penv->pdev->dev); + } + break; + + default: + pr_err("cnss: invalid event from PCIe callback %d\n", + notify->event); + } +} + +void cnss_wlan_pci_link_down(void) +{ + unsigned long flags; + + if (pcie_link_down_panic) + panic("PCIe link is down!\n"); + + spin_lock_irqsave(&pci_link_down_lock, flags); + if (penv->pcie_link_down_ind) { + pr_debug("PCI link down recovery is in progress, ignore!\n"); + spin_unlock_irqrestore(&pci_link_down_lock, flags); + return; + } + penv->pcie_link_down_ind = true; + spin_unlock_irqrestore(&pci_link_down_lock, flags); + + pr_err("PCI link down detected by host driver, schedule recovery!\n"); + schedule_work(&cnss_pci_recovery_work); +} +EXPORT_SYMBOL(cnss_wlan_pci_link_down); + +int cnss_pcie_shadow_control(struct pci_dev *dev, bool enable) +{ + return cnss_msm_pcie_shadow_control(dev, enable); +} +EXPORT_SYMBOL(cnss_pcie_shadow_control); + +int cnss_get_codeswap_struct(struct codeswap_codeseg_info *swap_seg) +{ + struct codeswap_codeseg_info *cnss_seg_info = penv->cnss_seg_info; + + mutex_lock(&penv->fw_setup_stat_lock); + if (!cnss_seg_info) { + swap_seg = NULL; + mutex_unlock(&penv->fw_setup_stat_lock); + return -ENOENT; + } + + if (!atomic_read(&penv->fw_available)) { + pr_debug("%s: fw is not available\n", __func__); + mutex_unlock(&penv->fw_setup_stat_lock); + return -ENOENT; + } + + *swap_seg = *cnss_seg_info; + mutex_unlock(&penv->fw_setup_stat_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_get_codeswap_struct); + +static void cnss_wlan_memory_expansion(void) +{ + struct device *dev; + const struct firmware *fw_entry; + const char *filename; + u_int32_t fw_entry_size, size_left, dma_size_left, length; + char *fw_temp; + char *fw_data; + char *dma_virt_addr; + struct codeswap_codeseg_info *cnss_seg_info; + u_int32_t total_length = 0; + struct pci_dev *pdev; + + mutex_lock(&penv->fw_setup_stat_lock); + filename = cnss_wlan_get_evicted_data_file(); + pdev = penv->pdev; + dev = &pdev->dev; + cnss_seg_info = penv->cnss_seg_info; + + if (!cnss_seg_info) { + pr_debug("cnss: cnss_seg_info is NULL\n"); + mutex_unlock(&penv->fw_setup_stat_lock); + goto end; + } + + if (atomic_read(&penv->fw_available)) { + pr_debug("cnss: fw code already copied to host memory\n"); + mutex_unlock(&penv->fw_setup_stat_lock); + goto end; + } + + if (request_firmware(&fw_entry, filename, dev) != 0) { + pr_debug("cnss: failed to get fw: %s\n", filename); + mutex_unlock(&penv->fw_setup_stat_lock); + goto end; + } + + if (!fw_entry || !fw_entry->data) { + pr_err("%s: INVALID FW entries\n", __func__); + mutex_unlock(&penv->fw_setup_stat_lock); + goto release_fw; + } + + dma_virt_addr = (char *)penv->codeseg_cpuaddr[0]; + fw_data = (u8 *) fw_entry->data; + fw_temp = fw_data; + fw_entry_size = fw_entry->size; + if (fw_entry_size > EVICT_BIN_MAX_SIZE) + fw_entry_size = EVICT_BIN_MAX_SIZE; + size_left = fw_entry_size; + dma_size_left = EVICT_BIN_MAX_SIZE; + while ((size_left && fw_temp) && (dma_size_left > 0)) { + fw_temp = fw_temp + 4; + size_left = size_left - 4; + length = *(int *)fw_temp; + if ((length > size_left || length <= 0) || + (dma_size_left <= 0 || length > dma_size_left)) { + pr_err("cnss: wrong length read:%d\n", + length); + break; + } + fw_temp = fw_temp + 4; + size_left = size_left - 4; + memcpy(dma_virt_addr, fw_temp, length); + dma_size_left = dma_size_left - length; + size_left = size_left - length; + fw_temp = fw_temp + length; + dma_virt_addr = dma_virt_addr + length; + total_length += length; + pr_debug("cnss: bytes_left to copy: fw:%d; dma_page:%d\n", + size_left, dma_size_left); + } + pr_debug("cnss: total_bytes copied: %d\n", total_length); + cnss_seg_info->codeseg_total_bytes = total_length; + + atomic_set(&penv->fw_available, 1); + mutex_unlock(&penv->fw_setup_stat_lock); + +release_fw: + release_firmware(fw_entry); +end: + return; +} + +/** + * cnss_get_wlan_mac_address() - API to return MAC addresses buffer + * @dev: struct device pointer + * @num: buffer for number of mac addresses supported + * + * API returns the pointer to the buffer filled with mac addresses and + * updates num with the number of mac addresses the buffer contains. + * + * Return: pointer to mac address buffer. + */ +u8 *cnss_pci_get_wlan_mac_address(uint32_t *num) +{ + struct cnss_wlan_mac_addr *addr = NULL; + + if (!penv) { + pr_err("%s: Invalid Platform Driver Context\n", __func__); + goto end; + } + + if (!penv->is_wlan_mac_set) { + pr_info("%s: Platform Driver doesn't have any mac address\n", + __func__); + goto end; + } + + addr = &penv->wlan_mac_addr; + *num = addr->no_of_mac_addr_set; + return &addr->mac_addr[0][0]; + +end: + *num = 0; + return NULL; +} + +/** + * cnss_get_wlan_mac_address() - API to return MAC addresses buffer + * @dev: struct device pointer + * @num: buffer for number of mac addresses supported + * + * API returns the pointer to the buffer filled with mac addresses and + * updates num with the number of mac addresses the buffer contains. + * + * Return: pointer to mac address buffer. + */ +u8 *cnss_get_wlan_mac_address(struct device *dev, uint32_t *num) +{ + struct cnss_wlan_mac_addr *addr = NULL; + + if (!penv) { + pr_err("%s: Invalid Platform Driver Context\n", __func__); + goto end; + } + + if (!penv->is_wlan_mac_set) { + pr_info("%s: Platform Driver doesn't have any mac address\n", + __func__); + goto end; + } + + addr = &penv->wlan_mac_addr; + *num = addr->no_of_mac_addr_set; + return &addr->mac_addr[0][0]; +end: + *num = 0; + return NULL; +} +EXPORT_SYMBOL(cnss_get_wlan_mac_address); + +/** +* cnss_pcie_set_wlan_mac_address() - API to get two wlan mac address +* @in: Input buffer with wlan mac addresses +* @len: Size of the buffer passed +* +* API to store wlan mac address passed by the caller. The stored mac +* addresses are used by the wlan functional driver to program wlan HW. +* +* Return: kernel error code. +*/ +int cnss_pcie_set_wlan_mac_address(const u8 *in, uint32_t len) +{ + uint32_t no_of_mac_addr; + struct cnss_wlan_mac_addr *addr = NULL; + int iter = 0; + u8 *temp = NULL; + + if (len == 0 || (len % ETH_ALEN) != 0) { + pr_err("%s: Invalid Length:%d\n", __func__, len); + return -EINVAL; + } + + no_of_mac_addr = len / ETH_ALEN; + + if (no_of_mac_addr > MAX_NO_OF_MAC_ADDR) { + pr_err("%s: Num of supported MAC addresses are:%d given:%d\n", + __func__, MAX_NO_OF_MAC_ADDR, no_of_mac_addr); + return -EINVAL; + } + + if (!penv) { + pr_err("%s: Invalid CNSS Platform Context\n", __func__); + return -ENOENT; + } + + if (penv->is_wlan_mac_set) { + pr_info("%s: Already MAC address are configured\n", __func__); + return 0; + } + + penv->is_wlan_mac_set = true; + addr = &penv->wlan_mac_addr; + addr->no_of_mac_addr_set = no_of_mac_addr; + temp = &addr->mac_addr[0][0]; + + for (; iter < no_of_mac_addr; ++iter, temp += ETH_ALEN, in += + ETH_ALEN) { + ether_addr_copy(temp, in); + pr_debug("%s MAC_ADDR:%02x:%02x:%02x:%02x:%02x:%02x\n", + __func__, temp[0], temp[1], temp[2], temp[3], temp[4], + temp[5]); + } + return 0; +} + +int cnss_wlan_register_driver(struct cnss_wlan_driver *driver) +{ + int ret = 0; + int probe_again = 0; + struct cnss_wlan_driver *wdrv; + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + struct pci_dev *pdev; + + if (!penv) + return -ENODEV; + + wdrv = penv->driver; + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + pdev = penv->pdev; + + if (!wdrv) { + penv->driver = wdrv = driver; + } else { + pr_err("driver already registered\n"); + return -EEXIST; + } + +again: + ret = cnss_wlan_vreg_set(vreg_info, VREG_ON); + if (ret) { + pr_err("wlan vreg ON failed\n"); + goto err_wlan_vreg_on; + } + + msleep(POWER_ON_DELAY); + + if (penv->wlan_bootstrap_gpio > 0) { + gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_HIGH); + msleep(WLAN_BOOTSTRAP_DELAY); + } + + cnss_configure_wlan_en_gpio(WLAN_EN_HIGH); + + if (!pdev) { + pr_debug("%s: invalid pdev. register pci device\n", __func__); + ret = pci_register_driver(&cnss_wlan_pci_driver); + + if (ret) { + pr_err("%s: pci registration failed\n", __func__); + goto err_pcie_reg; + } + pdev = penv->pdev; + if (!pdev) { + pr_err("%s: pdev is still invalid\n", __func__); + goto err_pcie_reg; + } + } + + penv->event_reg.events = MSM_PCIE_EVENT_LINKDOWN | + MSM_PCIE_EVENT_WAKEUP; + penv->event_reg.user = pdev; + penv->event_reg.mode = MSM_PCIE_TRIGGER_CALLBACK; + penv->event_reg.callback = cnss_pci_events_cb; + penv->event_reg.options = MSM_PCIE_CONFIG_NO_RECOVERY; + ret = cnss_msm_pcie_register_event(&penv->event_reg); + if (ret) + pr_err("%s: PCIe event register failed! %d\n", __func__, ret); + + if (!penv->pcie_link_state && !penv->pcie_link_down_ind) { + ret = cnss_msm_pcie_pm_control( + MSM_PCIE_RESUME, cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + if (ret) { + pr_err("PCIe link bring-up failed\n"); + goto err_pcie_link_up; + } + penv->pcie_link_state = PCIE_LINK_UP; + } else if (!penv->pcie_link_state && penv->pcie_link_down_ind) { + + ret = cnss_msm_pcie_pm_control( + MSM_PCIE_RESUME, cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS_RESUME_LINK_DOWN); + + if (ret) { + pr_err("PCIe link bring-up failed (link down option)\n"); + goto err_pcie_link_up; + } + penv->pcie_link_state = PCIE_LINK_UP; + + ret = cnss_msm_pcie_recover_config(pdev); + if (ret) { + pr_err("cnss: PCI link failed to recover\n"); + goto err_pcie_link_up; + } + penv->pcie_link_down_ind = false; + } + + if (!cnss_wlan_is_codeswap_supported(penv->revision_id)) + cnss_wlan_memory_expansion(); + + if (wdrv->probe) { + if (penv->saved_state) + cnss_pci_load_and_free_saved_state( + pdev, &penv->saved_state); + + pci_restore_state(pdev); + + ret = wdrv->probe(pdev, penv->id); + if (ret) { + wcnss_prealloc_check_memory_leak(); + wcnss_pre_alloc_reset(); + + if (probe_again > 3) { + pr_err("Failed to probe WLAN\n"); + goto err_wlan_probe; + } + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + cnss_msm_pcie_deregister_event(&penv->event_reg); + cnss_msm_pcie_pm_control( + MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + penv->pcie_link_state = PCIE_LINK_DOWN; + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + cnss_wlan_vreg_set(vreg_info, VREG_OFF); + msleep(POWER_ON_DELAY); + probe_again++; + goto again; + } + } + + if (penv->notify_modem_status && wdrv->modem_status) + wdrv->modem_status(pdev, penv->modem_current_status); + + return ret; + +err_wlan_probe: + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + +err_pcie_link_up: + cnss_msm_pcie_deregister_event(&penv->event_reg); + if (penv->pcie_link_state) { + cnss_msm_pcie_pm_control( + MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + penv->pcie_link_state = PCIE_LINK_DOWN; + } + +err_pcie_reg: + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + cnss_wlan_vreg_set(vreg_info, VREG_OFF); + if (penv->pdev) { + pr_err("%d: Unregistering PCI device\n", __LINE__); + pci_unregister_driver(&cnss_wlan_pci_driver); + penv->pdev = NULL; + penv->pci_register_again = true; + } + +err_wlan_vreg_on: + penv->driver = NULL; + + return ret; +} +EXPORT_SYMBOL(cnss_wlan_register_driver); + +void cnss_wlan_unregister_driver(struct cnss_wlan_driver *driver) +{ + struct cnss_wlan_driver *wdrv; + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + struct pci_dev *pdev; + + if (!penv) + return; + + wdrv = penv->driver; + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + pdev = penv->pdev; + + if (!wdrv) { + pr_err("driver not registered\n"); + return; + } + + if (penv->bus_client) + msm_bus_scale_client_update_request(penv->bus_client, + CNSS_BUS_WIDTH_NONE); + + if (!pdev) { + pr_err("%d: invalid pdev\n", __LINE__); + goto cut_power; + } + + if (wdrv->remove) + wdrv->remove(pdev); + + wcnss_prealloc_check_memory_leak(); + wcnss_pre_alloc_reset(); + + cnss_msm_pcie_deregister_event(&penv->event_reg); + + if (penv->pcie_link_state && !penv->pcie_link_down_ind) { + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + + if (cnss_msm_pcie_pm_control( + MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS)) { + pr_err("Failed to shutdown PCIe link\n"); + return; + } + } else if (penv->pcie_link_state && penv->pcie_link_down_ind) { + penv->saved_state = NULL; + + if (cnss_msm_pcie_pm_control( + MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS_SUSPEND_LINK_DOWN)) { + pr_err("Failed to shutdown PCIe link (with linkdown option)\n"); + return; + } + } + penv->pcie_link_state = PCIE_LINK_DOWN; + penv->driver_status = CNSS_UNINITIALIZED; + penv->monitor_wake_intr = false; + atomic_set(&penv->auto_suspended, 0); + +cut_power: + penv->driver = NULL; + + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + if (cnss_wlan_vreg_set(vreg_info, VREG_OFF)) + pr_err("wlan vreg OFF failed\n"); +} +EXPORT_SYMBOL(cnss_wlan_unregister_driver); + +#ifdef CONFIG_PCI_MSM +int cnss_wlan_pm_control(bool vote) +{ + if (!penv || !penv->pdev) + return -ENODEV; + + return cnss_msm_pcie_pm_control( + vote ? MSM_PCIE_DISABLE_PC : MSM_PCIE_ENABLE_PC, + cnss_get_pci_dev_bus_number(penv->pdev), + penv->pdev, PM_OPTIONS); +} +EXPORT_SYMBOL(cnss_wlan_pm_control); +#endif + +void cnss_lock_pm_sem(void) +{ + down_read(&cnss_pm_sem); +} +EXPORT_SYMBOL(cnss_lock_pm_sem); + +void cnss_release_pm_sem(void) +{ + up_read(&cnss_pm_sem); +} +EXPORT_SYMBOL(cnss_release_pm_sem); + +void cnss_pci_schedule_recovery_work(void) +{ + schedule_work(&cnss_pci_recovery_work); +} + +void *cnss_pci_get_virt_ramdump_mem(unsigned long *size) +{ + if (!penv || !penv->pldev) + return NULL; + + *size = penv->ramdump_size; + + return penv->ramdump_addr; +} + +void cnss_pci_device_crashed(void) +{ + if (penv && penv->subsys) { + subsys_set_crash_status(penv->subsys, CRASH_STATUS_ERR_FATAL); + subsystem_restart_dev(penv->subsys); + } +} + +void *cnss_get_virt_ramdump_mem(unsigned long *size) +{ + if (!penv || !penv->pldev) + return NULL; + + *size = penv->ramdump_size; + + return penv->ramdump_addr; +} +EXPORT_SYMBOL(cnss_get_virt_ramdump_mem); + +void cnss_device_crashed(void) +{ + if (penv && penv->subsys) { + subsys_set_crash_status(penv->subsys, CRASH_STATUS_ERR_FATAL); + subsystem_restart_dev(penv->subsys); + } +} +EXPORT_SYMBOL(cnss_device_crashed); + +static int cnss_shutdown(const struct subsys_desc *subsys, bool force_stop) +{ + struct cnss_wlan_driver *wdrv; + struct pci_dev *pdev; + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + int ret = 0; + + if (!penv) + return -ENODEV; + + penv->recovery_in_progress = true; + wdrv = penv->driver; + pdev = penv->pdev; + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + + if (!pdev) { + ret = -EINVAL; + goto cut_power; + } + + if (wdrv && wdrv->shutdown) + wdrv->shutdown(pdev); + + if (penv->pcie_link_state) { + if (cnss_msm_pcie_pm_control( + MSM_PCIE_SUSPEND, cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS_SUSPEND_LINK_DOWN)) { + pr_debug("cnss: Failed to shutdown PCIe link!\n"); + ret = -EFAULT; + } + penv->saved_state = NULL; + penv->pcie_link_state = PCIE_LINK_DOWN; + } + +cut_power: + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + if (cnss_wlan_vreg_set(vreg_info, VREG_OFF)) + pr_err("cnss: Failed to set WLAN VREG_OFF!\n"); + + return ret; +} + +static int cnss_powerup(const struct subsys_desc *subsys) +{ + struct cnss_wlan_driver *wdrv; + struct pci_dev *pdev; + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + int ret = 0; + + if (!penv) + return -ENODEV; + + if (!penv->driver) + goto out; + + wdrv = penv->driver; + pdev = penv->pdev; + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + + ret = cnss_wlan_vreg_set(vreg_info, VREG_ON); + if (ret) { + pr_err("cnss: Failed to set WLAN VREG_ON!\n"); + goto err_wlan_vreg_on; + } + + msleep(POWER_ON_DELAY); + cnss_configure_wlan_en_gpio(WLAN_EN_HIGH); + + if (!pdev) { + pr_err("%d: invalid pdev\n", __LINE__); + goto err_pcie_link_up; + } + + if (!penv->pcie_link_state) { + ret = cnss_msm_pcie_pm_control( + MSM_PCIE_RESUME, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS_RESUME_LINK_DOWN); + + if (ret) { + pr_err("cnss: Failed to bring-up PCIe link!\n"); + goto err_pcie_link_up; + } + penv->pcie_link_state = PCIE_LINK_UP; + ret = cnss_msm_pcie_recover_config(penv->pdev); + if (ret) { + pr_err("cnss: PCI link failed to recover\n"); + goto err_pcie_link_up; + } + penv->pcie_link_down_ind = false; + } + + if (wdrv && wdrv->reinit) { + if (penv->saved_state) + cnss_pci_load_and_free_saved_state( + pdev, &penv->saved_state); + + pci_restore_state(pdev); + + ret = wdrv->reinit(pdev, penv->id); + if (ret) { + pr_err("%d: Failed to do reinit\n", __LINE__); + goto err_wlan_reinit; + } + } else { + pr_err("%d: wdrv->reinit is invalid\n", __LINE__); + goto err_pcie_link_up; + } + + if (penv->notify_modem_status && wdrv->modem_status) + wdrv->modem_status(pdev, penv->modem_current_status); + +out: + penv->recovery_in_progress = false; + return ret; + +err_wlan_reinit: + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + cnss_msm_pcie_pm_control( + MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS); + penv->pcie_link_state = PCIE_LINK_DOWN; + +err_pcie_link_up: + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + cnss_wlan_vreg_set(vreg_info, VREG_OFF); + if (penv->pdev) { + if (wdrv && wdrv->update_status) + wdrv->update_status(penv->pdev, CNSS_SSR_FAIL); + if (!penv->recovery_in_progress) { + pr_err("%d: Unregistering pci device\n", __LINE__); + pci_unregister_driver(&cnss_wlan_pci_driver); + penv->pdev = NULL; + penv->pci_register_again = true; + } + } + +err_wlan_vreg_on: + return ret; +} + +void cnss_pci_device_self_recovery(void) +{ + if (!penv) + return; + + if (penv->recovery_in_progress) { + pr_err("cnss: Recovery already in progress\n"); + return; + } + + if (penv->driver_status == CNSS_LOAD_UNLOAD) { + pr_err("cnss: load unload in progress\n"); + return; + } + + penv->recovery_count++; + penv->recovery_in_progress = true; + cnss_pm_wake_lock(&penv->ws); + cnss_shutdown(NULL, false); + msleep(WLAN_RECOVERY_DELAY); + cnss_powerup(NULL); + cnss_pm_wake_lock_release(&penv->ws); + penv->recovery_in_progress = false; +} + +static int cnss_ramdump(int enable, const struct subsys_desc *subsys) +{ + struct ramdump_segment segment; + + if (!penv) + return -ENODEV; + + if (!penv->ramdump_size) + return -ENOENT; + + if (!enable) + return 0; + + memset(&segment, 0, sizeof(segment)); + segment.v_address = penv->ramdump_addr; + segment.size = penv->ramdump_size; + + return do_ramdump(penv->ramdump_dev, &segment, 1); +} + +static void cnss_crash_shutdown(const struct subsys_desc *subsys) +{ + struct cnss_wlan_driver *wdrv; + struct pci_dev *pdev; + + if (!penv) + return; + + wdrv = penv->driver; + pdev = penv->pdev; + + if (pdev && wdrv && wdrv->crash_shutdown) + wdrv->crash_shutdown(pdev); +} + +void cnss_device_self_recovery(void) +{ + if (!penv) + return; + + if (penv->recovery_in_progress) { + pr_err("cnss: Recovery already in progress\n"); + return; + } + if (penv->driver_status == CNSS_LOAD_UNLOAD) { + pr_err("cnss: load unload in progress\n"); + return; + } + penv->recovery_count++; + penv->recovery_in_progress = true; + cnss_pm_wake_lock(&penv->ws); + cnss_shutdown(NULL, false); + msleep(WLAN_RECOVERY_DELAY); + cnss_powerup(NULL); + cnss_pm_wake_lock_release(&penv->ws); + penv->recovery_in_progress = false; +} +EXPORT_SYMBOL(cnss_device_self_recovery); + +static int cnss_modem_notifier_nb(struct notifier_block *this, + unsigned long code, + void *ss_handle) +{ + struct cnss_wlan_driver *wdrv; + struct pci_dev *pdev; + + pr_debug("%s: Modem-Notify: event %lu\n", __func__, code); + + if (!penv) + return NOTIFY_DONE; + + if (SUBSYS_AFTER_POWERUP == code) + penv->modem_current_status = 1; + else if (SUBSYS_BEFORE_SHUTDOWN == code) + penv->modem_current_status = 0; + else + return NOTIFY_DONE; + + wdrv = penv->driver; + pdev = penv->pdev; + + if (!wdrv || !pdev || !wdrv->modem_status) + return NOTIFY_DONE; + + wdrv->modem_status(pdev, penv->modem_current_status); + + return NOTIFY_OK; +} + +static struct notifier_block mnb = { + .notifier_call = cnss_modem_notifier_nb, +}; + +static int cnss_init_dump_entry(void) +{ + struct msm_dump_entry dump_entry; + + if (!penv) + return -ENODEV; + + if (!penv->ramdump_dynamic) + return 0; + + penv->dump_data.addr = penv->ramdump_phys; + penv->dump_data.len = penv->ramdump_size; + penv->dump_data.version = CNSS_DUMP_FORMAT_VER; + penv->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2; + strlcpy(penv->dump_data.name, CNSS_DUMP_NAME, + sizeof(penv->dump_data.name)); + dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; + dump_entry.addr = virt_to_phys(&penv->dump_data); + + return msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); +} + +static int cnss_probe(struct platform_device *pdev) +{ + int ret = 0; + struct esoc_desc *desc; + const char *client_desc; + struct device *dev = &pdev->dev; +#ifndef CONFIG_MSM_GVM_QUIN + u32 rc_num; +#endif + struct resource *res; + u32 ramdump_size = 0; + u32 smmu_iova_address[2]; + + if (penv) + return -ENODEV; + + penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL); + if (!penv) + return -ENOMEM; + + penv->pldev = pdev; + penv->esoc_desc = NULL; + + penv->gpio_info.name = WLAN_EN_GPIO_NAME; + penv->gpio_info.num = 0; + penv->gpio_info.state = WLAN_EN_LOW; + penv->gpio_info.init = WLAN_EN_LOW; + penv->gpio_info.prop = false; + penv->vreg_info.wlan_reg = NULL; + penv->vreg_info.state = VREG_OFF; + penv->pci_register_again = false; + mutex_init(&penv->fw_setup_stat_lock); + + ret = cnss_wlan_get_resources(pdev); + if (ret) + goto err_get_wlan_res; + + ret = cnss_configure_wlan_en_gpio(WLAN_EN_HIGH); + if (ret) { + pr_err("%s: Failed to enable WLAN enable gpio\n", __func__); + goto err_get_rc; + } + +#ifndef CONFIG_MSM_GVM_QUIN + ret = of_property_read_u32(dev->of_node, "qcom,wlan-rc-num", &rc_num); + if (ret) { + pr_err("%s: Failed to find PCIe RC number!\n", __func__); + goto err_get_rc; + } + + ret = cnss_msm_pcie_enumerate(rc_num); + if (ret) { + pr_err("%s: Failed to enable PCIe RC%x!\n", __func__, rc_num); + goto err_pcie_enumerate; + } +#endif + + penv->pcie_link_state = PCIE_LINK_UP; + + penv->notify_modem_status = + of_property_read_bool(dev->of_node, + "qcom,notify-modem-status"); + + if (penv->notify_modem_status) { + ret = of_property_read_string_index(dev->of_node, "esoc-names", + 0, &client_desc); + if (ret) { + pr_debug("%s: esoc-names is not defined in DT, SKIP\n", + __func__); + } else { + desc = devm_register_esoc_client(dev, client_desc); + if (IS_ERR_OR_NULL(desc)) { + ret = PTR_RET(desc); + pr_err("%s: can't find esoc desc\n", __func__); + goto err_esoc_reg; + } + penv->esoc_desc = desc; + } + } + + penv->subsysdesc.name = "AR6320"; + penv->subsysdesc.owner = THIS_MODULE; + penv->subsysdesc.shutdown = cnss_shutdown; + penv->subsysdesc.powerup = cnss_powerup; + penv->subsysdesc.ramdump = cnss_ramdump; + penv->subsysdesc.crash_shutdown = cnss_crash_shutdown; + penv->subsysdesc.dev = &pdev->dev; + penv->subsys = subsys_register(&penv->subsysdesc); + if (IS_ERR(penv->subsys)) { + ret = PTR_ERR(penv->subsys); + goto err_subsys_reg; + } + + penv->subsys_handle = subsystem_get(penv->subsysdesc.name); + + if (of_property_read_bool(dev->of_node, "qcom,is-dual-wifi-enabled")) + penv->dual_wifi_info.is_dual_wifi_enabled = true; + + if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", + &ramdump_size) == 0) { + penv->ramdump_addr = dma_alloc_coherent(&pdev->dev, + ramdump_size, &penv->ramdump_phys, GFP_KERNEL); + + if (penv->ramdump_addr) + penv->ramdump_size = ramdump_size; + penv->ramdump_dynamic = true; + } else { + res = platform_get_resource_byname(penv->pldev, + IORESOURCE_MEM, "ramdump"); + if (res) { + penv->ramdump_phys = res->start; + ramdump_size = resource_size(res); + penv->ramdump_addr = ioremap(penv->ramdump_phys, + ramdump_size); + + if (penv->ramdump_addr) + penv->ramdump_size = ramdump_size; + + penv->ramdump_dynamic = false; + } + } + + pr_debug("%s: ramdump addr: %p, phys: %pa\n", __func__, + penv->ramdump_addr, &penv->ramdump_phys); + + if (penv->ramdump_size == 0) { + pr_info("%s: CNSS ramdump will not be collected", __func__); + goto skip_ramdump; + } + + ret = cnss_init_dump_entry(); + if (ret) { + pr_err("%s: Dump table setup failed: %d\n", __func__, ret); + goto err_ramdump_create; + } + + penv->ramdump_dev = create_ramdump_device(penv->subsysdesc.name, + penv->subsysdesc.dev); + if (!penv->ramdump_dev) { + ret = -ENOMEM; + goto err_ramdump_create; + } + +skip_ramdump: + penv->modem_current_status = 0; + + if (penv->notify_modem_status) { + penv->modem_notify_handler = + subsys_notif_register_notifier(penv->esoc_desc ? + penv->esoc_desc->name : + "modem", &mnb); + if (IS_ERR(penv->modem_notify_handler)) { + ret = PTR_ERR(penv->modem_notify_handler); + pr_err("%s: Register notifier Failed\n", __func__); + goto err_notif_modem; + } + } + + if (of_property_read_u32_array(dev->of_node, + "qcom,wlan-smmu-iova-address", + smmu_iova_address, 2) == 0) { + penv->smmu_iova_start = smmu_iova_address[0]; + penv->smmu_iova_len = smmu_iova_address[1]; + } + + ret = pci_register_driver(&cnss_wlan_pci_driver); + if (ret) + goto err_pci_reg; + + penv->bus_scale_table = 0; + penv->bus_scale_table = msm_bus_cl_get_pdata(pdev); + + if (penv->bus_scale_table) { + penv->bus_client = + msm_bus_scale_register_client(penv->bus_scale_table); + + if (!penv->bus_client) { + pr_err("Failed to register with bus_scale client\n"); + goto err_bus_reg; + } + } + cnss_pm_wake_lock_init(&penv->ws, "cnss_wlock"); + + register_pm_notifier(&cnss_pm_notifier); + +#ifdef CONFIG_CNSS_MAC_BUG + /* 0-4K memory is reserved for QCA6174 to address a MAC HW bug. + * MAC would do an invalid pointer fetch based on the data + * that was read from 0 to 4K. So fill it with zero's (to an + * address for which PCIe RC honored the read without any errors). + */ + memset(phys_to_virt(0), 0, SZ_4K); +#endif + + ret = device_create_file(dev, &dev_attr_fw_image_setup); + if (ret) { + pr_err("cnss: fw_image_setup sys file creation failed\n"); + goto err_bus_reg; + } + pr_info("cnss: Platform driver probed successfully.\n"); + return ret; + +err_bus_reg: + if (penv->bus_scale_table) + msm_bus_cl_clear_pdata(penv->bus_scale_table); + pci_unregister_driver(&cnss_wlan_pci_driver); + +err_pci_reg: + if (penv->notify_modem_status) + subsys_notif_unregister_notifier + (penv->modem_notify_handler, &mnb); + +err_notif_modem: + if (penv->ramdump_dev) + destroy_ramdump_device(penv->ramdump_dev); + +err_ramdump_create: + if (penv->ramdump_addr) { + if (penv->ramdump_dynamic) { + dma_free_coherent(&pdev->dev, penv->ramdump_size, + penv->ramdump_addr, penv->ramdump_phys); + } else { + iounmap(penv->ramdump_addr); + } + } + + if (penv->subsys_handle) + subsystem_put(penv->subsys_handle); + + subsys_unregister(penv->subsys); + +err_subsys_reg: + if (penv->esoc_desc) + devm_unregister_esoc_client(&pdev->dev, penv->esoc_desc); + +err_esoc_reg: +#ifndef CONFIG_MSM_GVM_QUIN +err_pcie_enumerate: +#endif +err_get_rc: + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + cnss_wlan_release_resources(); + +err_get_wlan_res: + penv = NULL; + + return ret; +} + +static int cnss_remove(struct platform_device *pdev) +{ + unregister_pm_notifier(&cnss_pm_notifier); + device_remove_file(&pdev->dev, &dev_attr_fw_image_setup); + + cnss_pm_wake_lock_destroy(&penv->ws); + + if (penv->bus_client) + msm_bus_scale_unregister_client(penv->bus_client); + + if (penv->bus_scale_table) + msm_bus_cl_clear_pdata(penv->bus_scale_table); + + if (penv->ramdump_addr) { + if (penv->ramdump_dynamic) { + dma_free_coherent(&pdev->dev, penv->ramdump_size, + penv->ramdump_addr, penv->ramdump_phys); + } else { + iounmap(penv->ramdump_addr); + } + } + + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + if (penv->wlan_bootstrap_gpio > 0) + gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_LOW); + cnss_wlan_release_resources(); + + return 0; +} + +static const struct of_device_id cnss_dt_match[] = { + {.compatible = "qcom,cnss"}, + {} +}; + +MODULE_DEVICE_TABLE(of, cnss_dt_match); + +static struct platform_driver cnss_driver = { + .probe = cnss_probe, + .remove = cnss_remove, + .driver = { + .name = "cnss", + .owner = THIS_MODULE, + .of_match_table = cnss_dt_match, +#ifdef CONFIG_CNSS_ASYNC + .probe_type = PROBE_PREFER_ASYNCHRONOUS, +#endif + }, +}; + +static int __init cnss_initialize(void) +{ + return platform_driver_register(&cnss_driver); +} + +static void __exit cnss_exit(void) +{ + struct platform_device *pdev = penv->pldev; + + if (penv->ramdump_dev) + destroy_ramdump_device(penv->ramdump_dev); + if (penv->notify_modem_status) + subsys_notif_unregister_notifier(penv->modem_notify_handler, + &mnb); + subsys_unregister(penv->subsys); + if (penv->esoc_desc) + devm_unregister_esoc_client(&pdev->dev, penv->esoc_desc); + platform_driver_unregister(&cnss_driver); +} + +void cnss_request_pm_qos_type(int latency_type, u32 qos_val) +{ + if (!penv) { + pr_err("%s: penv is NULL!\n", __func__); + return; + } + + pm_qos_add_request(&penv->qos_request, latency_type, qos_val); +} +EXPORT_SYMBOL(cnss_request_pm_qos_type); + +void cnss_request_pm_qos(u32 qos_val) +{ + if (!penv) { + pr_err("%s: penv is NULL!\n", __func__); + return; + } + + pm_qos_add_request(&penv->qos_request, PM_QOS_CPU_DMA_LATENCY, qos_val); +} +EXPORT_SYMBOL(cnss_request_pm_qos); + +void cnss_remove_pm_qos(void) +{ + if (!penv) { + pr_err("%s: penv is NULL!\n", __func__); + return; + } + + pm_qos_remove_request(&penv->qos_request); +} +EXPORT_SYMBOL(cnss_remove_pm_qos); + +void cnss_pci_request_pm_qos_type(int latency_type, u32 qos_val) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_add_request(&penv->qos_request, latency_type, qos_val); +} +EXPORT_SYMBOL(cnss_pci_request_pm_qos_type); + +void cnss_pci_request_pm_qos(u32 qos_val) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_add_request(&penv->qos_request, PM_QOS_CPU_DMA_LATENCY, qos_val); +} +EXPORT_SYMBOL(cnss_pci_request_pm_qos); + +void cnss_pci_remove_pm_qos(void) +{ + if (!penv) { + pr_err("%s: penv is NULL\n", __func__); + return; + } + + pm_qos_remove_request(&penv->qos_request); +} +EXPORT_SYMBOL(cnss_pci_remove_pm_qos); + +int cnss_pci_request_bus_bandwidth(int bandwidth) +{ + int ret = 0; + + if (!penv) + return -ENODEV; + + if (!penv->bus_client) + return -ENOSYS; + + switch (bandwidth) { + case CNSS_BUS_WIDTH_NONE: + case CNSS_BUS_WIDTH_LOW: + case CNSS_BUS_WIDTH_MEDIUM: + case CNSS_BUS_WIDTH_HIGH: + ret = msm_bus_scale_client_update_request( + penv->bus_client, bandwidth); + if (!ret) { + penv->current_bandwidth_vote = bandwidth; + } else { + pr_err("%s: could not set bus bandwidth %d, ret = %d\n", + __func__, bandwidth, ret); + } + break; + + default: + pr_err("%s: Invalid request %d", __func__, bandwidth); + ret = -EINVAL; + } + return ret; +} + +int cnss_request_bus_bandwidth(int bandwidth) +{ + int ret = 0; + + if (!penv) + return -ENODEV; + + if (!penv->bus_client) + return -ENOSYS; + + switch (bandwidth) { + case CNSS_BUS_WIDTH_NONE: + case CNSS_BUS_WIDTH_LOW: + case CNSS_BUS_WIDTH_MEDIUM: + case CNSS_BUS_WIDTH_HIGH: + ret = msm_bus_scale_client_update_request(penv->bus_client, + bandwidth); + if (!ret) { + penv->current_bandwidth_vote = bandwidth; + } else { + pr_err("%s: could not set bus bandwidth %d, ret = %d\n", + __func__, bandwidth, ret); + } + break; + + default: + pr_err("%s: Invalid request %d", __func__, bandwidth); + ret = -EINVAL; + } + return ret; +} +EXPORT_SYMBOL(cnss_request_bus_bandwidth); + +int cnss_get_platform_cap(struct cnss_platform_cap *cap) +{ + if (!penv) + return -ENODEV; + + if (cap) + *cap = penv->cap; + + return 0; +} +EXPORT_SYMBOL(cnss_get_platform_cap); + +void cnss_set_driver_status(enum cnss_driver_status driver_status) +{ + penv->driver_status = driver_status; +} +EXPORT_SYMBOL(cnss_set_driver_status); + +int cnss_get_bmi_setup(void) +{ + if (!penv) + return -ENODEV; + + return penv->bmi_test; +} +EXPORT_SYMBOL(cnss_get_bmi_setup); + +#ifdef CONFIG_CNSS_SECURE_FW +int cnss_get_sha_hash(const u8 *data, u32 data_len, u8 *hash_idx, u8 *out) +{ + struct scatterlist sg; + struct hash_desc desc; + int ret = 0; + + if (!out) { + pr_err("memory for output buffer is not allocated\n"); + ret = -EINVAL; + goto end; + } + + desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP; + desc.tfm = crypto_alloc_hash(hash_idx, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc.tfm)) { + pr_err("crypto_alloc_hash failed:%ld\n", PTR_ERR(desc.tfm)); + ret = PTR_ERR(desc.tfm); + goto end; + } + + sg_init_one(&sg, data, data_len); + ret = crypto_hash_digest(&desc, &sg, sg.length, out); + crypto_free_hash(desc.tfm); +end: + return ret; +} +EXPORT_SYMBOL(cnss_get_sha_hash); + +void *cnss_get_fw_ptr(void) +{ + if (!penv) + return NULL; + + return penv->fw_mem; +} +EXPORT_SYMBOL(cnss_get_fw_ptr); +#endif + +int cnss_auto_suspend(void) +{ + int ret = 0; + struct pci_dev *pdev; + + if (!penv || !penv->driver) + return -ENODEV; + + pdev = penv->pdev; + + if (penv->pcie_link_state) { + pci_save_state(pdev); + penv->saved_state = cnss_pci_store_saved_state(pdev); + pci_disable_device(pdev); + ret = pci_set_power_state(pdev, PCI_D3hot); + if (ret) + pr_err("%s: Set D3Hot failed: %d\n", __func__, ret); + if (cnss_msm_pcie_pm_control( + MSM_PCIE_SUSPEND, + cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS)) { + pr_err("%s: Failed to shutdown PCIe link\n", __func__); + ret = -EAGAIN; + goto out; + } + } + atomic_set(&penv->auto_suspended, 1); + penv->monitor_wake_intr = true; + penv->pcie_link_state = PCIE_LINK_DOWN; + + msm_bus_scale_client_update_request(penv->bus_client, + CNSS_BUS_WIDTH_NONE); +out: + return ret; +} +EXPORT_SYMBOL(cnss_auto_suspend); + +int cnss_auto_resume(void) +{ + int ret = 0; + struct pci_dev *pdev; + + if (!penv || !penv->driver) + return -ENODEV; + + pdev = penv->pdev; + if (!penv->pcie_link_state) { + if (cnss_msm_pcie_pm_control( + MSM_PCIE_RESUME, cnss_get_pci_dev_bus_number(pdev), + pdev, PM_OPTIONS)) { + pr_err("%s: Failed to resume PCIe link\n", __func__); + ret = -EAGAIN; + goto out; + } + ret = pci_enable_device(pdev); + if (ret) + pr_err("%s: enable device failed: %d\n", __func__, ret); + penv->pcie_link_state = PCIE_LINK_UP; + } + + if (penv->saved_state) + cnss_pci_load_and_free_saved_state(pdev, &penv->saved_state); + + pci_restore_state(pdev); + pci_set_master(pdev); + + atomic_set(&penv->auto_suspended, 0); + + msm_bus_scale_client_update_request(penv->bus_client, + penv->current_bandwidth_vote); +out: + return ret; +} +EXPORT_SYMBOL(cnss_auto_resume); + +int cnss_pm_runtime_request(struct device *dev, + enum cnss_runtime_request request) +{ + int ret = 0; + + switch (request) { + case CNSS_PM_RUNTIME_GET: + ret = pm_runtime_get(dev); + break; + case CNSS_PM_RUNTIME_PUT: + ret = pm_runtime_put(dev); + break; + case CNSS_PM_RUNTIME_MARK_LAST_BUSY: + pm_runtime_mark_last_busy(dev); + break; + case CNSS_PM_RUNTIME_RESUME: + ret = pm_runtime_resume(dev); + break; + case CNSS_PM_RUNTIME_PUT_AUTO: + ret = pm_runtime_put_autosuspend(dev); + break; + case CNSS_PM_RUNTIME_PUT_NOIDLE: + pm_runtime_put_noidle(dev); + break; + case CNSS_PM_REQUEST_RESUME: + ret = pm_request_resume(dev); + break; + case CNSS_PM_GET_NORESUME: + pm_runtime_get_noresume(dev); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL(cnss_pm_runtime_request); + +void cnss_runtime_init(struct device *dev, int auto_delay) +{ + pm_runtime_set_autosuspend_delay(dev, auto_delay); + pm_runtime_use_autosuspend(dev); + pm_runtime_allow(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_noidle(dev); + pm_suspend_ignore_children(dev, true); +} +EXPORT_SYMBOL(cnss_runtime_init); + +void cnss_runtime_exit(struct device *dev) +{ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); +} +EXPORT_SYMBOL(cnss_runtime_exit); + +static void __cnss_set_pcie_monitor_intr(struct device *dev, bool val) +{ + penv->monitor_wake_intr = val; +} + +static void __cnss_set_auto_suspend(struct device *dev, int val) +{ + atomic_set(&penv->auto_suspended, val); +} + +static int __cnss_resume_link(struct device *dev, u32 flags) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + u8 bus_num = cnss_get_pci_dev_bus_number(pdev); + + ret = cnss_msm_pcie_pm_control(MSM_PCIE_RESUME, bus_num, pdev, flags); + if (ret) + pr_err("%s: PCIe link resume failed with flags:%d bus_num:%d\n", + __func__, flags, bus_num); + + penv->pcie_link_state = PCIE_LINK_UP; + + return ret; +} + +static int __cnss_suspend_link(struct device *dev, u32 flags) +{ + struct pci_dev *pdev = to_pci_dev(dev); + u8 bus_num = cnss_get_pci_dev_bus_number(pdev); + int ret; + + if (!penv->pcie_link_state) + return 0; + + ret = cnss_msm_pcie_pm_control(MSM_PCIE_SUSPEND, bus_num, pdev, flags); + if (ret) { + pr_err("%s: Failed to suspend link\n", __func__); + return ret; + } + + penv->pcie_link_state = PCIE_LINK_DOWN; + + return ret; +} + +static int __cnss_pcie_recover_config(struct device *dev) +{ + int ret; + + ret = cnss_msm_pcie_recover_config(to_pci_dev(dev)); + if (ret) + pr_err("%s: PCIe Recover config failed\n", __func__); + + return ret; +} + +static int __cnss_event_reg(struct device *dev) +{ + int ret; + struct msm_pcie_register_event *event_reg; + + event_reg = &penv->event_reg; + + event_reg->events = MSM_PCIE_EVENT_LINKDOWN | + MSM_PCIE_EVENT_WAKEUP; + event_reg->user = to_pci_dev(dev); + event_reg->mode = MSM_PCIE_TRIGGER_CALLBACK; + event_reg->callback = cnss_pci_events_cb; + event_reg->options = MSM_PCIE_CONFIG_NO_RECOVERY; + + ret = cnss_msm_pcie_register_event(event_reg); + if (ret) + pr_err("%s: PCIe event register failed! %d\n", __func__, ret); + + return ret; +} + +static void __cnss_event_dereg(struct device *dev) +{ + cnss_msm_pcie_deregister_event(&penv->event_reg); +} + +static struct pci_dev *__cnss_get_pcie_dev(struct device *dev) +{ + int ret; + struct pci_dev *pdev = penv->pdev; + + if (pdev) + return pdev; + + ret = pci_register_driver(&cnss_wlan_pci_driver); + if (ret) { + pr_err("%s: pci re-registration failed\n", __func__); + return NULL; + } + + pdev = penv->pdev; + + return pdev; +} + +static int __cnss_pcie_power_up(struct device *dev) +{ + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + int ret; + + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + + ret = cnss_wlan_vreg_set(vreg_info, VREG_ON); + if (ret) { + pr_err("%s: WLAN VREG ON Failed\n", __func__); + return ret; + } + + msleep(POWER_ON_DELAY); + + if (penv->wlan_bootstrap_gpio > 0) { + gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_HIGH); + msleep(WLAN_BOOTSTRAP_DELAY); + } + + cnss_configure_wlan_en_gpio(WLAN_EN_HIGH); + return 0; +} + +static int __cnss_pcie_power_down(struct device *dev) +{ + struct cnss_wlan_vreg_info *vreg_info; + struct cnss_wlan_gpio_info *gpio_info; + int ret; + + vreg_info = &penv->vreg_info; + gpio_info = &penv->gpio_info; + + cnss_configure_wlan_en_gpio(WLAN_EN_LOW); + if (penv->wlan_bootstrap_gpio > 0) + gpio_set_value(penv->wlan_bootstrap_gpio, WLAN_BOOTSTRAP_LOW); + + ret = cnss_wlan_vreg_set(vreg_info, VREG_OFF); + if (ret) + pr_err("%s: Failed to turn off 3.3V regulator\n", __func__); + + return ret; +} + +static int __cnss_suspend_link_state(struct device *dev) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + int link_ind; + + if (!penv->pcie_link_state) { + pr_debug("%s: Link is already suspended\n", __func__); + return 0; + } + + link_ind = penv->pcie_link_down_ind; + + if (!link_ind) + pci_save_state(pdev); + + penv->saved_state = link_ind ? NULL : cnss_pci_store_saved_state(pdev); + + ret = link_ind ? __cnss_suspend_link(dev, PM_OPTIONS_SUSPEND_LINK_DOWN) + : __cnss_suspend_link(dev, PM_OPTIONS); + if (ret) { + pr_err("%s: Link Suspend failed in state:%s\n", __func__, + link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); + return ret; + } + + penv->pcie_link_state = PCIE_LINK_DOWN; + + return 0; +} + +static int __cnss_restore_pci_config_space(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int ret = 0; + + if (penv->saved_state) + ret = cnss_pci_load_and_free_saved_state(pdev, + &penv->saved_state); + pci_restore_state(pdev); + + return ret; +} + +static int __cnss_resume_link_state(struct device *dev) +{ + int ret; + int link_ind; + + if (penv->pcie_link_state) { + pr_debug("%s: Link is already in active state\n", __func__); + return 0; + } + + link_ind = penv->pcie_link_down_ind; + + ret = link_ind ? __cnss_resume_link(dev, PM_OPTIONS_RESUME_LINK_DOWN) : + __cnss_resume_link(dev, PM_OPTIONS); + + if (ret) { + pr_err("%s: Resume Link failed in link state:%s\n", __func__, + link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); + return ret; + } + + penv->pcie_link_state = PCIE_LINK_UP; + + ret = link_ind ? __cnss_pcie_recover_config(dev) : + __cnss_restore_pci_config_space(dev); + + if (ret) { + pr_err("%s: Link Recovery Config Failed link_state:%s\n", + __func__, link_ind ? "LINK_DOWN" : "LINK_ACTIVE"); + penv->pcie_link_state = PCIE_LINK_DOWN; + return ret; + } + + penv->pcie_link_down_ind = false; + return ret; +} + +int cnss_pcie_power_up(struct device *dev) +{ + int ret; + struct pci_dev *pdev; + + if (!penv) { + pr_err("%s: platform data is NULL\n", __func__); + return -ENODEV; + } + + ret = __cnss_pcie_power_up(dev); + if (ret) { + pr_err("%s: Power UP Failed\n", __func__); + return ret; + } + + pdev = __cnss_get_pcie_dev(dev); + if (!pdev) { + pr_err("%s: PCIe Dev is NULL\n", __func__); + goto power_down; + } + + ret = __cnss_event_reg(dev); + + if (ret) + pr_err("%s: PCIe event registration failed\n", __func__); + + ret = __cnss_resume_link_state(dev); + + if (ret) { + pr_err("%s: Link Bring Up Failed\n", __func__); + goto event_dereg; + } + + __cnss_set_pcie_monitor_intr(dev, true); + + return ret; + +event_dereg: + __cnss_event_dereg(dev); +power_down: + __cnss_pcie_power_down(dev); + pr_err("%s: Device Power Up Failed Fatal Error!\n", __func__); + return ret; +} + +static void __cnss_vote_bus_width(struct device *dev, uint32_t option) +{ + if (penv->bus_client) + msm_bus_scale_client_update_request(penv->bus_client, option); +} + +int cnss_pcie_power_down(struct device *dev) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + + if (!penv) { + pr_err("%s: Invalid Platform data\n", __func__); + return -ENODEV; + } + + if (!pdev) { + pr_err("%s: Invalid Pdev, Cut Power to device\n", __func__); + __cnss_pcie_power_down(dev); + return -ENODEV; + } + + __cnss_vote_bus_width(dev, CNSS_BUS_WIDTH_NONE); + __cnss_event_dereg(dev); + + ret = __cnss_suspend_link_state(dev); + + if (ret) { + pr_err("%s: Suspend Link failed\n", __func__); + return ret; + } + + __cnss_set_pcie_monitor_intr(dev, false); + __cnss_set_auto_suspend(dev, 0); + + ret = __cnss_pcie_power_down(dev); + if (ret) + pr_err("%s: Power Down Failed\n", __func__); + + return ret; +} + +module_init(cnss_initialize); +module_exit(cnss_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DEVICE "CNSS Driver"); diff --git a/drivers/net/wireless/cnss/cnss_sdio.c b/drivers/net/wireless/cnss/cnss_sdio.c new file mode 100644 index 000000000000..8586bb16cfd3 --- /dev/null +++ b/drivers/net/wireless/cnss/cnss_sdio.c @@ -0,0 +1,1384 @@ +/* Copyright (c) 2015-2017, 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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> +#include <linux/mmc/card.h> +#include <linux/mmc/host.h> +#include <linux/io.h> +#include <soc/qcom/subsystem_restart.h> +#include <soc/qcom/subsystem_notif.h> +#include <soc/qcom/ramdump.h> +#include <soc/qcom/memory_dump.h> +#include <net/cnss.h> +#include "cnss_common.h" +#include <linux/pm_qos.h> +#include <linux/msm-bus.h> +#include <linux/msm-bus-board.h> + +#define WLAN_VREG_NAME "vdd-wlan" +#define WLAN_VREG_DSRC_NAME "vdd-wlan-dsrc" +#define WLAN_VREG_IO_NAME "vdd-wlan-io" +#define WLAN_VREG_XTAL_NAME "vdd-wlan-xtal" + +#define WLAN_VREG_IO_MAX 1800000 +#define WLAN_VREG_IO_MIN 1800000 +#define WLAN_VREG_XTAL_MAX 1800000 +#define WLAN_VREG_XTAL_MIN 1800000 +#define POWER_ON_DELAY 4 + +/* Values for Dynamic Ramdump Collection*/ +#define CNSS_DUMP_FORMAT_VER 0x11 +#define CNSS_DUMP_MAGIC_VER_V2 0x42445953 +#define CNSS_DUMP_NAME "CNSS_WLAN_SDIO" +#define CNSS_PINCTRL_SLEEP_STATE "sleep" +#define CNSS_PINCTRL_ACTIVE_STATE "active" + +struct cnss_sdio_regulator { + struct regulator *wlan_io; + struct regulator *wlan_xtal; + struct regulator *wlan_vreg; + struct regulator *wlan_vreg_dsrc; +}; + +struct cnss_sdio_info { + struct cnss_sdio_wlan_driver *wdrv; + struct sdio_func *func; + struct mmc_card *card; + struct mmc_host *host; + struct device *dev; + const struct sdio_device_id *id; + bool skip_wlan_en_toggle; +}; + +struct cnss_ssr_info { + struct subsys_device *subsys; + struct subsys_desc subsysdesc; + void *subsys_handle; + struct ramdump_device *ramdump_dev; + unsigned long ramdump_size; + void *ramdump_addr; + phys_addr_t ramdump_phys; + struct msm_dump_data dump_data; + bool ramdump_dynamic; + char subsys_name[10]; +}; + +struct cnss_wlan_pinctrl_info { + bool is_antenna_shared; + struct pinctrl *pinctrl; + struct pinctrl_state *sleep; + struct pinctrl_state *active; +}; + +struct cnss_sdio_bus_bandwidth { + struct msm_bus_scale_pdata *bus_scale_table; + u32 bus_client; + int current_bandwidth_vote; +}; + +static struct cnss_sdio_data { + struct cnss_sdio_regulator regulator; + struct platform_device *pdev; + struct cnss_sdio_info cnss_sdio_info; + struct cnss_ssr_info ssr_info; + struct pm_qos_request qos_request; + struct cnss_wlan_pinctrl_info pinctrl_info; + struct cnss_sdio_bus_bandwidth bus_bandwidth; +} *cnss_pdata; + +#define WLAN_RECOVERY_DELAY 1 +/* cnss sdio subsytem device name, required property */ +#define CNSS_SUBSYS_NAME_KEY "subsys-name" + +/* SDIO manufacturer ID and Codes */ +#define MANUFACTURER_ID_AR6320_BASE 0x500 +#define MANUFACTURER_ID_QCA9377_BASE 0x700 +#define MANUFACTURER_CODE 0x271 + +static const struct sdio_device_id ar6k_id_table[] = { + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x0))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x1))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x2))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x3))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x4))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x5))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x6))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x7))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x8))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x9))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xA))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xB))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xC))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xD))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xE))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xF))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x0))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x1))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x2))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x3))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x4))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x5))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x6))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x7))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x8))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x9))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xA))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xB))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xC))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xD))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xE))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xF))}, + {}, +}; +MODULE_DEVICE_TABLE(sdio, ar6k_id_table); + +void cnss_sdio_request_pm_qos_type(int latency_type, u32 qos_val) +{ + if (!cnss_pdata) + return; + + pr_debug("%s: PM QoS value: %d\n", __func__, qos_val); + pm_qos_add_request(&cnss_pdata->qos_request, latency_type, qos_val); +} +EXPORT_SYMBOL(cnss_sdio_request_pm_qos_type); + +int cnss_sdio_request_bus_bandwidth(int bandwidth) +{ + int ret; + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + + if (!cnss_pdata) + return -ENODEV; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (!bus_bandwidth->bus_client) + return -ENODEV; + + switch (bandwidth) { + case CNSS_BUS_WIDTH_NONE: + case CNSS_BUS_WIDTH_LOW: + case CNSS_BUS_WIDTH_MEDIUM: + case CNSS_BUS_WIDTH_HIGH: + ret = msm_bus_scale_client_update_request( + bus_bandwidth->bus_client, bandwidth); + if (!ret) { + bus_bandwidth->current_bandwidth_vote = bandwidth; + } else { + pr_debug( + "%s: could not set bus bandwidth %d, ret = %d\n", + __func__, bandwidth, ret); + } + break; + default: + pr_debug("%s: Invalid request %d", __func__, bandwidth); + ret = -EINVAL; + } + + return ret; +} + +void cnss_sdio_request_pm_qos(u32 qos_val) +{ + if (!cnss_pdata) + return; + + pr_debug("%s: PM QoS value: %d\n", __func__, qos_val); + pm_qos_add_request( + &cnss_pdata->qos_request, + PM_QOS_CPU_DMA_LATENCY, qos_val); +} +EXPORT_SYMBOL(cnss_sdio_request_pm_qos); + +void cnss_sdio_remove_pm_qos(void) +{ + if (!cnss_pdata) + return; + + pm_qos_remove_request(&cnss_pdata->qos_request); + pr_debug("%s: PM QoS removed\n", __func__); +} +EXPORT_SYMBOL(cnss_sdio_remove_pm_qos); + +static int cnss_put_hw_resources(struct device *dev) +{ + int ret = -EINVAL; + struct cnss_sdio_info *info; + struct mmc_host *host; + + if (!cnss_pdata) + return ret; + + info = &cnss_pdata->cnss_sdio_info; + + if (info->skip_wlan_en_toggle) { + pr_debug("%s: HW doesn't support wlan toggling\n", __func__); + return 0; + } + + host = info->host; + + if (!host) { + pr_err("%s: MMC host is invalid\n", __func__); + return 0; + } + + ret = mmc_power_save_host(host); + if (ret) { + pr_err("%s: Failed to Power Save Host err:%d\n", __func__, + ret); + return ret; + } + + if (!cnss_pdata->regulator.wlan_vreg) { + pr_debug("%s: wlan_vreg regulator is invalid\n", __func__); + return ret; + } + + regulator_disable(cnss_pdata->regulator.wlan_vreg); + + return ret; +} + +static int cnss_get_hw_resources(struct device *dev) +{ + int ret = -EINVAL; + struct mmc_host *host; + struct cnss_sdio_info *info; + + if (!cnss_pdata) + return ret; + + info = &cnss_pdata->cnss_sdio_info; + + if (info->skip_wlan_en_toggle) { + pr_debug("%s: HW doesn't support wlan toggling\n", __func__); + return 0; + } + + host = info->host; + + if (!host) { + pr_err("%s: MMC host is invalid\n", __func__); + return -ENODEV; + } + + ret = regulator_enable(cnss_pdata->regulator.wlan_vreg); + if (ret) { + pr_err("%s: Failed to enable wlan vreg\n", __func__); + return ret; + } + + ret = mmc_power_restore_host(host); + if (ret) { + pr_err("%s: Failed to restore host power ret:%d\n", __func__, + ret); + regulator_disable(cnss_pdata->regulator.wlan_vreg); + } + + return ret; +} + +static int cnss_sdio_shutdown(const struct subsys_desc *subsys, bool force_stop) +{ + struct cnss_sdio_info *cnss_info; + struct cnss_sdio_wlan_driver *wdrv; + int ret = 0; + + if (!cnss_pdata) + return -ENODEV; + + cnss_info = &cnss_pdata->cnss_sdio_info; + wdrv = cnss_info->wdrv; + if (!wdrv) + return 0; + if (!wdrv->shutdown) + return 0; + + wdrv->shutdown(cnss_info->func); + ret = cnss_put_hw_resources(cnss_info->dev); + + if (ret) + pr_err("%s: Failed to put hw resources\n", __func__); + + return ret; +} + +static int cnss_sdio_powerup(const struct subsys_desc *subsys) +{ + struct cnss_sdio_info *cnss_info; + struct cnss_sdio_wlan_driver *wdrv; + int ret = 0; + + if (!cnss_pdata) + return -ENODEV; + + cnss_info = &cnss_pdata->cnss_sdio_info; + wdrv = cnss_info->wdrv; + + if (!wdrv) + return 0; + + if (!wdrv->reinit) + return 0; + + ret = cnss_get_hw_resources(cnss_info->dev); + if (ret) { + pr_err("%s: Failed to power up HW\n", __func__); + return ret; + } + + ret = wdrv->reinit(cnss_info->func, cnss_info->id); + if (ret) + pr_err("%s: wlan reinit error=%d\n", __func__, ret); + + return ret; +} + +static void cnss_sdio_crash_shutdown(const struct subsys_desc *subsys) +{ + struct cnss_sdio_info *cnss_info; + struct cnss_sdio_wlan_driver *wdrv; + + if (!cnss_pdata) + return; + + cnss_info = &cnss_pdata->cnss_sdio_info; + wdrv = cnss_info->wdrv; + if (wdrv && wdrv->crash_shutdown) + wdrv->crash_shutdown(cnss_info->func); +} + +static int cnss_sdio_ramdump(int enable, const struct subsys_desc *subsys) +{ + struct cnss_ssr_info *ssr_info; + struct ramdump_segment segment; + int ret; + + if (!cnss_pdata) + return -ENODEV; + + if (!cnss_pdata->ssr_info.ramdump_size) + return -ENOENT; + + if (!enable) + return 0; + + ssr_info = &cnss_pdata->ssr_info; + + memset(&segment, 0, sizeof(segment)); + segment.v_address = ssr_info->ramdump_addr; + segment.size = ssr_info->ramdump_size; + ret = do_ramdump(ssr_info->ramdump_dev, &segment, 1); + if (ret) + pr_err("%s: do_ramdump failed error=%d\n", __func__, ret); + return ret; +} + +static int cnss_subsys_init(void) +{ + struct cnss_ssr_info *ssr_info; + int ret = 0; + + if (!cnss_pdata) + return -ENODEV; + + ssr_info = &cnss_pdata->ssr_info; + ssr_info->subsysdesc.name = ssr_info->subsys_name; + ssr_info->subsysdesc.owner = THIS_MODULE; + ssr_info->subsysdesc.shutdown = cnss_sdio_shutdown; + ssr_info->subsysdesc.powerup = cnss_sdio_powerup; + ssr_info->subsysdesc.ramdump = cnss_sdio_ramdump; + ssr_info->subsysdesc.crash_shutdown = cnss_sdio_crash_shutdown; + ssr_info->subsysdesc.dev = &cnss_pdata->pdev->dev; + ssr_info->subsys = subsys_register(&ssr_info->subsysdesc); + if (IS_ERR(ssr_info->subsys)) { + ret = PTR_ERR(ssr_info->subsys); + ssr_info->subsys = NULL; + dev_err(&cnss_pdata->pdev->dev, "Failed to subsys_register error=%d\n", + ret); + goto err_subsys_reg; + } + ssr_info->subsys_handle = subsystem_get(ssr_info->subsysdesc.name); + if (IS_ERR(ssr_info->subsys_handle)) { + ret = PTR_ERR(ssr_info->subsys_handle); + ssr_info->subsys_handle = NULL; + dev_err(&cnss_pdata->pdev->dev, "Failed to subsystem_get error=%d\n", + ret); + goto err_subsys_get; + } + return 0; +err_subsys_get: + subsys_unregister(ssr_info->subsys); + ssr_info->subsys = NULL; +err_subsys_reg: + return ret; +} + +static void cnss_subsys_exit(void) +{ + struct cnss_ssr_info *ssr_info; + + if (!cnss_pdata) + return; + + ssr_info = &cnss_pdata->ssr_info; + if (ssr_info->subsys_handle) + subsystem_put(ssr_info->subsys_handle); + ssr_info->subsys_handle = NULL; + if (ssr_info->subsys) + subsys_unregister(ssr_info->subsys); + ssr_info->subsys = NULL; +} + +static int cnss_configure_dump_table(struct cnss_ssr_info *ssr_info) +{ + struct msm_dump_entry dump_entry; + int ret; + + ssr_info->dump_data.addr = ssr_info->ramdump_phys; + ssr_info->dump_data.len = ssr_info->ramdump_size; + ssr_info->dump_data.version = CNSS_DUMP_FORMAT_VER; + ssr_info->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2; + strlcpy(ssr_info->dump_data.name, CNSS_DUMP_NAME, + sizeof(ssr_info->dump_data.name)); + + dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; + dump_entry.addr = virt_to_phys(&ssr_info->dump_data); + + ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); + if (ret) + pr_err("%s: Dump table setup failed: %d\n", __func__, ret); + + return ret; +} + +static int cnss_configure_ramdump(void) +{ + struct cnss_ssr_info *ssr_info; + int ret = 0; + struct resource *res; + const char *name; + u32 ramdump_size = 0; + struct device *dev; + + if (!cnss_pdata) + return -ENODEV; + + dev = &cnss_pdata->pdev->dev; + + ssr_info = &cnss_pdata->ssr_info; + + ret = of_property_read_string(dev->of_node, CNSS_SUBSYS_NAME_KEY, + &name); + if (ret) { + pr_err("%s: cnss missing DT key '%s'\n", __func__, + CNSS_SUBSYS_NAME_KEY); + ret = -ENODEV; + goto err_subsys_name_query; + } + + strlcpy(ssr_info->subsys_name, name, sizeof(ssr_info->subsys_name)); + + if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", + &ramdump_size) == 0) { + ssr_info->ramdump_addr = dma_alloc_coherent(dev, ramdump_size, + &ssr_info->ramdump_phys, + GFP_KERNEL); + if (ssr_info->ramdump_addr) + ssr_info->ramdump_size = ramdump_size; + ssr_info->ramdump_dynamic = true; + } else { + res = platform_get_resource_byname(cnss_pdata->pdev, + IORESOURCE_MEM, "ramdump"); + if (res) { + ssr_info->ramdump_phys = res->start; + ramdump_size = resource_size(res); + ssr_info->ramdump_addr = ioremap(ssr_info->ramdump_phys, + ramdump_size); + if (ssr_info->ramdump_addr) + ssr_info->ramdump_size = ramdump_size; + ssr_info->ramdump_dynamic = false; + } + } + + pr_info("%s: ramdump addr: %p, phys: %pa subsys:'%s'\n", __func__, + ssr_info->ramdump_addr, &ssr_info->ramdump_phys, + ssr_info->subsys_name); + + if (ssr_info->ramdump_size == 0) { + pr_info("%s: CNSS ramdump will not be collected", __func__); + return 0; + } + + if (ssr_info->ramdump_dynamic) { + ret = cnss_configure_dump_table(ssr_info); + if (ret) + goto err_configure_dump_table; + } + + ssr_info->ramdump_dev = create_ramdump_device(ssr_info->subsys_name, + dev); + if (!ssr_info->ramdump_dev) { + ret = -ENOMEM; + pr_err("%s: ramdump dev create failed: error=%d\n", + __func__, ret); + goto err_configure_dump_table; + } + + return 0; + +err_configure_dump_table: + if (ssr_info->ramdump_dynamic) + dma_free_coherent(dev, ssr_info->ramdump_size, + ssr_info->ramdump_addr, + ssr_info->ramdump_phys); + else + iounmap(ssr_info->ramdump_addr); + + ssr_info->ramdump_addr = NULL; + ssr_info->ramdump_size = 0; +err_subsys_name_query: + return ret; +} + +static void cnss_ramdump_cleanup(void) +{ + struct cnss_ssr_info *ssr_info; + struct device *dev; + + if (!cnss_pdata) + return; + + dev = &cnss_pdata->pdev->dev; + ssr_info = &cnss_pdata->ssr_info; + if (ssr_info->ramdump_addr) { + if (ssr_info->ramdump_dynamic) + dma_free_coherent(dev, ssr_info->ramdump_size, + ssr_info->ramdump_addr, + ssr_info->ramdump_phys); + else + iounmap(ssr_info->ramdump_addr); + } + + ssr_info->ramdump_addr = NULL; + if (ssr_info->ramdump_dev) + destroy_ramdump_device(ssr_info->ramdump_dev); + ssr_info->ramdump_dev = NULL; +} + +void *cnss_sdio_get_virt_ramdump_mem(unsigned long *size) +{ + if (!cnss_pdata || !cnss_pdata->pdev) + return NULL; + + *size = cnss_pdata->ssr_info.ramdump_size; + + return cnss_pdata->ssr_info.ramdump_addr; +} + +void cnss_sdio_device_self_recovery(void) +{ + cnss_sdio_shutdown(NULL, false); + msleep(WLAN_RECOVERY_DELAY); + cnss_sdio_powerup(NULL); +} + +void cnss_sdio_device_crashed(void) +{ + struct cnss_ssr_info *ssr_info; + + if (!cnss_pdata) + return; + ssr_info = &cnss_pdata->ssr_info; + if (ssr_info->subsys) { + subsys_set_crash_status(ssr_info->subsys, + CRASH_STATUS_ERR_FATAL); + subsystem_restart_dev(ssr_info->subsys); + } +} + +static void cnss_sdio_recovery_work_handler(struct work_struct *recovery) +{ + cnss_sdio_device_self_recovery(); +} + +DECLARE_WORK(cnss_sdio_recovery_work, cnss_sdio_recovery_work_handler); + +void cnss_sdio_schedule_recovery_work(void) +{ + schedule_work(&cnss_sdio_recovery_work); +} + +/** + * cnss_get_restart_level() - cnss get restart level API + * + * Wlan sdio function driver uses this API to get the current + * subsystem restart level. + * + * Return: CNSS_RESET_SOC - "SYSTEM", restart system + * CNSS_RESET_SUBSYS_COUPLED - "RELATED",restart subsystem + */ +int cnss_get_restart_level(void) +{ + struct cnss_ssr_info *ssr_info; + int level; + + if (!cnss_pdata) + return CNSS_RESET_SOC; + ssr_info = &cnss_pdata->ssr_info; + if (!ssr_info->subsys) + return CNSS_RESET_SOC; + level = subsys_get_restart_level(ssr_info->subsys); + switch (level) { + case RESET_SOC: + return CNSS_RESET_SOC; + case RESET_SUBSYS_COUPLED: + return CNSS_RESET_SUBSYS_COUPLED; + default: + return CNSS_RESET_SOC; + } +} +EXPORT_SYMBOL(cnss_get_restart_level); + +static int cnss_sdio_wlan_inserted(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct cnss_sdio_info *info; + + if (!cnss_pdata) + return -ENODEV; + + info = &cnss_pdata->cnss_sdio_info; + + info->func = func; + info->card = func->card; + info->host = func->card->host; + info->id = id; + info->dev = &func->dev; + + cnss_put_hw_resources(cnss_pdata->cnss_sdio_info.dev); + + pr_info("%s: SDIO Device is Probed\n", __func__); + return 0; +} + +static void cnss_sdio_wlan_removed(struct sdio_func *func) +{ + struct cnss_sdio_info *info; + + if (!cnss_pdata) + return; + + info = &cnss_pdata->cnss_sdio_info; + + info->host = NULL; + info->card = NULL; + info->func = NULL; + info->id = NULL; +} + +#if defined(CONFIG_PM) +static int cnss_sdio_wlan_suspend(struct device *dev) +{ + struct cnss_sdio_wlan_driver *wdrv; + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + struct sdio_func *func; + + int error = 0; + + if (!cnss_pdata) + return -ENODEV; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (bus_bandwidth->bus_client) { + msm_bus_scale_client_update_request( + bus_bandwidth->bus_client, CNSS_BUS_WIDTH_NONE); + } + + func = cnss_pdata->cnss_sdio_info.func; + wdrv = cnss_pdata->cnss_sdio_info.wdrv; + if (!wdrv) { + /* This can happen when no wlan driver loaded (no register to + * platform driver). + */ + sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + pr_debug("wlan driver not registered\n"); + return 0; + } + if (wdrv->suspend) { + error = wdrv->suspend(dev); + if (error) + pr_err("wlan suspend failed error=%d\n", error); + } + + return error; +} + +static int cnss_sdio_wlan_resume(struct device *dev) +{ + struct cnss_sdio_wlan_driver *wdrv; + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + int error = 0; + + if (!cnss_pdata) + return -ENODEV; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (bus_bandwidth->bus_client) { + msm_bus_scale_client_update_request( + bus_bandwidth->bus_client, + bus_bandwidth->current_bandwidth_vote); + } + + wdrv = cnss_pdata->cnss_sdio_info.wdrv; + if (!wdrv) { + /* This can happen when no wlan driver loaded (no register to + * platform driver). + */ + pr_debug("wlan driver not registered\n"); + return 0; + } + if (wdrv->resume) { + error = wdrv->resume(dev); + if (error) + pr_err("wlan resume failed error=%d\n", error); + } + return error; +} +#endif + +#if defined(CONFIG_PM) +static const struct dev_pm_ops cnss_ar6k_device_pm_ops = { + .suspend = cnss_sdio_wlan_suspend, + .resume = cnss_sdio_wlan_resume, +}; +#endif /* CONFIG_PM */ + +static struct sdio_driver cnss_ar6k_driver = { + .name = "cnss_ar6k_wlan", + .id_table = ar6k_id_table, + .probe = cnss_sdio_wlan_inserted, + .remove = cnss_sdio_wlan_removed, +#if defined(CONFIG_PM) + .drv = { + .pm = &cnss_ar6k_device_pm_ops, + } +#endif +}; + +static int cnss_set_pinctrl_state(struct cnss_sdio_data *pdata, bool state) +{ + struct cnss_wlan_pinctrl_info *info = &pdata->pinctrl_info; + + if (!info->is_antenna_shared) + return 0; + + if (!info->pinctrl) + return -EIO; + + return state ? pinctrl_select_state(info->pinctrl, info->active) : + pinctrl_select_state(info->pinctrl, info->sleep); +} + +int cnss_sdio_configure_spdt(bool state) +{ + if (!cnss_pdata) + return -ENODEV; + + return cnss_set_pinctrl_state(cnss_pdata, state); +} +EXPORT_SYMBOL(cnss_sdio_configure_spdt); + +/** + * cnss_sdio_wlan_register_driver() - cnss wlan register API + * @driver: sdio wlan driver interface from wlan driver. + * + * wlan sdio function driver uses this API to register callback + * functions to cnss_sido platform driver. The callback will + * be invoked by corresponding wrapper function of this cnss + * platform driver. + */ +int cnss_sdio_wlan_register_driver(struct cnss_sdio_wlan_driver *driver) +{ + struct cnss_sdio_info *cnss_info; + struct device *dev; + int error = -EINVAL; + + if (!cnss_pdata) + return -ENODEV; + + cnss_info = &cnss_pdata->cnss_sdio_info; + dev = cnss_info->dev; + + if (cnss_info->wdrv) + pr_debug("%s:wdrv already exists wdrv(%p)\n", __func__, + cnss_info->wdrv); + + cnss_info->wdrv = driver; + + if (!driver) + return error; + + error = cnss_get_hw_resources(dev); + if (error) { + pr_err("%s: Failed to restore power err:%d\n", __func__, error); + return error; + } + + error = cnss_set_pinctrl_state(cnss_pdata, PINCTRL_ACTIVE); + if (error) { + pr_err("%s: Fail to set pinctrl to active state\n", __func__); + goto put_hw; + } + + error = driver->probe ? driver->probe(cnss_info->func, + cnss_info->id) : error; + if (error) { + pr_err("%s: wlan probe failed error=%d\n", __func__, error); + goto pinctrl_sleep; + } + + return error; + +pinctrl_sleep: + cnss_set_pinctrl_state(cnss_pdata, PINCTRL_SLEEP); +put_hw: + cnss_put_hw_resources(dev); + return error; +} +EXPORT_SYMBOL(cnss_sdio_wlan_register_driver); + +/** + * cnss_sdio_wlan_unregister_driver() - cnss wlan unregister API + * @driver: sdio wlan driver interface from wlan driver. + * + * wlan sdio function driver uses this API to detach it from cnss_sido + * platform driver. + */ +void +cnss_sdio_wlan_unregister_driver(struct cnss_sdio_wlan_driver *driver) +{ + struct cnss_sdio_info *cnss_info; + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + + if (!cnss_pdata) + return; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (bus_bandwidth->bus_client) { + msm_bus_scale_client_update_request( + bus_bandwidth->bus_client, CNSS_BUS_WIDTH_NONE); + } + + cnss_info = &cnss_pdata->cnss_sdio_info; + if (!cnss_info->wdrv) { + pr_err("%s: driver not registered\n", __func__); + return; + } + + if (!driver) + return; + + if (!driver->remove) + return; + + driver->remove(cnss_info->func); + cnss_info->wdrv = NULL; + cnss_set_pinctrl_state(cnss_pdata, PINCTRL_SLEEP); + cnss_put_hw_resources(cnss_info->dev); +} +EXPORT_SYMBOL(cnss_sdio_wlan_unregister_driver); + +/** + * cnss_wlan_query_oob_status() - cnss wlan query oob status API + * + * Wlan sdio function driver uses this API to check whether oob is + * supported in platform driver. + * + * Return: 0 means oob is supported, others means unsupported. + */ +int cnss_wlan_query_oob_status(void) +{ + return -ENOSYS; +} +EXPORT_SYMBOL(cnss_wlan_query_oob_status); + +/** + * cnss_wlan_register_oob_irq_handler() - cnss wlan register oob callback API + * @handler: oob callback function pointer which registered to platform driver. + * @pm_oob : parameter which registered to platform driver. + * + * Wlan sdio function driver uses this API to register oob callback + * function to platform driver. + * + * Return: 0 means register successfully, others means failure. + */ +int cnss_wlan_register_oob_irq_handler(oob_irq_handler_t handler, void *pm_oob) +{ + return -ENOSYS; +} +EXPORT_SYMBOL(cnss_wlan_register_oob_irq_handler); + +/** + * cnss_wlan_unregister_oob_irq_handler() - cnss wlan unregister oob callback API + * @pm_oob: parameter which unregistered from platform driver. + * + * Wlan sdio function driver uses this API to unregister oob callback + * function from platform driver. + * + * Return: 0 means unregister successfully, others means failure. + */ +int cnss_wlan_unregister_oob_irq_handler(void *pm_oob) +{ + return -ENOSYS; +} +EXPORT_SYMBOL(cnss_wlan_unregister_oob_irq_handler); + +static int cnss_sdio_wlan_init(void) +{ + int error = 0; + + error = sdio_register_driver(&cnss_ar6k_driver); + if (error) + pr_err("%s: registered fail error=%d\n", __func__, error); + else + pr_debug("%s: registered succ\n", __func__); + return error; +} + +static void cnss_sdio_wlan_exit(void) +{ + if (!cnss_pdata) + return; + + sdio_unregister_driver(&cnss_ar6k_driver); +} + +static void cnss_sdio_deinit_bus_bandwidth(void) +{ + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + if (bus_bandwidth->bus_client) { + msm_bus_scale_client_update_request( + bus_bandwidth->bus_client, CNSS_BUS_WIDTH_NONE); + msm_bus_scale_unregister_client(bus_bandwidth->bus_client); + } +} + +static int cnss_sdio_configure_wlan_enable_regulator(void) +{ + int error; + struct device *dev = &cnss_pdata->pdev->dev; + + if (of_get_property( + cnss_pdata->pdev->dev.of_node, + WLAN_VREG_NAME "-supply", NULL)) { + cnss_pdata->regulator.wlan_vreg = regulator_get( + &cnss_pdata->pdev->dev, WLAN_VREG_NAME); + if (IS_ERR(cnss_pdata->regulator.wlan_vreg)) { + error = PTR_ERR(cnss_pdata->regulator.wlan_vreg); + dev_err(dev, "VDD-VREG get failed error=%d\n", error); + return error; + } + + error = regulator_enable(cnss_pdata->regulator.wlan_vreg); + if (error) { + dev_err(dev, "VDD-VREG enable failed error=%d\n", + error); + goto err_vdd_vreg_regulator; + } + } + + return 0; + +err_vdd_vreg_regulator: + regulator_put(cnss_pdata->regulator.wlan_vreg); + + return error; +} + +static int cnss_sdio_configure_wlan_enable_dsrc_regulator(void) +{ + int error; + struct device *dev = &cnss_pdata->pdev->dev; + + if (of_get_property( + cnss_pdata->pdev->dev.of_node, + WLAN_VREG_DSRC_NAME "-supply", NULL)) { + cnss_pdata->regulator.wlan_vreg_dsrc = regulator_get( + &cnss_pdata->pdev->dev, WLAN_VREG_DSRC_NAME); + if (IS_ERR(cnss_pdata->regulator.wlan_vreg_dsrc)) { + error = PTR_ERR(cnss_pdata->regulator.wlan_vreg_dsrc); + dev_err(dev, "VDD-VREG-DSRC get failed error=%d\n", + error); + return error; + } + + error = regulator_enable(cnss_pdata->regulator.wlan_vreg_dsrc); + if (error) { + dev_err(dev, "VDD-VREG-DSRC enable failed error=%d\n", + error); + goto err_vdd_vreg_dsrc_regulator; + } + } + + return 0; + +err_vdd_vreg_dsrc_regulator: + regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); + + return error; +} + +static int cnss_sdio_configure_regulator(void) +{ + int error; + struct device *dev = &cnss_pdata->pdev->dev; + + if (of_get_property( + cnss_pdata->pdev->dev.of_node, + WLAN_VREG_IO_NAME "-supply", NULL)) { + cnss_pdata->regulator.wlan_io = regulator_get( + &cnss_pdata->pdev->dev, WLAN_VREG_IO_NAME); + if (IS_ERR(cnss_pdata->regulator.wlan_io)) { + error = PTR_ERR(cnss_pdata->regulator.wlan_io); + dev_err(dev, "VDD-IO get failed error=%d\n", error); + return error; + } + + error = regulator_set_voltage( + cnss_pdata->regulator.wlan_io, + WLAN_VREG_IO_MIN, WLAN_VREG_IO_MAX); + if (error) { + dev_err(dev, "VDD-IO set failed error=%d\n", error); + goto err_vdd_io_regulator; + } else { + error = regulator_enable(cnss_pdata->regulator.wlan_io); + if (error) { + dev_err(dev, "VDD-IO enable failed error=%d\n", + error); + goto err_vdd_io_regulator; + } + } + } + + if (of_get_property( + cnss_pdata->pdev->dev.of_node, + WLAN_VREG_XTAL_NAME "-supply", NULL)) { + cnss_pdata->regulator.wlan_xtal = regulator_get( + &cnss_pdata->pdev->dev, WLAN_VREG_XTAL_NAME); + if (IS_ERR(cnss_pdata->regulator.wlan_xtal)) { + error = PTR_ERR(cnss_pdata->regulator.wlan_xtal); + dev_err(dev, "VDD-XTAL get failed error=%d\n", error); + goto err_vdd_xtal_regulator; + } + + error = regulator_set_voltage( + cnss_pdata->regulator.wlan_xtal, + WLAN_VREG_XTAL_MIN, WLAN_VREG_XTAL_MAX); + if (error) { + dev_err(dev, "VDD-XTAL set failed error=%d\n", error); + goto err_vdd_xtal_regulator; + } else { + error = regulator_enable( + cnss_pdata->regulator.wlan_xtal); + if (error) { + dev_err(dev, "VDD-XTAL enable failed err=%d\n", + error); + goto err_vdd_xtal_regulator; + } + } + } + + return 0; + +err_vdd_xtal_regulator: + regulator_put(cnss_pdata->regulator.wlan_xtal); +err_vdd_io_regulator: + regulator_put(cnss_pdata->regulator.wlan_io); + return error; +} + +static void cnss_sdio_release_resource(void) +{ + if (cnss_pdata->regulator.wlan_xtal) + regulator_put(cnss_pdata->regulator.wlan_xtal); + if (cnss_pdata->regulator.wlan_vreg) + regulator_put(cnss_pdata->regulator.wlan_vreg); + if (cnss_pdata->regulator.wlan_io) + regulator_put(cnss_pdata->regulator.wlan_io); + if (cnss_pdata->regulator.wlan_vreg_dsrc) + regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); +} + +static int cnss_sdio_pinctrl_init(struct cnss_sdio_data *pdata, + struct platform_device *pdev) +{ + int ret = 0; + struct device *dev = &pdev->dev; + struct cnss_wlan_pinctrl_info *info = &pdata->pinctrl_info; + + if (!of_find_property(dev->of_node, "qcom,is-antenna-shared", NULL)) + return 0; + + info->is_antenna_shared = true; + info->pinctrl = devm_pinctrl_get(dev); + if ((IS_ERR_OR_NULL(info->pinctrl))) { + dev_err(dev, "%s: Failed to get pinctrl!\n", __func__); + return PTR_ERR(info->pinctrl); + } + + info->sleep = pinctrl_lookup_state(info->pinctrl, + CNSS_PINCTRL_SLEEP_STATE); + if (IS_ERR_OR_NULL(info->sleep)) { + dev_err(dev, "%s: Fail to get sleep state for pin\n", __func__); + ret = PTR_ERR(info->sleep); + goto release_pinctrl; + } + + info->active = pinctrl_lookup_state(info->pinctrl, + CNSS_PINCTRL_ACTIVE_STATE); + if (IS_ERR_OR_NULL(info->active)) { + dev_err(dev, "%s: Fail to get active state for pin\n", + __func__); + ret = PTR_ERR(info->active); + goto release_pinctrl; + } + + ret = cnss_set_pinctrl_state(pdata, PINCTRL_SLEEP); + + if (ret) { + dev_err(dev, "%s: Fail to set pin in sleep state\n", __func__); + goto release_pinctrl; + } + + return ret; + +release_pinctrl: + devm_pinctrl_put(info->pinctrl); + info->is_antenna_shared = false; + return ret; +} + +static int cnss_sdio_init_bus_bandwidth(void) +{ + int ret = 0; + struct cnss_sdio_bus_bandwidth *bus_bandwidth; + struct device *dev = &cnss_pdata->pdev->dev; + + bus_bandwidth = &cnss_pdata->bus_bandwidth; + bus_bandwidth->bus_scale_table = msm_bus_cl_get_pdata(cnss_pdata->pdev); + if (!bus_bandwidth->bus_scale_table) { + dev_err(dev, "Failed to get the bus scale platform data\n"); + ret = -EINVAL; + } + + bus_bandwidth->bus_client = msm_bus_scale_register_client( + bus_bandwidth->bus_scale_table); + if (!bus_bandwidth->bus_client) { + dev_err(dev, "Failed to register with bus_scale client\n"); + ret = -EINVAL; + } + + return ret; +} + +static int cnss_sdio_probe(struct platform_device *pdev) +{ + int error; + struct device *dev = &pdev->dev; + struct cnss_sdio_info *info; + + if (pdev->dev.of_node) { + cnss_pdata = devm_kzalloc( + &pdev->dev, sizeof(*cnss_pdata), GFP_KERNEL); + if (!cnss_pdata) + return -ENOMEM; + } else { + cnss_pdata = pdev->dev.platform_data; + } + + if (!cnss_pdata) + return -EINVAL; + + cnss_pdata->pdev = pdev; + info = &cnss_pdata->cnss_sdio_info; + + error = cnss_sdio_pinctrl_init(cnss_pdata, pdev); + if (error) { + dev_err(&pdev->dev, "Fail to configure pinctrl err:%d\n", + error); + return error; + } + + error = cnss_sdio_configure_regulator(); + if (error) { + dev_err(&pdev->dev, "Failed to configure voltage regulator error=%d\n", + error); + return error; + } + + if (of_get_property( + cnss_pdata->pdev->dev.of_node, + WLAN_VREG_NAME "-supply", NULL)) { + error = cnss_sdio_configure_wlan_enable_regulator(); + if (error) { + dev_err(&pdev->dev, + "Failed to enable wlan enable regulator error=%d\n", + error); + goto err_wlan_enable_regulator; + } + } + + if (of_get_property( + cnss_pdata->pdev->dev.of_node, + WLAN_VREG_DSRC_NAME "-supply", NULL)) { + error = cnss_sdio_configure_wlan_enable_dsrc_regulator(); + if (error) { + dev_err(&pdev->dev, + "Failed to enable wlan dsrc enable regulator\n"); + goto err_wlan_dsrc_enable_regulator; + } + } + + info->skip_wlan_en_toggle = of_property_read_bool(dev->of_node, + "qcom,skip-wlan-en-toggle"); + + error = cnss_sdio_wlan_init(); + if (error) { + dev_err(&pdev->dev, "cnss wlan init failed error=%d\n", error); + goto err_wlan_dsrc_enable_regulator; + } + + error = cnss_configure_ramdump(); + if (error) { + dev_err(&pdev->dev, "Failed to configure ramdump error=%d\n", + error); + goto err_ramdump_create; + } + + error = cnss_subsys_init(); + if (error) { + dev_err(&pdev->dev, "Failed to cnss_subsys_init error=%d\n", + error); + goto err_subsys_init; + } + + if (of_property_read_bool( + pdev->dev.of_node, "qcom,cnss-enable-bus-bandwidth")) { + error = cnss_sdio_init_bus_bandwidth(); + if (error) { + dev_err(&pdev->dev, "Failed to init bus bandwidth\n"); + goto err_bus_bandwidth_init; + } + } + + dev_info(&pdev->dev, "CNSS SDIO Driver registered"); + return 0; + +err_bus_bandwidth_init: + cnss_subsys_exit(); +err_subsys_init: + cnss_ramdump_cleanup(); +err_ramdump_create: + cnss_sdio_wlan_exit(); +err_wlan_dsrc_enable_regulator: + regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); +err_wlan_enable_regulator: + regulator_put(cnss_pdata->regulator.wlan_xtal); + regulator_put(cnss_pdata->regulator.wlan_io); + cnss_pdata = NULL; + return error; +} + +static int cnss_sdio_remove(struct platform_device *pdev) +{ + struct cnss_sdio_info *info; + + if (!cnss_pdata) + return -ENODEV; + + info = &cnss_pdata->cnss_sdio_info; + + cnss_sdio_deinit_bus_bandwidth(); + cnss_sdio_wlan_exit(); + cnss_subsys_exit(); + cnss_ramdump_cleanup(); + cnss_put_hw_resources(info->dev); + cnss_sdio_release_resource(); + cnss_pdata = NULL; + return 0; +} + +int cnss_sdio_set_wlan_mac_address(const u8 *in, uint32_t len) +{ + return 0; +} + +u8 *cnss_sdio_get_wlan_mac_address(uint32_t *num) +{ + *num = 0; + return NULL; +} + +int cnss_sdio_power_down(struct device *dev) +{ + return 0; +} + +int cnss_sdio_power_up(struct device *dev) +{ + return 0; +} + +static const struct of_device_id cnss_sdio_dt_match[] = { + {.compatible = "qcom,cnss_sdio"}, + {} +}; +MODULE_DEVICE_TABLE(of, cnss_sdio_dt_match); + +static struct platform_driver cnss_sdio_driver = { + .probe = cnss_sdio_probe, + .remove = cnss_sdio_remove, + .driver = { + .name = "cnss_sdio", + .owner = THIS_MODULE, + .of_match_table = cnss_sdio_dt_match, + }, +}; + +static int __init cnss_sdio_init(void) +{ + return platform_driver_register(&cnss_sdio_driver); +} + +static void __exit cnss_sdio_exit(void) +{ + platform_driver_unregister(&cnss_sdio_driver); +} + +module_init(cnss_sdio_init); +module_exit(cnss_sdio_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DEVICE "CNSS SDIO Driver"); diff --git a/drivers/net/wireless/cnss2/Kconfig b/drivers/net/wireless/cnss2/Kconfig new file mode 100644 index 000000000000..17c6860e719c --- /dev/null +++ b/drivers/net/wireless/cnss2/Kconfig @@ -0,0 +1,20 @@ +config CNSS2 + tristate "CNSS2 Platform Driver for Wi-Fi Module" + depends on !CNSS && (PCI_MSM || PCI_HOST_GENERIC) + select CNSS_UTILS + ---help--- + This module adds the support for Connectivity Subsystem (CNSS) used + for PCIe based Wi-Fi devices with QCA6174/QCA6290 chipsets. + This module in metal case depends on PCI_MSM, while in GVM case + depends on PCI_HOST_GENERIC. + This driver also adds support to integrate WLAN module to subsystem + restart framework. + +config CNSS2_DEBUG + bool "CNSS2 Platform Driver Debug Support" + depends on CNSS2 + ---help--- + This option is to enable CNSS2 platform driver debug support which + primarily includes providing additional verbose logs for certain + features, enabling kernel panic for certain cases to aid the + debugging, and enabling any other debug mechanisms. diff --git a/drivers/net/wireless/cnss2/Makefile b/drivers/net/wireless/cnss2/Makefile new file mode 100644 index 000000000000..318076f23213 --- /dev/null +++ b/drivers/net/wireless/cnss2/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_CNSS2) += cnss2.o + +cnss2-y := main.o +cnss2-y += bus.o +cnss2-y += debug.o +cnss2-y += pci.o +cnss2-y += power.o +cnss2-y += qmi.o +cnss2-y += wlan_firmware_service_v01.o diff --git a/drivers/net/wireless/cnss2/bus.c b/drivers/net/wireless/cnss2/bus.c new file mode 100644 index 000000000000..c0810df81bfc --- /dev/null +++ b/drivers/net/wireless/cnss2/bus.c @@ -0,0 +1,357 @@ +/* Copyright (c) 2018, 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 "bus.h" +#include "debug.h" +#include "pci.h" + +enum cnss_dev_bus_type cnss_get_dev_bus_type(struct device *dev) +{ + if (!dev) + return CNSS_BUS_NONE; + + if (!dev->bus) + return CNSS_BUS_NONE; + + if (memcmp(dev->bus->name, "pci", 3) == 0) + return CNSS_BUS_PCI; + else + return CNSS_BUS_NONE; +} + +enum cnss_dev_bus_type cnss_get_bus_type(unsigned long device_id) +{ + switch (device_id) { + case QCA6174_DEVICE_ID: + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + case QCN7605_DEVICE_ID: + return CNSS_BUS_PCI; + default: + cnss_pr_err("Unknown device_id: 0x%lx\n", device_id); + return CNSS_BUS_NONE; + } +} + +void *cnss_bus_dev_to_bus_priv(struct device *dev) +{ + if (!dev) + return NULL; + + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_PCI: + return cnss_get_pci_priv(to_pci_dev(dev)); + default: + return NULL; + } +} + +struct cnss_plat_data *cnss_bus_dev_to_plat_priv(struct device *dev) +{ + void *bus_priv; + + if (!dev) + return cnss_get_plat_priv(NULL); + + bus_priv = cnss_bus_dev_to_bus_priv(dev); + if (!bus_priv) + return NULL; + + switch (cnss_get_dev_bus_type(dev)) { + case CNSS_BUS_PCI: + return cnss_pci_priv_to_plat_priv(bus_priv); + default: + return NULL; + } +} + +int cnss_bus_init(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_init(plat_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +void cnss_bus_deinit(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + cnss_pci_deinit(plat_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return; + } +} + +int cnss_bus_load_m3(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_load_m3(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_alloc_fw_mem(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_alloc_fw_mem(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +u32 cnss_bus_get_wake_irq(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_get_wake_msi(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_force_fw_assert_hdlr(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_force_fw_assert_hdlr(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +void cnss_bus_fw_boot_timeout_hdlr(unsigned long data) +{ + struct cnss_plat_data *plat_priv = (struct cnss_plat_data *)data; + + if (!plat_priv) + return; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_fw_boot_timeout_hdlr(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return; + } +} + +void cnss_bus_collect_dump_info(struct cnss_plat_data *plat_priv) +{ + int ret; + + if (!plat_priv) + return; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + ret = cnss_pci_set_mhi_state(plat_priv->bus_priv, + CNSS_MHI_RDDM); + if (ret) { + cnss_pr_err("Failed to complete RDDM, err = %d\n", ret); + break; + } + return cnss_pci_collect_dump_info(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return; + } +} + +int cnss_bus_call_driver_probe(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_call_driver_probe(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_call_driver_remove(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_call_driver_remove(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_dev_powerup(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_dev_powerup(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_dev_shutdown(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_dev_shutdown(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_dev_crash_shutdown(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_dev_crash_shutdown(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_dev_ramdump(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_dev_ramdump(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_register_driver_hdlr(struct cnss_plat_data *plat_priv, void *data) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_register_driver_hdlr(plat_priv->bus_priv, data); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_unregister_driver_hdlr(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_unregister_driver_hdlr(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_call_driver_modem_status(struct cnss_plat_data *plat_priv, + int modem_current_status) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_call_driver_modem_status(plat_priv->bus_priv, + modem_current_status); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} + +int cnss_bus_recovery_update_status(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + switch (plat_priv->bus_type) { + case CNSS_BUS_PCI: + return cnss_pci_recovery_update_status(plat_priv->bus_priv); + default: + cnss_pr_err("Unsupported bus type: %d\n", + plat_priv->bus_type); + return -EINVAL; + } +} diff --git a/drivers/net/wireless/cnss2/bus.h b/drivers/net/wireless/cnss2/bus.h new file mode 100644 index 000000000000..bd32a94e5146 --- /dev/null +++ b/drivers/net/wireless/cnss2/bus.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2018, 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. + */ + +#ifndef _CNSS_BUS_H +#define _CNSS_BUS_H + +#include "main.h" + +#define QCA6174_VENDOR_ID 0x168C +#define QCA6174_DEVICE_ID 0x003E +#define QCA6174_REV_ID_OFFSET 0x08 +#define QCA6174_REV3_VERSION 0x5020000 +#define QCA6174_REV3_2_VERSION 0x5030000 +#define QCA6290_VENDOR_ID 0x17CB +#define QCA6290_DEVICE_ID 0x1100 +#define QCA6290_EMULATION_VENDOR_ID 0x168C +#define QCA6290_EMULATION_DEVICE_ID 0xABCD +#define QCN7605_VENDOR_ID 0x17CB +#define QCN7605_DEVICE_ID 0x1102 + +enum cnss_dev_bus_type cnss_get_dev_bus_type(struct device *dev); +enum cnss_dev_bus_type cnss_get_bus_type(unsigned long device_id); +void *cnss_bus_dev_to_bus_priv(struct device *dev); +struct cnss_plat_data *cnss_bus_dev_to_plat_priv(struct device *dev); +int cnss_bus_init(struct cnss_plat_data *plat_priv); +void cnss_bus_deinit(struct cnss_plat_data *plat_priv); +int cnss_bus_load_m3(struct cnss_plat_data *plat_priv); +int cnss_bus_alloc_fw_mem(struct cnss_plat_data *plat_priv); +u32 cnss_bus_get_wake_irq(struct cnss_plat_data *plat_priv); +int cnss_bus_force_fw_assert_hdlr(struct cnss_plat_data *plat_priv); +void cnss_bus_fw_boot_timeout_hdlr(unsigned long data); +void cnss_bus_collect_dump_info(struct cnss_plat_data *plat_priv); +int cnss_bus_call_driver_probe(struct cnss_plat_data *plat_priv); +int cnss_bus_call_driver_remove(struct cnss_plat_data *plat_priv); +int cnss_bus_dev_powerup(struct cnss_plat_data *plat_priv); +int cnss_bus_dev_shutdown(struct cnss_plat_data *plat_priv); +int cnss_bus_dev_crash_shutdown(struct cnss_plat_data *plat_priv); +int cnss_bus_dev_ramdump(struct cnss_plat_data *plat_priv); +int cnss_bus_register_driver_hdlr(struct cnss_plat_data *plat_priv, void *data); +int cnss_bus_unregister_driver_hdlr(struct cnss_plat_data *plat_priv); +int cnss_bus_call_driver_modem_status(struct cnss_plat_data *plat_priv, + int modem_current_status); +int cnss_bus_recovery_update_status(struct cnss_plat_data *plat_priv); +#endif /* _CNSS_BUS_H */ diff --git a/drivers/net/wireless/cnss2/debug.c b/drivers/net/wireless/cnss2/debug.c new file mode 100644 index 000000000000..5e2d44ce1c55 --- /dev/null +++ b/drivers/net/wireless/cnss2/debug.c @@ -0,0 +1,495 @@ +/* Copyright (c) 2016-2017, 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/err.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include "main.h" +#include "debug.h" +#include "pci.h" + +#define CNSS_IPC_LOG_PAGES 32 + +void *cnss_ipc_log_context; + +static int cnss_pin_connect_show(struct seq_file *s, void *data) +{ + struct cnss_plat_data *cnss_priv = s->private; + + seq_puts(s, "Pin connect results\n"); + seq_printf(s, "FW power pin result: %04x\n", + cnss_priv->pin_result.fw_pwr_pin_result); + seq_printf(s, "FW PHY IO pin result: %04x\n", + cnss_priv->pin_result.fw_phy_io_pin_result); + seq_printf(s, "FW RF pin result: %04x\n", + cnss_priv->pin_result.fw_rf_pin_result); + seq_printf(s, "Host pin result: %04x\n", + cnss_priv->pin_result.host_pin_result); + seq_puts(s, "\n"); + + return 0; +} + +static int cnss_pin_connect_open(struct inode *inode, struct file *file) +{ + return single_open(file, cnss_pin_connect_show, inode->i_private); +} + +static const struct file_operations cnss_pin_connect_fops = { + .read = seq_read, + .release = single_release, + .open = cnss_pin_connect_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static int cnss_stats_show_state(struct seq_file *s, + struct cnss_plat_data *plat_priv) +{ + enum cnss_driver_state i; + int skip = 0; + unsigned long state; + + seq_printf(s, "\nState: 0x%lx(", plat_priv->driver_state); + for (i = 0, state = plat_priv->driver_state; state != 0; + state >>= 1, i++) { + if (!(state & 0x1)) + continue; + + if (skip++) + seq_puts(s, " | "); + + switch (i) { + case CNSS_QMI_WLFW_CONNECTED: + seq_puts(s, "QMI_WLFW_CONNECTED"); + continue; + case CNSS_FW_MEM_READY: + seq_puts(s, "FW_MEM_READY"); + continue; + case CNSS_FW_READY: + seq_puts(s, "FW_READY"); + continue; + case CNSS_COLD_BOOT_CAL: + seq_puts(s, "COLD_BOOT_CAL"); + continue; + case CNSS_DRIVER_LOADING: + seq_puts(s, "DRIVER_LOADING"); + continue; + case CNSS_DRIVER_UNLOADING: + seq_puts(s, "DRIVER_UNLOADING"); + continue; + case CNSS_DRIVER_PROBED: + seq_puts(s, "DRIVER_PROBED"); + continue; + case CNSS_DRIVER_RECOVERY: + seq_puts(s, "DRIVER_RECOVERY"); + continue; + case CNSS_FW_BOOT_RECOVERY: + seq_puts(s, "FW_BOOT_RECOVERY"); + continue; + case CNSS_DEV_ERR_NOTIFY: + seq_puts(s, "DEV_ERR"); + continue; + case CNSS_DRIVER_DEBUG: + seq_puts(s, "DRIVER_DEBUG"); + continue; + } + + seq_printf(s, "UNKNOWN-%d", i); + } + seq_puts(s, ")\n"); + + return 0; +} + +static int cnss_stats_show(struct seq_file *s, void *data) +{ + struct cnss_plat_data *plat_priv = s->private; + + cnss_stats_show_state(s, plat_priv); + + return 0; +} + +static int cnss_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, cnss_stats_show, inode->i_private); +} + +static const struct file_operations cnss_stats_fops = { + .read = seq_read, + .release = single_release, + .open = cnss_stats_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static ssize_t cnss_dev_boot_debug_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct cnss_plat_data *plat_priv = + ((struct seq_file *)fp->private_data)->private; + char buf[64]; + char *cmd; + unsigned int len = 0; + int ret = 0; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + cmd = buf; + + if (sysfs_streq(cmd, "on")) { + ret = cnss_power_on_device(plat_priv); + } else if (sysfs_streq(cmd, "off")) { + cnss_power_off_device(plat_priv); + } else if (sysfs_streq(cmd, "enumerate")) { + ret = cnss_pci_init(plat_priv); + } else if (sysfs_streq(cmd, "download")) { + set_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state); + ret = cnss_pci_start_mhi(plat_priv->bus_priv); + } else if (sysfs_streq(cmd, "linkup")) { + ret = cnss_resume_pci_link(plat_priv->bus_priv); + } else if (sysfs_streq(cmd, "linkdown")) { + ret = cnss_suspend_pci_link(plat_priv->bus_priv); + } else if (sysfs_streq(cmd, "powerup")) { + set_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state); + ret = cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_POWER_UP, + CNSS_EVENT_SYNC, NULL); + } else if (sysfs_streq(cmd, "shutdown")) { + ret = cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_POWER_DOWN, + CNSS_EVENT_SYNC, NULL); + clear_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state); + } else { + cnss_pr_err("Device boot debugfs command is invalid\n"); + ret = -EINVAL; + } + + if (ret) + return ret; + + return count; +} + +static int cnss_dev_boot_debug_show(struct seq_file *s, void *data) +{ + seq_puts(s, "\nUsage: echo <action> > <debugfs_path>/cnss/dev_boot\n"); + seq_puts(s, "<action> can be one of below:\n"); + seq_puts(s, "on: turn on device power, assert WLAN_EN\n"); + seq_puts(s, "off: de-assert WLAN_EN, turn off device power\n"); + seq_puts(s, "enumerate: de-assert PERST, enumerate PCIe\n"); + seq_puts(s, "download: download FW and do QMI handshake with FW\n"); + seq_puts(s, "linkup: bring up PCIe link\n"); + seq_puts(s, "linkdown: bring down PCIe link\n"); + seq_puts(s, "powerup: full power on sequence to boot device, download FW and do QMI handshake with FW\n"); + seq_puts(s, "shutdown: full power off sequence to shutdown device\n"); + + return 0; +} + +static int cnss_dev_boot_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, cnss_dev_boot_debug_show, inode->i_private); +} + +static const struct file_operations cnss_dev_boot_debug_fops = { + .read = seq_read, + .write = cnss_dev_boot_debug_write, + .release = single_release, + .open = cnss_dev_boot_debug_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static int cnss_reg_read_debug_show(struct seq_file *s, void *data) +{ + struct cnss_plat_data *plat_priv = s->private; + + mutex_lock(&plat_priv->dev_lock); + if (!plat_priv->diag_reg_read_buf) { + seq_puts(s, "\nUsage: echo <mem_type> <offset> <data_len> > <debugfs_path>/cnss/reg_read\n"); + mutex_unlock(&plat_priv->dev_lock); + return 0; + } + + seq_printf(s, "\nRegister read, address: 0x%x memory type: 0x%x length: 0x%x\n\n", + plat_priv->diag_reg_read_addr, + plat_priv->diag_reg_read_mem_type, + plat_priv->diag_reg_read_len); + + seq_hex_dump(s, "", DUMP_PREFIX_OFFSET, 32, 4, + plat_priv->diag_reg_read_buf, + plat_priv->diag_reg_read_len, false); + + plat_priv->diag_reg_read_len = 0; + kfree(plat_priv->diag_reg_read_buf); + plat_priv->diag_reg_read_buf = NULL; + mutex_unlock(&plat_priv->dev_lock); + + return 0; +} + +static ssize_t cnss_reg_read_debug_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct cnss_plat_data *plat_priv = + ((struct seq_file *)fp->private_data)->private; + char buf[64]; + char *sptr, *token; + unsigned int len = 0; + u32 reg_offset, mem_type; + u32 data_len = 0; + u8 *reg_buf = NULL; + const char *delim = " "; + int ret = 0; + + if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { + cnss_pr_err("Firmware is not ready yet\n"); + return -EINVAL; + } + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, &mem_type)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, ®_offset)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (kstrtou32(token, 0, &data_len)) + return -EINVAL; + + if (data_len == 0 || + data_len > QMI_WLFW_MAX_ATHDIAG_DATA_SIZE_V01) + return -EINVAL; + + mutex_lock(&plat_priv->dev_lock); + kfree(plat_priv->diag_reg_read_buf); + plat_priv->diag_reg_read_buf = NULL; + + reg_buf = kzalloc(data_len, GFP_KERNEL); + if (!reg_buf) { + mutex_unlock(&plat_priv->dev_lock); + return -ENOMEM; + } + + ret = cnss_wlfw_athdiag_read_send_sync(plat_priv, reg_offset, + mem_type, data_len, + reg_buf); + if (ret) { + kfree(reg_buf); + mutex_unlock(&plat_priv->dev_lock); + return ret; + } + + plat_priv->diag_reg_read_addr = reg_offset; + plat_priv->diag_reg_read_mem_type = mem_type; + plat_priv->diag_reg_read_len = data_len; + plat_priv->diag_reg_read_buf = reg_buf; + mutex_unlock(&plat_priv->dev_lock); + + return count; +} + +static int cnss_reg_read_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, cnss_reg_read_debug_show, inode->i_private); +} + +static const struct file_operations cnss_reg_read_debug_fops = { + .read = seq_read, + .write = cnss_reg_read_debug_write, + .open = cnss_reg_read_debug_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static int cnss_reg_write_debug_show(struct seq_file *s, void *data) +{ + seq_puts(s, "\nUsage: echo <mem_type> <offset> <reg_val> > <debugfs_path>/cnss/reg_write\n"); + + return 0; +} + +static ssize_t cnss_reg_write_debug_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct cnss_plat_data *plat_priv = + ((struct seq_file *)fp->private_data)->private; + char buf[64]; + char *sptr, *token; + unsigned int len = 0; + u32 reg_offset, mem_type, reg_val; + const char *delim = " "; + int ret = 0; + + if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { + cnss_pr_err("Firmware is not ready yet\n"); + return -EINVAL; + } + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, &mem_type)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (!sptr) + return -EINVAL; + + if (kstrtou32(token, 0, ®_offset)) + return -EINVAL; + + token = strsep(&sptr, delim); + if (!token) + return -EINVAL; + + if (kstrtou32(token, 0, ®_val)) + return -EINVAL; + + ret = cnss_wlfw_athdiag_write_send_sync(plat_priv, reg_offset, mem_type, + sizeof(u32), + (u8 *)®_val); + if (ret) + return ret; + + return count; +} + +static int cnss_reg_write_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, cnss_reg_write_debug_show, inode->i_private); +} + +static const struct file_operations cnss_reg_write_debug_fops = { + .read = seq_read, + .write = cnss_reg_write_debug_write, + .open = cnss_reg_write_debug_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +#ifdef CONFIG_CNSS2_DEBUG +static int cnss_create_debug_only_node(struct cnss_plat_data *plat_priv) +{ + struct dentry *root_dentry = plat_priv->root_dentry; + + debugfs_create_file("dev_boot", 0600, root_dentry, plat_priv, + &cnss_dev_boot_debug_fops); + debugfs_create_file("reg_read", 0600, root_dentry, plat_priv, + &cnss_reg_read_debug_fops); + debugfs_create_file("reg_write", 0600, root_dentry, plat_priv, + &cnss_reg_write_debug_fops); + + return 0; +} +#else +static int cnss_create_debug_only_node(struct cnss_plat_data *plat_priv) +{ + return 0; +} +#endif + +int cnss_debugfs_create(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct dentry *root_dentry; + + root_dentry = debugfs_create_dir("cnss", 0); + if (IS_ERR(root_dentry)) { + ret = PTR_ERR(root_dentry); + cnss_pr_err("Unable to create debugfs %d\n", ret); + goto out; + } + + plat_priv->root_dentry = root_dentry; + + debugfs_create_file("pin_connect_result", 0644, root_dentry, plat_priv, + &cnss_pin_connect_fops); + debugfs_create_file("stats", 0644, root_dentry, plat_priv, + &cnss_stats_fops); + + cnss_create_debug_only_node(plat_priv); + +out: + return ret; +} + +void cnss_debugfs_destroy(struct cnss_plat_data *plat_priv) +{ + debugfs_remove_recursive(plat_priv->root_dentry); +} + +int cnss_debug_init(void) +{ + cnss_ipc_log_context = ipc_log_context_create(CNSS_IPC_LOG_PAGES, + "cnss", 0); + if (!cnss_ipc_log_context) { + cnss_pr_err("Unable to create IPC log context!\n"); + return -EINVAL; + } + + return 0; +} + +void cnss_debug_deinit(void) +{ + if (cnss_ipc_log_context) { + ipc_log_context_destroy(cnss_ipc_log_context); + cnss_ipc_log_context = NULL; + } +} diff --git a/drivers/net/wireless/cnss2/debug.h b/drivers/net/wireless/cnss2/debug.h new file mode 100644 index 000000000000..1621514eb5b2 --- /dev/null +++ b/drivers/net/wireless/cnss2/debug.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2016-2017, 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. + */ + +#ifndef _CNSS_DEBUG_H +#define _CNSS_DEBUG_H + +#include <linux/ipc_logging.h> +#include <linux/printk.h> + +extern void *cnss_ipc_log_context; + +#define cnss_ipc_log_string(_x...) do { \ + if (cnss_ipc_log_context) \ + ipc_log_string(cnss_ipc_log_context, _x); \ + } while (0) + +#define cnss_pr_err(_fmt, ...) do { \ + pr_err("cnss: " _fmt, ##__VA_ARGS__); \ + cnss_ipc_log_string("ERR: " pr_fmt(_fmt), \ + ##__VA_ARGS__); \ + } while (0) + +#define cnss_pr_warn(_fmt, ...) do { \ + pr_warn("cnss: " _fmt, ##__VA_ARGS__); \ + cnss_ipc_log_string("WRN: " pr_fmt(_fmt), \ + ##__VA_ARGS__); \ + } while (0) + +#define cnss_pr_info(_fmt, ...) do { \ + pr_info("cnss: " _fmt, ##__VA_ARGS__); \ + cnss_ipc_log_string("INF: " pr_fmt(_fmt), \ + ##__VA_ARGS__); \ + } while (0) + +#define cnss_pr_dbg(_fmt, ...) do { \ + pr_debug("cnss: " _fmt, ##__VA_ARGS__); \ + cnss_ipc_log_string("DBG: " pr_fmt(_fmt), \ + ##__VA_ARGS__); \ + } while (0) + +#ifdef CONFIG_CNSS2_DEBUG +#define CNSS_ASSERT(_condition) do { \ + if (!(_condition)) { \ + cnss_pr_err("ASSERT at line %d\n", \ + __LINE__); \ + WARN_ON(1); \ + } \ + } while (0) +#else +#define CNSS_ASSERT(_condition) do { \ + if (!(_condition)) { \ + cnss_pr_err("ASSERT at line %d\n", \ + __LINE__); \ + WARN_ON(1); \ + } \ + } while (0) +#endif + +int cnss_debug_init(void); +void cnss_debug_deinit(void); +int cnss_debugfs_create(struct cnss_plat_data *plat_priv); +void cnss_debugfs_destroy(struct cnss_plat_data *plat_priv); + +#endif /* _CNSS_DEBUG_H */ diff --git a/drivers/net/wireless/cnss2/main.c b/drivers/net/wireless/cnss2/main.c new file mode 100644 index 000000000000..8aa9eabdc73f --- /dev/null +++ b/drivers/net/wireless/cnss2/main.c @@ -0,0 +1,1798 @@ +/* Copyright (c) 2016-2018, 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/delay.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_wakeup.h> +#include <linux/rwsem.h> +#include <linux/suspend.h> +#include <linux/timer.h> +#include <soc/qcom/ramdump.h> +#include <soc/qcom/subsystem_notif.h> + +#include "main.h" +#include "bus.h" +#include "debug.h" + +#define CNSS_DUMP_FORMAT_VER 0x11 +#define CNSS_DUMP_FORMAT_VER_V2 0x22 +#define CNSS_DUMP_MAGIC_VER_V2 0x42445953 +#define CNSS_DUMP_NAME "CNSS_WLAN" +#define CNSS_DUMP_DESC_SIZE 0x1000 +#define CNSS_DUMP_SEG_VER 0x1 +#define WLAN_RECOVERY_DELAY 1000 +#define FILE_SYSTEM_READY 1 +#define FW_READY_TIMEOUT 20000 +#define FW_ASSERT_TIMEOUT 5000 +#define CNSS_EVENT_PENDING 2989 + +static struct cnss_plat_data *plat_env; + +static DECLARE_RWSEM(cnss_pm_sem); + +static bool qmi_bypass; +#ifdef CONFIG_CNSS2_DEBUG +module_param(qmi_bypass, bool, 0600); +MODULE_PARM_DESC(qmi_bypass, "Bypass QMI from platform driver"); +#endif + +static bool enable_waltest; +#ifdef CONFIG_CNSS2_DEBUG +module_param(enable_waltest, bool, 0600); +MODULE_PARM_DESC(enable_waltest, "Enable to handle firmware waltest"); +#endif + +unsigned long quirks; +#ifdef CONFIG_CNSS2_DEBUG +module_param(quirks, ulong, 0600); +MODULE_PARM_DESC(quirks, "Debug quirks for the driver"); +#endif + +static struct cnss_fw_files FW_FILES_QCA6174_FW_3_0 = { + "qwlan30.bin", "bdwlan30.bin", "otp30.bin", "utf30.bin", + "utfbd30.bin", "epping30.bin", "evicted30.bin" +}; + +static struct cnss_fw_files FW_FILES_DEFAULT = { + "qwlan.bin", "bdwlan.bin", "otp.bin", "utf.bin", + "utfbd.bin", "epping.bin", "evicted.bin" +}; + +struct cnss_driver_event { + struct list_head list; + enum cnss_driver_event_type type; + bool sync; + struct completion complete; + int ret; + void *data; +}; + +static void cnss_set_plat_priv(struct platform_device *plat_dev, + struct cnss_plat_data *plat_priv) +{ + plat_env = plat_priv; +} + +struct cnss_plat_data *cnss_get_plat_priv(struct platform_device *plat_dev) +{ + return plat_env; +} + +static int cnss_pm_notify(struct notifier_block *b, + unsigned long event, void *p) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + down_write(&cnss_pm_sem); + break; + case PM_POST_SUSPEND: + up_write(&cnss_pm_sem); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block cnss_pm_notifier = { + .notifier_call = cnss_pm_notify, +}; + +static void cnss_pm_stay_awake(struct cnss_plat_data *plat_priv) +{ + if (atomic_inc_return(&plat_priv->pm_count) != 1) + return; + + cnss_pr_dbg("PM stay awake, state: 0x%lx, count: %d\n", + plat_priv->driver_state, + atomic_read(&plat_priv->pm_count)); + pm_stay_awake(&plat_priv->plat_dev->dev); +} + +static void cnss_pm_relax(struct cnss_plat_data *plat_priv) +{ + int r = atomic_dec_return(&plat_priv->pm_count); + + WARN_ON(r < 0); + + if (r != 0) + return; + + cnss_pr_dbg("PM relax, state: 0x%lx, count: %d\n", + plat_priv->driver_state, + atomic_read(&plat_priv->pm_count)); + pm_relax(&plat_priv->plat_dev->dev); +} + +void cnss_lock_pm_sem(struct device *dev) +{ + down_read(&cnss_pm_sem); +} +EXPORT_SYMBOL(cnss_lock_pm_sem); + +void cnss_release_pm_sem(struct device *dev) +{ + up_read(&cnss_pm_sem); +} +EXPORT_SYMBOL(cnss_release_pm_sem); + +int cnss_get_fw_files_for_target(struct device *dev, + struct cnss_fw_files *pfw_files, + u32 target_type, u32 target_version) +{ + if (!pfw_files) + return -ENODEV; + + switch (target_version) { + case QCA6174_REV3_VERSION: + case QCA6174_REV3_2_VERSION: + memcpy(pfw_files, &FW_FILES_QCA6174_FW_3_0, sizeof(*pfw_files)); + break; + default: + memcpy(pfw_files, &FW_FILES_DEFAULT, sizeof(*pfw_files)); + cnss_pr_err("Unknown target version, type: 0x%X, version: 0x%X", + target_type, target_version); + break; + } + + return 0; +} +EXPORT_SYMBOL(cnss_get_fw_files_for_target); + +int cnss_request_bus_bandwidth(struct device *dev, int bandwidth) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct cnss_bus_bw_info *bus_bw_info; + + if (!plat_priv) + return -ENODEV; + + bus_bw_info = &plat_priv->bus_bw_info; + if (!bus_bw_info->bus_client) + return -EINVAL; + + switch (bandwidth) { + case CNSS_BUS_WIDTH_NONE: + case CNSS_BUS_WIDTH_LOW: + case CNSS_BUS_WIDTH_MEDIUM: + case CNSS_BUS_WIDTH_HIGH: + ret = msm_bus_scale_client_update_request( + bus_bw_info->bus_client, bandwidth); + if (!ret) + bus_bw_info->current_bw_vote = bandwidth; + else + cnss_pr_err("Could not set bus bandwidth: %d, err = %d\n", + bandwidth, ret); + break; + default: + cnss_pr_err("Invalid bus bandwidth: %d", bandwidth); + ret = -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL(cnss_request_bus_bandwidth); + +int cnss_get_platform_cap(struct device *dev, struct cnss_platform_cap *cap) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + + if (!plat_priv) + return -ENODEV; + + if (cap) + *cap = plat_priv->cap; + + return 0; +} +EXPORT_SYMBOL(cnss_get_platform_cap); + +void cnss_request_pm_qos(struct device *dev, u32 qos_val) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + + if (!plat_priv) + return; + + pm_qos_add_request(&plat_priv->qos_request, PM_QOS_CPU_DMA_LATENCY, + qos_val); +} +EXPORT_SYMBOL(cnss_request_pm_qos); + +void cnss_remove_pm_qos(struct device *dev) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + + if (!plat_priv) + return; + + pm_qos_remove_request(&plat_priv->qos_request); +} +EXPORT_SYMBOL(cnss_remove_pm_qos); + +int cnss_wlan_enable(struct device *dev, + struct cnss_wlan_enable_cfg *config, + enum cnss_driver_mode mode, + const char *host_version) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct wlfw_wlan_cfg_req_msg_v01 req; + u32 i; + int ret = 0; + + if (plat_priv->device_id == QCA6174_DEVICE_ID) + return 0; + + if (qmi_bypass) + return 0; + + if (!config || !host_version) { + cnss_pr_err("Invalid config or host_version pointer\n"); + return -EINVAL; + } + + cnss_pr_dbg("Mode: %d, config: %pK, host_version: %s\n", + mode, config, host_version); + + if (mode == CNSS_WALTEST || mode == CNSS_CCPM) + goto skip_cfg; + + memset(&req, 0, sizeof(req)); + + req.host_version_valid = 1; + strlcpy(req.host_version, host_version, + QMI_WLFW_MAX_STR_LEN_V01 + 1); + + req.tgt_cfg_valid = 1; + if (config->num_ce_tgt_cfg > QMI_WLFW_MAX_NUM_CE_V01) + req.tgt_cfg_len = QMI_WLFW_MAX_NUM_CE_V01; + else + req.tgt_cfg_len = config->num_ce_tgt_cfg; + for (i = 0; i < req.tgt_cfg_len; i++) { + req.tgt_cfg[i].pipe_num = config->ce_tgt_cfg[i].pipe_num; + req.tgt_cfg[i].pipe_dir = config->ce_tgt_cfg[i].pipe_dir; + req.tgt_cfg[i].nentries = config->ce_tgt_cfg[i].nentries; + req.tgt_cfg[i].nbytes_max = config->ce_tgt_cfg[i].nbytes_max; + req.tgt_cfg[i].flags = config->ce_tgt_cfg[i].flags; + } + + req.svc_cfg_valid = 1; + if (config->num_ce_svc_pipe_cfg > QMI_WLFW_MAX_NUM_SVC_V01) + req.svc_cfg_len = QMI_WLFW_MAX_NUM_SVC_V01; + else + req.svc_cfg_len = config->num_ce_svc_pipe_cfg; + for (i = 0; i < req.svc_cfg_len; i++) { + req.svc_cfg[i].service_id = config->ce_svc_cfg[i].service_id; + req.svc_cfg[i].pipe_dir = config->ce_svc_cfg[i].pipe_dir; + req.svc_cfg[i].pipe_num = config->ce_svc_cfg[i].pipe_num; + } + + req.shadow_reg_v2_valid = 1; + if (config->num_shadow_reg_v2_cfg > + QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01) + req.shadow_reg_v2_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01; + else + req.shadow_reg_v2_len = config->num_shadow_reg_v2_cfg; + + memcpy(req.shadow_reg_v2, config->shadow_reg_v2_cfg, + sizeof(struct wlfw_shadow_reg_v2_cfg_s_v01) + * req.shadow_reg_v2_len); + + ret = cnss_wlfw_wlan_cfg_send_sync(plat_priv, &req); + if (ret) + goto out; + +skip_cfg: + ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, mode); +out: + return ret; +} +EXPORT_SYMBOL(cnss_wlan_enable); + +int cnss_wlan_disable(struct device *dev, enum cnss_driver_mode mode) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + + if (plat_priv->device_id == QCA6174_DEVICE_ID) + return 0; + + if (qmi_bypass) + return 0; + + return cnss_wlfw_wlan_mode_send_sync(plat_priv, QMI_WLFW_OFF_V01); +} +EXPORT_SYMBOL(cnss_wlan_disable); + +#ifdef CONFIG_CNSS2_DEBUG +int cnss_athdiag_read(struct device *dev, u32 offset, u32 mem_type, + u32 data_len, u8 *output) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + int ret = 0; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return -EINVAL; + } + + if (plat_priv->device_id == QCA6174_DEVICE_ID) + return 0; + + if (!output || data_len == 0 || data_len > QMI_WLFW_MAX_DATA_SIZE_V01) { + cnss_pr_err("Invalid parameters for athdiag read: output %p, data_len %u\n", + output, data_len); + ret = -EINVAL; + goto out; + } + + if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { + cnss_pr_err("Invalid state for athdiag read: 0x%lx\n", + plat_priv->driver_state); + ret = -EINVAL; + goto out; + } + + ret = cnss_wlfw_athdiag_read_send_sync(plat_priv, offset, mem_type, + data_len, output); + +out: + return ret; +} +EXPORT_SYMBOL(cnss_athdiag_read); + +int cnss_athdiag_write(struct device *dev, u32 offset, u32 mem_type, + u32 data_len, u8 *input) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + int ret = 0; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return -EINVAL; + } + + if (plat_priv->device_id == QCA6174_DEVICE_ID) + return 0; + + if (!input || data_len == 0 || data_len > QMI_WLFW_MAX_DATA_SIZE_V01) { + cnss_pr_err("Invalid parameters for athdiag write: input %p, data_len %u\n", + input, data_len); + ret = -EINVAL; + goto out; + } + + if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { + cnss_pr_err("Invalid state for athdiag write: 0x%lx\n", + plat_priv->driver_state); + ret = -EINVAL; + goto out; + } + + ret = cnss_wlfw_athdiag_write_send_sync(plat_priv, offset, mem_type, + data_len, input); + +out: + return ret; +} +EXPORT_SYMBOL(cnss_athdiag_write); +#else +int cnss_athdiag_read(struct device *dev, u32 offset, u32 mem_type, + u32 data_len, u8 *output) +{ + return -EPERM; +} +EXPORT_SYMBOL(cnss_athdiag_read); + +int cnss_athdiag_write(struct device *dev, u32 offset, u32 mem_type, + u32 data_len, u8 *input) +{ + return -EPERM; +} +EXPORT_SYMBOL(cnss_athdiag_write); +#endif + +int cnss_set_fw_log_mode(struct device *dev, u8 fw_log_mode) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + + if (plat_priv->device_id == QCA6174_DEVICE_ID) + return 0; + + return cnss_wlfw_ini_send_sync(plat_priv, fw_log_mode); +} +EXPORT_SYMBOL(cnss_set_fw_log_mode); + +unsigned long *cnss_get_debug_quirks(void) +{ + return &quirks; +} + +bool *cnss_get_qmi_bypass(void) +{ + return &qmi_bypass; +} + +static int cnss_fw_mem_ready_hdlr(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + if (!plat_priv) + return -ENODEV; + + set_bit(CNSS_FW_MEM_READY, &plat_priv->driver_state); + + ret = cnss_wlfw_tgt_cap_send_sync(plat_priv); + if (ret) + goto out; + + ret = cnss_wlfw_bdf_dnld_send_sync(plat_priv); + if (ret) + goto out; + + if (plat_priv->device_id == QCN7605_DEVICE_ID) + goto skip_m3_dnld; + ret = cnss_bus_load_m3(plat_priv); + if (ret) + goto out; + + ret = cnss_wlfw_m3_dnld_send_sync(plat_priv); + if (ret) + goto out; +skip_m3_dnld: + return 0; +out: + return ret; +} + +static int cnss_fw_ready_hdlr(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + if (!plat_priv) + return -ENODEV; + + del_timer(&plat_priv->fw_boot_timer); + set_bit(CNSS_FW_READY, &plat_priv->driver_state); + + if (test_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state)) { + clear_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state); + clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); + } + + if (enable_waltest) { + ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, + QMI_WLFW_WALTEST_V01); + } else if (test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state)) { + ret = cnss_wlfw_wlan_mode_send_sync(plat_priv, + QMI_WLFW_CALIBRATION_V01); + } else if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state) || + test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { + ret = cnss_bus_call_driver_probe(plat_priv); + } else { + complete(&plat_priv->power_up_complete); + } + + if (ret && test_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state)) + goto out; + else if (ret) + goto shutdown; + + return 0; + +shutdown: + cnss_bus_dev_shutdown(plat_priv); + + clear_bit(CNSS_FW_READY, &plat_priv->driver_state); + clear_bit(CNSS_FW_MEM_READY, &plat_priv->driver_state); + +out: + return ret; +} + +static char *cnss_driver_event_to_str(enum cnss_driver_event_type type) +{ + switch (type) { + case CNSS_DRIVER_EVENT_SERVER_ARRIVE: + return "SERVER_ARRIVE"; + case CNSS_DRIVER_EVENT_SERVER_EXIT: + return "SERVER_EXIT"; + case CNSS_DRIVER_EVENT_REQUEST_MEM: + return "REQUEST_MEM"; + case CNSS_DRIVER_EVENT_FW_MEM_READY: + return "FW_MEM_READY"; + case CNSS_DRIVER_EVENT_FW_READY: + return "FW_READY"; + case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START: + return "COLD_BOOT_CAL_START"; + case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE: + return "COLD_BOOT_CAL_DONE"; + case CNSS_DRIVER_EVENT_REGISTER_DRIVER: + return "REGISTER_DRIVER"; + case CNSS_DRIVER_EVENT_UNREGISTER_DRIVER: + return "UNREGISTER_DRIVER"; + case CNSS_DRIVER_EVENT_RECOVERY: + return "RECOVERY"; + case CNSS_DRIVER_EVENT_FORCE_FW_ASSERT: + return "FORCE_FW_ASSERT"; + case CNSS_DRIVER_EVENT_POWER_UP: + return "POWER_UP"; + case CNSS_DRIVER_EVENT_POWER_DOWN: + return "POWER_DOWN"; + case CNSS_DRIVER_EVENT_MAX: + return "EVENT_MAX"; + } + + return "UNKNOWN"; +}; + +int cnss_driver_event_post(struct cnss_plat_data *plat_priv, + enum cnss_driver_event_type type, + u32 flags, void *data) +{ + struct cnss_driver_event *event; + unsigned long irq_flags; + int gfp = GFP_KERNEL; + int ret = 0; + + if (!plat_priv) + return -ENODEV; + + cnss_pr_dbg("Posting event: %s(%d)%s, state: 0x%lx flags: 0x%0x\n", + cnss_driver_event_to_str(type), type, + flags ? "-sync" : "", plat_priv->driver_state, flags); + + if (type >= CNSS_DRIVER_EVENT_MAX) { + cnss_pr_err("Invalid Event type: %d, can't post", type); + return -EINVAL; + } + + if (in_interrupt() || irqs_disabled()) + gfp = GFP_ATOMIC; + + event = kzalloc(sizeof(*event), gfp); + if (!event) + return -ENOMEM; + + cnss_pm_stay_awake(plat_priv); + + event->type = type; + event->data = data; + init_completion(&event->complete); + event->ret = CNSS_EVENT_PENDING; + event->sync = !!(flags & CNSS_EVENT_SYNC); + + spin_lock_irqsave(&plat_priv->event_lock, irq_flags); + list_add_tail(&event->list, &plat_priv->event_list); + spin_unlock_irqrestore(&plat_priv->event_lock, irq_flags); + + queue_work(plat_priv->event_wq, &plat_priv->event_work); + + if (!(flags & CNSS_EVENT_SYNC)) + goto out; + + if (flags & CNSS_EVENT_UNINTERRUPTIBLE) + wait_for_completion(&event->complete); + else + ret = wait_for_completion_interruptible(&event->complete); + + cnss_pr_dbg("Completed event: %s(%d), state: 0x%lx, ret: %d/%d\n", + cnss_driver_event_to_str(type), type, + plat_priv->driver_state, ret, event->ret); + spin_lock_irqsave(&plat_priv->event_lock, irq_flags); + if (ret == -ERESTARTSYS && event->ret == CNSS_EVENT_PENDING) { + event->sync = false; + spin_unlock_irqrestore(&plat_priv->event_lock, irq_flags); + ret = -EINTR; + goto out; + } + spin_unlock_irqrestore(&plat_priv->event_lock, irq_flags); + + ret = event->ret; + kfree(event); + +out: + cnss_pm_relax(plat_priv); + return ret; +} + +int cnss_power_up(struct device *dev) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + unsigned int timeout; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return -ENODEV; + } + + cnss_pr_dbg("Powering up device\n"); + + ret = cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_POWER_UP, + CNSS_EVENT_SYNC, NULL); + if (ret) + goto out; + + if (plat_priv->device_id == QCA6174_DEVICE_ID) + goto out; + + timeout = cnss_get_qmi_timeout(); + + reinit_completion(&plat_priv->power_up_complete); + ret = wait_for_completion_timeout(&plat_priv->power_up_complete, + msecs_to_jiffies(timeout) << 2); + if (!ret) { + cnss_pr_err("Timeout waiting for power up to complete\n"); + ret = -EAGAIN; + goto out; + } + + return 0; + +out: + return ret; +} +EXPORT_SYMBOL(cnss_power_up); + +int cnss_power_down(struct device *dev) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return -ENODEV; + } + + cnss_pr_dbg("Powering down device\n"); + + return cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_POWER_DOWN, + CNSS_EVENT_SYNC, NULL); +} +EXPORT_SYMBOL(cnss_power_down); + +static int cnss_get_resources(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + ret = cnss_get_vreg(plat_priv); + if (ret) { + cnss_pr_err("Failed to get vreg, err = %d\n", ret); + goto out; + } + + ret = cnss_get_pinctrl(plat_priv); + if (ret) { + cnss_pr_err("Failed to get pinctrl, err = %d\n", ret); + goto out; + } + + return 0; +out: + return ret; +} + +static void cnss_put_resources(struct cnss_plat_data *plat_priv) +{ +} + +static int cnss_modem_notifier_nb(struct notifier_block *nb, + unsigned long code, + void *ss_handle) +{ + struct cnss_plat_data *plat_priv = + container_of(nb, struct cnss_plat_data, modem_nb); + struct cnss_esoc_info *esoc_info; + + cnss_pr_dbg("Modem notifier: event %lu\n", code); + + if (!plat_priv) + return NOTIFY_DONE; + + esoc_info = &plat_priv->esoc_info; + + if (code == SUBSYS_AFTER_POWERUP) + esoc_info->modem_current_status = 1; + else if (code == SUBSYS_BEFORE_SHUTDOWN) + esoc_info->modem_current_status = 0; + else + return NOTIFY_DONE; + + if (!cnss_bus_call_driver_modem_status(plat_priv, + esoc_info->modem_current_status)) + return NOTIFY_DONE; + + return NOTIFY_OK; +} + +static int cnss_register_esoc(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct device *dev; + struct cnss_esoc_info *esoc_info; + struct esoc_desc *esoc_desc; + const char *client_desc; + + dev = &plat_priv->plat_dev->dev; + esoc_info = &plat_priv->esoc_info; + + esoc_info->notify_modem_status = + of_property_read_bool(dev->of_node, + "qcom,notify-modem-status"); + + if (esoc_info->notify_modem_status) + goto out; + + ret = of_property_read_string_index(dev->of_node, "esoc-names", 0, + &client_desc); + if (ret) { + cnss_pr_dbg("esoc-names is not defined in DT, skip!\n"); + } else { + esoc_desc = devm_register_esoc_client(dev, client_desc); + if (IS_ERR_OR_NULL(esoc_desc)) { + ret = PTR_RET(esoc_desc); + cnss_pr_err("Failed to register esoc_desc, err = %d\n", + ret); + goto out; + } + esoc_info->esoc_desc = esoc_desc; + } + + plat_priv->modem_nb.notifier_call = cnss_modem_notifier_nb; + esoc_info->modem_current_status = 0; + esoc_info->modem_notify_handler = + subsys_notif_register_notifier(esoc_info->esoc_desc ? + esoc_info->esoc_desc->name : + "modem", &plat_priv->modem_nb); + if (IS_ERR(esoc_info->modem_notify_handler)) { + ret = PTR_ERR(esoc_info->modem_notify_handler); + cnss_pr_err("Failed to register esoc notifier, err = %d\n", + ret); + goto unreg_esoc; + } + + return 0; +unreg_esoc: + if (esoc_info->esoc_desc) + devm_unregister_esoc_client(dev, esoc_info->esoc_desc); +out: + return ret; +} + +static void cnss_unregister_esoc(struct cnss_plat_data *plat_priv) +{ + struct device *dev; + struct cnss_esoc_info *esoc_info; + + dev = &plat_priv->plat_dev->dev; + esoc_info = &plat_priv->esoc_info; + + if (esoc_info->notify_modem_status) + subsys_notif_unregister_notifier(esoc_info-> + modem_notify_handler, + &plat_priv->modem_nb); + if (esoc_info->esoc_desc) + devm_unregister_esoc_client(dev, esoc_info->esoc_desc); +} + +static int cnss_subsys_powerup(const struct subsys_desc *subsys_desc) +{ + struct cnss_plat_data *plat_priv; + + if (!subsys_desc->dev) { + cnss_pr_err("dev from subsys_desc is NULL\n"); + return -ENODEV; + } + + plat_priv = dev_get_drvdata(subsys_desc->dev); + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return -ENODEV; + } + + if (!plat_priv->driver_state) { + cnss_pr_dbg("Powerup is ignored\n"); + return 0; + } + + return cnss_bus_dev_powerup(plat_priv); +} + +static int cnss_subsys_shutdown(const struct subsys_desc *subsys_desc, + bool force_stop) +{ + struct cnss_plat_data *plat_priv; + + if (!subsys_desc->dev) { + cnss_pr_err("dev from subsys_desc is NULL\n"); + return -ENODEV; + } + + plat_priv = dev_get_drvdata(subsys_desc->dev); + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return -ENODEV; + } + + if (!plat_priv->driver_state) { + cnss_pr_dbg("shutdown is ignored\n"); + return 0; + } + + return cnss_bus_dev_shutdown(plat_priv); +} + +static int cnss_subsys_ramdump(int enable, + const struct subsys_desc *subsys_desc) +{ + struct cnss_plat_data *plat_priv = dev_get_drvdata(subsys_desc->dev); + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return -ENODEV; + } + + if (!enable) + return 0; + + return cnss_bus_dev_ramdump(plat_priv); +} + +void *cnss_get_virt_ramdump_mem(struct device *dev, unsigned long *size) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct cnss_ramdump_info *ramdump_info; + + if (!plat_priv) + return NULL; + + ramdump_info = &plat_priv->ramdump_info; + *size = ramdump_info->ramdump_size; + + return ramdump_info->ramdump_va; +} +EXPORT_SYMBOL(cnss_get_virt_ramdump_mem); + +void cnss_device_crashed(struct device *dev) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct cnss_subsys_info *subsys_info; + + if (!plat_priv) + return; + + subsys_info = &plat_priv->subsys_info; + if (subsys_info->subsys_device) { + set_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); + subsys_set_crash_status(subsys_info->subsys_device, true); + subsystem_restart_dev(subsys_info->subsys_device); + } +} +EXPORT_SYMBOL(cnss_device_crashed); + +static void cnss_subsys_crash_shutdown(const struct subsys_desc *subsys_desc) +{ + struct cnss_plat_data *plat_priv = dev_get_drvdata(subsys_desc->dev); + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return; + } + cnss_bus_dev_crash_shutdown(plat_priv); +} + +static const char *cnss_recovery_reason_to_str(enum cnss_recovery_reason reason) +{ + switch (reason) { + case CNSS_REASON_DEFAULT: + return "DEFAULT"; + case CNSS_REASON_LINK_DOWN: + return "LINK_DOWN"; + case CNSS_REASON_RDDM: + return "RDDM"; + case CNSS_REASON_TIMEOUT: + return "TIMEOUT"; + } + + return "UNKNOWN"; +}; + +static int cnss_do_recovery(struct cnss_plat_data *plat_priv, + enum cnss_recovery_reason reason) +{ + struct cnss_subsys_info *subsys_info = + &plat_priv->subsys_info; + + plat_priv->recovery_count++; + + if (plat_priv->device_id == QCA6174_DEVICE_ID) + goto self_recovery; + + cnss_bus_recovery_update_status(plat_priv); + + if (test_bit(SKIP_RECOVERY, &quirks)) { + cnss_pr_dbg("Skip device recovery\n"); + return 0; + } + + switch (reason) { + case CNSS_REASON_LINK_DOWN: + if (test_bit(LINK_DOWN_SELF_RECOVERY, &quirks)) + goto self_recovery; + break; + case CNSS_REASON_RDDM: + cnss_bus_collect_dump_info(plat_priv); + clear_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state); + break; + case CNSS_REASON_DEFAULT: + case CNSS_REASON_TIMEOUT: + break; + default: + cnss_pr_err("Unsupported recovery reason: %s(%d)\n", + cnss_recovery_reason_to_str(reason), reason); + break; + } + + if (!subsys_info->subsys_device) + return 0; + + subsys_set_crash_status(subsys_info->subsys_device, true); + subsystem_restart_dev(subsys_info->subsys_device); + + return 0; + +self_recovery: + cnss_bus_dev_shutdown(plat_priv); + cnss_bus_dev_powerup(plat_priv); + + return 0; +} + +static int cnss_driver_recovery_hdlr(struct cnss_plat_data *plat_priv, + void *data) +{ + struct cnss_recovery_data *recovery_data = data; + int ret = 0; + + cnss_pr_dbg("Driver recovery is triggered with reason: %s(%d)\n", + cnss_recovery_reason_to_str(recovery_data->reason), + recovery_data->reason); + + if (!plat_priv->driver_state) { + cnss_pr_err("Improper driver state, ignore recovery\n"); + ret = -EINVAL; + goto out; + } + + if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { + cnss_pr_err("Recovery is already in progress\n"); + ret = -EINVAL; + goto out; + } + + if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) { + cnss_pr_err("Driver unload is in progress, ignore recovery\n"); + ret = -EINVAL; + goto out; + } + + switch (plat_priv->device_id) { + case QCA6174_DEVICE_ID: + if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state)) { + cnss_pr_err("Driver load is in progress, ignore recovery\n"); + ret = -EINVAL; + goto out; + } + break; + default: + if (!test_bit(CNSS_FW_READY, &plat_priv->driver_state)) { + set_bit(CNSS_FW_BOOT_RECOVERY, + &plat_priv->driver_state); + } + break; + } + + set_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); + ret = cnss_do_recovery(plat_priv, recovery_data->reason); + +out: + kfree(data); + return ret; +} + +int cnss_self_recovery(struct device *dev, + enum cnss_recovery_reason reason) +{ + cnss_schedule_recovery(dev, reason); + return 0; +} +EXPORT_SYMBOL(cnss_self_recovery); + +void cnss_schedule_recovery(struct device *dev, + enum cnss_recovery_reason reason) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct cnss_recovery_data *data; + int gfp = GFP_KERNEL; + + if (in_interrupt() || irqs_disabled()) + gfp = GFP_ATOMIC; + + data = kzalloc(sizeof(*data), gfp); + if (!data) + return; + + data->reason = reason; + cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_RECOVERY, + 0, data); +} +EXPORT_SYMBOL(cnss_schedule_recovery); + +int cnss_force_fw_assert(struct device *dev) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return -ENODEV; + } + + if (plat_priv->device_id == QCA6174_DEVICE_ID) { + cnss_pr_info("Forced FW assert is not supported\n"); + return -EOPNOTSUPP; + } + + if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { + cnss_pr_info("Recovery is already in progress, ignore forced FW assert\n"); + return 0; + } + + cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_FORCE_FW_ASSERT, + 0, NULL); + + return 0; +} +EXPORT_SYMBOL(cnss_force_fw_assert); + +static int cnss_cold_boot_cal_start_hdlr(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + set_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state); + ret = cnss_bus_dev_powerup(plat_priv); + if (ret) + clear_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state); + + return ret; +} + +static int cnss_cold_boot_cal_done_hdlr(struct cnss_plat_data *plat_priv) +{ + plat_priv->cal_done = true; + cnss_wlfw_wlan_mode_send_sync(plat_priv, QMI_WLFW_OFF_V01); + if (plat_priv->device_id == QCN7605_DEVICE_ID) + goto skip_shutdown; + cnss_bus_dev_shutdown(plat_priv); +skip_shutdown: + clear_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state); + + return 0; +} + +static int cnss_power_up_hdlr(struct cnss_plat_data *plat_priv) +{ + return cnss_bus_dev_powerup(plat_priv); +} + +static int cnss_power_down_hdlr(struct cnss_plat_data *plat_priv) +{ + cnss_bus_dev_shutdown(plat_priv); + + return 0; +} + +static void cnss_driver_event_work(struct work_struct *work) +{ + struct cnss_plat_data *plat_priv = + container_of(work, struct cnss_plat_data, event_work); + struct cnss_driver_event *event; + unsigned long flags; + int ret = 0; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return; + } + + cnss_pm_stay_awake(plat_priv); + + spin_lock_irqsave(&plat_priv->event_lock, flags); + + while (!list_empty(&plat_priv->event_list)) { + event = list_first_entry(&plat_priv->event_list, + struct cnss_driver_event, list); + list_del(&event->list); + spin_unlock_irqrestore(&plat_priv->event_lock, flags); + + cnss_pr_dbg("Processing driver event: %s%s(%d), state: 0x%lx\n", + cnss_driver_event_to_str(event->type), + event->sync ? "-sync" : "", event->type, + plat_priv->driver_state); + + switch (event->type) { + case CNSS_DRIVER_EVENT_SERVER_ARRIVE: + ret = cnss_wlfw_server_arrive(plat_priv); + break; + case CNSS_DRIVER_EVENT_SERVER_EXIT: + ret = cnss_wlfw_server_exit(plat_priv); + break; + case CNSS_DRIVER_EVENT_REQUEST_MEM: + ret = cnss_bus_alloc_fw_mem(plat_priv); + if (ret) + break; + ret = cnss_wlfw_respond_mem_send_sync(plat_priv); + break; + case CNSS_DRIVER_EVENT_FW_MEM_READY: + ret = cnss_fw_mem_ready_hdlr(plat_priv); + break; + case CNSS_DRIVER_EVENT_FW_READY: + ret = cnss_fw_ready_hdlr(plat_priv); + break; + case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START: + ret = cnss_cold_boot_cal_start_hdlr(plat_priv); + break; + case CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE: + ret = cnss_cold_boot_cal_done_hdlr(plat_priv); + break; + case CNSS_DRIVER_EVENT_REGISTER_DRIVER: + ret = cnss_bus_register_driver_hdlr(plat_priv, + event->data); + break; + case CNSS_DRIVER_EVENT_UNREGISTER_DRIVER: + ret = cnss_bus_unregister_driver_hdlr(plat_priv); + break; + case CNSS_DRIVER_EVENT_RECOVERY: + ret = cnss_driver_recovery_hdlr(plat_priv, + event->data); + break; + case CNSS_DRIVER_EVENT_FORCE_FW_ASSERT: + ret = cnss_bus_force_fw_assert_hdlr(plat_priv); + break; + case CNSS_DRIVER_EVENT_POWER_UP: + ret = cnss_power_up_hdlr(plat_priv); + break; + case CNSS_DRIVER_EVENT_POWER_DOWN: + ret = cnss_power_down_hdlr(plat_priv); + break; + default: + cnss_pr_err("Invalid driver event type: %d", + event->type); + kfree(event); + spin_lock_irqsave(&plat_priv->event_lock, flags); + continue; + } + + spin_lock_irqsave(&plat_priv->event_lock, flags); + if (event->sync) { + event->ret = ret; + complete(&event->complete); + continue; + } + spin_unlock_irqrestore(&plat_priv->event_lock, flags); + + kfree(event); + + spin_lock_irqsave(&plat_priv->event_lock, flags); + } + spin_unlock_irqrestore(&plat_priv->event_lock, flags); + + cnss_pm_relax(plat_priv); +} + +int cnss_register_subsys(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct cnss_subsys_info *subsys_info; + + subsys_info = &plat_priv->subsys_info; + + switch (plat_priv->device_id) { + case QCA6174_DEVICE_ID: + subsys_info->subsys_desc.name = "AR6320"; + break; + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + subsys_info->subsys_desc.name = "QCA6290"; + break; + case QCN7605_DEVICE_ID: + subsys_info->subsys_desc.name = "QCN7605"; + break; + default: + cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id); + ret = -ENODEV; + goto out; + } + + subsys_info->subsys_desc.owner = THIS_MODULE; + subsys_info->subsys_desc.powerup = cnss_subsys_powerup; + subsys_info->subsys_desc.shutdown = cnss_subsys_shutdown; + subsys_info->subsys_desc.ramdump = cnss_subsys_ramdump; + subsys_info->subsys_desc.crash_shutdown = cnss_subsys_crash_shutdown; + subsys_info->subsys_desc.dev = &plat_priv->plat_dev->dev; + + subsys_info->subsys_device = subsys_register(&subsys_info->subsys_desc); + if (IS_ERR(subsys_info->subsys_device)) { + ret = PTR_ERR(subsys_info->subsys_device); + cnss_pr_err("Failed to register subsys, err = %d\n", ret); + goto out; + } + + subsys_info->subsys_handle = + subsystem_get(subsys_info->subsys_desc.name); + if (!subsys_info->subsys_handle) { + cnss_pr_err("Failed to get subsys_handle!\n"); + ret = -EINVAL; + goto unregister_subsys; + } else if (IS_ERR(subsys_info->subsys_handle)) { + ret = PTR_ERR(subsys_info->subsys_handle); + cnss_pr_err("Failed to do subsystem_get, err = %d\n", ret); + goto unregister_subsys; + } + + return 0; + +unregister_subsys: + subsys_unregister(subsys_info->subsys_device); +out: + return ret; +} + +void cnss_unregister_subsys(struct cnss_plat_data *plat_priv) +{ + struct cnss_subsys_info *subsys_info; + + subsys_info = &plat_priv->subsys_info; + subsystem_put(subsys_info->subsys_handle); + subsys_unregister(subsys_info->subsys_device); +} + +static int cnss_init_dump_entry(struct cnss_plat_data *plat_priv) +{ + struct cnss_ramdump_info *ramdump_info; + struct msm_dump_entry dump_entry; + + ramdump_info = &plat_priv->ramdump_info; + ramdump_info->dump_data.addr = ramdump_info->ramdump_pa; + ramdump_info->dump_data.len = ramdump_info->ramdump_size; + ramdump_info->dump_data.version = CNSS_DUMP_FORMAT_VER; + ramdump_info->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2; + strlcpy(ramdump_info->dump_data.name, CNSS_DUMP_NAME, + sizeof(ramdump_info->dump_data.name)); + dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; + dump_entry.addr = virt_to_phys(&ramdump_info->dump_data); + + return msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); +} + +static int cnss_register_ramdump_v1(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct device *dev; + struct cnss_subsys_info *subsys_info; + struct cnss_ramdump_info *ramdump_info; + u32 ramdump_size = 0; + + dev = &plat_priv->plat_dev->dev; + subsys_info = &plat_priv->subsys_info; + ramdump_info = &plat_priv->ramdump_info; + + if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", + &ramdump_size) == 0) { + ramdump_info->ramdump_va = dma_alloc_coherent(dev, ramdump_size, + &ramdump_info->ramdump_pa, GFP_KERNEL); + + if (ramdump_info->ramdump_va) + ramdump_info->ramdump_size = ramdump_size; + } + + cnss_pr_dbg("ramdump va: %pK, pa: %pa\n", + ramdump_info->ramdump_va, &ramdump_info->ramdump_pa); + + if (ramdump_info->ramdump_size == 0) { + cnss_pr_info("Ramdump will not be collected"); + goto out; + } + + ret = cnss_init_dump_entry(plat_priv); + if (ret) { + cnss_pr_err("Failed to setup dump table, err = %d\n", ret); + goto free_ramdump; + } + + ramdump_info->ramdump_dev = create_ramdump_device( + subsys_info->subsys_desc.name, subsys_info->subsys_desc.dev); + if (!ramdump_info->ramdump_dev) { + cnss_pr_err("Failed to create ramdump device!"); + ret = -ENOMEM; + goto free_ramdump; + } + + return 0; +free_ramdump: + dma_free_coherent(dev, ramdump_info->ramdump_size, + ramdump_info->ramdump_va, ramdump_info->ramdump_pa); +out: + return ret; +} + +static void cnss_unregister_ramdump_v1(struct cnss_plat_data *plat_priv) +{ + struct device *dev; + struct cnss_ramdump_info *ramdump_info; + + dev = &plat_priv->plat_dev->dev; + ramdump_info = &plat_priv->ramdump_info; + + if (ramdump_info->ramdump_dev) + destroy_ramdump_device(ramdump_info->ramdump_dev); + + if (ramdump_info->ramdump_va) + dma_free_coherent(dev, ramdump_info->ramdump_size, + ramdump_info->ramdump_va, + ramdump_info->ramdump_pa); +} + +static int cnss_register_ramdump_v2(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct cnss_subsys_info *subsys_info; + struct cnss_ramdump_info_v2 *info_v2; + struct cnss_dump_data *dump_data; + struct msm_dump_entry dump_entry; + struct device *dev = &plat_priv->plat_dev->dev; + u32 ramdump_size = 0; + + subsys_info = &plat_priv->subsys_info; + info_v2 = &plat_priv->ramdump_info_v2; + dump_data = &info_v2->dump_data; + + if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", + &ramdump_size) == 0) + info_v2->ramdump_size = ramdump_size; + + cnss_pr_dbg("Ramdump size 0x%lx\n", info_v2->ramdump_size); + + info_v2->dump_data_vaddr = kzalloc(CNSS_DUMP_DESC_SIZE, GFP_KERNEL); + if (!info_v2->dump_data_vaddr) + return -ENOMEM; + + dump_data->paddr = virt_to_phys(info_v2->dump_data_vaddr); + dump_data->version = CNSS_DUMP_FORMAT_VER_V2; + dump_data->magic = CNSS_DUMP_MAGIC_VER_V2; + dump_data->seg_version = CNSS_DUMP_SEG_VER; + strlcpy(dump_data->name, CNSS_DUMP_NAME, + sizeof(dump_data->name)); + dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; + dump_entry.addr = virt_to_phys(dump_data); + + ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); + if (ret) { + cnss_pr_err("Failed to setup dump table, err = %d\n", ret); + goto free_ramdump; + } + + info_v2->ramdump_dev = + create_ramdump_device(subsys_info->subsys_desc.name, + subsys_info->subsys_desc.dev); + if (!info_v2->ramdump_dev) { + cnss_pr_err("Failed to create ramdump device!\n"); + ret = -ENOMEM; + goto free_ramdump; + } + + return 0; + +free_ramdump: + kfree(info_v2->dump_data_vaddr); + info_v2->dump_data_vaddr = NULL; + return ret; +} + +static void cnss_unregister_ramdump_v2(struct cnss_plat_data *plat_priv) +{ + struct cnss_ramdump_info_v2 *info_v2; + + info_v2 = &plat_priv->ramdump_info_v2; + + if (info_v2->ramdump_dev) + destroy_ramdump_device(info_v2->ramdump_dev); + + kfree(info_v2->dump_data_vaddr); + info_v2->dump_data_vaddr = NULL; + info_v2->dump_data_valid = false; +} + +int cnss_register_ramdump(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + switch (plat_priv->device_id) { + case QCA6174_DEVICE_ID: + ret = cnss_register_ramdump_v1(plat_priv); + break; + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + case QCN7605_DEVICE_ID: + ret = cnss_register_ramdump_v2(plat_priv); + break; + default: + cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id); + ret = -ENODEV; + break; + } + return ret; +} + +void cnss_unregister_ramdump(struct cnss_plat_data *plat_priv) +{ + switch (plat_priv->device_id) { + case QCA6174_DEVICE_ID: + cnss_unregister_ramdump_v1(plat_priv); + break; + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + cnss_unregister_ramdump_v2(plat_priv); + break; + default: + cnss_pr_err("Unknown device ID: 0x%lx\n", plat_priv->device_id); + break; + } +} + +static int cnss_register_bus_scale(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct cnss_bus_bw_info *bus_bw_info; + + bus_bw_info = &plat_priv->bus_bw_info; + + bus_bw_info->bus_scale_table = + msm_bus_cl_get_pdata(plat_priv->plat_dev); + if (bus_bw_info->bus_scale_table) { + bus_bw_info->bus_client = + msm_bus_scale_register_client( + bus_bw_info->bus_scale_table); + if (!bus_bw_info->bus_client) { + cnss_pr_err("Failed to register bus scale client!\n"); + ret = -EINVAL; + goto out; + } + } + + return 0; +out: + return ret; +} + +static void cnss_unregister_bus_scale(struct cnss_plat_data *plat_priv) +{ + struct cnss_bus_bw_info *bus_bw_info; + + bus_bw_info = &plat_priv->bus_bw_info; + + if (bus_bw_info->bus_client) + msm_bus_scale_unregister_client(bus_bw_info->bus_client); +} + +static ssize_t cnss_fs_ready_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int fs_ready = 0; + struct cnss_plat_data *plat_priv = dev_get_drvdata(dev); + + if (sscanf(buf, "%du", &fs_ready) != 1) + return -EINVAL; + + cnss_pr_dbg("File system is ready, fs_ready is %d, count is %zu\n", + fs_ready, count); + + if (qmi_bypass) { + cnss_pr_dbg("QMI is bypassed.\n"); + return count; + } + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return count; + } + + switch (plat_priv->device_id) { + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + case QCN7605_DEVICE_ID: + break; + default: + cnss_pr_err("Not supported for device ID 0x%lx\n", + plat_priv->device_id); + return count; + } + + if (fs_ready == FILE_SYSTEM_READY) { + cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START, + CNSS_EVENT_SYNC, NULL); + } + + return count; +} + +static DEVICE_ATTR(fs_ready, 0220, NULL, cnss_fs_ready_store); + +static int cnss_create_sysfs(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + ret = device_create_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready); + if (ret) { + cnss_pr_err("Failed to create device file, err = %d\n", ret); + goto out; + } + + return 0; +out: + return ret; +} + +static void cnss_remove_sysfs(struct cnss_plat_data *plat_priv) +{ + device_remove_file(&plat_priv->plat_dev->dev, &dev_attr_fs_ready); +} + +static int cnss_event_work_init(struct cnss_plat_data *plat_priv) +{ + spin_lock_init(&plat_priv->event_lock); + plat_priv->event_wq = alloc_workqueue("cnss_driver_event", + WQ_UNBOUND, 1); + if (!plat_priv->event_wq) { + cnss_pr_err("Failed to create event workqueue!\n"); + return -EFAULT; + } + + INIT_WORK(&plat_priv->event_work, cnss_driver_event_work); + INIT_LIST_HEAD(&plat_priv->event_list); + + return 0; +} + +static void cnss_event_work_deinit(struct cnss_plat_data *plat_priv) +{ + destroy_workqueue(plat_priv->event_wq); +} + +static const struct platform_device_id cnss_platform_id_table[] = { + { .name = "qca6174", .driver_data = QCA6174_DEVICE_ID, }, + { .name = "qca6290", .driver_data = QCA6290_DEVICE_ID, }, + { .name = "qcn7605", .driver_data = QCN7605_DEVICE_ID, }, +}; + +static const struct of_device_id cnss_of_match_table[] = { + { + .compatible = "qcom,cnss", + .data = (void *)&cnss_platform_id_table[0]}, + { + .compatible = "qcom,cnss-qca6290", + .data = (void *)&cnss_platform_id_table[1]}, + { }, +}; +MODULE_DEVICE_TABLE(of, cnss_of_match_table); + +static int cnss_probe(struct platform_device *plat_dev) +{ + int ret = 0; + struct cnss_plat_data *plat_priv; + const struct of_device_id *of_id; + const struct platform_device_id *device_id; + + if (cnss_get_plat_priv(plat_dev)) { + cnss_pr_err("Driver is already initialized!\n"); + ret = -EEXIST; + goto out; + } + + of_id = of_match_device(cnss_of_match_table, &plat_dev->dev); + if (!of_id || !of_id->data) { + cnss_pr_err("Failed to find of match device!\n"); + ret = -ENODEV; + goto out; + } + + device_id = of_id->data; + + plat_priv = devm_kzalloc(&plat_dev->dev, sizeof(*plat_priv), + GFP_KERNEL); + if (!plat_priv) { + ret = -ENOMEM; + goto out; + } + + plat_priv->plat_dev = plat_dev; + plat_priv->device_id = device_id->driver_data; + plat_priv->bus_type = cnss_get_bus_type(plat_priv->device_id); + cnss_set_plat_priv(plat_dev, plat_priv); + platform_set_drvdata(plat_dev, plat_priv); + + ret = cnss_get_resources(plat_priv); + if (ret) + goto reset_ctx; + + if (!test_bit(SKIP_DEVICE_BOOT, &quirks)) { + ret = cnss_power_on_device(plat_priv); + if (ret) + goto free_res; + + ret = cnss_bus_init(plat_priv); + if (ret) + goto power_off; + } + + ret = cnss_register_esoc(plat_priv); + if (ret) + goto deinit_bus; + + ret = cnss_register_bus_scale(plat_priv); + if (ret) + goto unreg_esoc; + + ret = cnss_create_sysfs(plat_priv); + if (ret) + goto unreg_bus_scale; + + ret = cnss_event_work_init(plat_priv); + if (ret) + goto remove_sysfs; + + ret = cnss_qmi_init(plat_priv); + if (ret) + goto deinit_event_work; + + ret = cnss_debugfs_create(plat_priv); + if (ret) + goto deinit_qmi; + + setup_timer(&plat_priv->fw_boot_timer, + cnss_bus_fw_boot_timeout_hdlr, (unsigned long)plat_priv); + + register_pm_notifier(&cnss_pm_notifier); + + ret = device_init_wakeup(&plat_dev->dev, true); + if (ret) + cnss_pr_err("Failed to init platform device wakeup source, err = %d\n", + ret); + + init_completion(&plat_priv->power_up_complete); + mutex_init(&plat_priv->dev_lock); + + cnss_pr_info("Platform driver probed successfully.\n"); + + return 0; + +deinit_qmi: + cnss_qmi_deinit(plat_priv); +deinit_event_work: + cnss_event_work_deinit(plat_priv); +remove_sysfs: + cnss_remove_sysfs(plat_priv); +unreg_bus_scale: + cnss_unregister_bus_scale(plat_priv); +unreg_esoc: + cnss_unregister_esoc(plat_priv); +deinit_bus: + if (!test_bit(SKIP_DEVICE_BOOT, &quirks)) + cnss_bus_deinit(plat_priv); +power_off: + if (!test_bit(SKIP_DEVICE_BOOT, &quirks)) + cnss_power_off_device(plat_priv); +free_res: + cnss_put_resources(plat_priv); +reset_ctx: + platform_set_drvdata(plat_dev, NULL); + cnss_set_plat_priv(plat_dev, NULL); +out: + return ret; +} + +static int cnss_remove(struct platform_device *plat_dev) +{ + struct cnss_plat_data *plat_priv = platform_get_drvdata(plat_dev); + + complete_all(&plat_priv->power_up_complete); + device_init_wakeup(&plat_dev->dev, false); + unregister_pm_notifier(&cnss_pm_notifier); + del_timer(&plat_priv->fw_boot_timer); + cnss_debugfs_destroy(plat_priv); + cnss_qmi_deinit(plat_priv); + cnss_event_work_deinit(plat_priv); + cnss_remove_sysfs(plat_priv); + cnss_unregister_bus_scale(plat_priv); + cnss_unregister_esoc(plat_priv); + cnss_bus_deinit(plat_priv); + cnss_put_resources(plat_priv); + platform_set_drvdata(plat_dev, NULL); + plat_env = NULL; + + return 0; +} + +static struct platform_driver cnss_platform_driver = { + .probe = cnss_probe, + .remove = cnss_remove, + .driver = { + .name = "cnss2", + .owner = THIS_MODULE, + .of_match_table = cnss_of_match_table, +#ifdef CONFIG_CNSS_ASYNC + .probe_type = PROBE_PREFER_ASYNCHRONOUS, +#endif + }, +}; + +static int __init cnss_initialize(void) +{ + int ret = 0; + + cnss_debug_init(); + ret = platform_driver_register(&cnss_platform_driver); + if (ret) + cnss_debug_deinit(); + + return ret; +} + +static void __exit cnss_exit(void) +{ + platform_driver_unregister(&cnss_platform_driver); + cnss_debug_deinit(); +} + +module_init(cnss_initialize); +module_exit(cnss_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CNSS2 Platform Driver"); diff --git a/drivers/net/wireless/cnss2/main.h b/drivers/net/wireless/cnss2/main.h new file mode 100644 index 000000000000..9dc64e016d82 --- /dev/null +++ b/drivers/net/wireless/cnss2/main.h @@ -0,0 +1,248 @@ +/* Copyright (c) 2016-2018, 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. + */ + +#ifndef _CNSS_MAIN_H +#define _CNSS_MAIN_H + +#include <linux/esoc_client.h> +#include <linux/etherdevice.h> +#include <linux/msm-bus.h> +#include <linux/pm_qos.h> +#include <net/cnss2.h> +#include <soc/qcom/memory_dump.h> +#include <soc/qcom/subsystem_restart.h> + +#include "qmi.h" + +#define MAX_NO_OF_MAC_ADDR 4 + +#define CNSS_EVENT_SYNC BIT(0) +#define CNSS_EVENT_UNINTERRUPTIBLE BIT(1) +#define CNSS_EVENT_SYNC_UNINTERRUPTIBLE (CNSS_EVENT_SYNC | \ + CNSS_EVENT_UNINTERRUPTIBLE) + +enum cnss_dev_bus_type { + CNSS_BUS_NONE = -1, + CNSS_BUS_PCI, +}; + +struct cnss_vreg_info { + struct regulator *reg; + const char *name; + u32 min_uv; + u32 max_uv; + u32 load_ua; + u32 delay_us; +}; + +struct cnss_pinctrl_info { + struct pinctrl *pinctrl; + struct pinctrl_state *bootstrap_active; + struct pinctrl_state *wlan_en_active; + struct pinctrl_state *wlan_en_sleep; +}; + +struct cnss_subsys_info { + struct subsys_device *subsys_device; + struct subsys_desc subsys_desc; + void *subsys_handle; +}; + +struct cnss_ramdump_info { + struct ramdump_device *ramdump_dev; + unsigned long ramdump_size; + void *ramdump_va; + phys_addr_t ramdump_pa; + struct msm_dump_data dump_data; +}; + +struct cnss_dump_seg { + unsigned long address; + void *v_address; + unsigned long size; + u32 type; +}; + +struct cnss_dump_data { + u32 version; + u32 magic; + char name[32]; + phys_addr_t paddr; + int nentries; + u32 seg_version; +}; + +struct cnss_ramdump_info_v2 { + struct ramdump_device *ramdump_dev; + unsigned long ramdump_size; + void *dump_data_vaddr; + bool dump_data_valid; + struct cnss_dump_data dump_data; +}; + +struct cnss_esoc_info { + struct esoc_desc *esoc_desc; + bool notify_modem_status; + void *modem_notify_handler; + int modem_current_status; +}; + +struct cnss_bus_bw_info { + struct msm_bus_scale_pdata *bus_scale_table; + u32 bus_client; + int current_bw_vote; +}; + +struct cnss_fw_mem { + size_t size; + void *va; + phys_addr_t pa; + bool valid; + u32 type; +}; + +enum cnss_driver_event_type { + CNSS_DRIVER_EVENT_SERVER_ARRIVE, + CNSS_DRIVER_EVENT_SERVER_EXIT, + CNSS_DRIVER_EVENT_REQUEST_MEM, + CNSS_DRIVER_EVENT_FW_MEM_READY, + CNSS_DRIVER_EVENT_FW_READY, + CNSS_DRIVER_EVENT_COLD_BOOT_CAL_START, + CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE, + CNSS_DRIVER_EVENT_REGISTER_DRIVER, + CNSS_DRIVER_EVENT_UNREGISTER_DRIVER, + CNSS_DRIVER_EVENT_RECOVERY, + CNSS_DRIVER_EVENT_FORCE_FW_ASSERT, + CNSS_DRIVER_EVENT_POWER_UP, + CNSS_DRIVER_EVENT_POWER_DOWN, + CNSS_DRIVER_EVENT_MAX, +}; + +enum cnss_driver_state { + CNSS_QMI_WLFW_CONNECTED, + CNSS_FW_MEM_READY, + CNSS_FW_READY, + CNSS_COLD_BOOT_CAL, + CNSS_DRIVER_LOADING, + CNSS_DRIVER_UNLOADING, + CNSS_DRIVER_PROBED, + CNSS_DRIVER_RECOVERY, + CNSS_FW_BOOT_RECOVERY, + CNSS_DEV_ERR_NOTIFY, + CNSS_DRIVER_DEBUG, +}; + +struct cnss_recovery_data { + enum cnss_recovery_reason reason; +}; + +enum cnss_pins { + CNSS_WLAN_EN, + CNSS_PCIE_TXP, + CNSS_PCIE_TXN, + CNSS_PCIE_RXP, + CNSS_PCIE_RXN, + CNSS_PCIE_REFCLKP, + CNSS_PCIE_REFCLKN, + CNSS_PCIE_RST, + CNSS_PCIE_WAKE, +}; + +struct cnss_pin_connect_result { + u32 fw_pwr_pin_result; + u32 fw_phy_io_pin_result; + u32 fw_rf_pin_result; + u32 host_pin_result; +}; + +enum cnss_debug_quirks { + LINK_DOWN_SELF_RECOVERY, + SKIP_DEVICE_BOOT, + USE_CORE_ONLY_FW, + SKIP_RECOVERY, +}; + +struct cnss_plat_data { + struct platform_device *plat_dev; + void *bus_priv; + enum cnss_dev_bus_type bus_type; + struct cnss_vreg_info *vreg_info; + struct cnss_pinctrl_info pinctrl_info; + struct cnss_subsys_info subsys_info; + struct cnss_ramdump_info ramdump_info; + struct cnss_ramdump_info_v2 ramdump_info_v2; + struct cnss_esoc_info esoc_info; + struct cnss_bus_bw_info bus_bw_info; + struct notifier_block modem_nb; + struct cnss_platform_cap cap; + struct pm_qos_request qos_request; + unsigned long device_id; + enum cnss_driver_status driver_status; + u32 recovery_count; + unsigned long driver_state; + struct list_head event_list; + spinlock_t event_lock; /* spinlock for driver work event handling */ + struct work_struct event_work; + struct workqueue_struct *event_wq; + struct qmi_handle *qmi_wlfw_clnt; + struct work_struct qmi_recv_msg_work; + struct notifier_block qmi_wlfw_clnt_nb; + struct wlfw_rf_chip_info_s_v01 chip_info; + struct wlfw_rf_board_info_s_v01 board_info; + struct wlfw_soc_info_s_v01 soc_info; + struct wlfw_fw_version_info_s_v01 fw_version_info; + u32 fw_mem_seg_len; + struct cnss_fw_mem fw_mem[QMI_WLFW_MAX_NUM_MEM_SEG_V01]; + struct cnss_fw_mem m3_mem; + struct cnss_pin_connect_result pin_result; + struct dentry *root_dentry; + atomic_t pm_count; + struct timer_list fw_boot_timer; + struct completion power_up_complete; + struct mutex dev_lock; /* mutex for register access through debugfs */ + u32 diag_reg_read_addr; + u32 diag_reg_read_mem_type; + u32 diag_reg_read_len; + u8 *diag_reg_read_buf; + bool cal_done; +}; + +struct cnss_plat_data *cnss_get_plat_priv(struct platform_device *plat_dev); +unsigned long *cnss_get_debug_quirks(void); +int cnss_driver_event_post(struct cnss_plat_data *plat_priv, + enum cnss_driver_event_type type, + u32 flags, void *data); +int cnss_get_vreg(struct cnss_plat_data *plat_priv); +int cnss_get_pinctrl(struct cnss_plat_data *plat_priv); + +#ifndef CONFIG_MSM_GVM_QUIN +int cnss_power_on_device(struct cnss_plat_data *plat_priv); +void cnss_power_off_device(struct cnss_plat_data *plat_priv); +#else /* CONFIG_MSM_GVM_QUIN */ +static inline int cnss_power_on_device(struct cnss_plat_data *plat_priv) +{ + return 0; +} + +static inline void cnss_power_off_device(struct cnss_plat_data *plat_priv) +{ +} +#endif /* CONFIG_MSM_GVM_QUIN */ + +int cnss_register_subsys(struct cnss_plat_data *plat_priv); +void cnss_unregister_subsys(struct cnss_plat_data *plat_priv); +int cnss_register_ramdump(struct cnss_plat_data *plat_priv); +void cnss_unregister_ramdump(struct cnss_plat_data *plat_priv); +void cnss_set_pin_connect_status(struct cnss_plat_data *plat_priv); +u32 cnss_get_wake_msi(struct cnss_plat_data *plat_priv); +bool *cnss_get_qmi_bypass(void); +#endif /* _CNSS_MAIN_H */ diff --git a/drivers/net/wireless/cnss2/pci.c b/drivers/net/wireless/cnss2/pci.c new file mode 100644 index 000000000000..1d4b5b9ea9c9 --- /dev/null +++ b/drivers/net/wireless/cnss2/pci.c @@ -0,0 +1,2412 @@ +/* Copyright (c) 2016-2018, 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/firmware.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/msi.h> +#include <linux/of.h> +#include <linux/pm_runtime.h> +#include <linux/memblock.h> +#include <soc/qcom/ramdump.h> + +#include "main.h" +#include "bus.h" +#include "debug.h" +#include "pci.h" + +#define PCI_LINK_UP 1 +#define PCI_LINK_DOWN 0 + +#define SAVE_PCI_CONFIG_SPACE 1 +#define RESTORE_PCI_CONFIG_SPACE 0 + +#define PM_OPTIONS_DEFAULT 0 +#define PM_OPTIONS_LINK_DOWN \ + (MSM_PCIE_CONFIG_NO_CFG_RESTORE | MSM_PCIE_CONFIG_LINKDOWN) + +#define PCI_BAR_NUM 0 + +#ifdef CONFIG_ARM_LPAE +#define PCI_DMA_MASK 64 +#else +#define PCI_DMA_MASK 32 +#endif + +#define MHI_NODE_NAME "qcom,mhi" +#define MHI_MSI_NAME "MHI" + +#define MAX_M3_FILE_NAME_LENGTH 13 +#define DEFAULT_M3_FILE_NAME "m3.bin" + +#define WAKE_MSI_NAME "WAKE" + +#define FW_ASSERT_TIMEOUT 5000 + +#ifdef CONFIG_PCI_MSM +static DEFINE_SPINLOCK(pci_link_down_lock); +#endif + +static unsigned int pci_link_down_panic; +module_param(pci_link_down_panic, uint, 0600); +MODULE_PARM_DESC(pci_link_down_panic, + "Trigger kernel panic when PCI link down is detected"); + +static bool fbc_bypass; +#ifdef CONFIG_CNSS2_DEBUG +module_param(fbc_bypass, bool, 0600); +MODULE_PARM_DESC(fbc_bypass, + "Bypass firmware download when loading WLAN driver"); +#endif + +static int cnss_set_pci_config_space(struct cnss_pci_data *pci_priv, bool save) +{ + struct pci_dev *pci_dev = pci_priv->pci_dev; + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + bool link_down_or_recovery; + + if (!plat_priv) + return -ENODEV; + + link_down_or_recovery = pci_priv->pci_link_down_ind || + (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)); + + if (save) { + if (link_down_or_recovery) { + pci_priv->saved_state = NULL; + } else { + pci_save_state(pci_dev); + pci_priv->saved_state = pci_store_saved_state(pci_dev); + } + } else { + if (link_down_or_recovery) { + pci_load_saved_state(pci_dev, pci_priv->default_state); + pci_restore_state(pci_dev); + } else if (pci_priv->saved_state) { + pci_load_and_free_saved_state(pci_dev, + &pci_priv->saved_state); + pci_restore_state(pci_dev); + } + } + + return 0; +} + +#ifdef CONFIG_PCI_MSM +static int cnss_set_pci_link(struct cnss_pci_data *pci_priv, bool link_up) +{ + int ret = 0; + struct pci_dev *pci_dev = pci_priv->pci_dev; + + ret = msm_pcie_pm_control(link_up ? MSM_PCIE_RESUME : + MSM_PCIE_SUSPEND, + pci_dev->bus->number, + pci_dev, NULL, + PM_OPTIONS_DEFAULT); + if (ret) { + cnss_pr_err("Failed to %s PCI link with default option, err = %d\n", + link_up ? "resume" : "suspend", ret); + return ret; + } + + return 0; +} + +int cnss_suspend_pci_link(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + + if (!pci_priv) + return -ENODEV; + + cnss_pr_dbg("Suspending PCI link\n"); + if (!pci_priv->pci_link_state) { + cnss_pr_info("PCI link is already suspended!\n"); + ret = -EINVAL; + goto out; + } + + ret = cnss_set_pci_config_space(pci_priv, SAVE_PCI_CONFIG_SPACE); + if (ret) + goto out; + + pci_disable_device(pci_priv->pci_dev); + + if (pci_priv->pci_dev->device != QCA6174_DEVICE_ID) { + ret = pci_set_power_state(pci_priv->pci_dev, PCI_D3hot); + if (ret) + cnss_pr_err("Failed to set D3Hot, err = %d\n", ret); + } + + ret = cnss_set_pci_link(pci_priv, PCI_LINK_DOWN); + if (ret) + goto out; + + pci_priv->pci_link_state = PCI_LINK_DOWN; + + return 0; +out: + return ret; +} + +int cnss_resume_pci_link(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + + if (!pci_priv) + return -ENODEV; + + cnss_pr_dbg("Resuming PCI link\n"); + if (pci_priv->pci_link_state) { + cnss_pr_info("PCI link is already resumed!\n"); + goto out; + } + + ret = cnss_set_pci_link(pci_priv, PCI_LINK_UP); + if (ret) + goto out; + + pci_priv->pci_link_state = PCI_LINK_UP; + + ret = cnss_set_pci_config_space(pci_priv, RESTORE_PCI_CONFIG_SPACE); + if (ret) + goto out; + + ret = pci_enable_device(pci_priv->pci_dev); + if (ret) { + cnss_pr_err("Failed to enable PCI device, err = %d\n", ret); + goto out; + } + + pci_set_master(pci_priv->pci_dev); + + if (pci_priv->pci_link_down_ind) + pci_priv->pci_link_down_ind = false; + + return 0; +out: + return ret; +} + +int cnss_pci_link_down(struct device *dev) +{ + unsigned long flags; + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev); + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL!\n"); + return -EINVAL; + } + + if (pci_link_down_panic) + panic("cnss: PCI link is down!\n"); + + spin_lock_irqsave(&pci_link_down_lock, flags); + if (pci_priv->pci_link_down_ind) { + cnss_pr_dbg("PCI link down recovery is in progress, ignore!\n"); + spin_unlock_irqrestore(&pci_link_down_lock, flags); + return -EINVAL; + } + pci_priv->pci_link_down_ind = true; + spin_unlock_irqrestore(&pci_link_down_lock, flags); + + cnss_pr_err("PCI link down is detected by host driver, schedule recovery!\n"); + + cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_NOTIFY_LINK_ERROR); + cnss_schedule_recovery(dev, CNSS_REASON_LINK_DOWN); + + return 0; +} +EXPORT_SYMBOL(cnss_pci_link_down); + +#else /* CONFIG_PCI_MSM */ +static int cnss_set_pci_link(struct cnss_pci_data *pci_priv, bool link_up) +{ + return 0; +} +#endif /* CONFIG_PCI_MSM */ + +int cnss_pci_is_device_down(struct device *dev) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct cnss_pci_data *pci_priv; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return -ENODEV; + } + + pci_priv = plat_priv->bus_priv; + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL\n"); + return -ENODEV; + } + + return test_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state) | + pci_priv->pci_link_down_ind; +} +EXPORT_SYMBOL(cnss_pci_is_device_down); + +int cnss_pci_recovery_update_status(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv; + + plat_priv = pci_priv->plat_priv; + + if (pci_priv->driver_ops && + test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) + pci_priv->driver_ops->update_status(pci_priv->pci_dev, + CNSS_RECOVERY); + return 0; +} + +int cnss_pci_call_driver_probe(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct cnss_plat_data *plat_priv; + + if (!pci_priv) + return -ENODEV; + + plat_priv = pci_priv->plat_priv; + + if (test_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state)) { + clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); + cnss_pr_dbg("Skip driver probe\n"); + goto out; + } + + if (!pci_priv->driver_ops) { + cnss_pr_err("driver_ops is NULL\n"); + ret = -EINVAL; + goto out; + } + + if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) && + test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) { + ret = pci_priv->driver_ops->reinit(pci_priv->pci_dev, + pci_priv->pci_device_id); + if (ret) { + cnss_pr_err("Failed to reinit host driver, err = %d\n", + ret); + goto out; + } + clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); + } else if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state)) { + ret = pci_priv->driver_ops->probe(pci_priv->pci_dev, + pci_priv->pci_device_id); + if (ret) { + cnss_pr_err("Failed to probe host driver, err = %d\n", + ret); + goto out; + } + clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); + clear_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); + set_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state); + } + + return 0; + +out: + return ret; +} + +int cnss_pci_call_driver_remove(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv; + + if (!pci_priv) + return -ENODEV; + + plat_priv = pci_priv->plat_priv; + + if (test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state) || + test_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state) || + test_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state)) { + cnss_pr_dbg("Skip driver remove\n"); + return 0; + } + + if (!pci_priv->driver_ops) { + cnss_pr_err("driver_ops is NULL\n"); + return -EINVAL; + } + + if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) && + test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) { + pci_priv->driver_ops->shutdown(pci_priv->pci_dev); + } else if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) { + pci_priv->driver_ops->remove(pci_priv->pci_dev); + clear_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state); + } + + return 0; +} + +int cnss_pci_call_driver_modem_status(struct cnss_pci_data *pci_priv, + int modem_current_status) +{ + struct cnss_wlan_driver *driver_ops; + + if (!pci_priv) + return -ENODEV; + + driver_ops = pci_priv->driver_ops; + if (!driver_ops || !driver_ops->modem_status) + return -EINVAL; + + driver_ops->modem_status(pci_priv->pci_dev, modem_current_status); + + return 0; +} + +static int cnss_qca6174_powerup(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + + ret = cnss_power_on_device(plat_priv); + if (ret) { + cnss_pr_err("Failed to power on device, err = %d\n", ret); + goto out; + } + + ret = cnss_resume_pci_link(pci_priv); + if (ret) { + cnss_pr_err("Failed to resume PCI link, err = %d\n", ret); + goto power_off; + } + + ret = cnss_pci_call_driver_probe(pci_priv); + if (ret) + goto suspend_link; + + return 0; +suspend_link: + cnss_suspend_pci_link(pci_priv); +power_off: + cnss_power_off_device(plat_priv); +out: + return ret; +} + +static int cnss_qca6174_shutdown(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + + cnss_pm_request_resume(pci_priv); + + cnss_pci_call_driver_remove(pci_priv); + + cnss_request_bus_bandwidth(&plat_priv->plat_dev->dev, + CNSS_BUS_WIDTH_NONE); + cnss_pci_set_monitor_wake_intr(pci_priv, false); + cnss_pci_set_auto_suspended(pci_priv, 0); + + ret = cnss_suspend_pci_link(pci_priv); + if (ret) { + cnss_pr_err("Failed to suspend PCI link, err = %d\n", ret); + return -EINVAL; + } + + cnss_power_off_device(plat_priv); + + return ret; +} + +static void cnss_qca6174_crash_shutdown(struct cnss_pci_data *pci_priv) +{ + if (pci_priv->driver_ops && pci_priv->driver_ops->crash_shutdown) + pci_priv->driver_ops->crash_shutdown(pci_priv->pci_dev); +} + +static int cnss_qca6174_ramdump(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + struct cnss_ramdump_info *ramdump_info; + struct ramdump_segment segment; + + ramdump_info = &plat_priv->ramdump_info; + if (!ramdump_info->ramdump_size) + return -EINVAL; + + memset(&segment, 0, sizeof(segment)); + segment.v_address = ramdump_info->ramdump_va; + segment.size = ramdump_info->ramdump_size; + ret = do_ramdump(ramdump_info->ramdump_dev, &segment, 1); + + return ret; +} + +static int cnss_qca6290_powerup(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + unsigned int timeout; + + if (plat_priv->ramdump_info_v2.dump_data_valid || + test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { + cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_DEINIT); + cnss_pci_clear_dump_info(pci_priv); + } + + ret = cnss_power_on_device(plat_priv); + if (ret) { + cnss_pr_err("Failed to power on device, err = %d\n", ret); + goto out; + } + + ret = cnss_resume_pci_link(pci_priv); + if (ret) { + cnss_pr_err("Failed to resume PCI link, err = %d\n", ret); + goto power_off; + } + + timeout = cnss_get_qmi_timeout(); + + ret = cnss_pci_start_mhi(pci_priv); + if (ret) { + cnss_pr_err("Failed to start MHI, err = %d\n", ret); + if (!test_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state) && + !pci_priv->pci_link_down_ind && timeout) + mod_timer(&plat_priv->fw_boot_timer, + jiffies + msecs_to_jiffies(timeout >> 1)); + return 0; + } + + if (test_bit(USE_CORE_ONLY_FW, cnss_get_debug_quirks())) { + clear_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state); + clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); + return 0; + } + + cnss_set_pin_connect_status(plat_priv); + + if (*cnss_get_qmi_bypass()) { + ret = cnss_pci_call_driver_probe(pci_priv); + if (ret) + goto stop_mhi; + } else if (timeout) { + mod_timer(&plat_priv->fw_boot_timer, + jiffies + msecs_to_jiffies(timeout << 1)); + } + + return 0; + +stop_mhi: + cnss_pci_stop_mhi(pci_priv); + cnss_suspend_pci_link(pci_priv); +power_off: + cnss_power_off_device(plat_priv); +out: + return ret; +} + +static int cnss_qca6290_shutdown(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + + cnss_pm_request_resume(pci_priv); + + cnss_pci_call_driver_remove(pci_priv); + + cnss_request_bus_bandwidth(&plat_priv->plat_dev->dev, + CNSS_BUS_WIDTH_NONE); + cnss_pci_set_monitor_wake_intr(pci_priv, false); + cnss_pci_set_auto_suspended(pci_priv, 0); + + cnss_pci_stop_mhi(pci_priv); + + ret = cnss_suspend_pci_link(pci_priv); + if (ret) + cnss_pr_err("Failed to suspend PCI link, err = %d\n", ret); + + cnss_power_off_device(plat_priv); + + clear_bit(CNSS_FW_READY, &plat_priv->driver_state); + clear_bit(CNSS_FW_MEM_READY, &plat_priv->driver_state); + + return ret; +} + +static void cnss_qca6290_crash_shutdown(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + int ret = 0; + + cnss_pr_dbg("Crash shutdown with driver_state 0x%lx\n", + plat_priv->driver_state); + + if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) || + test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state) || + test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) { + cnss_pr_dbg("Ignore crash shutdown\n"); + return; + } + + ret = cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_RDDM_KERNEL_PANIC); + if (ret) { + cnss_pr_err("Fail to complete RDDM, err = %d\n", ret); + return; + } + + cnss_pci_collect_dump_info(pci_priv); +} + +static int cnss_qca6290_ramdump(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + struct cnss_ramdump_info_v2 *info_v2 = &plat_priv->ramdump_info_v2; + struct cnss_dump_data *dump_data = &info_v2->dump_data; + struct cnss_dump_seg *dump_seg = info_v2->dump_data_vaddr; + struct ramdump_segment *ramdump_segs, *s; + int i, ret = 0; + + if (!info_v2->dump_data_valid || + dump_data->nentries == 0) + return 0; + + ramdump_segs = kcalloc(dump_data->nentries, + sizeof(*ramdump_segs), + GFP_KERNEL); + if (!ramdump_segs) + return -ENOMEM; + + s = ramdump_segs; + for (i = 0; i < dump_data->nentries; i++) { + s->address = dump_seg->address; + s->v_address = dump_seg->v_address; + s->size = dump_seg->size; + s++; + dump_seg++; + } + + ret = do_elf_ramdump(info_v2->ramdump_dev, ramdump_segs, + dump_data->nentries); + kfree(ramdump_segs); + + cnss_pci_set_mhi_state(plat_priv->bus_priv, CNSS_MHI_DEINIT); + cnss_pci_clear_dump_info(plat_priv->bus_priv); + + return ret; +} + +int cnss_pci_dev_powerup(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL\n"); + return -ENODEV; + } + + switch (pci_priv->device_id) { + case QCA6174_DEVICE_ID: + ret = cnss_qca6174_powerup(pci_priv); + break; + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + case QCN7605_DEVICE_ID: + ret = cnss_qca6290_powerup(pci_priv); + break; + default: + cnss_pr_err("Unknown device_id found: 0x%x\n", + pci_priv->device_id); + ret = -ENODEV; + } + + return ret; +} + +int cnss_pci_dev_shutdown(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL\n"); + return -ENODEV; + } + + switch (pci_priv->device_id) { + case QCA6174_DEVICE_ID: + ret = cnss_qca6174_shutdown(pci_priv); + break; + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + case QCN7605_DEVICE_ID: + ret = cnss_qca6290_shutdown(pci_priv); + break; + default: + cnss_pr_err("Unknown device_id found: 0x%x\n", + pci_priv->device_id); + ret = -ENODEV; + } + + return ret; +} + +int cnss_pci_dev_crash_shutdown(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL\n"); + return -ENODEV; + } + + switch (pci_priv->device_id) { + case QCA6174_DEVICE_ID: + cnss_qca6174_crash_shutdown(pci_priv); + break; + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + cnss_qca6290_crash_shutdown(pci_priv); + break; + default: + cnss_pr_err("Unknown device_id found: 0x%x\n", + pci_priv->device_id); + ret = -ENODEV; + } + + return ret; +} + +int cnss_pci_dev_ramdump(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL\n"); + return -ENODEV; + } + + switch (pci_priv->device_id) { + case QCA6174_DEVICE_ID: + ret = cnss_qca6174_ramdump(pci_priv); + break; + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + ret = cnss_qca6290_ramdump(pci_priv); + break; + default: + cnss_pr_err("Unknown device_id found: 0x%x\n", + pci_priv->device_id); + ret = -ENODEV; + } + + return ret; +} + +int cnss_wlan_register_driver(struct cnss_wlan_driver *driver_ops) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); + struct cnss_pci_data *pci_priv; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return -ENODEV; + } + + pci_priv = plat_priv->bus_priv; + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL\n"); + return -ENODEV; + } + + if (pci_priv->driver_ops) { + cnss_pr_err("Driver has already registered\n"); + return -EEXIST; + } + + ret = cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_REGISTER_DRIVER, + CNSS_EVENT_SYNC_UNINTERRUPTIBLE, + driver_ops); + return ret; +} +EXPORT_SYMBOL(cnss_wlan_register_driver); + +void cnss_wlan_unregister_driver(struct cnss_wlan_driver *driver_ops) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL\n"); + return; + } + + cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_UNREGISTER_DRIVER, + CNSS_EVENT_SYNC_UNINTERRUPTIBLE, NULL); +} +EXPORT_SYMBOL(cnss_wlan_unregister_driver); + +int cnss_pci_register_driver_hdlr(struct cnss_pci_data *pci_priv, + void *data) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + + set_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); + pci_priv->driver_ops = data; + + ret = cnss_pci_dev_powerup(pci_priv); + if (ret) { + clear_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); + pci_priv->driver_ops = NULL; + } + + return ret; +} + +int cnss_pci_unregister_driver_hdlr(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + + set_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state); + cnss_pci_dev_shutdown(pci_priv); + pci_priv->driver_ops = NULL; + clear_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state); + + return 0; +} + +static int cnss_pci_init_smmu(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct device *dev; + struct dma_iommu_mapping *mapping; + int atomic_ctx = 1; + int s1_bypass = 1; + + dev = &pci_priv->pci_dev->dev; + + mapping = arm_iommu_create_mapping(&platform_bus_type, + pci_priv->smmu_iova_start, + pci_priv->smmu_iova_len); + if (IS_ERR(mapping)) { + ret = PTR_ERR(mapping); + cnss_pr_err("Failed to create SMMU mapping, err = %d\n", ret); + goto out; + } + + ret = iommu_domain_set_attr(mapping->domain, + DOMAIN_ATTR_ATOMIC, + &atomic_ctx); + if (ret) { + pr_err("Failed to set SMMU atomic_ctx attribute, err = %d\n", + ret); + goto release_mapping; + } + + ret = iommu_domain_set_attr(mapping->domain, + DOMAIN_ATTR_S1_BYPASS, + &s1_bypass); + if (ret) { + pr_err("Failed to set SMMU s1_bypass attribute, err = %d\n", + ret); + goto release_mapping; + } + + ret = arm_iommu_attach_device(dev, mapping); + if (ret) { + pr_err("Failed to attach SMMU device, err = %d\n", ret); + goto release_mapping; + } + + pci_priv->smmu_mapping = mapping; + + return ret; +release_mapping: + arm_iommu_release_mapping(mapping); +out: + return ret; +} + +static void cnss_pci_deinit_smmu(struct cnss_pci_data *pci_priv) +{ + arm_iommu_detach_device(&pci_priv->pci_dev->dev); + arm_iommu_release_mapping(pci_priv->smmu_mapping); + + pci_priv->smmu_mapping = NULL; +} + +#ifdef CONFIG_PCI_MSM +static void cnss_pci_event_cb(struct msm_pcie_notify *notify) +{ + unsigned long flags; + struct pci_dev *pci_dev; + struct cnss_pci_data *pci_priv; + + if (!notify) + return; + + pci_dev = notify->user; + if (!pci_dev) + return; + + pci_priv = cnss_get_pci_priv(pci_dev); + if (!pci_priv) + return; + + switch (notify->event) { + case MSM_PCIE_EVENT_LINKDOWN: + if (pci_link_down_panic) + panic("cnss: PCI link is down!\n"); + + spin_lock_irqsave(&pci_link_down_lock, flags); + if (pci_priv->pci_link_down_ind) { + cnss_pr_dbg("PCI link down recovery is in progress, ignore!\n"); + spin_unlock_irqrestore(&pci_link_down_lock, flags); + return; + } + pci_priv->pci_link_down_ind = true; + spin_unlock_irqrestore(&pci_link_down_lock, flags); + + cnss_pr_err("PCI link down, schedule recovery!\n"); + cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_NOTIFY_LINK_ERROR); + if (pci_dev->device == QCA6174_DEVICE_ID) + disable_irq(pci_dev->irq); + cnss_schedule_recovery(&pci_dev->dev, CNSS_REASON_LINK_DOWN); + break; + case MSM_PCIE_EVENT_WAKEUP: + if (cnss_pci_get_monitor_wake_intr(pci_priv) && + cnss_pci_get_auto_suspended(pci_priv)) { + cnss_pci_set_monitor_wake_intr(pci_priv, false); + pm_request_resume(&pci_dev->dev); + } + break; + default: + cnss_pr_err("Received invalid PCI event: %d\n", notify->event); + } +} + +static int cnss_reg_pci_event(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct msm_pcie_register_event *pci_event; + + pci_event = &pci_priv->msm_pci_event; + pci_event->events = MSM_PCIE_EVENT_LINKDOWN | + MSM_PCIE_EVENT_WAKEUP; + pci_event->user = pci_priv->pci_dev; + pci_event->mode = MSM_PCIE_TRIGGER_CALLBACK; + pci_event->callback = cnss_pci_event_cb; + pci_event->options = MSM_PCIE_CONFIG_NO_RECOVERY; + + ret = msm_pcie_register_event(pci_event); + if (ret) + cnss_pr_err("Failed to register MSM PCI event, err = %d\n", + ret); + + return ret; +} + +static void cnss_dereg_pci_event(struct cnss_pci_data *pci_priv) +{ + msm_pcie_deregister_event(&pci_priv->msm_pci_event); +} + +static int cnss_pci_suspend(struct device *dev) +{ + int ret = 0; + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev); + struct cnss_plat_data *plat_priv; + struct cnss_wlan_driver *driver_ops; + + pm_message_t state = { .event = PM_EVENT_SUSPEND }; + + if (!pci_priv) + goto out; + + plat_priv = pci_priv->plat_priv; + if (!plat_priv) + goto out; + + driver_ops = pci_priv->driver_ops; + if (driver_ops && driver_ops->suspend) { + ret = driver_ops->suspend(pci_dev, state); + if (ret) { + cnss_pr_err("Failed to suspend host driver, err = %d\n", + ret); + ret = -EAGAIN; + goto out; + } + } + + if (pci_priv->pci_link_state) { + ret = cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_SUSPEND); + if (ret) { + if (driver_ops && driver_ops->resume) + driver_ops->resume(pci_dev); + ret = -EAGAIN; + goto out; + } + + cnss_set_pci_config_space(pci_priv, + SAVE_PCI_CONFIG_SPACE); + pci_disable_device(pci_dev); + + if (pci_dev->device != QCA6174_DEVICE_ID) { + ret = pci_set_power_state(pci_dev, PCI_D3hot); + if (ret) + cnss_pr_err("Failed to set D3Hot, err = %d\n", + ret); + } + } + + cnss_pci_set_monitor_wake_intr(pci_priv, false); + + return 0; + +out: + return ret; +} + +static int cnss_pci_resume(struct device *dev) +{ + int ret = 0; + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev); + struct cnss_plat_data *plat_priv; + struct cnss_wlan_driver *driver_ops; + + if (!pci_priv) + goto out; + + plat_priv = pci_priv->plat_priv; + if (!plat_priv) + goto out; + + if (pci_priv->pci_link_down_ind) + goto out; + + if (pci_priv->pci_link_state) { + ret = pci_enable_device(pci_dev); + if (ret) + cnss_pr_err("Failed to enable PCI device, err = %d\n", + ret); + + if (pci_priv->saved_state) + cnss_set_pci_config_space(pci_priv, + RESTORE_PCI_CONFIG_SPACE); + + pci_set_master(pci_dev); + cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_RESUME); + } + + driver_ops = pci_priv->driver_ops; + if (driver_ops && driver_ops->resume) { + ret = driver_ops->resume(pci_dev); + if (ret) + cnss_pr_err("Failed to resume host driver, err = %d\n", + ret); + } + + return 0; + +out: + return ret; +} + +static int cnss_pci_suspend_noirq(struct device *dev) +{ + int ret = 0; + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev); + struct cnss_plat_data *plat_priv; + struct cnss_wlan_driver *driver_ops; + + if (!pci_priv) + goto out; + + plat_priv = pci_priv->plat_priv; + if (!plat_priv) + goto out; + + driver_ops = pci_priv->driver_ops; + if (driver_ops && driver_ops->suspend_noirq) + ret = driver_ops->suspend_noirq(pci_dev); + +out: + return ret; +} + +static int cnss_pci_resume_noirq(struct device *dev) +{ + int ret = 0; + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev); + struct cnss_plat_data *plat_priv; + struct cnss_wlan_driver *driver_ops; + + if (!pci_priv) + goto out; + + plat_priv = pci_priv->plat_priv; + if (!plat_priv) + goto out; + + driver_ops = pci_priv->driver_ops; + if (driver_ops && driver_ops->resume_noirq && + !pci_priv->pci_link_down_ind) + ret = driver_ops->resume_noirq(pci_dev); + +out: + return ret; +} + +static int cnss_pci_runtime_suspend(struct device *dev) +{ + int ret = 0; + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev); + struct cnss_plat_data *plat_priv; + struct cnss_wlan_driver *driver_ops; + + if (!pci_priv) + return -EAGAIN; + + plat_priv = pci_priv->plat_priv; + if (!plat_priv) + return -EAGAIN; + + if (pci_priv->pci_link_down_ind) { + cnss_pr_dbg("PCI link down recovery is in progress!\n"); + return -EAGAIN; + } + + cnss_pr_dbg("Runtime suspend start\n"); + + driver_ops = pci_priv->driver_ops; + if (driver_ops && driver_ops->runtime_ops && + driver_ops->runtime_ops->runtime_suspend) + ret = driver_ops->runtime_ops->runtime_suspend(pci_dev); + + cnss_pr_info("Runtime suspend status: %d\n", ret); + + return ret; +} + +static int cnss_pci_runtime_resume(struct device *dev) +{ + int ret = 0; + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev); + struct cnss_plat_data *plat_priv; + struct cnss_wlan_driver *driver_ops; + + if (!pci_priv) + return -EAGAIN; + + plat_priv = pci_priv->plat_priv; + if (!plat_priv) + return -EAGAIN; + + if (pci_priv->pci_link_down_ind) { + cnss_pr_dbg("PCI link down recovery is in progress!\n"); + return -EAGAIN; + } + + cnss_pr_dbg("Runtime resume start\n"); + + driver_ops = pci_priv->driver_ops; + if (driver_ops && driver_ops->runtime_ops && + driver_ops->runtime_ops->runtime_resume) + ret = driver_ops->runtime_ops->runtime_resume(pci_dev); + + cnss_pr_info("Runtime resume status: %d\n", ret); + + return ret; +} + +static int cnss_pci_runtime_idle(struct device *dev) +{ + cnss_pr_dbg("Runtime idle\n"); + + pm_request_autosuspend(dev); + + return -EBUSY; +} + +int cnss_wlan_pm_control(struct device *dev, bool vote) +{ + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct cnss_pci_data *pci_priv; + struct pci_dev *pci_dev; + + if (!plat_priv) + return -ENODEV; + + pci_priv = plat_priv->bus_priv; + if (!pci_priv) + return -ENODEV; + + pci_dev = pci_priv->pci_dev; + + return msm_pcie_pm_control(vote ? MSM_PCIE_DISABLE_PC : + MSM_PCIE_ENABLE_PC, + pci_dev->bus->number, pci_dev, + NULL, PM_OPTIONS_DEFAULT); +} +EXPORT_SYMBOL(cnss_wlan_pm_control); + +#else /* CONFIG_PCI_MSM */ +static int cnss_reg_pci_event(struct cnss_pci_data *pci_priv) +{ + return 0; +} + +static void cnss_dereg_pci_event(struct cnss_pci_data *pci_priv) +{ +} +#endif /* CONFIG_PCI_MSM */ + +int cnss_auto_suspend(struct device *dev) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct pci_dev *pci_dev; + struct cnss_pci_data *pci_priv; + struct cnss_bus_bw_info *bus_bw_info; + + if (!plat_priv) + return -ENODEV; + + pci_priv = plat_priv->bus_priv; + if (!pci_priv) + return -ENODEV; + + pci_dev = pci_priv->pci_dev; + + if (pci_priv->pci_link_state) { + if (cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_SUSPEND)) { + ret = -EAGAIN; + goto out; + } + + cnss_set_pci_config_space(pci_priv, SAVE_PCI_CONFIG_SPACE); + pci_disable_device(pci_dev); + + if (pci_dev->device != QCA6174_DEVICE_ID) { + ret = pci_set_power_state(pci_dev, PCI_D3hot); + if (ret) + cnss_pr_err("Failed to set D3Hot, err = %d\n", + ret); + } + + cnss_pr_dbg("Suspending PCI link\n"); + if (cnss_set_pci_link(pci_priv, PCI_LINK_DOWN)) { + cnss_pr_err("Failed to suspend PCI link!\n"); + ret = -EAGAIN; + goto resume_mhi; + } + + pci_priv->pci_link_state = PCI_LINK_DOWN; + } + + cnss_pci_set_auto_suspended(pci_priv, 1); + cnss_pci_set_monitor_wake_intr(pci_priv, true); + + bus_bw_info = &plat_priv->bus_bw_info; + msm_bus_scale_client_update_request(bus_bw_info->bus_client, + CNSS_BUS_WIDTH_NONE); + + return 0; + +resume_mhi: + if (pci_enable_device(pci_dev)) + cnss_pr_err("Failed to enable PCI device!\n"); + cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_RESUME); +out: + return ret; +} +EXPORT_SYMBOL(cnss_auto_suspend); + +int cnss_auto_resume(struct device *dev) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + struct pci_dev *pci_dev; + struct cnss_pci_data *pci_priv; + struct cnss_bus_bw_info *bus_bw_info; + + if (!plat_priv) + return -ENODEV; + + pci_priv = plat_priv->bus_priv; + if (!pci_priv) + return -ENODEV; + + pci_dev = pci_priv->pci_dev; + if (!pci_priv->pci_link_state) { + cnss_pr_dbg("Resuming PCI link\n"); + if (cnss_set_pci_link(pci_priv, PCI_LINK_UP)) { + cnss_pr_err("Failed to resume PCI link!\n"); + ret = -EAGAIN; + goto out; + } + pci_priv->pci_link_state = PCI_LINK_UP; + + ret = pci_enable_device(pci_dev); + if (ret) + cnss_pr_err("Failed to enable PCI device, err = %d\n", + ret); + + cnss_set_pci_config_space(pci_priv, RESTORE_PCI_CONFIG_SPACE); + pci_set_master(pci_dev); + cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_RESUME); + } + + cnss_pci_set_auto_suspended(pci_priv, 0); + + bus_bw_info = &plat_priv->bus_bw_info; + msm_bus_scale_client_update_request(bus_bw_info->bus_client, + bus_bw_info->current_bw_vote); +out: + return ret; +} +EXPORT_SYMBOL(cnss_auto_resume); + +int cnss_pm_request_resume(struct cnss_pci_data *pci_priv) +{ + struct pci_dev *pci_dev; + + if (!pci_priv) + return -ENODEV; + + pci_dev = pci_priv->pci_dev; + if (!pci_dev) + return -ENODEV; + + return pm_request_resume(&pci_dev->dev); +} + +int cnss_pci_alloc_fw_mem(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + struct cnss_fw_mem *fw_mem = plat_priv->fw_mem; + int i; + + for (i = 0; i < plat_priv->fw_mem_seg_len; i++) { + if (!fw_mem[i].va && fw_mem[i].size) { + fw_mem[i].va = + dma_alloc_coherent(&pci_priv->pci_dev->dev, + fw_mem[i].size, + &fw_mem[i].pa, GFP_KERNEL); + if (!fw_mem[i].va) { + cnss_pr_err("Failed to allocate memory for FW, size: 0x%zx, type: %u\n", + fw_mem[i].size, fw_mem[i].type); + + return -ENOMEM; + } + } + } + + return 0; +} + +static void cnss_pci_free_fw_mem(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + struct cnss_fw_mem *fw_mem = plat_priv->fw_mem; + int i; + + for (i = 0; i < plat_priv->fw_mem_seg_len; i++) { + if (fw_mem[i].va && fw_mem[i].size) { + cnss_pr_dbg("Freeing memory for FW, va: 0x%pK, pa: %pa, size: 0x%zx, type: %u\n", + fw_mem[i].va, &fw_mem[i].pa, + fw_mem[i].size, fw_mem[i].type); + dma_free_coherent(&pci_priv->pci_dev->dev, + fw_mem[i].size, fw_mem[i].va, + fw_mem[i].pa); + fw_mem[i].va = NULL; + fw_mem[i].pa = 0; + fw_mem[i].size = 0; + fw_mem[i].type = 0; + } + } + + plat_priv->fw_mem_seg_len = 0; +} + +int cnss_pci_load_m3(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + struct cnss_fw_mem *m3_mem = &plat_priv->m3_mem; + char filename[MAX_M3_FILE_NAME_LENGTH]; + const struct firmware *fw_entry; + int ret = 0; + + if (!m3_mem->va && !m3_mem->size) { + snprintf(filename, sizeof(filename), DEFAULT_M3_FILE_NAME); + + ret = request_firmware(&fw_entry, filename, + &pci_priv->pci_dev->dev); + if (ret) { + cnss_pr_err("Failed to load M3 image: %s\n", filename); + return ret; + } + + m3_mem->va = dma_alloc_coherent(&pci_priv->pci_dev->dev, + fw_entry->size, &m3_mem->pa, + GFP_KERNEL); + if (!m3_mem->va) { + cnss_pr_err("Failed to allocate memory for M3, size: 0x%zx\n", + fw_entry->size); + release_firmware(fw_entry); + return -ENOMEM; + } + + memcpy(m3_mem->va, fw_entry->data, fw_entry->size); + m3_mem->size = fw_entry->size; + release_firmware(fw_entry); + } + + return 0; +} + +static void cnss_pci_free_m3_mem(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + struct cnss_fw_mem *m3_mem = &plat_priv->m3_mem; + + if (m3_mem->va && m3_mem->size) { + cnss_pr_dbg("Freeing memory for M3, va: 0x%pK, pa: %pa, size: 0x%zx\n", + m3_mem->va, &m3_mem->pa, m3_mem->size); + dma_free_coherent(&pci_priv->pci_dev->dev, m3_mem->size, + m3_mem->va, m3_mem->pa); + } + + m3_mem->va = NULL; + m3_mem->pa = 0; + m3_mem->size = 0; +} + +int cnss_pci_force_fw_assert_hdlr(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv; + int ret; + + if (!pci_priv) + return -ENODEV; + + plat_priv = pci_priv->plat_priv; + if (!plat_priv) + return -ENODEV; + + ret = cnss_pci_set_mhi_state(pci_priv, + CNSS_MHI_TRIGGER_RDDM); + if (ret) { + cnss_pr_err("Failed to trigger RDDM, err = %d\n", ret); + cnss_schedule_recovery(&pci_priv->pci_dev->dev, + CNSS_REASON_DEFAULT); + return 0; + } + + if (!test_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state)) { + mod_timer(&plat_priv->fw_boot_timer, + jiffies + msecs_to_jiffies(FW_ASSERT_TIMEOUT)); + } + + return 0; +} + +void cnss_pci_fw_boot_timeout_hdlr(struct cnss_pci_data *pci_priv) +{ + if (!pci_priv) + return; + + cnss_pr_err("Timeout waiting for FW ready indication\n"); + + cnss_schedule_recovery(&pci_priv->pci_dev->dev, + CNSS_REASON_TIMEOUT); +} + +struct dma_iommu_mapping *cnss_smmu_get_mapping(struct device *dev) +{ + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(to_pci_dev(dev)); + + if (!pci_priv) + return NULL; + + return pci_priv->smmu_mapping; +} +EXPORT_SYMBOL(cnss_smmu_get_mapping); + +int cnss_smmu_map(struct device *dev, + phys_addr_t paddr, uint32_t *iova_addr, size_t size) +{ + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(to_pci_dev(dev)); + unsigned long iova; + size_t len; + int ret = 0; + + if (!pci_priv) + return -ENODEV; + + if (!iova_addr) { + cnss_pr_err("iova_addr is NULL, paddr %pa, size %zu\n", + &paddr, size); + return -EINVAL; + } + + len = roundup(size + paddr - rounddown(paddr, PAGE_SIZE), PAGE_SIZE); + iova = roundup(pci_priv->smmu_iova_ipa_start, PAGE_SIZE); + + if (iova >= + (pci_priv->smmu_iova_ipa_start + pci_priv->smmu_iova_ipa_len)) { + cnss_pr_err("No IOVA space to map, iova %lx, smmu_iova_ipa_start %pad, smmu_iova_ipa_len %zu\n", + iova, + &pci_priv->smmu_iova_ipa_start, + pci_priv->smmu_iova_ipa_len); + return -ENOMEM; + } + + ret = iommu_map(pci_priv->smmu_mapping->domain, iova, + rounddown(paddr, PAGE_SIZE), len, + IOMMU_READ | IOMMU_WRITE); + if (ret) { + cnss_pr_err("PA to IOVA mapping failed, ret %d\n", ret); + return ret; + } + + pci_priv->smmu_iova_ipa_start = iova + len; + *iova_addr = (uint32_t)(iova + paddr - rounddown(paddr, PAGE_SIZE)); + + return 0; +} +EXPORT_SYMBOL(cnss_smmu_map); + +int cnss_get_soc_info(struct device *dev, struct cnss_soc_info *info) +{ + int ret = 0; + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev); + void *bus_priv = cnss_bus_dev_to_bus_priv(dev); + + if (!plat_priv) + return -ENODEV; + + ret = cnss_pci_get_bar_info(bus_priv, &info->va, &info->pa); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(cnss_get_soc_info); + +int cnss_pci_get_bar_info(struct cnss_pci_data *pci_priv, void __iomem **va, + phys_addr_t *pa) +{ + if (!pci_priv) + return -ENODEV; + + *va = pci_priv->bar; + *pa = pci_resource_start(pci_priv->pci_dev, PCI_BAR_NUM); + + return 0; +} + +static struct cnss_msi_config msi_config = { + .total_vectors = 32, + .total_users = 4, + .users = (struct cnss_msi_user[]) { + { .name = "MHI", .num_vectors = 2, .base_vector = 0 }, + { .name = "CE", .num_vectors = 11, .base_vector = 2 }, + { .name = "WAKE", .num_vectors = 1, .base_vector = 13 }, + { .name = "DP", .num_vectors = 18, .base_vector = 14 }, + }, +}; + +static int cnss_pci_get_msi_assignment(struct cnss_pci_data *pci_priv) +{ + pci_priv->msi_config = &msi_config; + + return 0; +} + +static int cnss_pci_enable_msi(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct pci_dev *pci_dev = pci_priv->pci_dev; + int num_vectors; + struct cnss_msi_config *msi_config; + struct msi_desc *msi_desc; + + ret = cnss_pci_get_msi_assignment(pci_priv); + if (ret) { + cnss_pr_err("Failed to get MSI assignment, err = %d\n", ret); + goto out; + } + + msi_config = pci_priv->msi_config; + if (!msi_config) { + cnss_pr_err("msi_config is NULL!\n"); + ret = -EINVAL; + goto out; + } + + num_vectors = pci_enable_msi_range(pci_dev, + msi_config->total_vectors, + msi_config->total_vectors); + if (num_vectors != msi_config->total_vectors) { + cnss_pr_err("Failed to get enough MSI vectors (%d), available vectors = %d", + msi_config->total_vectors, num_vectors); + ret = -EINVAL; + goto reset_msi_config; + } + + msi_desc = irq_get_msi_desc(pci_dev->irq); + if (!msi_desc) { + cnss_pr_err("msi_desc is NULL!\n"); + ret = -EINVAL; + goto disable_msi; + } + + pci_priv->msi_ep_base_data = msi_desc->msg.data; + if (!pci_priv->msi_ep_base_data) { + cnss_pr_err("Got 0 MSI base data!\n"); + CNSS_ASSERT(0); + } + + cnss_pr_dbg("MSI base data is %d\n", pci_priv->msi_ep_base_data); + + return 0; + +disable_msi: + pci_disable_msi(pci_priv->pci_dev); +reset_msi_config: + pci_priv->msi_config = NULL; +out: + return ret; +} + +static void cnss_pci_disable_msi(struct cnss_pci_data *pci_priv) +{ + pci_disable_msi(pci_priv->pci_dev); +} + +int cnss_get_user_msi_assignment(struct device *dev, char *user_name, + int *num_vectors, u32 *user_base_data, + u32 *base_vector) +{ + struct cnss_pci_data *pci_priv = dev_get_drvdata(dev); + struct cnss_msi_config *msi_config; + int idx; + + if (!pci_priv) + return -ENODEV; + + msi_config = pci_priv->msi_config; + if (!msi_config) { + cnss_pr_err("MSI is not supported.\n"); + return -EINVAL; + } + + for (idx = 0; idx < msi_config->total_users; idx++) { + if (strcmp(user_name, msi_config->users[idx].name) == 0) { + *num_vectors = msi_config->users[idx].num_vectors; + *user_base_data = msi_config->users[idx].base_vector + + pci_priv->msi_ep_base_data; + *base_vector = msi_config->users[idx].base_vector; + + cnss_pr_dbg("Assign MSI to user: %s, num_vectors: %d, user_base_data: %u, base_vector: %u\n", + user_name, *num_vectors, *user_base_data, + *base_vector); + + return 0; + } + } + + cnss_pr_err("Failed to find MSI assignment for %s!\n", user_name); + + return -EINVAL; +} +EXPORT_SYMBOL(cnss_get_user_msi_assignment); + +int cnss_get_msi_irq(struct device *dev, unsigned int vector) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + + return pci_dev->irq + vector; +} +EXPORT_SYMBOL(cnss_get_msi_irq); + +void cnss_get_msi_address(struct device *dev, u32 *msi_addr_low, + u32 *msi_addr_high) +{ + struct pci_dev *pci_dev = to_pci_dev(dev); + + pci_read_config_dword(pci_dev, pci_dev->msi_cap + PCI_MSI_ADDRESS_LO, + msi_addr_low); + + pci_read_config_dword(pci_dev, pci_dev->msi_cap + PCI_MSI_ADDRESS_HI, + msi_addr_high); +} +EXPORT_SYMBOL(cnss_get_msi_address); + +static char *get_wake_msi_name(void) +{ + return (char *)WAKE_MSI_NAME; +} + +u32 cnss_pci_get_wake_msi(struct cnss_pci_data *pci_priv) +{ + int ret, num_vectors; + u32 user_base_data, base_vector; + char *wake_msi_name = get_wake_msi_name(); + + ret = cnss_get_user_msi_assignment(&pci_priv->pci_dev->dev, + wake_msi_name, &num_vectors, + &user_base_data, &base_vector); + + if (ret) { + cnss_pr_err("WAKE MSI is not valid\n"); + return 0; + } + + return user_base_data; +} + +#ifdef CONFIG_PCI_MSM +static inline int cnss_pci_set_dma_mask(struct pci_dev *pci_dev) +{ + int ret; + + ret = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(PCI_DMA_MASK)); + if (ret) { + cnss_pr_err("PCI DMA mask: %d, err: %d\n", PCI_DMA_MASK, ret); + return ret; + } + + ret = pci_set_consistent_dma_mask(pci_dev, DMA_BIT_MASK(PCI_DMA_MASK)); + if (ret) + cnss_pr_err("PCI consistent DMA mask: %d, err: %d\n", + PCI_DMA_MASK, ret); + + return ret; +} +#else /* CONFIG_PCI_MSM */ +static inline int cnss_pci_set_dma_mask(struct pci_dev *pci_dev) +{ + return 0; +} +#endif /* CONFIG_PCI_MSM */ + +static int cnss_pci_enable_bus(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct pci_dev *pci_dev = pci_priv->pci_dev; + u16 device_id; + + pci_read_config_word(pci_dev, PCI_DEVICE_ID, &device_id); + if (device_id != pci_priv->pci_device_id->device) { + cnss_pr_err("PCI device ID mismatch, config ID: 0x%x, probe ID: 0x%x\n", + device_id, pci_priv->pci_device_id->device); + ret = -EIO; + goto out; + } + + ret = pci_assign_resource(pci_dev, PCI_BAR_NUM); + if (ret) { + pr_err("Failed to assign PCI resource, err = %d\n", ret); + goto out; + } + + ret = pci_enable_device(pci_dev); + if (ret) { + cnss_pr_err("Failed to enable PCI device, err = %d\n", ret); + goto out; + } + + ret = pci_request_region(pci_dev, PCI_BAR_NUM, "cnss"); + if (ret) { + cnss_pr_err("Failed to request PCI region, err = %d\n", ret); + goto disable_device; + } + + ret = cnss_pci_set_dma_mask(pci_dev); + if (ret) + goto release_region; + + pci_set_master(pci_dev); + + pci_priv->bar = pci_iomap(pci_dev, PCI_BAR_NUM, 0); + if (!pci_priv->bar) { + cnss_pr_err("Failed to do PCI IO map!\n"); + ret = -EIO; + goto clear_master; + } + return 0; + +clear_master: + pci_clear_master(pci_dev); +release_region: + pci_release_region(pci_dev, PCI_BAR_NUM); +disable_device: + pci_disable_device(pci_dev); +out: + return ret; +} + +static void cnss_pci_disable_bus(struct cnss_pci_data *pci_priv) +{ + struct pci_dev *pci_dev = pci_priv->pci_dev; + + if (pci_priv->bar) { + pci_iounmap(pci_dev, pci_priv->bar); + pci_priv->bar = NULL; + } + + pci_clear_master(pci_dev); + pci_release_region(pci_dev, PCI_BAR_NUM); + pci_disable_device(pci_dev); +} + +static int cnss_mhi_pm_runtime_get(struct pci_dev *pci_dev) +{ + return pm_runtime_get(&pci_dev->dev); +} + +static void cnss_mhi_pm_runtime_put_noidle(struct pci_dev *pci_dev) +{ + pm_runtime_put_noidle(&pci_dev->dev); +} + +static char *cnss_mhi_state_to_str(enum cnss_mhi_state mhi_state) +{ + switch (mhi_state) { + case CNSS_MHI_INIT: + return "INIT"; + case CNSS_MHI_DEINIT: + return "DEINIT"; + case CNSS_MHI_POWER_ON: + return "POWER_ON"; + case CNSS_MHI_POWER_OFF: + return "POWER_OFF"; + case CNSS_MHI_SUSPEND: + return "SUSPEND"; + case CNSS_MHI_RESUME: + return "RESUME"; + case CNSS_MHI_TRIGGER_RDDM: + return "TRIGGER_RDDM"; + case CNSS_MHI_RDDM: + return "RDDM"; + case CNSS_MHI_RDDM_KERNEL_PANIC: + return "RDDM_KERNEL_PANIC"; + case CNSS_MHI_NOTIFY_LINK_ERROR: + return "NOTIFY_LINK_ERROR"; + default: + return "UNKNOWN"; + } +}; + +static void *cnss_pci_collect_dump_seg(struct cnss_pci_data *pci_priv, + enum mhi_rddm_segment type, + void *start_addr) +{ + int count; + unsigned int i; + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + struct cnss_dump_data *dump_data = + &plat_priv->ramdump_info_v2.dump_data; + struct cnss_dump_seg *dump_seg = start_addr; + struct scatterlist *sg_list = NULL; + struct scatterlist *s = NULL; + + count = mhi_xfer_rddm(&pci_priv->mhi_dev, type, &sg_list); + if (count <= 0 || !sg_list) { + cnss_pr_err("Invalid dump_seg for type %u, count %u, sg_list %pK\n", + type, count, sg_list); + return start_addr; + } + + cnss_pr_dbg("Collect dump seg: type %u, nentries %d\n", type, count); + + for_each_sg(sg_list, s, count, i) { + dump_seg->address = sg_dma_address(s); + dump_seg->v_address = sg_virt(s); + dump_seg->size = s->length; + dump_seg->type = type; + cnss_pr_dbg("seg-%d: address 0x%lx, v_address %pK, size 0x%lx\n", + i, dump_seg->address, + dump_seg->v_address, dump_seg->size); + dump_seg++; + } + + dump_data->nentries += count; + + return dump_seg; +} + +void cnss_pci_collect_dump_info(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + struct cnss_dump_data *dump_data = + &plat_priv->ramdump_info_v2.dump_data; + void *start_addr, *end_addr; + + dump_data->nentries = 0; + + start_addr = plat_priv->ramdump_info_v2.dump_data_vaddr; + end_addr = cnss_pci_collect_dump_seg(pci_priv, + MHI_RDDM_FW_SEGMENT, start_addr); + + start_addr = end_addr; + end_addr = cnss_pci_collect_dump_seg(pci_priv, + MHI_RDDM_RD_SEGMENT, start_addr); + + if (dump_data->nentries > 0) + plat_priv->ramdump_info_v2.dump_data_valid = true; +} + +void cnss_pci_clear_dump_info(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv = pci_priv->plat_priv; + + plat_priv->ramdump_info_v2.dump_data.nentries = 0; + plat_priv->ramdump_info_v2.dump_data_valid = false; +} + +static void cnss_mhi_notify_status(enum MHI_CB_REASON reason, void *priv) +{ + struct cnss_pci_data *pci_priv = priv; + struct cnss_plat_data *plat_priv; + enum cnss_recovery_reason cnss_reason = CNSS_REASON_RDDM; + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL"); + return; + } + + plat_priv = pci_priv->plat_priv; + + cnss_pr_dbg("MHI status cb is called with reason %d\n", reason); + + if (pci_priv->driver_ops && pci_priv->driver_ops->update_status) + pci_priv->driver_ops->update_status(pci_priv->pci_dev, + CNSS_FW_DOWN); + + set_bit(CNSS_DEV_ERR_NOTIFY, &plat_priv->driver_state); + del_timer(&plat_priv->fw_boot_timer); + + if (reason == MHI_CB_SYS_ERROR) + cnss_reason = CNSS_REASON_TIMEOUT; + + cnss_schedule_recovery(&pci_priv->pci_dev->dev, + cnss_reason); +} + +static int cnss_pci_register_mhi(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + struct pci_dev *pci_dev = pci_priv->pci_dev; + struct mhi_device *mhi_dev = &pci_priv->mhi_dev; + + mhi_dev->dev = &pci_priv->plat_priv->plat_dev->dev; + mhi_dev->pci_dev = pci_dev; + + mhi_dev->resources[0].start = (resource_size_t)pci_priv->bar; + mhi_dev->resources[0].end = (resource_size_t)pci_priv->bar + + pci_resource_len(pci_dev, PCI_BAR_NUM); + mhi_dev->resources[0].flags = + pci_resource_flags(pci_dev, PCI_BAR_NUM); + mhi_dev->resources[0].name = "BAR"; + cnss_pr_dbg("BAR start is %pa, BAR end is %pa\n", + &mhi_dev->resources[0].start, &mhi_dev->resources[0].end); + + if (!mhi_dev->resources[1].start) { + mhi_dev->resources[1].start = pci_dev->irq; + mhi_dev->resources[1].end = pci_dev->irq + 1; + mhi_dev->resources[1].flags = IORESOURCE_IRQ; + mhi_dev->resources[1].name = "IRQ"; + } + cnss_pr_dbg("IRQ start is %pa, IRQ end is %pa\n", + &mhi_dev->resources[1].start, &mhi_dev->resources[1].end); + + mhi_dev->pm_runtime_get = cnss_mhi_pm_runtime_get; + mhi_dev->pm_runtime_put_noidle = cnss_mhi_pm_runtime_put_noidle; + + mhi_dev->support_rddm = true; + mhi_dev->rddm_size = pci_priv->plat_priv->ramdump_info_v2.ramdump_size; + mhi_dev->status_cb = cnss_mhi_notify_status; + + ret = mhi_register_device(mhi_dev, MHI_NODE_NAME, pci_priv); + if (ret) { + cnss_pr_err("Failed to register as MHI device, err = %d\n", + ret); + return ret; + } + + return 0; +} + +static void cnss_pci_unregister_mhi(struct cnss_pci_data *pci_priv) +{ +} + +static enum mhi_dev_ctrl cnss_to_mhi_dev_state(enum cnss_mhi_state state) +{ + switch (state) { + case CNSS_MHI_INIT: + return MHI_DEV_CTRL_INIT; + case CNSS_MHI_DEINIT: + return MHI_DEV_CTRL_DE_INIT; + case CNSS_MHI_POWER_ON: + return MHI_DEV_CTRL_POWER_ON; + case CNSS_MHI_POWER_OFF: + return MHI_DEV_CTRL_POWER_OFF; + case CNSS_MHI_SUSPEND: + return MHI_DEV_CTRL_SUSPEND; + case CNSS_MHI_RESUME: + return MHI_DEV_CTRL_RESUME; + case CNSS_MHI_TRIGGER_RDDM: + return MHI_DEV_CTRL_TRIGGER_RDDM; + case CNSS_MHI_RDDM: + return MHI_DEV_CTRL_RDDM; + case CNSS_MHI_RDDM_KERNEL_PANIC: + return MHI_DEV_CTRL_RDDM_KERNEL_PANIC; + case CNSS_MHI_NOTIFY_LINK_ERROR: + return MHI_DEV_CTRL_NOTIFY_LINK_ERROR; + default: + cnss_pr_err("Unknown CNSS MHI state (%d)\n", state); + return -EINVAL; + } +} + +static int cnss_pci_check_mhi_state_bit(struct cnss_pci_data *pci_priv, + enum cnss_mhi_state mhi_state) +{ + switch (mhi_state) { + case CNSS_MHI_INIT: + if (!test_bit(CNSS_MHI_INIT, &pci_priv->mhi_state)) + return 0; + break; + case CNSS_MHI_DEINIT: + case CNSS_MHI_POWER_ON: + if (test_bit(CNSS_MHI_INIT, &pci_priv->mhi_state) && + !test_bit(CNSS_MHI_POWER_ON, &pci_priv->mhi_state)) + return 0; + break; + case CNSS_MHI_POWER_OFF: + case CNSS_MHI_SUSPEND: + if (test_bit(CNSS_MHI_POWER_ON, &pci_priv->mhi_state) && + !test_bit(CNSS_MHI_SUSPEND, &pci_priv->mhi_state)) + return 0; + break; + case CNSS_MHI_RESUME: + if (test_bit(CNSS_MHI_SUSPEND, &pci_priv->mhi_state)) + return 0; + break; + case CNSS_MHI_TRIGGER_RDDM: + case CNSS_MHI_RDDM: + case CNSS_MHI_RDDM_KERNEL_PANIC: + case CNSS_MHI_NOTIFY_LINK_ERROR: + return 0; + default: + cnss_pr_err("Unhandled MHI state: %s(%d)\n", + cnss_mhi_state_to_str(mhi_state), mhi_state); + } + + cnss_pr_err("Cannot set MHI state %s(%d) in current MHI state (0x%lx)\n", + cnss_mhi_state_to_str(mhi_state), mhi_state, + pci_priv->mhi_state); + + return -EINVAL; +} + +static void cnss_pci_set_mhi_state_bit(struct cnss_pci_data *pci_priv, + enum cnss_mhi_state mhi_state) +{ + switch (mhi_state) { + case CNSS_MHI_INIT: + set_bit(CNSS_MHI_INIT, &pci_priv->mhi_state); + break; + case CNSS_MHI_DEINIT: + clear_bit(CNSS_MHI_INIT, &pci_priv->mhi_state); + break; + case CNSS_MHI_POWER_ON: + set_bit(CNSS_MHI_POWER_ON, &pci_priv->mhi_state); + break; + case CNSS_MHI_POWER_OFF: + clear_bit(CNSS_MHI_POWER_ON, &pci_priv->mhi_state); + break; + case CNSS_MHI_SUSPEND: + set_bit(CNSS_MHI_SUSPEND, &pci_priv->mhi_state); + break; + case CNSS_MHI_RESUME: + clear_bit(CNSS_MHI_SUSPEND, &pci_priv->mhi_state); + break; + case CNSS_MHI_TRIGGER_RDDM: + case CNSS_MHI_RDDM: + case CNSS_MHI_RDDM_KERNEL_PANIC: + case CNSS_MHI_NOTIFY_LINK_ERROR: + break; + default: + cnss_pr_err("Unhandled MHI state (%d)\n", mhi_state); + } +} + +int cnss_pci_set_mhi_state(struct cnss_pci_data *pci_priv, + enum cnss_mhi_state mhi_state) +{ + int ret = 0; + enum mhi_dev_ctrl mhi_dev_state = cnss_to_mhi_dev_state(mhi_state); + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL!\n"); + return -ENODEV; + } + + if (pci_priv->device_id == QCA6174_DEVICE_ID) + return 0; + + if (mhi_dev_state < 0) { + cnss_pr_err("Invalid MHI DEV state (%d)\n", mhi_dev_state); + return -EINVAL; + } + + ret = cnss_pci_check_mhi_state_bit(pci_priv, mhi_state); + if (ret) + goto out; + + cnss_pr_dbg("Setting MHI state: %s(%d)\n", + cnss_mhi_state_to_str(mhi_state), mhi_state); + ret = mhi_pm_control_device(&pci_priv->mhi_dev, mhi_dev_state); + if (ret) { + cnss_pr_err("Failed to set MHI state: %s(%d)\n", + cnss_mhi_state_to_str(mhi_state), mhi_state); + goto out; + } + + cnss_pci_set_mhi_state_bit(pci_priv, mhi_state); + +out: + return ret; +} + +int cnss_pci_start_mhi(struct cnss_pci_data *pci_priv) +{ + int ret = 0; + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL!\n"); + return -ENODEV; + } + + if (fbc_bypass) + return 0; + + ret = cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_INIT); + if (ret) + goto out; + + ret = cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_POWER_ON); + if (ret) + goto out; + + return 0; + +out: + return ret; +} + +void cnss_pci_stop_mhi(struct cnss_pci_data *pci_priv) +{ + struct cnss_plat_data *plat_priv; + + if (!pci_priv) { + cnss_pr_err("pci_priv is NULL!\n"); + return; + } + + if (fbc_bypass) + return; + + plat_priv = pci_priv->plat_priv; + + cnss_pci_set_mhi_state_bit(pci_priv, CNSS_MHI_RESUME); + cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_POWER_OFF); + + if (plat_priv->ramdump_info_v2.dump_data_valid || + test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) + return; + + cnss_pci_set_mhi_state(pci_priv, CNSS_MHI_DEINIT); +} + +static int cnss_pci_probe(struct pci_dev *pci_dev, + const struct pci_device_id *id) +{ + int ret = 0; + struct cnss_pci_data *pci_priv; + struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); + struct resource *res; + + cnss_pr_dbg("PCI is probing, vendor ID: 0x%x, device ID: 0x%x\n", + id->vendor, pci_dev->device); + + switch (pci_dev->device) { + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + case QCN7605_DEVICE_ID: + if (!mhi_is_device_ready(&plat_priv->plat_dev->dev, + MHI_NODE_NAME)) { + cnss_pr_err("MHI driver is not ready, defer PCI probe!\n"); + ret = -EPROBE_DEFER; + goto out; + } + break; + default: + break; + } + + pci_priv = devm_kzalloc(&pci_dev->dev, sizeof(*pci_priv), + GFP_KERNEL); + if (!pci_priv) { + ret = -ENOMEM; + goto out; + } + + pci_priv->pci_link_state = PCI_LINK_UP; + pci_priv->plat_priv = plat_priv; + pci_priv->pci_dev = pci_dev; + pci_priv->pci_device_id = id; + pci_priv->device_id = pci_dev->device; + cnss_set_pci_priv(pci_dev, pci_priv); + plat_priv->device_id = pci_dev->device; + plat_priv->bus_priv = pci_priv; + + ret = cnss_register_subsys(plat_priv); + if (ret) + goto reset_ctx; + + ret = cnss_register_ramdump(plat_priv); + if (ret) + goto unregister_subsys; + + res = platform_get_resource_byname(plat_priv->plat_dev, IORESOURCE_MEM, + "smmu_iova_base"); + if (res) { + pci_priv->smmu_iova_start = res->start; + pci_priv->smmu_iova_len = resource_size(res); + cnss_pr_dbg("smmu_iova_start: %pa, smmu_iova_len: %zu\n", + &pci_priv->smmu_iova_start, + pci_priv->smmu_iova_len); + + res = platform_get_resource_byname(plat_priv->plat_dev, + IORESOURCE_MEM, + "smmu_iova_ipa"); + if (res) { + pci_priv->smmu_iova_ipa_start = res->start; + pci_priv->smmu_iova_ipa_len = resource_size(res); + cnss_pr_dbg("smmu_iova_ipa_start: %pa, smmu_iova_ipa_len: %zu\n", + &pci_priv->smmu_iova_ipa_start, + pci_priv->smmu_iova_ipa_len); + } + + ret = cnss_pci_init_smmu(pci_priv); + if (ret) { + cnss_pr_err("Failed to init SMMU, err = %d\n", ret); + goto unregister_ramdump; + } + } + + ret = cnss_reg_pci_event(pci_priv); + if (ret) { + cnss_pr_err("Failed to register PCI event, err = %d\n", ret); + goto deinit_smmu; + } + + ret = cnss_pci_enable_bus(pci_priv); + if (ret) + goto dereg_pci_event; + + pci_save_state(pci_dev); + pci_priv->default_state = pci_store_saved_state(pci_dev); + + switch (pci_dev->device) { + case QCA6174_DEVICE_ID: + pci_read_config_word(pci_dev, QCA6174_REV_ID_OFFSET, + &pci_priv->revision_id); + ret = cnss_suspend_pci_link(pci_priv); + if (ret) + cnss_pr_err("Failed to suspend PCI link, err = %d\n", + ret); + cnss_power_off_device(plat_priv); + break; + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + case QCN7605_DEVICE_ID: + ret = cnss_pci_enable_msi(pci_priv); + if (ret) + goto disable_bus; + ret = cnss_pci_register_mhi(pci_priv); + if (ret) { + cnss_pci_disable_msi(pci_priv); + goto disable_bus; + } + ret = cnss_suspend_pci_link(pci_priv); + if (ret) + cnss_pr_err("Failed to suspend PCI link, err = %d\n", + ret); + cnss_power_off_device(plat_priv); + break; + default: + cnss_pr_err("Unknown PCI device found: 0x%x\n", + pci_dev->device); + ret = -ENODEV; + goto disable_bus; + } + + return 0; + +disable_bus: + cnss_pci_disable_bus(pci_priv); +dereg_pci_event: + cnss_dereg_pci_event(pci_priv); +deinit_smmu: + if (pci_priv->smmu_mapping) + cnss_pci_deinit_smmu(pci_priv); +unregister_ramdump: + cnss_unregister_ramdump(plat_priv); +unregister_subsys: + cnss_unregister_subsys(plat_priv); +reset_ctx: + plat_priv->bus_priv = NULL; +out: + return ret; +} + +static void cnss_pci_remove(struct pci_dev *pci_dev) +{ + struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev); + struct cnss_plat_data *plat_priv = + cnss_bus_dev_to_plat_priv(&pci_dev->dev); + + cnss_pci_free_m3_mem(pci_priv); + cnss_pci_free_fw_mem(pci_priv); + + switch (pci_dev->device) { + case QCA6290_EMULATION_DEVICE_ID: + case QCA6290_DEVICE_ID: + cnss_pci_unregister_mhi(pci_priv); + cnss_pci_disable_msi(pci_priv); + break; + default: + break; + } + + pci_load_and_free_saved_state(pci_dev, &pci_priv->saved_state); + + cnss_pci_disable_bus(pci_priv); + cnss_dereg_pci_event(pci_priv); + if (pci_priv->smmu_mapping) + cnss_pci_deinit_smmu(pci_priv); + cnss_unregister_ramdump(plat_priv); + cnss_unregister_subsys(plat_priv); + plat_priv->bus_priv = NULL; +} + +static const struct pci_device_id cnss_pci_id_table[] = { + { QCA6174_VENDOR_ID, QCA6174_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID }, + { QCA6290_EMULATION_VENDOR_ID, QCA6290_EMULATION_DEVICE_ID, + PCI_ANY_ID, PCI_ANY_ID }, + { QCA6290_VENDOR_ID, QCA6290_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID }, + {QCN7605_VENDOR_ID, QCN7605_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID}, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cnss_pci_id_table); + +#ifdef CONFIG_PCI_MSM +static const struct dev_pm_ops cnss_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cnss_pci_suspend, cnss_pci_resume) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cnss_pci_suspend_noirq, + cnss_pci_resume_noirq) + SET_RUNTIME_PM_OPS(cnss_pci_runtime_suspend, cnss_pci_runtime_resume, + cnss_pci_runtime_idle) +}; +#endif + +struct pci_driver cnss_pci_driver = { + .name = "cnss_pci", + .id_table = cnss_pci_id_table, + .probe = cnss_pci_probe, + .remove = cnss_pci_remove, +#ifdef CONFIG_PCI_MSM + .driver = { + .pm = &cnss_pm_ops, + }, +#endif +}; + +#ifdef CONFIG_PCI_MSM +static inline int cnss_msm_pcie_enumerate(struct cnss_plat_data *plat_priv) +{ + int ret; + struct device *dev = &plat_priv->plat_dev->dev; + u32 rc_num; + + ret = of_property_read_u32(dev->of_node, "qcom,wlan-rc-num", &rc_num); + if (ret) { + cnss_pr_err("Failed to find PCIe RC number, err = %d\n", ret); + goto out; + } + + ret = msm_pcie_enumerate(rc_num); + if (ret) { + cnss_pr_err("Failed to enable PCIe RC%x, err = %d\n", + rc_num, ret); + goto out; + } + + return 0; +out: + return ret; +} +#else /* CONFIG_PCI_MSM */ +static inline int cnss_msm_pcie_enumerate(struct cnss_plat_data *plat_priv) +{ + return 0; +} +#endif /* CONFIG_PCI_MSM */ + +int cnss_pci_init(struct cnss_plat_data *plat_priv) +{ + int ret; + + ret = cnss_msm_pcie_enumerate(plat_priv); + if (ret) + goto out; + + ret = pci_register_driver(&cnss_pci_driver); + if (ret) { + cnss_pr_err("Failed to register to PCI framework, err = %d\n", + ret); + goto out; + } + + return 0; +out: + return ret; +} + +void cnss_pci_deinit(struct cnss_plat_data *plat_priv) +{ + pci_unregister_driver(&cnss_pci_driver); +} diff --git a/drivers/net/wireless/cnss2/pci.h b/drivers/net/wireless/cnss2/pci.h new file mode 100644 index 000000000000..e47f14e8a325 --- /dev/null +++ b/drivers/net/wireless/cnss2/pci.h @@ -0,0 +1,172 @@ +/* Copyright (c) 2016-2018, 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. + */ + +#ifndef _CNSS_PCI_H +#define _CNSS_PCI_H + +#include <asm/dma-iommu.h> +#include <linux/iommu.h> +#include <linux/msm_mhi.h> +#include <linux/msm_pcie.h> +#include <linux/pci.h> + +#include "main.h" + +#define QCA6174_VENDOR_ID 0x168C +#define QCA6174_DEVICE_ID 0x003E +#define QCA6174_REV_ID_OFFSET 0x08 +#define QCA6174_REV3_VERSION 0x5020000 +#define QCA6174_REV3_2_VERSION 0x5030000 +#define QCA6290_VENDOR_ID 0x17CB +#define QCA6290_DEVICE_ID 0x1100 +#define QCA6290_EMULATION_VENDOR_ID 0x168C +#define QCA6290_EMULATION_DEVICE_ID 0xABCD + +enum cnss_mhi_state { + CNSS_MHI_INIT, + CNSS_MHI_DEINIT, + CNSS_MHI_SUSPEND, + CNSS_MHI_RESUME, + CNSS_MHI_POWER_OFF, + CNSS_MHI_POWER_ON, + CNSS_MHI_TRIGGER_RDDM, + CNSS_MHI_RDDM, + CNSS_MHI_RDDM_KERNEL_PANIC, + CNSS_MHI_NOTIFY_LINK_ERROR, +}; + +struct cnss_msi_user { + char *name; + int num_vectors; + u32 base_vector; +}; + +struct cnss_msi_config { + int total_vectors; + int total_users; + struct cnss_msi_user *users; +}; + +struct cnss_pci_data { + struct pci_dev *pci_dev; + struct cnss_plat_data *plat_priv; + const struct pci_device_id *pci_device_id; + u32 device_id; + u16 revision_id; + struct cnss_wlan_driver *driver_ops; + bool pci_link_state; + bool pci_link_down_ind; + struct pci_saved_state *saved_state; + struct pci_saved_state *default_state; + struct msm_pcie_register_event msm_pci_event; + atomic_t auto_suspended; + bool monitor_wake_intr; + struct dma_iommu_mapping *smmu_mapping; + dma_addr_t smmu_iova_start; + size_t smmu_iova_len; + dma_addr_t smmu_iova_ipa_start; + size_t smmu_iova_ipa_len; + void __iomem *bar; + struct cnss_msi_config *msi_config; + u32 msi_ep_base_data; + struct mhi_device mhi_dev; + unsigned long mhi_state; +}; + +static inline void cnss_set_pci_priv(struct pci_dev *pci_dev, void *data) +{ + pci_set_drvdata(pci_dev, data); +} + +static inline struct cnss_pci_data *cnss_get_pci_priv(struct pci_dev *pci_dev) +{ + return pci_get_drvdata(pci_dev); +} + +static inline struct cnss_plat_data *cnss_pci_priv_to_plat_priv(void *bus_priv) +{ + struct cnss_pci_data *pci_priv = bus_priv; + + return pci_priv->plat_priv; +} + +static inline void cnss_pci_set_monitor_wake_intr(void *bus_priv, bool val) +{ + struct cnss_pci_data *pci_priv = bus_priv; + + pci_priv->monitor_wake_intr = val; +} + +static inline bool cnss_pci_get_monitor_wake_intr(void *bus_priv) +{ + struct cnss_pci_data *pci_priv = bus_priv; + + return pci_priv->monitor_wake_intr; +} + +static inline void cnss_pci_set_auto_suspended(void *bus_priv, int val) +{ + struct cnss_pci_data *pci_priv = bus_priv; + + atomic_set(&pci_priv->auto_suspended, val); +} + +static inline int cnss_pci_get_auto_suspended(void *bus_priv) +{ + struct cnss_pci_data *pci_priv = bus_priv; + + return atomic_read(&pci_priv->auto_suspended); +} + +#ifdef CONFIG_PCI_MSM +int cnss_suspend_pci_link(struct cnss_pci_data *pci_priv); +int cnss_resume_pci_link(struct cnss_pci_data *pci_priv); +#else /* CONFIG_PCI_MSM */ +static inline int cnss_suspend_pci_link(struct cnss_pci_data *pci_priv) +{ + return 0; +} + +static inline int cnss_resume_pci_link(struct cnss_pci_data *pci_priv) +{ + return 0; +} +#endif /* CONFIG_PCI_MSM */ + +int cnss_pci_init(struct cnss_plat_data *plat_priv); +void cnss_pci_deinit(struct cnss_plat_data *plat_priv); +int cnss_pci_alloc_fw_mem(struct cnss_pci_data *pci_priv); +int cnss_pci_load_m3(struct cnss_pci_data *pci_priv); +int cnss_pci_get_bar_info(struct cnss_pci_data *pci_priv, void __iomem **va, + phys_addr_t *pa); +int cnss_pci_set_mhi_state(struct cnss_pci_data *pci_priv, + enum cnss_mhi_state state); +int cnss_pci_start_mhi(struct cnss_pci_data *pci_priv); +void cnss_pci_stop_mhi(struct cnss_pci_data *pci_priv); +void cnss_pci_collect_dump_info(struct cnss_pci_data *pci_priv); +void cnss_pci_clear_dump_info(struct cnss_pci_data *pci_priv); +int cnss_pm_request_resume(struct cnss_pci_data *pci_priv); +u32 cnss_pci_get_wake_msi(struct cnss_pci_data *pci_priv); +int cnss_pci_force_fw_assert_hdlr(struct cnss_pci_data *pci_priv); +void cnss_pci_fw_boot_timeout_hdlr(struct cnss_pci_data *pci_priv); +int cnss_pci_call_driver_probe(struct cnss_pci_data *pci_priv); +int cnss_pci_call_driver_remove(struct cnss_pci_data *pci_priv); +int cnss_pci_dev_powerup(struct cnss_pci_data *pci_priv); +int cnss_pci_dev_shutdown(struct cnss_pci_data *pci_priv); +int cnss_pci_dev_crash_shutdown(struct cnss_pci_data *pci_priv); +int cnss_pci_dev_ramdump(struct cnss_pci_data *pci_priv); +int cnss_pci_register_driver_hdlr(struct cnss_pci_data *pci_priv, void *data); +int cnss_pci_unregister_driver_hdlr(struct cnss_pci_data *pci_priv); +int cnss_pci_call_driver_modem_status(struct cnss_pci_data *pci_priv, + int modem_current_status); +int cnss_pci_recovery_update_status(struct cnss_pci_data *pci_priv); +#endif /* _CNSS_PCI_H */ diff --git a/drivers/net/wireless/cnss2/power.c b/drivers/net/wireless/cnss2/power.c new file mode 100644 index 000000000000..8a58a5357765 --- /dev/null +++ b/drivers/net/wireless/cnss2/power.c @@ -0,0 +1,390 @@ +/* Copyright (c) 2016-2018, 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/delay.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/regulator/consumer.h> + +#include "main.h" +#include "debug.h" + +static struct cnss_vreg_info cnss_vreg_info[] = { + {NULL, "vdd-wlan-core", 1300000, 1300000, 0, 0}, + {NULL, "vdd-wlan-io", 1800000, 1800000, 0, 0}, + {NULL, "vdd-wlan-xtal-aon", 0, 0, 0, 0}, + {NULL, "vdd-wlan-xtal", 1800000, 1800000, 0, 2}, + {NULL, "vdd-wlan", 0, 0, 0, 0}, + {NULL, "vdd-wlan-sp2t", 2700000, 2700000, 0, 0}, + {NULL, "wlan-ant-switch", 2700000, 2700000, 20000, 0}, + {NULL, "wlan-soc-swreg", 1200000, 1200000, 0, 0}, + {NULL, "vdd-wlan-en", 0, 0, 0, 10}, +}; + +#define CNSS_VREG_INFO_SIZE ARRAY_SIZE(cnss_vreg_info) +#define MAX_PROP_SIZE 32 + +#define BOOTSTRAP_GPIO "qcom,enable-bootstrap-gpio" +#define BOOTSTRAP_ACTIVE "bootstrap_active" +#define WLAN_EN_GPIO "wlan-en-gpio" +#define WLAN_EN_ACTIVE "wlan_en_active" +#define WLAN_EN_SLEEP "wlan_en_sleep" + +#define BOOTSTRAP_DELAY 1000 +#define WLAN_ENABLE_DELAY 1000 + +int cnss_get_vreg(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + int i; + struct cnss_vreg_info *vreg_info; + struct device *dev; + struct regulator *reg; + const __be32 *prop; + char prop_name[MAX_PROP_SIZE]; + int len; + + dev = &plat_priv->plat_dev->dev; + + plat_priv->vreg_info = devm_kzalloc(dev, sizeof(cnss_vreg_info), + GFP_KERNEL); + if (!plat_priv->vreg_info) { + ret = -ENOMEM; + goto out; + } + + memcpy(plat_priv->vreg_info, cnss_vreg_info, sizeof(cnss_vreg_info)); + + for (i = 0; i < CNSS_VREG_INFO_SIZE; i++) { + vreg_info = &plat_priv->vreg_info[i]; + reg = devm_regulator_get_optional(dev, vreg_info->name); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + if (ret == -ENODEV) + continue; + else if (ret == -EPROBE_DEFER) + cnss_pr_info("EPROBE_DEFER for regulator: %s\n", + vreg_info->name); + else + cnss_pr_err("Failed to get regulator %s, err = %d\n", + vreg_info->name, ret); + goto out; + } + + vreg_info->reg = reg; + + snprintf(prop_name, MAX_PROP_SIZE, "qcom,%s-info", + vreg_info->name); + + prop = of_get_property(dev->of_node, prop_name, &len); + cnss_pr_dbg("Got regulator info, name: %s, len: %d\n", + prop_name, len); + + if (!prop || len != (4 * sizeof(__be32))) { + cnss_pr_dbg("Property %s %s, use default\n", prop_name, + prop ? "invalid format" : "doesn't exist"); + } else { + vreg_info->min_uv = be32_to_cpup(&prop[0]); + vreg_info->max_uv = be32_to_cpup(&prop[1]); + vreg_info->load_ua = be32_to_cpup(&prop[2]); + vreg_info->delay_us = be32_to_cpup(&prop[3]); + } + + cnss_pr_dbg("Got regulator: %s, min_uv: %u, max_uv: %u, load_ua: %u, delay_us: %u\n", + vreg_info->name, vreg_info->min_uv, + vreg_info->max_uv, vreg_info->load_ua, + vreg_info->delay_us); + } + + return 0; +out: + return ret; +} + +#ifndef CONFIG_MSM_GVM_QUIN +static int cnss_vreg_on(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct cnss_vreg_info *vreg_info; + int i; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return -ENODEV; + } + + for (i = 0; i < CNSS_VREG_INFO_SIZE; i++) { + vreg_info = &plat_priv->vreg_info[i]; + + if (!vreg_info->reg) + continue; + + cnss_pr_dbg("Regulator %s is being enabled\n", vreg_info->name); + + if (vreg_info->min_uv != 0 && vreg_info->max_uv != 0) { + ret = regulator_set_voltage(vreg_info->reg, + vreg_info->min_uv, + vreg_info->max_uv); + + if (ret) { + cnss_pr_err("Failed to set voltage for regulator %s, min_uv: %u, max_uv: %u, err = %d\n", + vreg_info->name, vreg_info->min_uv, + vreg_info->max_uv, ret); + break; + } + } + + if (vreg_info->load_ua) { + ret = regulator_set_load(vreg_info->reg, + vreg_info->load_ua); + + if (ret < 0) { + cnss_pr_err("Failed to set load for regulator %s, load: %u, err = %d\n", + vreg_info->name, vreg_info->load_ua, + ret); + break; + } + } + + if (vreg_info->delay_us) + udelay(vreg_info->delay_us); + + ret = regulator_enable(vreg_info->reg); + if (ret) { + cnss_pr_err("Failed to enable regulator %s, err = %d\n", + vreg_info->name, ret); + break; + } + } + + if (ret) { + for (; i >= 0; i--) { + vreg_info = &plat_priv->vreg_info[i]; + + if (!vreg_info->reg) + continue; + + regulator_disable(vreg_info->reg); + if (vreg_info->load_ua) + regulator_set_load(vreg_info->reg, 0); + if (vreg_info->min_uv != 0 && vreg_info->max_uv != 0) + regulator_set_voltage(vreg_info->reg, 0, + vreg_info->max_uv); + } + + return ret; + } + + return 0; +} + +static int cnss_vreg_off(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct cnss_vreg_info *vreg_info; + int i; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return -ENODEV; + } + + for (i = CNSS_VREG_INFO_SIZE - 1; i >= 0; i--) { + vreg_info = &plat_priv->vreg_info[i]; + + if (!vreg_info->reg) + continue; + + cnss_pr_dbg("Regulator %s is being disabled\n", + vreg_info->name); + + ret = regulator_disable(vreg_info->reg); + if (ret) + cnss_pr_err("Failed to disable regulator %s, err = %d\n", + vreg_info->name, ret); + + if (vreg_info->load_ua) { + ret = regulator_set_load(vreg_info->reg, 0); + if (ret < 0) + cnss_pr_err("Failed to set load for regulator %s, err = %d\n", + vreg_info->name, ret); + } + + if (vreg_info->min_uv != 0 && vreg_info->max_uv != 0) { + ret = regulator_set_voltage(vreg_info->reg, 0, + vreg_info->max_uv); + if (ret) + cnss_pr_err("Failed to set voltage for regulator %s, err = %d\n", + vreg_info->name, ret); + } + } + + return ret; +} +#endif /* CONFIG_MSM_GVM_QUIN */ + +int cnss_get_pinctrl(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + struct device *dev; + struct cnss_pinctrl_info *pinctrl_info; + + dev = &plat_priv->plat_dev->dev; + pinctrl_info = &plat_priv->pinctrl_info; + + pinctrl_info->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR_OR_NULL(pinctrl_info->pinctrl)) { + ret = PTR_ERR(pinctrl_info->pinctrl); + cnss_pr_err("Failed to get pinctrl, err = %d\n", ret); + goto out; + } + + if (of_find_property(dev->of_node, BOOTSTRAP_GPIO, NULL)) { + pinctrl_info->bootstrap_active = + pinctrl_lookup_state(pinctrl_info->pinctrl, + BOOTSTRAP_ACTIVE); + if (IS_ERR_OR_NULL(pinctrl_info->bootstrap_active)) { + ret = PTR_ERR(pinctrl_info->bootstrap_active); + cnss_pr_err("Failed to get bootstrap active state, err = %d\n", + ret); + goto out; + } + } + + if (of_find_property(dev->of_node, WLAN_EN_GPIO, NULL)) { + pinctrl_info->wlan_en_active = + pinctrl_lookup_state(pinctrl_info->pinctrl, + WLAN_EN_ACTIVE); + if (IS_ERR_OR_NULL(pinctrl_info->wlan_en_active)) { + ret = PTR_ERR(pinctrl_info->wlan_en_active); + cnss_pr_err("Failed to get wlan_en active state, err = %d\n", + ret); + goto out; + } + + pinctrl_info->wlan_en_sleep = + pinctrl_lookup_state(pinctrl_info->pinctrl, + WLAN_EN_SLEEP); + if (IS_ERR_OR_NULL(pinctrl_info->wlan_en_sleep)) { + ret = PTR_ERR(pinctrl_info->wlan_en_sleep); + cnss_pr_err("Failed to get wlan_en sleep state, err = %d\n", + ret); + goto out; + } + } + + return 0; +out: + return ret; +} + +#ifndef CONFIG_MSM_GVM_QUIN +static int cnss_select_pinctrl_state(struct cnss_plat_data *plat_priv, + bool state) +{ + int ret = 0; + struct cnss_pinctrl_info *pinctrl_info; + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + ret = -ENODEV; + goto out; + } + + pinctrl_info = &plat_priv->pinctrl_info; + + if (state) { + if (!IS_ERR_OR_NULL(pinctrl_info->bootstrap_active)) { + ret = pinctrl_select_state(pinctrl_info->pinctrl, + pinctrl_info-> + bootstrap_active); + if (ret) { + cnss_pr_err("Failed to select bootstrap active state, err = %d\n", + ret); + goto out; + } + udelay(BOOTSTRAP_DELAY); + } + + if (!IS_ERR_OR_NULL(pinctrl_info->wlan_en_active)) { + ret = pinctrl_select_state(pinctrl_info->pinctrl, + pinctrl_info-> + wlan_en_active); + if (ret) { + cnss_pr_err("Failed to select wlan_en active state, err = %d\n", + ret); + goto out; + } + udelay(WLAN_ENABLE_DELAY); + } + } else { + if (!IS_ERR_OR_NULL(pinctrl_info->wlan_en_sleep)) { + ret = pinctrl_select_state(pinctrl_info->pinctrl, + pinctrl_info->wlan_en_sleep); + if (ret) { + cnss_pr_err("Failed to select wlan_en sleep state, err = %d\n", + ret); + goto out; + } + } + } + + return 0; +out: + return ret; +} + +int cnss_power_on_device(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + ret = cnss_vreg_on(plat_priv); + if (ret) { + cnss_pr_err("Failed to turn on vreg, err = %d\n", ret); + goto out; + } + + ret = cnss_select_pinctrl_state(plat_priv, true); + if (ret) { + cnss_pr_err("Failed to select pinctrl state, err = %d\n", ret); + goto vreg_off; + } + + return 0; +vreg_off: + cnss_vreg_off(plat_priv); +out: + return ret; +} + +void cnss_power_off_device(struct cnss_plat_data *plat_priv) +{ + cnss_select_pinctrl_state(plat_priv, false); + cnss_vreg_off(plat_priv); +} +#endif /* CONFIG_MSM_GVM_QUIN */ + +void cnss_set_pin_connect_status(struct cnss_plat_data *plat_priv) +{ + unsigned long pin_status = 0; + + set_bit(CNSS_WLAN_EN, &pin_status); + set_bit(CNSS_PCIE_TXN, &pin_status); + set_bit(CNSS_PCIE_TXP, &pin_status); + set_bit(CNSS_PCIE_RXN, &pin_status); + set_bit(CNSS_PCIE_RXP, &pin_status); + set_bit(CNSS_PCIE_REFCLKN, &pin_status); + set_bit(CNSS_PCIE_REFCLKP, &pin_status); + set_bit(CNSS_PCIE_RST, &pin_status); + + plat_priv->pin_result.host_pin_result = pin_status; +} diff --git a/drivers/net/wireless/cnss2/qmi.c b/drivers/net/wireless/cnss2/qmi.c new file mode 100644 index 000000000000..669816c84e37 --- /dev/null +++ b/drivers/net/wireless/cnss2/qmi.c @@ -0,0 +1,1093 @@ +/* Copyright (c) 2015-2018, 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/firmware.h> +#include <linux/module.h> +#include <linux/qmi_encdec.h> +#include <soc/qcom/msm_qmi_interface.h> + +#include "bus.h" +#include "debug.h" +#include "main.h" +#include "qmi.h" + +#define WLFW_SERVICE_INS_ID_V01 1 +#define WLFW_CLIENT_ID 0x4b4e454c +#define MAX_BDF_FILE_NAME 11 +#define DEFAULT_BDF_FILE_NAME "bdwlan.elf" +#define BDF_FILE_NAME_PREFIX "bdwlan.e" + +#ifdef CONFIG_CNSS2_DEBUG +static unsigned int qmi_timeout = 10000; +module_param(qmi_timeout, uint, 0600); +MODULE_PARM_DESC(qmi_timeout, "Timeout for QMI message in milliseconds"); + +#define QMI_WLFW_TIMEOUT_MS qmi_timeout +#else +#define QMI_WLFW_TIMEOUT_MS 10000 +#endif + +static bool daemon_support; +module_param(daemon_support, bool, 0600); +MODULE_PARM_DESC(daemon_support, "User space has cnss-daemon support or not"); + +static bool bdf_bypass; +#ifdef CONFIG_CNSS2_DEBUG +module_param(bdf_bypass, bool, 0600); +MODULE_PARM_DESC(bdf_bypass, "If BDF is not found, send dummy BDF to FW"); +#endif + +enum cnss_bdf_type { + CNSS_BDF_BIN, + CNSS_BDF_ELF, +}; + +static char *cnss_qmi_mode_to_str(enum wlfw_driver_mode_enum_v01 mode) +{ + switch (mode) { + case QMI_WLFW_MISSION_V01: + return "MISSION"; + case QMI_WLFW_FTM_V01: + return "FTM"; + case QMI_WLFW_EPPING_V01: + return "EPPING"; + case QMI_WLFW_WALTEST_V01: + return "WALTEST"; + case QMI_WLFW_OFF_V01: + return "OFF"; + case QMI_WLFW_CCPM_V01: + return "CCPM"; + case QMI_WLFW_QVIT_V01: + return "QVIT"; + case QMI_WLFW_CALIBRATION_V01: + return "CALIBRATION"; + default: + return "UNKNOWN"; + } +}; + +static void cnss_wlfw_clnt_notifier_work(struct work_struct *work) +{ + struct cnss_plat_data *plat_priv = + container_of(work, struct cnss_plat_data, qmi_recv_msg_work); + int ret = 0; + + cnss_pr_dbg("Receiving QMI WLFW event in work queue context\n"); + + do { + ret = qmi_recv_msg(plat_priv->qmi_wlfw_clnt); + } while (ret == 0); + + if (ret != -ENOMSG) + cnss_pr_err("Error receiving message: %d\n", ret); + + cnss_pr_dbg("Receiving QMI event completed\n"); +} + +static void cnss_wlfw_clnt_notifier(struct qmi_handle *handle, + enum qmi_event_type event, + void *notify_priv) +{ + struct cnss_plat_data *plat_priv = notify_priv; + + cnss_pr_dbg("Received QMI WLFW event: %d\n", event); + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return; + } + + switch (event) { + case QMI_RECV_MSG: + schedule_work(&plat_priv->qmi_recv_msg_work); + break; + case QMI_SERVER_EXIT: + break; + default: + cnss_pr_dbg("Unhandled QMI event: %d\n", event); + break; + } +} + +static int cnss_wlfw_clnt_svc_event_notifier(struct notifier_block *nb, + unsigned long code, void *_cmd) +{ + struct cnss_plat_data *plat_priv = + container_of(nb, struct cnss_plat_data, qmi_wlfw_clnt_nb); + int ret = 0; + + cnss_pr_dbg("Received QMI WLFW service event: %ld\n", code); + + switch (code) { + case QMI_SERVER_ARRIVE: + ret = cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_SERVER_ARRIVE, + 0, NULL); + break; + + case QMI_SERVER_EXIT: + ret = cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_SERVER_EXIT, + 0, NULL); + break; + default: + cnss_pr_dbg("Invalid QMI service event: %ld\n", code); + break; + } + + return ret; +} + +static int cnss_wlfw_host_cap_send_sync(struct cnss_plat_data *plat_priv) +{ + struct wlfw_host_cap_req_msg_v01 req; + struct wlfw_host_cap_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + int ret = 0; + + cnss_pr_dbg("Sending host capability message, state: 0x%lx\n", + plat_priv->driver_state); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.num_clients_valid = 1; + req.num_clients = daemon_support ? 2 : 1; + cnss_pr_dbg("Number of clients is %d\n", req.num_clients); + + req.wake_msi = cnss_bus_get_wake_irq(plat_priv); + if (req.wake_msi) { + cnss_pr_dbg("WAKE MSI base data is %d\n", req.wake_msi); + req.wake_msi_valid = 1; + } + + req.bdf_support_valid = 1; + req.bdf_support = 1; + + req.m3_support_valid = 1; + req.m3_support = 1; + + req.m3_cache_support_valid = 1; + req.m3_cache_support = 1; + + req.cal_done_valid = 1; + req.cal_done = plat_priv->cal_done; + cnss_pr_dbg("Calibration done is %d\n", plat_priv->cal_done); + + req_desc.max_msg_len = WLFW_HOST_CAP_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_HOST_CAP_REQ_V01; + req_desc.ei_array = wlfw_host_cap_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_HOST_CAP_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_HOST_CAP_RESP_V01; + resp_desc.ei_array = wlfw_host_cap_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, &req, + sizeof(req), &resp_desc, &resp, sizeof(resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send host capability request, err = %d\n", + ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("Host capability request failed, result: %d, err: %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } + + return 0; +out: + CNSS_ASSERT(0); + return ret; +} + +static int cnss_wlfw_ind_register_send_sync(struct cnss_plat_data *plat_priv) +{ + struct wlfw_ind_register_req_msg_v01 req; + struct wlfw_ind_register_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + int ret = 0; + + cnss_pr_dbg("Sending indication register message, state: 0x%lx\n", + plat_priv->driver_state); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.client_id_valid = 1; + req.client_id = WLFW_CLIENT_ID; + req.fw_ready_enable_valid = 1; + req.fw_ready_enable = 1; + req.request_mem_enable_valid = 1; + req.request_mem_enable = 1; + req.fw_mem_ready_enable_valid = 1; + req.fw_mem_ready_enable = 1; + req.fw_init_done_enable_valid = 1; + req.fw_init_done_enable = 1; + req.pin_connect_result_enable_valid = 1; + req.pin_connect_result_enable = 1; + + req_desc.max_msg_len = WLFW_IND_REGISTER_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_IND_REGISTER_REQ_V01; + req_desc.ei_array = wlfw_ind_register_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_IND_REGISTER_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_IND_REGISTER_RESP_V01; + resp_desc.ei_array = wlfw_ind_register_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, &req, + sizeof(req), &resp_desc, &resp, sizeof(resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send indication register request, err = %d\n", + ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("Indication register request failed, result: %d, err: %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } + + return 0; +out: + CNSS_ASSERT(0); + return ret; +} + +static int cnss_wlfw_request_mem_ind_hdlr(struct cnss_plat_data *plat_priv, + void *msg, unsigned int msg_len) +{ + struct msg_desc ind_desc; + struct wlfw_request_mem_ind_msg_v01 *ind_msg; + int ret = 0, i; + + ind_msg = kzalloc(sizeof(*ind_msg), GFP_KERNEL); + if (!ind_msg) + return -ENOMEM; + + ind_desc.msg_id = QMI_WLFW_REQUEST_MEM_IND_V01; + ind_desc.max_msg_len = WLFW_REQUEST_MEM_IND_MSG_V01_MAX_MSG_LEN; + ind_desc.ei_array = wlfw_request_mem_ind_msg_v01_ei; + + ret = qmi_kernel_decode(&ind_desc, ind_msg, msg, msg_len); + if (ret < 0) { + cnss_pr_err("Failed to decode request memory indication, msg_len: %u, err = %d\n", + ret, msg_len); + goto out; + } + + if (ind_msg->mem_seg_len == 0 || + ind_msg->mem_seg_len > QMI_WLFW_MAX_NUM_MEM_SEG_V01) { + cnss_pr_err("Invalid memory segment length: %u\n", + ind_msg->mem_seg_len); + ret = -EINVAL; + goto out; + } + + cnss_pr_dbg("FW memory segment count is %u\n", ind_msg->mem_seg_len); + plat_priv->fw_mem_seg_len = ind_msg->mem_seg_len; + for (i = 0; i < plat_priv->fw_mem_seg_len; i++) { + plat_priv->fw_mem[i].type = ind_msg->mem_seg[i].type; + plat_priv->fw_mem[i].size = ind_msg->mem_seg[i].size; + } + + cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_REQUEST_MEM, + 0, NULL); + + kfree(ind_msg); + return 0; + +out: + kfree(ind_msg); + return ret; +} + +static int cnss_qmi_pin_result_ind_hdlr(struct cnss_plat_data *plat_priv, + void *msg, unsigned int msg_len) +{ + struct msg_desc ind_desc; + struct wlfw_pin_connect_result_ind_msg_v01 ind_msg; + int ret = 0; + + ind_desc.msg_id = QMI_WLFW_PIN_CONNECT_RESULT_IND_V01; + ind_desc.max_msg_len = WLFW_PIN_CONNECT_RESULT_IND_MSG_V01_MAX_MSG_LEN; + ind_desc.ei_array = wlfw_pin_connect_result_ind_msg_v01_ei; + + ret = qmi_kernel_decode(&ind_desc, &ind_msg, msg, msg_len); + if (ret < 0) { + cnss_pr_err("Failed to decode pin connect result indication, msg_len: %u, err = %d\n", + msg_len, ret); + return ret; + } + if (ind_msg.pwr_pin_result_valid) + plat_priv->pin_result.fw_pwr_pin_result = + ind_msg.pwr_pin_result; + if (ind_msg.phy_io_pin_result_valid) + plat_priv->pin_result.fw_phy_io_pin_result = + ind_msg.phy_io_pin_result; + if (ind_msg.rf_pin_result_valid) + plat_priv->pin_result.fw_rf_pin_result = ind_msg.rf_pin_result; + + cnss_pr_dbg("Pin connect Result: pwr_pin: 0x%x phy_io_pin: 0x%x rf_io_pin: 0x%x\n", + ind_msg.pwr_pin_result, ind_msg.phy_io_pin_result, + ind_msg.rf_pin_result); + return ret; +} + +int cnss_wlfw_respond_mem_send_sync(struct cnss_plat_data *plat_priv) +{ + struct wlfw_respond_mem_req_msg_v01 *req; + struct wlfw_respond_mem_resp_msg_v01 *resp; + struct msg_desc req_desc, resp_desc; + struct cnss_fw_mem *fw_mem = plat_priv->fw_mem; + int ret = 0, i; + + cnss_pr_dbg("Sending respond memory message, state: 0x%lx\n", + plat_priv->driver_state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) + return -ENOMEM; + + req->mem_seg_len = plat_priv->fw_mem_seg_len; + for (i = 0; i < req->mem_seg_len; i++) { + if (!fw_mem[i].pa || !fw_mem[i].size) { + if (fw_mem[i].type == 0) { + cnss_pr_err("Invalid memory for FW type, segment = %d\n", + i); + ret = -EINVAL; + goto out; + } + cnss_pr_err("Memory for FW is not available for type: %u\n", + fw_mem[i].type); + ret = -ENOMEM; + goto out; + } + + cnss_pr_dbg("Memory for FW, va: 0x%pK, pa: %pa, size: 0x%zx, type: %u\n", + fw_mem[i].va, &fw_mem[i].pa, + fw_mem[i].size, fw_mem[i].type); + + req->mem_seg[i].addr = fw_mem[i].pa; + req->mem_seg[i].size = fw_mem[i].size; + req->mem_seg[i].type = fw_mem[i].type; + } + + req_desc.max_msg_len = WLFW_RESPOND_MEM_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_RESPOND_MEM_REQ_V01; + req_desc.ei_array = wlfw_respond_mem_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_RESPOND_MEM_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_RESPOND_MEM_RESP_V01; + resp_desc.ei_array = wlfw_respond_mem_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, req, + sizeof(*req), &resp_desc, resp, sizeof(*resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send respond memory request, err = %d\n", + ret); + goto out; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("Respond memory request failed, result: %d, err: %d\n", + resp->resp.result, resp->resp.error); + ret = resp->resp.result; + goto out; + } + + kfree(req); + kfree(resp); + return 0; + +out: + CNSS_ASSERT(0); + kfree(req); + kfree(resp); + return ret; +} + +int cnss_wlfw_tgt_cap_send_sync(struct cnss_plat_data *plat_priv) +{ + struct wlfw_cap_req_msg_v01 req; + struct wlfw_cap_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + int ret = 0; + + cnss_pr_dbg("Sending target capability message, state: 0x%lx\n", + plat_priv->driver_state); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req_desc.max_msg_len = WLFW_CAP_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_CAP_REQ_V01; + req_desc.ei_array = wlfw_cap_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_CAP_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_CAP_RESP_V01; + resp_desc.ei_array = wlfw_cap_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, &req, + sizeof(req), &resp_desc, &resp, sizeof(resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send target capability request, err = %d\n", + ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("Target capability request failed, result: %d, err: %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } + + if (resp.chip_info_valid) + plat_priv->chip_info = resp.chip_info; + if (resp.board_info_valid) + plat_priv->board_info = resp.board_info; + else + plat_priv->board_info.board_id = 0xFF; + if (resp.soc_info_valid) + plat_priv->soc_info = resp.soc_info; + if (resp.fw_version_info_valid) + plat_priv->fw_version_info = resp.fw_version_info; + + cnss_pr_dbg("Target capability: chip_id: 0x%x, chip_family: 0x%x, board_id: 0x%x, soc_id: 0x%x, fw_version: 0x%x, fw_build_timestamp: %s", + plat_priv->chip_info.chip_id, + plat_priv->chip_info.chip_family, + plat_priv->board_info.board_id, plat_priv->soc_info.soc_id, + plat_priv->fw_version_info.fw_version, + plat_priv->fw_version_info.fw_build_timestamp); + + return 0; +out: + CNSS_ASSERT(0); + return ret; +} + +int cnss_wlfw_bdf_dnld_send_sync(struct cnss_plat_data *plat_priv) +{ + struct wlfw_bdf_download_req_msg_v01 *req; + struct wlfw_bdf_download_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + char filename[MAX_BDF_FILE_NAME]; + const struct firmware *fw_entry; + const u8 *temp; + unsigned int remaining; + int ret = 0; + + cnss_pr_dbg("Sending BDF download message, state: 0x%lx\n", + plat_priv->driver_state); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out; + } + + if (plat_priv->board_info.board_id == 0xFF) + snprintf(filename, sizeof(filename), DEFAULT_BDF_FILE_NAME); + else + snprintf(filename, sizeof(filename), + BDF_FILE_NAME_PREFIX "%02x", + plat_priv->board_info.board_id); + + if (bdf_bypass) { + cnss_pr_info("bdf_bypass is enabled, sending dummy BDF\n"); + temp = filename; + remaining = MAX_BDF_FILE_NAME; + goto bypass_bdf; + } + + ret = request_firmware(&fw_entry, filename, &plat_priv->plat_dev->dev); + if (ret) { + cnss_pr_err("Failed to load BDF: %s\n", filename); + goto err_req_fw; + } + + temp = fw_entry->data; + remaining = fw_entry->size; + +bypass_bdf: + cnss_pr_dbg("Downloading BDF: %s, size: %u\n", filename, remaining); + + memset(&resp, 0, sizeof(resp)); + + req_desc.max_msg_len = WLFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_BDF_DOWNLOAD_REQ_V01; + req_desc.ei_array = wlfw_bdf_download_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_BDF_DOWNLOAD_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_BDF_DOWNLOAD_RESP_V01; + resp_desc.ei_array = wlfw_bdf_download_resp_msg_v01_ei; + + while (remaining) { + req->valid = 1; + req->file_id_valid = 1; + req->file_id = plat_priv->board_info.board_id; + req->total_size_valid = 1; + req->total_size = remaining; + req->seg_id_valid = 1; + req->data_valid = 1; + req->end_valid = 1; + req->bdf_type_valid = 1; + req->bdf_type = CNSS_BDF_ELF; + + if (remaining > QMI_WLFW_MAX_DATA_SIZE_V01) { + req->data_len = QMI_WLFW_MAX_DATA_SIZE_V01; + } else { + req->data_len = remaining; + req->end = 1; + } + + memcpy(req->data, temp, req->data_len); + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, + req, sizeof(*req), &resp_desc, &resp, + sizeof(resp), QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send BDF download request, err = %d\n", + ret); + goto err_send; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("BDF download request failed, result: %d, err: %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto err_send; + } + + remaining -= req->data_len; + temp += req->data_len; + req->seg_id++; + } + +err_send: + if (!bdf_bypass) + release_firmware(fw_entry); +err_req_fw: + kfree(req); +out: + if (ret) + CNSS_ASSERT(0); + return ret; +} + +int cnss_wlfw_m3_dnld_send_sync(struct cnss_plat_data *plat_priv) +{ + struct wlfw_m3_info_req_msg_v01 req; + struct wlfw_m3_info_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + struct cnss_fw_mem *m3_mem = &plat_priv->m3_mem; + int ret = 0; + + cnss_pr_dbg("Sending M3 information message, state: 0x%lx\n", + plat_priv->driver_state); + + if (!m3_mem->pa || !m3_mem->size) { + cnss_pr_err("Memory for M3 is not available!\n"); + ret = -ENOMEM; + goto out; + } + + cnss_pr_dbg("M3 memory, va: 0x%pK, pa: %pa, size: 0x%zx\n", + m3_mem->va, &m3_mem->pa, m3_mem->size); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.addr = plat_priv->m3_mem.pa; + req.size = plat_priv->m3_mem.size; + + req_desc.max_msg_len = WLFW_M3_INFO_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_M3_INFO_REQ_V01; + req_desc.ei_array = wlfw_m3_info_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_M3_INFO_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_M3_INFO_RESP_V01; + resp_desc.ei_array = wlfw_m3_info_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, &req, + sizeof(req), &resp_desc, &resp, sizeof(resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send M3 information request, err = %d\n", + ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("M3 information request failed, result: %d, err: %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } + + return 0; + +out: + CNSS_ASSERT(0); + return ret; +} + +int cnss_wlfw_wlan_mode_send_sync(struct cnss_plat_data *plat_priv, + enum wlfw_driver_mode_enum_v01 mode) +{ + struct wlfw_wlan_mode_req_msg_v01 req; + struct wlfw_wlan_mode_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + int ret = 0; + + if (!plat_priv) + return -ENODEV; + + cnss_pr_dbg("Sending mode message, mode: %s(%d), state: 0x%lx\n", + cnss_qmi_mode_to_str(mode), mode, plat_priv->driver_state); + + if (mode == QMI_WLFW_OFF_V01 && + test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state)) { + cnss_pr_dbg("Recovery is in progress, ignore mode off request.\n"); + return 0; + } + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.mode = mode; + req.hw_debug_valid = 1; + req.hw_debug = 0; + + req_desc.max_msg_len = WLFW_WLAN_MODE_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_WLAN_MODE_REQ_V01; + req_desc.ei_array = wlfw_wlan_mode_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_WLAN_MODE_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_WLAN_MODE_RESP_V01; + resp_desc.ei_array = wlfw_wlan_mode_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, &req, + sizeof(req), &resp_desc, &resp, sizeof(resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + if (mode == QMI_WLFW_OFF_V01 && ret == -ENETRESET) { + cnss_pr_dbg("WLFW service is disconnected while sending mode off request.\n"); + return 0; + } + cnss_pr_err("Failed to send mode request, mode: %s(%d), err: %d\n", + cnss_qmi_mode_to_str(mode), mode, ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("Mode request failed, mode: %s(%d), result: %d, err: %d\n", + cnss_qmi_mode_to_str(mode), mode, resp.resp.result, + resp.resp.error); + ret = resp.resp.result; + goto out; + } + + return 0; +out: + if (mode != QMI_WLFW_OFF_V01) + CNSS_ASSERT(0); + return ret; +} + +int cnss_wlfw_wlan_cfg_send_sync(struct cnss_plat_data *plat_priv, + struct wlfw_wlan_cfg_req_msg_v01 *data) +{ + struct wlfw_wlan_cfg_req_msg_v01 req; + struct wlfw_wlan_cfg_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + int ret = 0; + + cnss_pr_dbg("Sending WLAN config message, state: 0x%lx\n", + plat_priv->driver_state); + + if (!plat_priv) + return -ENODEV; + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + memcpy(&req, data, sizeof(req)); + + req_desc.max_msg_len = WLFW_WLAN_CFG_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_WLAN_CFG_REQ_V01; + req_desc.ei_array = wlfw_wlan_cfg_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_WLAN_CFG_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_WLAN_CFG_RESP_V01; + resp_desc.ei_array = wlfw_wlan_cfg_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, &req, + sizeof(req), &resp_desc, &resp, sizeof(resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send WLAN config request, err = %d\n", + ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("WLAN config request failed, result: %d, err: %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } + + return 0; +out: + CNSS_ASSERT(0); + return ret; +} + +int cnss_wlfw_athdiag_read_send_sync(struct cnss_plat_data *plat_priv, + u32 offset, u32 mem_type, + u32 data_len, u8 *data) +{ + struct wlfw_athdiag_read_req_msg_v01 req; + struct wlfw_athdiag_read_resp_msg_v01 *resp; + struct msg_desc req_desc, resp_desc; + int ret = 0; + + if (!plat_priv) + return -ENODEV; + + if (!plat_priv->qmi_wlfw_clnt) + return -EINVAL; + + cnss_pr_dbg("athdiag read: state 0x%lx, offset %x, mem_type %x, data_len %u\n", + plat_priv->driver_state, offset, mem_type, data_len); + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) + return -ENOMEM; + + memset(&req, 0, sizeof(req)); + + req.offset = offset; + req.mem_type = mem_type; + req.data_len = data_len; + + req_desc.max_msg_len = WLFW_ATHDIAG_READ_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_ATHDIAG_READ_REQ_V01; + req_desc.ei_array = wlfw_athdiag_read_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_ATHDIAG_READ_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_ATHDIAG_READ_RESP_V01; + resp_desc.ei_array = wlfw_athdiag_read_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, &req, + sizeof(req), &resp_desc, resp, sizeof(*resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send athdiag read request, err = %d\n", + ret); + goto out; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("athdiag read request failed, result: %d, err: %d\n", + resp->resp.result, resp->resp.error); + ret = resp->resp.result; + goto out; + } + + if (!resp->data_valid || resp->data_len != data_len) { + cnss_pr_err("athdiag read data is invalid, data_valid = %u, data_len = %u\n", + resp->data_valid, resp->data_len); + ret = -EINVAL; + goto out; + } + + memcpy(data, resp->data, resp->data_len); + +out: + kfree(resp); + return ret; +} + +int cnss_wlfw_athdiag_write_send_sync(struct cnss_plat_data *plat_priv, + u32 offset, u32 mem_type, + u32 data_len, u8 *data) +{ + struct wlfw_athdiag_write_req_msg_v01 *req; + struct wlfw_athdiag_write_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + int ret = 0; + + if (!plat_priv) + return -ENODEV; + + if (!plat_priv->qmi_wlfw_clnt) + return -EINVAL; + + cnss_pr_dbg("athdiag write: state 0x%lx, offset %x, mem_type %x, data_len %u, data %p\n", + plat_priv->driver_state, offset, mem_type, data_len, data); + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + memset(&resp, 0, sizeof(resp)); + + req->offset = offset; + req->mem_type = mem_type; + req->data_len = data_len; + memcpy(req->data, data, data_len); + + req_desc.max_msg_len = WLFW_ATHDIAG_WRITE_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_ATHDIAG_WRITE_REQ_V01; + req_desc.ei_array = wlfw_athdiag_write_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_ATHDIAG_WRITE_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_ATHDIAG_WRITE_RESP_V01; + resp_desc.ei_array = wlfw_athdiag_write_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, &req_desc, req, + sizeof(*req), &resp_desc, &resp, sizeof(resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Failed to send athdiag write request, err = %d\n", + ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("athdiag write request failed, result: %d, err: %d\n", + resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } + +out: + kfree(req); + return ret; +} + +int cnss_wlfw_ini_send_sync(struct cnss_plat_data *plat_priv, + u8 fw_log_mode) +{ + int ret; + struct wlfw_ini_req_msg_v01 req; + struct wlfw_ini_resp_msg_v01 resp; + struct msg_desc req_desc, resp_desc; + + if (!plat_priv) + return -ENODEV; + + cnss_pr_dbg("Sending ini sync request, state: 0x%lx, fw_log_mode: %d\n", + plat_priv->driver_state, fw_log_mode); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.enablefwlog_valid = 1; + req.enablefwlog = fw_log_mode; + + req_desc.max_msg_len = WLFW_INI_REQ_MSG_V01_MAX_MSG_LEN; + req_desc.msg_id = QMI_WLFW_INI_REQ_V01; + req_desc.ei_array = wlfw_ini_req_msg_v01_ei; + + resp_desc.max_msg_len = WLFW_INI_RESP_MSG_V01_MAX_MSG_LEN; + resp_desc.msg_id = QMI_WLFW_INI_RESP_V01; + resp_desc.ei_array = wlfw_ini_resp_msg_v01_ei; + + ret = qmi_send_req_wait(plat_priv->qmi_wlfw_clnt, + &req_desc, &req, sizeof(req), + &resp_desc, &resp, sizeof(resp), + QMI_WLFW_TIMEOUT_MS); + if (ret < 0) { + cnss_pr_err("Send INI req failed fw_log_mode: %d, ret: %d\n", + fw_log_mode, ret); + goto out; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + cnss_pr_err("QMI INI request rejected, fw_log_mode:%d result:%d error:%d\n", + fw_log_mode, resp.resp.result, resp.resp.error); + ret = resp.resp.result; + goto out; + } + + return 0; + +out: + return ret; +} + +static void cnss_wlfw_clnt_ind(struct qmi_handle *handle, + unsigned int msg_id, void *msg, + unsigned int msg_len, void *ind_cb_priv) +{ + struct cnss_plat_data *plat_priv = ind_cb_priv; + + cnss_pr_dbg("Received QMI WLFW indication, msg_id: 0x%x, msg_len: %d\n", + msg_id, msg_len); + + if (!plat_priv) { + cnss_pr_err("plat_priv is NULL!\n"); + return; + } + + switch (msg_id) { + case QMI_WLFW_REQUEST_MEM_IND_V01: + cnss_wlfw_request_mem_ind_hdlr(plat_priv, msg, msg_len); + break; + case QMI_WLFW_FW_MEM_READY_IND_V01: + cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_FW_MEM_READY, + 0, NULL); + break; + case QMI_WLFW_FW_READY_IND_V01: + cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_COLD_BOOT_CAL_DONE, + 0, NULL); + break; + case QMI_WLFW_FW_INIT_DONE_IND_V01: + cnss_driver_event_post(plat_priv, + CNSS_DRIVER_EVENT_FW_READY, + 0, NULL); + break; + case QMI_WLFW_PIN_CONNECT_RESULT_IND_V01: + cnss_qmi_pin_result_ind_hdlr(plat_priv, msg, msg_len); + break; + default: + cnss_pr_err("Invalid QMI WLFW indication, msg_id: 0x%x\n", + msg_id); + break; + } +} + +unsigned int cnss_get_qmi_timeout(void) +{ + cnss_pr_dbg("QMI timeout is %u ms\n", QMI_WLFW_TIMEOUT_MS); + + return QMI_WLFW_TIMEOUT_MS; +} +EXPORT_SYMBOL(cnss_get_qmi_timeout); + +int cnss_wlfw_server_arrive(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + if (!plat_priv) + return -ENODEV; + + plat_priv->qmi_wlfw_clnt = + qmi_handle_create(cnss_wlfw_clnt_notifier, plat_priv); + if (!plat_priv->qmi_wlfw_clnt) { + cnss_pr_err("Failed to create QMI client handle!\n"); + ret = -ENOMEM; + goto err_create_handle; + } + + ret = qmi_connect_to_service(plat_priv->qmi_wlfw_clnt, + WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_INS_ID_V01); + if (ret < 0) { + cnss_pr_err("Failed to connect to QMI WLFW service, err = %d\n", + ret); + goto out; + } + + ret = qmi_register_ind_cb(plat_priv->qmi_wlfw_clnt, + cnss_wlfw_clnt_ind, plat_priv); + if (ret < 0) { + cnss_pr_err("Failed to register QMI WLFW service indication callback, err = %d\n", + ret); + goto out; + } + + set_bit(CNSS_QMI_WLFW_CONNECTED, &plat_priv->driver_state); + + cnss_pr_info("QMI WLFW service connected, state: 0x%lx\n", + plat_priv->driver_state); + + ret = cnss_wlfw_ind_register_send_sync(plat_priv); + if (ret < 0) + goto out; + + ret = cnss_wlfw_host_cap_send_sync(plat_priv); + if (ret < 0) + goto out; + + return 0; +out: + qmi_handle_destroy(plat_priv->qmi_wlfw_clnt); + plat_priv->qmi_wlfw_clnt = NULL; +err_create_handle: + CNSS_ASSERT(0); + return ret; +} + +int cnss_wlfw_server_exit(struct cnss_plat_data *plat_priv) +{ + if (!plat_priv) + return -ENODEV; + + qmi_handle_destroy(plat_priv->qmi_wlfw_clnt); + plat_priv->qmi_wlfw_clnt = NULL; + + clear_bit(CNSS_QMI_WLFW_CONNECTED, &plat_priv->driver_state); + + cnss_pr_info("QMI WLFW service disconnected, state: 0x%lx\n", + plat_priv->driver_state); + + return 0; +} + +int cnss_qmi_init(struct cnss_plat_data *plat_priv) +{ + int ret = 0; + + INIT_WORK(&plat_priv->qmi_recv_msg_work, + cnss_wlfw_clnt_notifier_work); + + plat_priv->qmi_wlfw_clnt_nb.notifier_call = + cnss_wlfw_clnt_svc_event_notifier; + + ret = qmi_svc_event_notifier_register(WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_INS_ID_V01, + &plat_priv->qmi_wlfw_clnt_nb); + if (ret < 0) + cnss_pr_err("Failed to register QMI event notifier, err = %d\n", + ret); + + return ret; +} + +void cnss_qmi_deinit(struct cnss_plat_data *plat_priv) +{ + qmi_svc_event_notifier_unregister(WLFW_SERVICE_ID_V01, + WLFW_SERVICE_VERS_V01, + WLFW_SERVICE_INS_ID_V01, + &plat_priv->qmi_wlfw_clnt_nb); +} diff --git a/drivers/net/wireless/cnss2/qmi.h b/drivers/net/wireless/cnss2/qmi.h new file mode 100644 index 000000000000..70d8d404c48e --- /dev/null +++ b/drivers/net/wireless/cnss2/qmi.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2015-2017, 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. + */ + +#ifndef _CNSS_QMI_H +#define _CNSS_QMI_H + +#include "wlan_firmware_service_v01.h" + +struct cnss_plat_data; + +int cnss_qmi_init(struct cnss_plat_data *plat_priv); +void cnss_qmi_deinit(struct cnss_plat_data *plat_priv); +int cnss_wlfw_server_arrive(struct cnss_plat_data *plat_priv); +int cnss_wlfw_server_exit(struct cnss_plat_data *plat_priv); +int cnss_wlfw_respond_mem_send_sync(struct cnss_plat_data *plat_priv); +int cnss_wlfw_tgt_cap_send_sync(struct cnss_plat_data *plat_priv); +int cnss_wlfw_bdf_dnld_send_sync(struct cnss_plat_data *plat_priv); +int cnss_wlfw_m3_dnld_send_sync(struct cnss_plat_data *plat_priv); +int cnss_wlfw_wlan_mode_send_sync(struct cnss_plat_data *plat_priv, + enum wlfw_driver_mode_enum_v01 mode); +int cnss_wlfw_wlan_cfg_send_sync(struct cnss_plat_data *plat_priv, + struct wlfw_wlan_cfg_req_msg_v01 *data); +int cnss_wlfw_athdiag_read_send_sync(struct cnss_plat_data *plat_priv, + u32 offset, u32 mem_type, + u32 data_len, u8 *data); +int cnss_wlfw_athdiag_write_send_sync(struct cnss_plat_data *plat_priv, + u32 offset, u32 mem_type, + u32 data_len, u8 *data); +int cnss_wlfw_ini_send_sync(struct cnss_plat_data *plat_priv, + u8 fw_log_mode); + +#endif /* _CNSS_QMI_H */ diff --git a/drivers/net/wireless/cnss2/wlan_firmware_service_v01.c b/drivers/net/wireless/cnss2/wlan_firmware_service_v01.c new file mode 100644 index 000000000000..bbf707b869bd --- /dev/null +++ b/drivers/net/wireless/cnss2/wlan_firmware_service_v01.c @@ -0,0 +1,2607 @@ +/* Copyright (c) 2015-2018, 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 "wlan_firmware_service_v01.h" + +static struct elem_info wlfw_ce_tgt_pipe_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + pipe_num), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_pipedir_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + pipe_dir), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + nentries), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + nbytes_max), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_tgt_pipe_cfg_s_v01, + flags), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_ce_svc_pipe_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_svc_pipe_cfg_s_v01, + service_id), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_pipedir_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_svc_pipe_cfg_s_v01, + pipe_dir), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_ce_svc_pipe_cfg_s_v01, + pipe_num), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_shadow_reg_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_shadow_reg_cfg_s_v01, + id), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_shadow_reg_cfg_s_v01, + offset), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_shadow_reg_v2_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_shadow_reg_v2_cfg_s_v01, + addr), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_memory_region_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_memory_region_info_s_v01, + region_addr), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_memory_region_info_s_v01, + size), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_memory_region_info_s_v01, + secure_flag), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_mem_cfg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_cfg_s_v01, + offset), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_cfg_s_v01, + size), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_cfg_s_v01, + secure_flag), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_mem_seg_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_seg_s_v01, + size), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_mem_type_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_seg_s_v01, + type), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_seg_s_v01, + mem_cfg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_MEM_CFG_V01, + .elem_size = sizeof(struct wlfw_mem_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_seg_s_v01, + mem_cfg), + .ei_array = wlfw_mem_cfg_s_v01_ei, + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_mem_seg_resp_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_seg_resp_s_v01, + addr), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_seg_resp_s_v01, + size), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_mem_type_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_seg_resp_s_v01, + type), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_mem_seg_resp_s_v01, + restore), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_rf_chip_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_rf_chip_info_s_v01, + chip_id), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_rf_chip_info_s_v01, + chip_family), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_rf_board_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_rf_board_info_s_v01, + board_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_soc_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_soc_info_s_v01, + soc_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +static struct elem_info wlfw_fw_version_info_s_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_fw_version_info_s_v01, + fw_version), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_MAX_TIMESTAMP_LEN_V01 + 1, + .elem_size = sizeof(char), + .is_array = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct wlfw_fw_version_info_s_v01, + fw_build_timestamp), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_ind_register_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_ind_register_req_msg_v01, + fw_ready_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_ready_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + initiate_cal_download_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + initiate_cal_download_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + initiate_cal_update_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + initiate_cal_update_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + msa_ready_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + msa_ready_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + pin_connect_result_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + pin_connect_result_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + client_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + client_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + request_mem_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + request_mem_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_mem_ready_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_mem_ready_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_init_done_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + fw_init_done_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + rejuvenate_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + rejuvenate_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1A, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + xo_cal_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1A, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + xo_cal_enable), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + cal_done_enable_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof(struct wlfw_ind_register_req_msg_v01, + cal_done_enable), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_ind_register_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_ind_register_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_ind_register_resp_msg_v01, + fw_status_valid), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_ind_register_resp_msg_v01, + fw_status), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_fw_ready_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_ready_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_pin_connect_result_ind_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_pin_connect_result_ind_msg_v01, + pwr_pin_result_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_pin_connect_result_ind_msg_v01, + pwr_pin_result), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct + wlfw_pin_connect_result_ind_msg_v01, + phy_io_pin_result_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct + wlfw_pin_connect_result_ind_msg_v01, + phy_io_pin_result), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct + wlfw_pin_connect_result_ind_msg_v01, + rf_pin_result_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct + wlfw_pin_connect_result_ind_msg_v01, + rf_pin_result), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_wlan_mode_req_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_driver_mode_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_wlan_mode_req_msg_v01, + mode), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_wlan_mode_req_msg_v01, + hw_debug_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_wlan_mode_req_msg_v01, + hw_debug), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_wlan_mode_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_wlan_mode_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_wlan_cfg_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + host_version_valid), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_MAX_STR_LEN_V01 + 1, + .elem_size = sizeof(char), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + host_version), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + tgt_cfg_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + tgt_cfg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_CE_V01, + .elem_size = sizeof(struct wlfw_ce_tgt_pipe_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + tgt_cfg), + .ei_array = wlfw_ce_tgt_pipe_cfg_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + svc_cfg_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + svc_cfg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_SVC_V01, + .elem_size = sizeof(struct wlfw_ce_svc_pipe_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + svc_cfg), + .ei_array = wlfw_ce_svc_pipe_cfg_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V01, + .elem_size = sizeof(struct wlfw_shadow_reg_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg), + .ei_array = wlfw_shadow_reg_cfg_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_v2_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_v2_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01, + .elem_size = sizeof(struct wlfw_shadow_reg_v2_cfg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_wlan_cfg_req_msg_v01, + shadow_reg_v2), + .ei_array = wlfw_shadow_reg_v2_cfg_s_v01_ei, + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_wlan_cfg_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_wlan_cfg_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cap_req_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cap_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + chip_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct wlfw_rf_chip_info_s_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + chip_info), + .ei_array = wlfw_rf_chip_info_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + board_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct wlfw_rf_board_info_s_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + board_info), + .ei_array = wlfw_rf_board_info_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + soc_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct wlfw_soc_info_s_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + soc_info), + .ei_array = wlfw_soc_info_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + fw_version_info_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct wlfw_fw_version_info_s_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + fw_version_info), + .ei_array = wlfw_fw_version_info_s_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + fw_build_id_valid), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_MAX_BUILD_ID_LEN_V01 + 1, + .elem_size = sizeof(char), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + fw_build_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + num_macs_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_cap_resp_msg_v01, + num_macs), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_bdf_download_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + valid), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + file_id_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + file_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + total_size_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + total_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + seg_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + seg_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_DATA_SIZE_V01, + .elem_size = sizeof(u8), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + end_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + end), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + bdf_type_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_bdf_download_req_msg_v01, + bdf_type), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_bdf_download_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_bdf_download_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_report_req_msg_v01_ei[] = { + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_cal_report_req_msg_v01, + meta_data_len), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = QMI_WLFW_MAX_NUM_CAL_V01, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_cal_report_req_msg_v01, + meta_data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_report_req_msg_v01, + xo_cal_data_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_report_req_msg_v01, + xo_cal_data), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_report_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_cal_report_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_initiate_cal_download_ind_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof( + struct wlfw_initiate_cal_download_ind_msg_v01, + cal_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_download_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + valid), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + file_id_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + file_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + total_size_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + total_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + seg_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + seg_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_DATA_SIZE_V01, + .elem_size = sizeof(u8), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + end_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cal_download_req_msg_v01, + end), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_download_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_cal_download_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_initiate_cal_update_ind_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + wlfw_initiate_cal_update_ind_msg_v01, + cal_id), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_initiate_cal_update_ind_msg_v01, + total_size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_update_req_msg_v01_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_cal_update_req_msg_v01, + cal_id), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_cal_update_req_msg_v01, + seg_id), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_update_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + file_id_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum wlfw_cal_temp_id_enum_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + file_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + total_size_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + total_size), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + seg_id_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + seg_id), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_DATA_SIZE_V01, + .elem_size = sizeof(u8), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + end_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_cal_update_resp_msg_v01, + end), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_info_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_msa_info_req_msg_v01, + msa_addr), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_msa_info_req_msg_v01, + size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_info_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_msa_info_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct wlfw_msa_info_resp_msg_v01, + mem_region_info_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01, + .elem_size = sizeof(struct wlfw_memory_region_info_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct wlfw_msa_info_resp_msg_v01, + mem_region_info), + .ei_array = wlfw_memory_region_info_s_v01_ei, + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_ready_req_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_msa_ready_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_msa_ready_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_ini_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_ini_req_msg_v01, + enablefwlog_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_ini_req_msg_v01, + enablefwlog), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_ini_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_ini_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_athdiag_read_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_athdiag_read_req_msg_v01, + offset), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_athdiag_read_req_msg_v01, + mem_type), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct wlfw_athdiag_read_req_msg_v01, + data_len), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_athdiag_read_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_athdiag_read_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_athdiag_read_resp_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_athdiag_read_resp_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_ATHDIAG_DATA_SIZE_V01, + .elem_size = sizeof(u8), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct + wlfw_athdiag_read_resp_msg_v01, + data), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_athdiag_write_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct + wlfw_athdiag_write_req_msg_v01, + offset), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_athdiag_write_req_msg_v01, + mem_type), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct + wlfw_athdiag_write_req_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAX_ATHDIAG_DATA_SIZE_V01, + .elem_size = sizeof(u8), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct + wlfw_athdiag_write_req_msg_v01, + data), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_athdiag_write_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_athdiag_write_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_vbatt_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_vbatt_req_msg_v01, + voltage_uv), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_vbatt_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_vbatt_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_mac_addr_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_mac_addr_req_msg_v01, + mac_addr_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = QMI_WLFW_MAC_ADDR_SIZE_V01, + .elem_size = sizeof(u8), + .is_array = STATIC_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_mac_addr_req_msg_v01, + mac_addr), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_mac_addr_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_mac_addr_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_host_cap_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + num_clients_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + num_clients), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + wake_msi_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + wake_msi), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + gpios_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + gpios_len), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = QMI_WLFW_MAX_NUM_GPIO_V01, + .elem_size = sizeof(u32), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + gpios), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + nm_modem_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + nm_modem), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + bdf_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x14, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + bdf_support), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + bdf_cache_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x15, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + bdf_cache_support), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + m3_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x16, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + m3_support), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + m3_cache_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x17, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + m3_cache_support), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + cal_filesys_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x18, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + cal_filesys_support), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + cal_cache_support_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x19, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + cal_cache_support), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1A, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + cal_done_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1A, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + cal_done), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + mem_bucket_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x1B, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + mem_bucket), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1C, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + mem_cfg_mode_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x1C, + .offset = offsetof(struct wlfw_host_cap_req_msg_v01, + mem_cfg_mode), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_host_cap_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_host_cap_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_request_mem_ind_msg_v01_ei[] = { + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_request_mem_ind_msg_v01, + mem_seg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_MEM_SEG_V01, + .elem_size = sizeof(struct wlfw_mem_seg_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_request_mem_ind_msg_v01, + mem_seg), + .ei_array = wlfw_mem_seg_s_v01_ei, + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_respond_mem_req_msg_v01_ei[] = { + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_respond_mem_req_msg_v01, + mem_seg_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = QMI_WLFW_MAX_NUM_MEM_SEG_V01, + .elem_size = sizeof(struct wlfw_mem_seg_resp_s_v01), + .is_array = VAR_LEN_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_respond_mem_req_msg_v01, + mem_seg), + .ei_array = wlfw_mem_seg_resp_s_v01_ei, + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_respond_mem_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_respond_mem_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_fw_mem_ready_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_fw_init_done_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_rejuvenate_ind_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + cause_for_rejuvenation_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + cause_for_rejuvenation), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + requesting_sub_system_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + requesting_sub_system), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + line_number_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .is_array = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + line_number), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + function_name_valid), + }, + { + .data_type = QMI_STRING, + .elem_len = QMI_WLFW_FUNCTION_NAME_LEN_V01 + 1, + .elem_size = sizeof(char), + .is_array = NO_ARRAY, + .tlv_type = 0x13, + .offset = offsetof(struct wlfw_rejuvenate_ind_msg_v01, + function_name), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_rejuvenate_ack_req_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_rejuvenate_ack_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct + wlfw_rejuvenate_ack_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_dynamic_feature_mask_req_msg_v01_ei[] = { + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_req_msg_v01, + mask_valid), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_req_msg_v01, + mask), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_dynamic_feature_mask_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + prev_mask_valid), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + prev_mask), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + curr_mask_valid), + }, + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof( + struct wlfw_dynamic_feature_mask_resp_msg_v01, + curr_mask), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_m3_info_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_8_BYTE, + .elem_len = 1, + .elem_size = sizeof(u64), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_m3_info_req_msg_v01, + addr), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_m3_info_req_msg_v01, + size), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_m3_info_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .is_array = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct wlfw_m3_info_resp_msg_v01, + resp), + .ei_array = get_qmi_response_type_v01_ei(), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_xo_cal_ind_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .is_array = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct wlfw_xo_cal_ind_msg_v01, + xo_cal_data), + }, + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; + +struct elem_info wlfw_cal_done_ind_msg_v01_ei[] = { + { + .data_type = QMI_EOTI, + .is_array = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + }, +}; diff --git a/drivers/net/wireless/cnss2/wlan_firmware_service_v01.h b/drivers/net/wireless/cnss2/wlan_firmware_service_v01.h new file mode 100644 index 000000000000..00a873d11d14 --- /dev/null +++ b/drivers/net/wireless/cnss2/wlan_firmware_service_v01.h @@ -0,0 +1,728 @@ +/* Copyright (c) 2015-2018, 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. + * + */ + +#ifndef WLAN_FIRMWARE_SERVICE_V01_H +#define WLAN_FIRMWARE_SERVICE_V01_H + +#include <linux/qmi_encdec.h> +#include <soc/qcom/msm_qmi_interface.h> + +#define WLFW_SERVICE_ID_V01 0x45 +#define WLFW_SERVICE_VERS_V01 0x01 + +#define QMI_WLFW_BDF_DOWNLOAD_REQ_V01 0x0025 +#define QMI_WLFW_FW_MEM_READY_IND_V01 0x0037 +#define QMI_WLFW_INITIATE_CAL_UPDATE_IND_V01 0x002A +#define QMI_WLFW_CAL_DONE_IND_V01 0x003E +#define QMI_WLFW_HOST_CAP_REQ_V01 0x0034 +#define QMI_WLFW_DYNAMIC_FEATURE_MASK_RESP_V01 0x003B +#define QMI_WLFW_M3_INFO_REQ_V01 0x003C +#define QMI_WLFW_CAP_REQ_V01 0x0024 +#define QMI_WLFW_FW_INIT_DONE_IND_V01 0x0038 +#define QMI_WLFW_CAL_REPORT_REQ_V01 0x0026 +#define QMI_WLFW_M3_INFO_RESP_V01 0x003C +#define QMI_WLFW_CAL_UPDATE_RESP_V01 0x0029 +#define QMI_WLFW_CAL_DOWNLOAD_RESP_V01 0x0027 +#define QMI_WLFW_XO_CAL_IND_V01 0x003D +#define QMI_WLFW_INI_RESP_V01 0x002F +#define QMI_WLFW_CAL_REPORT_RESP_V01 0x0026 +#define QMI_WLFW_MAC_ADDR_RESP_V01 0x0033 +#define QMI_WLFW_INITIATE_CAL_DOWNLOAD_IND_V01 0x0028 +#define QMI_WLFW_HOST_CAP_RESP_V01 0x0034 +#define QMI_WLFW_MSA_READY_IND_V01 0x002B +#define QMI_WLFW_ATHDIAG_WRITE_RESP_V01 0x0031 +#define QMI_WLFW_WLAN_MODE_REQ_V01 0x0022 +#define QMI_WLFW_IND_REGISTER_REQ_V01 0x0020 +#define QMI_WLFW_WLAN_CFG_RESP_V01 0x0023 +#define QMI_WLFW_REQUEST_MEM_IND_V01 0x0035 +#define QMI_WLFW_REJUVENATE_IND_V01 0x0039 +#define QMI_WLFW_DYNAMIC_FEATURE_MASK_REQ_V01 0x003B +#define QMI_WLFW_ATHDIAG_WRITE_REQ_V01 0x0031 +#define QMI_WLFW_WLAN_MODE_RESP_V01 0x0022 +#define QMI_WLFW_RESPOND_MEM_REQ_V01 0x0036 +#define QMI_WLFW_PIN_CONNECT_RESULT_IND_V01 0x002C +#define QMI_WLFW_FW_READY_IND_V01 0x0021 +#define QMI_WLFW_MSA_READY_RESP_V01 0x002E +#define QMI_WLFW_CAL_UPDATE_REQ_V01 0x0029 +#define QMI_WLFW_INI_REQ_V01 0x002F +#define QMI_WLFW_BDF_DOWNLOAD_RESP_V01 0x0025 +#define QMI_WLFW_REJUVENATE_ACK_RESP_V01 0x003A +#define QMI_WLFW_MSA_INFO_RESP_V01 0x002D +#define QMI_WLFW_MSA_READY_REQ_V01 0x002E +#define QMI_WLFW_CAP_RESP_V01 0x0024 +#define QMI_WLFW_REJUVENATE_ACK_REQ_V01 0x003A +#define QMI_WLFW_ATHDIAG_READ_RESP_V01 0x0030 +#define QMI_WLFW_VBATT_REQ_V01 0x0032 +#define QMI_WLFW_MAC_ADDR_REQ_V01 0x0033 +#define QMI_WLFW_RESPOND_MEM_RESP_V01 0x0036 +#define QMI_WLFW_VBATT_RESP_V01 0x0032 +#define QMI_WLFW_MSA_INFO_REQ_V01 0x002D +#define QMI_WLFW_CAL_DOWNLOAD_REQ_V01 0x0027 +#define QMI_WLFW_ATHDIAG_READ_REQ_V01 0x0030 +#define QMI_WLFW_WLAN_CFG_REQ_V01 0x0023 +#define QMI_WLFW_IND_REGISTER_RESP_V01 0x0020 + +#define QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01 2 +#define QMI_WLFW_MAX_NUM_MEM_SEG_V01 32 +#define QMI_WLFW_MAX_NUM_CAL_V01 5 +#define QMI_WLFW_MAX_DATA_SIZE_V01 6144 +#define QMI_WLFW_FUNCTION_NAME_LEN_V01 128 +#define QMI_WLFW_MAX_NUM_CE_V01 12 +#define QMI_WLFW_MAX_TIMESTAMP_LEN_V01 32 +#define QMI_WLFW_MAX_ATHDIAG_DATA_SIZE_V01 6144 +#define QMI_WLFW_MAX_NUM_GPIO_V01 32 +#define QMI_WLFW_MAX_BUILD_ID_LEN_V01 128 +#define QMI_WLFW_MAX_NUM_MEM_CFG_V01 2 +#define QMI_WLFW_MAX_STR_LEN_V01 16 +#define QMI_WLFW_MAX_NUM_SHADOW_REG_V01 24 +#define QMI_WLFW_MAC_ADDR_SIZE_V01 6 +#define QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01 36 +#define QMI_WLFW_MAX_NUM_SVC_V01 24 + +enum wlfw_driver_mode_enum_v01 { + WLFW_DRIVER_MODE_ENUM_MIN_VAL_V01 = INT_MIN, + QMI_WLFW_MISSION_V01 = 0, + QMI_WLFW_FTM_V01 = 1, + QMI_WLFW_EPPING_V01 = 2, + QMI_WLFW_WALTEST_V01 = 3, + QMI_WLFW_OFF_V01 = 4, + QMI_WLFW_CCPM_V01 = 5, + QMI_WLFW_QVIT_V01 = 6, + QMI_WLFW_CALIBRATION_V01 = 7, + WLFW_DRIVER_MODE_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum wlfw_cal_temp_id_enum_v01 { + WLFW_CAL_TEMP_ID_ENUM_MIN_VAL_V01 = INT_MIN, + QMI_WLFW_CAL_TEMP_IDX_0_V01 = 0, + QMI_WLFW_CAL_TEMP_IDX_1_V01 = 1, + QMI_WLFW_CAL_TEMP_IDX_2_V01 = 2, + QMI_WLFW_CAL_TEMP_IDX_3_V01 = 3, + QMI_WLFW_CAL_TEMP_IDX_4_V01 = 4, + WLFW_CAL_TEMP_ID_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum wlfw_pipedir_enum_v01 { + WLFW_PIPEDIR_ENUM_MIN_VAL_V01 = INT_MIN, + QMI_WLFW_PIPEDIR_NONE_V01 = 0, + QMI_WLFW_PIPEDIR_IN_V01 = 1, + QMI_WLFW_PIPEDIR_OUT_V01 = 2, + QMI_WLFW_PIPEDIR_INOUT_V01 = 3, + WLFW_PIPEDIR_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +enum wlfw_mem_type_enum_v01 { + WLFW_MEM_TYPE_ENUM_MIN_VAL_V01 = INT_MIN, + QMI_WLFW_MEM_TYPE_MSA_V01 = 0, + QMI_WLFW_MEM_TYPE_DDR_V01 = 1, + QMI_WLFW_MEM_BDF_V01 = 2, + QMI_WLFW_MEM_M3_V01 = 3, + QMI_WLFW_MEM_CAL_V01 = 4, + QMI_WLFW_MEM_DPD_V01 = 5, + WLFW_MEM_TYPE_ENUM_MAX_VAL_V01 = INT_MAX, +}; + +#define QMI_WLFW_CE_ATTR_FLAGS_V01 ((u32)0x00) +#define QMI_WLFW_CE_ATTR_NO_SNOOP_V01 ((u32)0x01) +#define QMI_WLFW_CE_ATTR_BYTE_SWAP_DATA_V01 ((u32)0x02) +#define QMI_WLFW_CE_ATTR_SWIZZLE_DESCRIPTORS_V01 ((u32)0x04) +#define QMI_WLFW_CE_ATTR_DISABLE_INTR_V01 ((u32)0x08) +#define QMI_WLFW_CE_ATTR_ENABLE_POLL_V01 ((u32)0x10) + +#define QMI_WLFW_ALREADY_REGISTERED_V01 ((u64)0x01ULL) +#define QMI_WLFW_FW_READY_V01 ((u64)0x02ULL) +#define QMI_WLFW_MSA_READY_V01 ((u64)0x04ULL) +#define QMI_WLFW_FW_MEM_READY_V01 ((u64)0x08ULL) +#define QMI_WLFW_FW_INIT_DONE_V01 ((u64)0x10ULL) + +#define QMI_WLFW_FW_REJUVENATE_V01 ((u64)0x01ULL) + +struct wlfw_ce_tgt_pipe_cfg_s_v01 { + u32 pipe_num; + enum wlfw_pipedir_enum_v01 pipe_dir; + u32 nentries; + u32 nbytes_max; + u32 flags; +}; + +struct wlfw_ce_svc_pipe_cfg_s_v01 { + u32 service_id; + enum wlfw_pipedir_enum_v01 pipe_dir; + u32 pipe_num; +}; + +struct wlfw_shadow_reg_cfg_s_v01 { + u16 id; + u16 offset; +}; + +struct wlfw_shadow_reg_v2_cfg_s_v01 { + u32 addr; +}; + +struct wlfw_memory_region_info_s_v01 { + u64 region_addr; + u32 size; + u8 secure_flag; +}; + +struct wlfw_mem_cfg_s_v01 { + u64 offset; + u32 size; + u8 secure_flag; +}; + +struct wlfw_mem_seg_s_v01 { + u32 size; + enum wlfw_mem_type_enum_v01 type; + u32 mem_cfg_len; + struct wlfw_mem_cfg_s_v01 mem_cfg[QMI_WLFW_MAX_NUM_MEM_CFG_V01]; +}; + +struct wlfw_mem_seg_resp_s_v01 { + u64 addr; + u32 size; + enum wlfw_mem_type_enum_v01 type; + u8 restore; +}; + +struct wlfw_rf_chip_info_s_v01 { + u32 chip_id; + u32 chip_family; +}; + +struct wlfw_rf_board_info_s_v01 { + u32 board_id; +}; + +struct wlfw_soc_info_s_v01 { + u32 soc_id; +}; + +struct wlfw_fw_version_info_s_v01 { + u32 fw_version; + char fw_build_timestamp[QMI_WLFW_MAX_TIMESTAMP_LEN_V01 + 1]; +}; + +struct wlfw_ind_register_req_msg_v01 { + u8 fw_ready_enable_valid; + u8 fw_ready_enable; + u8 initiate_cal_download_enable_valid; + u8 initiate_cal_download_enable; + u8 initiate_cal_update_enable_valid; + u8 initiate_cal_update_enable; + u8 msa_ready_enable_valid; + u8 msa_ready_enable; + u8 pin_connect_result_enable_valid; + u8 pin_connect_result_enable; + u8 client_id_valid; + u32 client_id; + u8 request_mem_enable_valid; + u8 request_mem_enable; + u8 fw_mem_ready_enable_valid; + u8 fw_mem_ready_enable; + u8 fw_init_done_enable_valid; + u8 fw_init_done_enable; + u8 rejuvenate_enable_valid; + u32 rejuvenate_enable; + u8 xo_cal_enable_valid; + u8 xo_cal_enable; + u8 cal_done_enable_valid; + u8 cal_done_enable; +}; + +#define WLFW_IND_REGISTER_REQ_MSG_V01_MAX_MSG_LEN 54 +extern struct elem_info wlfw_ind_register_req_msg_v01_ei[]; + +struct wlfw_ind_register_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 fw_status_valid; + u64 fw_status; +}; + +#define WLFW_IND_REGISTER_RESP_MSG_V01_MAX_MSG_LEN 18 +extern struct elem_info wlfw_ind_register_resp_msg_v01_ei[]; + +struct wlfw_fw_ready_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_FW_READY_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_fw_ready_ind_msg_v01_ei[]; + +struct wlfw_msa_ready_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_MSA_READY_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_msa_ready_ind_msg_v01_ei[]; + +struct wlfw_pin_connect_result_ind_msg_v01 { + u8 pwr_pin_result_valid; + u32 pwr_pin_result; + u8 phy_io_pin_result_valid; + u32 phy_io_pin_result; + u8 rf_pin_result_valid; + u32 rf_pin_result; +}; + +#define WLFW_PIN_CONNECT_RESULT_IND_MSG_V01_MAX_MSG_LEN 21 +extern struct elem_info wlfw_pin_connect_result_ind_msg_v01_ei[]; + +struct wlfw_wlan_mode_req_msg_v01 { + enum wlfw_driver_mode_enum_v01 mode; + u8 hw_debug_valid; + u8 hw_debug; +}; + +#define WLFW_WLAN_MODE_REQ_MSG_V01_MAX_MSG_LEN 11 +extern struct elem_info wlfw_wlan_mode_req_msg_v01_ei[]; + +struct wlfw_wlan_mode_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_WLAN_MODE_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_wlan_mode_resp_msg_v01_ei[]; + +struct wlfw_wlan_cfg_req_msg_v01 { + u8 host_version_valid; + char host_version[QMI_WLFW_MAX_STR_LEN_V01 + 1]; + u8 tgt_cfg_valid; + u32 tgt_cfg_len; + struct wlfw_ce_tgt_pipe_cfg_s_v01 tgt_cfg[QMI_WLFW_MAX_NUM_CE_V01]; + u8 svc_cfg_valid; + u32 svc_cfg_len; + struct wlfw_ce_svc_pipe_cfg_s_v01 svc_cfg[QMI_WLFW_MAX_NUM_SVC_V01]; + u8 shadow_reg_valid; + u32 shadow_reg_len; + struct wlfw_shadow_reg_cfg_s_v01 + shadow_reg[QMI_WLFW_MAX_NUM_SHADOW_REG_V01]; + u8 shadow_reg_v2_valid; + u32 shadow_reg_v2_len; + struct wlfw_shadow_reg_v2_cfg_s_v01 + shadow_reg_v2[QMI_WLFW_MAX_NUM_SHADOW_REG_V2_V01]; +}; + +#define WLFW_WLAN_CFG_REQ_MSG_V01_MAX_MSG_LEN 803 +extern struct elem_info wlfw_wlan_cfg_req_msg_v01_ei[]; + +struct wlfw_wlan_cfg_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_WLAN_CFG_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_wlan_cfg_resp_msg_v01_ei[]; + +struct wlfw_cap_req_msg_v01 { + char placeholder; +}; + +#define WLFW_CAP_REQ_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_cap_req_msg_v01_ei[]; + +struct wlfw_cap_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 chip_info_valid; + struct wlfw_rf_chip_info_s_v01 chip_info; + u8 board_info_valid; + struct wlfw_rf_board_info_s_v01 board_info; + u8 soc_info_valid; + struct wlfw_soc_info_s_v01 soc_info; + u8 fw_version_info_valid; + struct wlfw_fw_version_info_s_v01 fw_version_info; + u8 fw_build_id_valid; + char fw_build_id[QMI_WLFW_MAX_BUILD_ID_LEN_V01 + 1]; + u8 num_macs_valid; + u8 num_macs; +}; + +#define WLFW_CAP_RESP_MSG_V01_MAX_MSG_LEN 207 +extern struct elem_info wlfw_cap_resp_msg_v01_ei[]; + +struct wlfw_bdf_download_req_msg_v01 { + u8 valid; + u8 file_id_valid; + enum wlfw_cal_temp_id_enum_v01 file_id; + u8 total_size_valid; + u32 total_size; + u8 seg_id_valid; + u32 seg_id; + u8 data_valid; + u32 data_len; + u8 data[QMI_WLFW_MAX_DATA_SIZE_V01]; + u8 end_valid; + u8 end; + u8 bdf_type_valid; + u8 bdf_type; +}; + +#define WLFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_MSG_LEN 6182 +extern struct elem_info wlfw_bdf_download_req_msg_v01_ei[]; + +struct wlfw_bdf_download_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_BDF_DOWNLOAD_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_bdf_download_resp_msg_v01_ei[]; + +struct wlfw_cal_report_req_msg_v01 { + u32 meta_data_len; + enum wlfw_cal_temp_id_enum_v01 meta_data[QMI_WLFW_MAX_NUM_CAL_V01]; + u8 xo_cal_data_valid; + u8 xo_cal_data; +}; + +#define WLFW_CAL_REPORT_REQ_MSG_V01_MAX_MSG_LEN 28 +extern struct elem_info wlfw_cal_report_req_msg_v01_ei[]; + +struct wlfw_cal_report_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_CAL_REPORT_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_cal_report_resp_msg_v01_ei[]; + +struct wlfw_initiate_cal_download_ind_msg_v01 { + enum wlfw_cal_temp_id_enum_v01 cal_id; +}; + +#define WLFW_INITIATE_CAL_DOWNLOAD_IND_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_initiate_cal_download_ind_msg_v01_ei[]; + +struct wlfw_cal_download_req_msg_v01 { + u8 valid; + u8 file_id_valid; + enum wlfw_cal_temp_id_enum_v01 file_id; + u8 total_size_valid; + u32 total_size; + u8 seg_id_valid; + u32 seg_id; + u8 data_valid; + u32 data_len; + u8 data[QMI_WLFW_MAX_DATA_SIZE_V01]; + u8 end_valid; + u8 end; +}; + +#define WLFW_CAL_DOWNLOAD_REQ_MSG_V01_MAX_MSG_LEN 6178 +extern struct elem_info wlfw_cal_download_req_msg_v01_ei[]; + +struct wlfw_cal_download_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_CAL_DOWNLOAD_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_cal_download_resp_msg_v01_ei[]; + +struct wlfw_initiate_cal_update_ind_msg_v01 { + enum wlfw_cal_temp_id_enum_v01 cal_id; + u32 total_size; +}; + +#define WLFW_INITIATE_CAL_UPDATE_IND_MSG_V01_MAX_MSG_LEN 14 +extern struct elem_info wlfw_initiate_cal_update_ind_msg_v01_ei[]; + +struct wlfw_cal_update_req_msg_v01 { + enum wlfw_cal_temp_id_enum_v01 cal_id; + u32 seg_id; +}; + +#define WLFW_CAL_UPDATE_REQ_MSG_V01_MAX_MSG_LEN 14 +extern struct elem_info wlfw_cal_update_req_msg_v01_ei[]; + +struct wlfw_cal_update_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 file_id_valid; + enum wlfw_cal_temp_id_enum_v01 file_id; + u8 total_size_valid; + u32 total_size; + u8 seg_id_valid; + u32 seg_id; + u8 data_valid; + u32 data_len; + u8 data[QMI_WLFW_MAX_DATA_SIZE_V01]; + u8 end_valid; + u8 end; +}; + +#define WLFW_CAL_UPDATE_RESP_MSG_V01_MAX_MSG_LEN 6181 +extern struct elem_info wlfw_cal_update_resp_msg_v01_ei[]; + +struct wlfw_msa_info_req_msg_v01 { + u64 msa_addr; + u32 size; +}; + +#define WLFW_MSA_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 +extern struct elem_info wlfw_msa_info_req_msg_v01_ei[]; + +struct wlfw_msa_info_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u32 mem_region_info_len; + struct wlfw_memory_region_info_s_v01 + mem_region_info[QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01]; +}; + +#define WLFW_MSA_INFO_RESP_MSG_V01_MAX_MSG_LEN 37 +extern struct elem_info wlfw_msa_info_resp_msg_v01_ei[]; + +struct wlfw_msa_ready_req_msg_v01 { + char placeholder; +}; + +#define WLFW_MSA_READY_REQ_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_msa_ready_req_msg_v01_ei[]; + +struct wlfw_msa_ready_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_MSA_READY_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_msa_ready_resp_msg_v01_ei[]; + +struct wlfw_ini_req_msg_v01 { + u8 enablefwlog_valid; + u8 enablefwlog; +}; + +#define WLFW_INI_REQ_MSG_V01_MAX_MSG_LEN 4 +extern struct elem_info wlfw_ini_req_msg_v01_ei[]; + +struct wlfw_ini_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_INI_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_ini_resp_msg_v01_ei[]; + +struct wlfw_athdiag_read_req_msg_v01 { + u32 offset; + u32 mem_type; + u32 data_len; +}; + +#define WLFW_ATHDIAG_READ_REQ_MSG_V01_MAX_MSG_LEN 21 +extern struct elem_info wlfw_athdiag_read_req_msg_v01_ei[]; + +struct wlfw_athdiag_read_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 data_valid; + u32 data_len; + u8 data[QMI_WLFW_MAX_ATHDIAG_DATA_SIZE_V01]; +}; + +#define WLFW_ATHDIAG_READ_RESP_MSG_V01_MAX_MSG_LEN 6156 +extern struct elem_info wlfw_athdiag_read_resp_msg_v01_ei[]; + +struct wlfw_athdiag_write_req_msg_v01 { + u32 offset; + u32 mem_type; + u32 data_len; + u8 data[QMI_WLFW_MAX_ATHDIAG_DATA_SIZE_V01]; +}; + +#define WLFW_ATHDIAG_WRITE_REQ_MSG_V01_MAX_MSG_LEN 6163 +extern struct elem_info wlfw_athdiag_write_req_msg_v01_ei[]; + +struct wlfw_athdiag_write_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_ATHDIAG_WRITE_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_athdiag_write_resp_msg_v01_ei[]; + +struct wlfw_vbatt_req_msg_v01 { + u64 voltage_uv; +}; + +#define WLFW_VBATT_REQ_MSG_V01_MAX_MSG_LEN 11 +extern struct elem_info wlfw_vbatt_req_msg_v01_ei[]; + +struct wlfw_vbatt_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_VBATT_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_vbatt_resp_msg_v01_ei[]; + +struct wlfw_mac_addr_req_msg_v01 { + u8 mac_addr_valid; + u8 mac_addr[QMI_WLFW_MAC_ADDR_SIZE_V01]; +}; + +#define WLFW_MAC_ADDR_REQ_MSG_V01_MAX_MSG_LEN 9 +extern struct elem_info wlfw_mac_addr_req_msg_v01_ei[]; + +struct wlfw_mac_addr_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_MAC_ADDR_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_mac_addr_resp_msg_v01_ei[]; + +struct wlfw_host_cap_req_msg_v01 { + u8 num_clients_valid; + u32 num_clients; + u8 wake_msi_valid; + u32 wake_msi; + u8 gpios_valid; + u32 gpios_len; + u32 gpios[QMI_WLFW_MAX_NUM_GPIO_V01]; + u8 nm_modem_valid; + u8 nm_modem; + u8 bdf_support_valid; + u8 bdf_support; + u8 bdf_cache_support_valid; + u8 bdf_cache_support; + u8 m3_support_valid; + u8 m3_support; + u8 m3_cache_support_valid; + u8 m3_cache_support; + u8 cal_filesys_support_valid; + u8 cal_filesys_support; + u8 cal_cache_support_valid; + u8 cal_cache_support; + u8 cal_done_valid; + u8 cal_done; + u8 mem_bucket_valid; + u32 mem_bucket; + u8 mem_cfg_mode_valid; + u8 mem_cfg_mode; +}; + +#define WLFW_HOST_CAP_REQ_MSG_V01_MAX_MSG_LEN 189 +extern struct elem_info wlfw_host_cap_req_msg_v01_ei[]; + +struct wlfw_host_cap_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_HOST_CAP_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_host_cap_resp_msg_v01_ei[]; + +struct wlfw_request_mem_ind_msg_v01 { + u32 mem_seg_len; + struct wlfw_mem_seg_s_v01 mem_seg[QMI_WLFW_MAX_NUM_MEM_SEG_V01]; +}; + +#define WLFW_REQUEST_MEM_IND_MSG_V01_MAX_MSG_LEN 1124 +extern struct elem_info wlfw_request_mem_ind_msg_v01_ei[]; + +struct wlfw_respond_mem_req_msg_v01 { + u32 mem_seg_len; + struct wlfw_mem_seg_resp_s_v01 mem_seg[QMI_WLFW_MAX_NUM_MEM_SEG_V01]; +}; + +#define WLFW_RESPOND_MEM_REQ_MSG_V01_MAX_MSG_LEN 548 +extern struct elem_info wlfw_respond_mem_req_msg_v01_ei[]; + +struct wlfw_respond_mem_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_RESPOND_MEM_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_respond_mem_resp_msg_v01_ei[]; + +struct wlfw_fw_mem_ready_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_FW_MEM_READY_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_fw_mem_ready_ind_msg_v01_ei[]; + +struct wlfw_fw_init_done_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_FW_INIT_DONE_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_fw_init_done_ind_msg_v01_ei[]; + +struct wlfw_rejuvenate_ind_msg_v01 { + u8 cause_for_rejuvenation_valid; + u8 cause_for_rejuvenation; + u8 requesting_sub_system_valid; + u8 requesting_sub_system; + u8 line_number_valid; + u16 line_number; + u8 function_name_valid; + char function_name[QMI_WLFW_FUNCTION_NAME_LEN_V01 + 1]; +}; + +#define WLFW_REJUVENATE_IND_MSG_V01_MAX_MSG_LEN 144 +extern struct elem_info wlfw_rejuvenate_ind_msg_v01_ei[]; + +struct wlfw_rejuvenate_ack_req_msg_v01 { + char placeholder; +}; + +#define WLFW_REJUVENATE_ACK_REQ_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_rejuvenate_ack_req_msg_v01_ei[]; + +struct wlfw_rejuvenate_ack_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_REJUVENATE_ACK_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_rejuvenate_ack_resp_msg_v01_ei[]; + +struct wlfw_dynamic_feature_mask_req_msg_v01 { + u8 mask_valid; + u64 mask; +}; + +#define WLFW_DYNAMIC_FEATURE_MASK_REQ_MSG_V01_MAX_MSG_LEN 11 +extern struct elem_info wlfw_dynamic_feature_mask_req_msg_v01_ei[]; + +struct wlfw_dynamic_feature_mask_resp_msg_v01 { + struct qmi_response_type_v01 resp; + u8 prev_mask_valid; + u64 prev_mask; + u8 curr_mask_valid; + u64 curr_mask; +}; + +#define WLFW_DYNAMIC_FEATURE_MASK_RESP_MSG_V01_MAX_MSG_LEN 29 +extern struct elem_info wlfw_dynamic_feature_mask_resp_msg_v01_ei[]; + +struct wlfw_m3_info_req_msg_v01 { + u64 addr; + u32 size; +}; + +#define WLFW_M3_INFO_REQ_MSG_V01_MAX_MSG_LEN 18 +extern struct elem_info wlfw_m3_info_req_msg_v01_ei[]; + +struct wlfw_m3_info_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +#define WLFW_M3_INFO_RESP_MSG_V01_MAX_MSG_LEN 7 +extern struct elem_info wlfw_m3_info_resp_msg_v01_ei[]; + +struct wlfw_xo_cal_ind_msg_v01 { + u8 xo_cal_data; +}; + +#define WLFW_XO_CAL_IND_MSG_V01_MAX_MSG_LEN 4 +extern struct elem_info wlfw_xo_cal_ind_msg_v01_ei[]; + +struct wlfw_cal_done_ind_msg_v01 { + char placeholder; +}; + +#define WLFW_CAL_DONE_IND_MSG_V01_MAX_MSG_LEN 0 +extern struct elem_info wlfw_cal_done_ind_msg_v01_ei[]; + +#endif diff --git a/drivers/net/wireless/cnss_crypto/Makefile b/drivers/net/wireless/cnss_crypto/Makefile new file mode 100644 index 000000000000..cfe0285eb31e --- /dev/null +++ b/drivers/net/wireless/cnss_crypto/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_CNSS_CRYPTO) += cnss_secif.o diff --git a/drivers/net/wireless/cnss_crypto/cnss_secif.c b/drivers/net/wireless/cnss_crypto/cnss_secif.c new file mode 100644 index 000000000000..24202d3a7c58 --- /dev/null +++ b/drivers/net/wireless/cnss_crypto/cnss_secif.c @@ -0,0 +1,177 @@ +/* Copyright (c) 2011-2013, 2015, 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/export.h> +#include <linux/qcomwlan_secif.h> +#include <crypto/aes.h> + +/* APIs for calling crypto routines from kernel + */ +struct crypto_ahash *wcnss_wlan_crypto_alloc_ahash(const char *alg_name, + u32 type, u32 mask) +{ + return crypto_alloc_ahash(alg_name, type, mask); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_alloc_ahash); + +int wcnss_wlan_crypto_ahash_digest(struct ahash_request *req) +{ + return crypto_ahash_digest(req); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_ahash_digest); + +void wcnss_wlan_crypto_free_ahash(struct crypto_ahash *tfm) +{ + crypto_free_ahash(tfm); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_free_ahash); + +int wcnss_wlan_crypto_ahash_setkey(struct crypto_ahash *tfm, const u8 *key, + unsigned int keylen) +{ + return crypto_ahash_setkey(tfm, key, keylen); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_ahash_setkey); + +struct crypto_ablkcipher * +wcnss_wlan_crypto_alloc_ablkcipher(const char *alg_name, u32 type, u32 mask) +{ + return crypto_alloc_ablkcipher(alg_name, type, mask); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_alloc_ablkcipher); + +void wcnss_wlan_ablkcipher_request_free(struct ablkcipher_request *req) +{ + ablkcipher_request_free(req); +} +EXPORT_SYMBOL(wcnss_wlan_ablkcipher_request_free); + +void wcnss_wlan_crypto_free_ablkcipher(struct crypto_ablkcipher *tfm) +{ + crypto_free_ablkcipher(tfm); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_free_ablkcipher); + +void wcnss_wlan_crypto_free_cipher(struct crypto_cipher *tfm) +{ + crypto_free_cipher(tfm); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_free_cipher); + +struct crypto_cipher * +wcnss_wlan_crypto_alloc_cipher(const char *alg_name, u32 type, u32 mask) +{ + return crypto_alloc_cipher(alg_name, type, mask); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_alloc_cipher); + +static inline void xor_128(const u8 *a, const u8 *b, u8 *out) +{ + u8 i; + + for (i = 0; i < AES_BLOCK_SIZE; i++) + out[i] = a[i] ^ b[i]; +} + +static inline void leftshift_onebit(const u8 *input, u8 *output) +{ + int i, overflow = 0; + + for (i = (AES_BLOCK_SIZE - 1); i >= 0; i--) { + output[i] = input[i] << 1; + output[i] |= overflow; + overflow = (input[i] & 0x80) ? 1 : 0; + } +} + +static void generate_subkey(struct crypto_cipher *tfm, u8 *k1, u8 *k2) +{ + u8 l[AES_BLOCK_SIZE], tmp[AES_BLOCK_SIZE]; + u8 const_rb[AES_BLOCK_SIZE] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87}; + u8 const_zero[AES_BLOCK_SIZE] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + crypto_cipher_encrypt_one(tfm, l, const_zero); + + if ((l[0] & 0x80) == 0) { /* If MSB(l) = 0, then k1 = l << 1 */ + leftshift_onebit(l, k1); + } else { /* Else k1 = ( l << 1 ) (+) Rb */ + leftshift_onebit(l, tmp); + xor_128(tmp, const_rb, k1); + } + + if ((k1[0] & 0x80) == 0) { + leftshift_onebit(k1, k2); + } else { + leftshift_onebit(k1, tmp); + xor_128(tmp, const_rb, k2); + } +} + +static inline void padding(u8 *lastb, u8 *pad, u16 length) +{ + u8 j; + + /* original last block */ + for (j = 0; j < AES_BLOCK_SIZE; j++) { + if (j < length) + pad[j] = lastb[j]; + else if (j == length) + pad[j] = 0x80; + else + pad[j] = 0x00; + } +} + +void wcnss_wlan_cmac_calc_mic(struct crypto_cipher *tfm, u8 *m, + u16 length, u8 *mac) +{ + u8 x[AES_BLOCK_SIZE], y[AES_BLOCK_SIZE]; + u8 m_last[AES_BLOCK_SIZE], padded[AES_BLOCK_SIZE]; + u8 k1[AES_KEYSIZE_128], k2[AES_KEYSIZE_128]; + int cmpBlk; + int i, nblocks = (length + 15) / AES_BLOCK_SIZE; + + generate_subkey(tfm, k1, k2); + + if (nblocks == 0) { + nblocks = 1; + cmpBlk = 0; + } else { + cmpBlk = ((length % AES_BLOCK_SIZE) == 0) ? 1 : 0; + } + + if (cmpBlk) { /* Last block is complete block */ + xor_128(&m[AES_BLOCK_SIZE * (nblocks - 1)], k1, m_last); + } else { /* Last block is not complete block */ + padding(&m[AES_BLOCK_SIZE * (nblocks - 1)], padded, + length % AES_BLOCK_SIZE); + xor_128(padded, k2, m_last); + } + + for (i = 0; i < AES_BLOCK_SIZE; i++) + x[i] = 0; + + for (i = 0; i < (nblocks - 1); i++) { + xor_128(x, &m[AES_BLOCK_SIZE * i], y); /* y = Mi (+) x */ + crypto_cipher_encrypt_one(tfm, x, y); /* x = AES-128(KEY, y) */ + } + + xor_128(x, m_last, y); + crypto_cipher_encrypt_one(tfm, x, y); + + memcpy(mac, x, CMAC_TLEN); +} +EXPORT_SYMBOL(wcnss_wlan_cmac_calc_mic); diff --git a/drivers/net/wireless/cnss_genl/Kconfig b/drivers/net/wireless/cnss_genl/Kconfig new file mode 100644 index 000000000000..f1b8a586ec90 --- /dev/null +++ b/drivers/net/wireless/cnss_genl/Kconfig @@ -0,0 +1,7 @@ +config CNSS_GENL + tristate "CNSS Generic Netlink Socket Driver" + ---help--- + This module creates generic netlink family "CLD80211". This can be + used by cld driver and userspace utilities to communicate over + netlink sockets. This module creates different multicast groups to + facilitate the same. diff --git a/drivers/net/wireless/cnss_genl/Makefile b/drivers/net/wireless/cnss_genl/Makefile new file mode 100644 index 000000000000..9431c9e596bb --- /dev/null +++ b/drivers/net/wireless/cnss_genl/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_CNSS_GENL) := cnss_nl.o diff --git a/drivers/net/wireless/cnss_genl/cnss_nl.c b/drivers/net/wireless/cnss_genl/cnss_nl.c new file mode 100644 index 000000000000..29dd4c999f2d --- /dev/null +++ b/drivers/net/wireless/cnss_genl/cnss_nl.c @@ -0,0 +1,206 @@ +/* Copyright (c) 2017, 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 <net/genetlink.h> +#include <net/cnss_nl.h> +#include <linux/module.h> + +#define CLD80211_GENL_NAME "cld80211" + +#define CLD80211_MULTICAST_GROUP_SVC_MSGS "svc_msgs" +#define CLD80211_MULTICAST_GROUP_HOST_LOGS "host_logs" +#define CLD80211_MULTICAST_GROUP_FW_LOGS "fw_logs" +#define CLD80211_MULTICAST_GROUP_PER_PKT_STATS "per_pkt_stats" +#define CLD80211_MULTICAST_GROUP_DIAG_EVENTS "diag_events" +#define CLD80211_MULTICAST_GROUP_FATAL_EVENTS "fatal_events" +#define CLD80211_MULTICAST_GROUP_OEM_MSGS "oem_msgs" + +static const struct genl_multicast_group nl_mcgrps[] = { + [CLD80211_MCGRP_SVC_MSGS] = { .name = + CLD80211_MULTICAST_GROUP_SVC_MSGS}, + [CLD80211_MCGRP_HOST_LOGS] = { .name = + CLD80211_MULTICAST_GROUP_HOST_LOGS}, + [CLD80211_MCGRP_FW_LOGS] = { .name = + CLD80211_MULTICAST_GROUP_FW_LOGS}, + [CLD80211_MCGRP_PER_PKT_STATS] = { .name = + CLD80211_MULTICAST_GROUP_PER_PKT_STATS}, + [CLD80211_MCGRP_DIAG_EVENTS] = { .name = + CLD80211_MULTICAST_GROUP_DIAG_EVENTS}, + [CLD80211_MCGRP_FATAL_EVENTS] = { .name = + CLD80211_MULTICAST_GROUP_FATAL_EVENTS}, + [CLD80211_MCGRP_OEM_MSGS] = { .name = + CLD80211_MULTICAST_GROUP_OEM_MSGS}, +}; + +struct cld_ops { + cld80211_cb cb; + void *cb_ctx; +}; + +struct cld80211_nl_data { + struct cld_ops cld_ops[CLD80211_MAX_COMMANDS]; +}; + +static struct cld80211_nl_data nl_data; + +static inline struct cld80211_nl_data *get_local_ctx(void) +{ + return &nl_data; +} + +static struct genl_ops nl_ops[CLD80211_MAX_COMMANDS]; + +/* policy for the attributes */ +static const struct nla_policy cld80211_policy[CLD80211_ATTR_MAX + 1] = { + [CLD80211_ATTR_VENDOR_DATA] = { .type = NLA_NESTED }, + [CLD80211_ATTR_DATA] = { .type = NLA_BINARY, + .len = CLD80211_MAX_NL_DATA }, + [CLD80211_ATTR_META_DATA] = { .type = NLA_BINARY, + .len = CLD80211_MAX_NL_DATA }, +}; + +static int cld80211_pre_doit(const struct genl_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + u8 cmd_id = ops->cmd; + struct cld80211_nl_data *nl = get_local_ctx(); + + if (cmd_id < 1 || cmd_id > CLD80211_MAX_COMMANDS) { + pr_err("CLD80211: Command Not supported: %u\n", cmd_id); + return -EOPNOTSUPP; + } + info->user_ptr[0] = nl->cld_ops[cmd_id - 1].cb; + info->user_ptr[1] = nl->cld_ops[cmd_id - 1].cb_ctx; + + return 0; +} + +/* The netlink family */ +static struct genl_family cld80211_fam = { + .id = GENL_ID_GENERATE, + .name = CLD80211_GENL_NAME, + .hdrsize = 0, /* no private header */ + .version = 1, /* no particular meaning now */ + .maxattr = CLD80211_ATTR_MAX, + .netnsok = true, + .pre_doit = cld80211_pre_doit, + .post_doit = NULL, +}; + +int register_cld_cmd_cb(u8 cmd_id, cld80211_cb func, void *cb_ctx) +{ + struct cld80211_nl_data *nl = get_local_ctx(); + + pr_debug("CLD80211: Registering command: %d\n", cmd_id); + if (!cmd_id || cmd_id > CLD80211_MAX_COMMANDS) { + pr_debug("CLD80211: invalid command: %d\n", cmd_id); + return -EINVAL; + } + + nl->cld_ops[cmd_id - 1].cb = func; + nl->cld_ops[cmd_id - 1].cb_ctx = cb_ctx; + + return 0; +} +EXPORT_SYMBOL(register_cld_cmd_cb); + +int deregister_cld_cmd_cb(u8 cmd_id) +{ + struct cld80211_nl_data *nl = get_local_ctx(); + + pr_debug("CLD80211: De-registering command: %d\n", cmd_id); + if (!cmd_id || cmd_id > CLD80211_MAX_COMMANDS) { + pr_debug("CLD80211: invalid command: %d\n", cmd_id); + return -EINVAL; + } + + nl->cld_ops[cmd_id - 1].cb = NULL; + nl->cld_ops[cmd_id - 1].cb_ctx = NULL; + + return 0; +} +EXPORT_SYMBOL(deregister_cld_cmd_cb); + +struct genl_family *cld80211_get_genl_family(void) +{ + return &cld80211_fam; +} +EXPORT_SYMBOL(cld80211_get_genl_family); + +static int cld80211_doit(struct sk_buff *skb, struct genl_info *info) +{ + cld80211_cb cld_cb; + void *cld_ctx; + + cld_cb = info->user_ptr[0]; + + if (!cld_cb) { + pr_err("CLD80211: Not supported\n"); + return -EOPNOTSUPP; + } + cld_ctx = info->user_ptr[1]; + + if (info->attrs[CLD80211_ATTR_VENDOR_DATA]) { + cld_cb(nla_data(info->attrs[CLD80211_ATTR_VENDOR_DATA]), + nla_len(info->attrs[CLD80211_ATTR_VENDOR_DATA]), + cld_ctx, info->snd_portid); + } else { + pr_err("CLD80211: No CLD80211_ATTR_VENDOR_DATA\n"); + return -EINVAL; + } + return 0; +} + +static int __cld80211_init(void) +{ + int err, i; + + memset(&nl_ops[0], 0, sizeof(nl_ops)); + + pr_info("CLD80211: Initializing\n"); + for (i = 0; i < CLD80211_MAX_COMMANDS; i++) { + nl_ops[i].cmd = i + 1; + nl_ops[i].doit = cld80211_doit; + nl_ops[i].flags = GENL_ADMIN_PERM; + nl_ops[i].policy = cld80211_policy; + } + + err = genl_register_family_with_ops_groups(&cld80211_fam, nl_ops, + nl_mcgrps); + if (err) { + pr_err("CLD80211: Failed to register cld80211 family: %d\n", + err); + } + + return err; +} + +static void __cld80211_exit(void) +{ + genl_unregister_family(&cld80211_fam); +} + +static int __init cld80211_init(void) +{ + return __cld80211_init(); +} + +static void __exit cld80211_exit(void) +{ + __cld80211_exit(); +} + +module_init(cld80211_init); +module_exit(cld80211_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CNSS generic netlink module"); diff --git a/drivers/net/wireless/cnss_prealloc/Makefile b/drivers/net/wireless/cnss_prealloc/Makefile new file mode 100644 index 000000000000..0adbd4206e9c --- /dev/null +++ b/drivers/net/wireless/cnss_prealloc/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_WCNSS_MEM_PRE_ALLOC) += cnss_prealloc.o diff --git a/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c b/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c new file mode 100644 index 000000000000..9eaa694570ac --- /dev/null +++ b/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c @@ -0,0 +1,339 @@ +/* Copyright (c) 2012,2014-2015 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/module.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <linux/stacktrace.h> +#include <linux/wcnss_wlan.h> +#include <linux/spinlock.h> +#include <linux/debugfs.h> +#include <net/cnss_prealloc.h> +#ifdef CONFIG_WCNSS_SKB_PRE_ALLOC +#include <linux/skbuff.h> +#endif + +static DEFINE_SPINLOCK(alloc_lock); + +#ifdef CONFIG_SLUB_DEBUG +#define WCNSS_MAX_STACK_TRACE 64 +#endif + +#define PRE_ALLOC_DEBUGFS_DIR "cnss-prealloc" +#define PRE_ALLOC_DEBUGFS_FILE_OBJ "status" + +static struct dentry *debug_base; + +struct wcnss_prealloc { + int occupied; + size_t size; + void *ptr; +#ifdef CONFIG_SLUB_DEBUG + unsigned long stack_trace[WCNSS_MAX_STACK_TRACE]; + struct stack_trace trace; +#endif +}; + +/* pre-alloced mem for WLAN driver */ +static struct wcnss_prealloc wcnss_allocs[] = { + {0, 8 * 1024, NULL}, + {0, 8 * 1024, NULL}, + {0, 8 * 1024, NULL}, + {0, 8 * 1024, NULL}, + {0, 8 * 1024, NULL}, + {0, 8 * 1024, NULL}, + {0, 8 * 1024, NULL}, + {0, 8 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 16 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 32 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 64 * 1024, NULL}, + {0, 128 * 1024, NULL}, + {0, 128 * 1024, NULL}, +}; + +int wcnss_prealloc_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) { + wcnss_allocs[i].occupied = 0; + wcnss_allocs[i].ptr = kmalloc(wcnss_allocs[i].size, GFP_KERNEL); + if (wcnss_allocs[i].ptr == NULL) + return -ENOMEM; + } + + return 0; +} + +void wcnss_prealloc_deinit(void) +{ + int i = 0; + + for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) { + kfree(wcnss_allocs[i].ptr); + wcnss_allocs[i].ptr = NULL; + } +} + +#ifdef CONFIG_SLUB_DEBUG +static void wcnss_prealloc_save_stack_trace(struct wcnss_prealloc *entry) +{ + struct stack_trace *trace = &entry->trace; + + memset(&entry->stack_trace, 0, sizeof(entry->stack_trace)); + trace->nr_entries = 0; + trace->max_entries = WCNSS_MAX_STACK_TRACE; + trace->entries = entry->stack_trace; + trace->skip = 2; + + save_stack_trace(trace); + + return; +} +#else +static inline void wcnss_prealloc_save_stack_trace(struct wcnss_prealloc *entry) +{ + return; +} +#endif + +void *wcnss_prealloc_get(size_t size) +{ + int i = 0; + unsigned long flags; + + spin_lock_irqsave(&alloc_lock, flags); + for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) { + if (wcnss_allocs[i].occupied) + continue; + + if (wcnss_allocs[i].size >= size) { + /* we found the slot */ + wcnss_allocs[i].occupied = 1; + spin_unlock_irqrestore(&alloc_lock, flags); + wcnss_prealloc_save_stack_trace(&wcnss_allocs[i]); + return wcnss_allocs[i].ptr; + } + } + spin_unlock_irqrestore(&alloc_lock, flags); + + pr_err("wcnss: %s: prealloc not available for size: %zu\n", + __func__, size); + + return NULL; +} +EXPORT_SYMBOL(wcnss_prealloc_get); + +int wcnss_prealloc_put(void *ptr) +{ + int i = 0; + unsigned long flags; + + spin_lock_irqsave(&alloc_lock, flags); + for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) { + if (wcnss_allocs[i].ptr == ptr) { + wcnss_allocs[i].occupied = 0; + spin_unlock_irqrestore(&alloc_lock, flags); + return 1; + } + } + spin_unlock_irqrestore(&alloc_lock, flags); + + return 0; +} +EXPORT_SYMBOL(wcnss_prealloc_put); + +#ifdef CONFIG_SLUB_DEBUG +void wcnss_prealloc_check_memory_leak(void) +{ + int i, j = 0; + + for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) { + if (!wcnss_allocs[i].occupied) + continue; + + if (j == 0) { + pr_err("wcnss_prealloc: Memory leak detected\n"); + j++; + } + + pr_err("Size: %zu, addr: %pK, backtrace:\n", + wcnss_allocs[i].size, wcnss_allocs[i].ptr); + print_stack_trace(&wcnss_allocs[i].trace, 1); + } + +} +#else +void wcnss_prealloc_check_memory_leak(void) {} +#endif +EXPORT_SYMBOL(wcnss_prealloc_check_memory_leak); + +int wcnss_pre_alloc_reset(void) +{ + int i, n = 0; + + for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) { + if (!wcnss_allocs[i].occupied) + continue; + + wcnss_allocs[i].occupied = 0; + n++; + } + + return n; +} +EXPORT_SYMBOL(wcnss_pre_alloc_reset); + +int prealloc_memory_stats_show(struct seq_file *fp, void *data) +{ + int i = 0; + int used_slots = 0, free_slots = 0; + unsigned int tsize = 0, tused = 0, size = 0; + + seq_puts(fp, "\nSlot_Size(Kb)\t\t[Used : Free]\n"); + for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) { + tsize += wcnss_allocs[i].size; + if (size != wcnss_allocs[i].size) { + if (size) { + seq_printf( + fp, "[%d : %d]\n", + used_slots, free_slots); + } + + size = wcnss_allocs[i].size; + used_slots = 0; + free_slots = 0; + seq_printf(fp, "%d Kb\t\t\t", size / 1024); + } + + if (wcnss_allocs[i].occupied) { + tused += wcnss_allocs[i].size; + ++used_slots; + } else { + ++free_slots; + } + } + seq_printf(fp, "[%d : %d]\n", used_slots, free_slots); + + /* Convert byte to Kb */ + if (tsize) + tsize = tsize / 1024; + if (tused) + tused = tused / 1024; + seq_printf(fp, "\nMemory Status:\nTotal Memory: %dKb\n", tsize); + seq_printf(fp, "Used: %dKb\nFree: %dKb\n", tused, tsize - tused); + + return 0; +} + +int prealloc_memory_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, prealloc_memory_stats_show, NULL); +} + +static const struct file_operations prealloc_memory_stats_fops = { + .owner = THIS_MODULE, + .open = prealloc_memory_stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init wcnss_pre_alloc_init(void) +{ + int ret; + + ret = wcnss_prealloc_init(); + if (ret) { + pr_err("%s: Failed to init the prealloc pool\n", __func__); + return ret; + } + + debug_base = debugfs_create_dir(PRE_ALLOC_DEBUGFS_DIR, NULL); + if (IS_ERR_OR_NULL(debug_base)) { + pr_err("%s: Failed to create debugfs dir\n", __func__); + } else if (IS_ERR_OR_NULL(debugfs_create_file( + PRE_ALLOC_DEBUGFS_FILE_OBJ, + 0644, debug_base, NULL, + &prealloc_memory_stats_fops))) { + pr_err("%s: Failed to create debugfs file\n", __func__); + debugfs_remove_recursive(debug_base); + } + + return ret; +} + +static void __exit wcnss_pre_alloc_exit(void) +{ + wcnss_prealloc_deinit(); + debugfs_remove_recursive(debug_base); +} + +module_init(wcnss_pre_alloc_init); +module_exit(wcnss_pre_alloc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DEVICE "WCNSS Prealloc Driver"); diff --git a/drivers/net/wireless/cnss_utils/Kconfig b/drivers/net/wireless/cnss_utils/Kconfig new file mode 100644 index 000000000000..5f43e4872d65 --- /dev/null +++ b/drivers/net/wireless/cnss_utils/Kconfig @@ -0,0 +1,6 @@ +config CNSS_UTILS + bool "CNSS utilities support" + ---help--- + Add CNSS utilities support for the WLAN driver module. + This feature enable wlan driver to use CNSS utilities APIs to set + and get wlan related information.
\ No newline at end of file diff --git a/drivers/net/wireless/cnss_utils/Makefile b/drivers/net/wireless/cnss_utils/Makefile new file mode 100644 index 000000000000..0d1ed7ae939e --- /dev/null +++ b/drivers/net/wireless/cnss_utils/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_CNSS_UTILS) += cnss_utils.o diff --git a/drivers/net/wireless/cnss_utils/cnss_utils.c b/drivers/net/wireless/cnss_utils/cnss_utils.c new file mode 100644 index 000000000000..77a58c8cb6a4 --- /dev/null +++ b/drivers/net/wireless/cnss_utils/cnss_utils.c @@ -0,0 +1,483 @@ +/* Copyright (c) 2017 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. + */ + +#define pr_fmt(fmt) "cnss_utils: " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/etherdevice.h> +#include <linux/debugfs.h> +#include <net/cnss_utils.h> + +#define CNSS_MAX_CH_NUM 45 +struct cnss_unsafe_channel_list { + u16 unsafe_ch_count; + u16 unsafe_ch_list[CNSS_MAX_CH_NUM]; +}; + +struct cnss_dfs_nol_info { + void *dfs_nol_info; + u16 dfs_nol_info_len; +}; + +#define MAX_NO_OF_MAC_ADDR 4 +#define MAC_PREFIX_LEN 2 +struct cnss_wlan_mac_addr { + u8 mac_addr[MAX_NO_OF_MAC_ADDR][ETH_ALEN]; + u32 no_of_mac_addr_set; +}; + +enum mac_type { + CNSS_MAC_PROVISIONED, + CNSS_MAC_DERIVED, +}; + +static struct cnss_utils_priv { + struct cnss_unsafe_channel_list unsafe_channel_list; + struct cnss_dfs_nol_info dfs_nol_info; + /* generic mutex for unsafe channel */ + struct mutex unsafe_channel_list_lock; + /* generic spin-lock for dfs_nol info */ + spinlock_t dfs_nol_info_lock; + int driver_load_cnt; + struct cnss_wlan_mac_addr wlan_mac_addr; + struct cnss_wlan_mac_addr wlan_der_mac_addr; + enum cnss_utils_cc_src cc_source; + struct dentry *root_dentry; +} *cnss_utils_priv; + +int cnss_utils_set_wlan_unsafe_channel(struct device *dev, + u16 *unsafe_ch_list, u16 ch_count) +{ + struct cnss_utils_priv *priv = cnss_utils_priv; + + if (!priv) + return -EINVAL; + + mutex_lock(&priv->unsafe_channel_list_lock); + if ((!unsafe_ch_list) || (ch_count > CNSS_MAX_CH_NUM)) { + mutex_unlock(&priv->unsafe_channel_list_lock); + return -EINVAL; + } + + priv->unsafe_channel_list.unsafe_ch_count = ch_count; + + if (ch_count == 0) + goto end; + + memcpy(priv->unsafe_channel_list.unsafe_ch_list, + unsafe_ch_list, ch_count * sizeof(u16)); + +end: + mutex_unlock(&priv->unsafe_channel_list_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_utils_set_wlan_unsafe_channel); + +int cnss_utils_get_wlan_unsafe_channel(struct device *dev, + u16 *unsafe_ch_list, + u16 *ch_count, u16 buf_len) +{ + struct cnss_utils_priv *priv = cnss_utils_priv; + + if (!priv) + return -EINVAL; + + mutex_lock(&priv->unsafe_channel_list_lock); + if (!unsafe_ch_list || !ch_count) { + mutex_unlock(&priv->unsafe_channel_list_lock); + return -EINVAL; + } + + if (buf_len < + (priv->unsafe_channel_list.unsafe_ch_count * sizeof(u16))) { + mutex_unlock(&priv->unsafe_channel_list_lock); + return -ENOMEM; + } + + *ch_count = priv->unsafe_channel_list.unsafe_ch_count; + memcpy(unsafe_ch_list, priv->unsafe_channel_list.unsafe_ch_list, + priv->unsafe_channel_list.unsafe_ch_count * sizeof(u16)); + mutex_unlock(&priv->unsafe_channel_list_lock); + + return 0; +} +EXPORT_SYMBOL(cnss_utils_get_wlan_unsafe_channel); + +int cnss_utils_wlan_set_dfs_nol(struct device *dev, + const void *info, u16 info_len) +{ + void *temp; + void *old_nol_info; + struct cnss_dfs_nol_info *dfs_info; + struct cnss_utils_priv *priv = cnss_utils_priv; + + if (!priv) + return -EINVAL; + + if (!info || !info_len) + return -EINVAL; + + temp = kmalloc(info_len, GFP_ATOMIC); + if (!temp) + return -ENOMEM; + + memcpy(temp, info, info_len); + spin_lock_bh(&priv->dfs_nol_info_lock); + dfs_info = &priv->dfs_nol_info; + old_nol_info = dfs_info->dfs_nol_info; + dfs_info->dfs_nol_info = temp; + dfs_info->dfs_nol_info_len = info_len; + spin_unlock_bh(&priv->dfs_nol_info_lock); + kfree(old_nol_info); + + return 0; +} +EXPORT_SYMBOL(cnss_utils_wlan_set_dfs_nol); + +int cnss_utils_wlan_get_dfs_nol(struct device *dev, + void *info, u16 info_len) +{ + int len; + struct cnss_dfs_nol_info *dfs_info; + struct cnss_utils_priv *priv = cnss_utils_priv; + + if (!priv) + return -EINVAL; + + if (!info || !info_len) + return -EINVAL; + + spin_lock_bh(&priv->dfs_nol_info_lock); + + dfs_info = &priv->dfs_nol_info; + if (!dfs_info->dfs_nol_info || + dfs_info->dfs_nol_info_len == 0) { + spin_unlock_bh(&priv->dfs_nol_info_lock); + return -ENOENT; + } + + len = min(info_len, dfs_info->dfs_nol_info_len); + memcpy(info, dfs_info->dfs_nol_info, len); + spin_unlock_bh(&priv->dfs_nol_info_lock); + + return len; +} +EXPORT_SYMBOL(cnss_utils_wlan_get_dfs_nol); + +void cnss_utils_increment_driver_load_cnt(struct device *dev) +{ + struct cnss_utils_priv *priv = cnss_utils_priv; + + if (!priv) + return; + + ++(priv->driver_load_cnt); +} +EXPORT_SYMBOL(cnss_utils_increment_driver_load_cnt); + +int cnss_utils_get_driver_load_cnt(struct device *dev) +{ + struct cnss_utils_priv *priv = cnss_utils_priv; + + if (!priv) + return -EINVAL; + + return priv->driver_load_cnt; +} +EXPORT_SYMBOL(cnss_utils_get_driver_load_cnt); + +static int set_wlan_mac_address(const u8 *mac_list, const uint32_t len, + enum mac_type type) +{ + struct cnss_utils_priv *priv = cnss_utils_priv; + u32 no_of_mac_addr; + struct cnss_wlan_mac_addr *addr = NULL; + int iter; + u8 *temp = NULL; + + if (!priv) + return -EINVAL; + + if (len == 0 || (len % ETH_ALEN) != 0) { + pr_err("Invalid length %d\n", len); + return -EINVAL; + } + + no_of_mac_addr = len / ETH_ALEN; + if (no_of_mac_addr > MAX_NO_OF_MAC_ADDR) { + pr_err("Exceed maximum supported MAC address %u %u\n", + MAX_NO_OF_MAC_ADDR, no_of_mac_addr); + return -EINVAL; + } + + if (type == CNSS_MAC_PROVISIONED) + addr = &priv->wlan_mac_addr; + else + addr = &priv->wlan_der_mac_addr; + + if (addr->no_of_mac_addr_set) { + pr_err("WLAN MAC address is already set, num %d type %d\n", + addr->no_of_mac_addr_set, type); + return 0; + } + + addr->no_of_mac_addr_set = no_of_mac_addr; + temp = &addr->mac_addr[0][0]; + + for (iter = 0; iter < no_of_mac_addr; + ++iter, temp += ETH_ALEN, mac_list += ETH_ALEN) { + ether_addr_copy(temp, mac_list); + pr_debug("MAC_ADDR:%02x:%02x:%02x:%02x:%02x:%02x\n", + temp[0], temp[1], temp[2], + temp[3], temp[4], temp[5]); + } + return 0; +} + +int cnss_utils_set_wlan_mac_address(const u8 *mac_list, const uint32_t len) +{ + return set_wlan_mac_address(mac_list, len, CNSS_MAC_PROVISIONED); +} +EXPORT_SYMBOL(cnss_utils_set_wlan_mac_address); + +int cnss_utils_set_wlan_derived_mac_address( + const u8 *mac_list, const uint32_t len) +{ + return set_wlan_mac_address(mac_list, len, CNSS_MAC_DERIVED); +} +EXPORT_SYMBOL(cnss_utils_set_wlan_derived_mac_address); + +static u8 *get_wlan_mac_address(struct device *dev, + u32 *num, enum mac_type type) +{ + struct cnss_utils_priv *priv = cnss_utils_priv; + struct cnss_wlan_mac_addr *addr = NULL; + + if (!priv) + goto out; + + if (type == CNSS_MAC_PROVISIONED) + addr = &priv->wlan_mac_addr; + else + addr = &priv->wlan_der_mac_addr; + + if (!addr->no_of_mac_addr_set) { + pr_err("WLAN MAC address is not set, type %d\n", type); + goto out; + } + *num = addr->no_of_mac_addr_set; + return &addr->mac_addr[0][0]; + +out: + *num = 0; + return NULL; +} + +u8 *cnss_utils_get_wlan_mac_address(struct device *dev, uint32_t *num) +{ + return get_wlan_mac_address(dev, num, CNSS_MAC_PROVISIONED); +} +EXPORT_SYMBOL(cnss_utils_get_wlan_mac_address); + +u8 *cnss_utils_get_wlan_derived_mac_address( + struct device *dev, uint32_t *num) +{ + return get_wlan_mac_address(dev, num, CNSS_MAC_DERIVED); +} +EXPORT_SYMBOL(cnss_utils_get_wlan_derived_mac_address); + +void cnss_utils_set_cc_source(struct device *dev, + enum cnss_utils_cc_src cc_source) +{ + struct cnss_utils_priv *priv = cnss_utils_priv; + + if (!priv) + return; + + priv->cc_source = cc_source; +} +EXPORT_SYMBOL(cnss_utils_set_cc_source); + +enum cnss_utils_cc_src cnss_utils_get_cc_source(struct device *dev) +{ + struct cnss_utils_priv *priv = cnss_utils_priv; + + if (!priv) + return -EINVAL; + + return priv->cc_source; +} +EXPORT_SYMBOL(cnss_utils_get_cc_source); + +static ssize_t cnss_utils_mac_write(struct file *fp, + const char __user *user_buf, + size_t count, loff_t *off) +{ + struct cnss_utils_priv *priv = + ((struct seq_file *)fp->private_data)->private; + char buf[128]; + char *input, *mac_type, *mac_address; + u8 *dest_mac; + u8 val; + const char *delim = " \n"; + size_t len = 0; + char temp[3] = ""; + + len = min_t(size_t, count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EINVAL; + buf[len] = '\0'; + + input = buf; + + mac_type = strsep(&input, delim); + if (!mac_type) + return -EINVAL; + if (!input) + return -EINVAL; + + mac_address = strsep(&input, delim); + if (!mac_address) + return -EINVAL; + if (strncmp("0x", mac_address, MAC_PREFIX_LEN)) { + pr_err("Invalid MAC prefix\n"); + return -EINVAL; + } + + len = strlen(mac_address); + mac_address += MAC_PREFIX_LEN; + len -= MAC_PREFIX_LEN; + if (len < ETH_ALEN * 2 || len > ETH_ALEN * 2 * MAX_NO_OF_MAC_ADDR || + len % (ETH_ALEN * 2) != 0) { + pr_err("Invalid MAC address length %zu\n", len); + return -EINVAL; + } + + if (!strcmp("provisioned", mac_type)) { + dest_mac = &priv->wlan_mac_addr.mac_addr[0][0]; + priv->wlan_mac_addr.no_of_mac_addr_set = len / (ETH_ALEN * 2); + } else if (!strcmp("derived", mac_type)) { + dest_mac = &priv->wlan_der_mac_addr.mac_addr[0][0]; + priv->wlan_der_mac_addr.no_of_mac_addr_set = + len / (ETH_ALEN * 2); + } else { + pr_err("Invalid MAC address type %s\n", mac_type); + return -EINVAL; + } + + while (len--) { + temp[0] = *mac_address++; + temp[1] = *mac_address++; + if (kstrtou8(temp, 16, &val)) + return -EINVAL; + *dest_mac++ = val; + } + return count; +} + +static int cnss_utils_mac_show(struct seq_file *s, void *data) +{ + u8 mac[6]; + int i; + struct cnss_utils_priv *priv = s->private; + struct cnss_wlan_mac_addr *addr = NULL; + + addr = &priv->wlan_mac_addr; + if (addr->no_of_mac_addr_set) { + seq_puts(s, "\nProvisioned MAC addresseses\n"); + for (i = 0; i < addr->no_of_mac_addr_set; i++) { + ether_addr_copy(mac, addr->mac_addr[i]); + seq_printf(s, "MAC_ADDR:%02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]); + } + } + + addr = &priv->wlan_der_mac_addr; + if (addr->no_of_mac_addr_set) { + seq_puts(s, "\nDerived MAC addresseses\n"); + for (i = 0; i < addr->no_of_mac_addr_set; i++) { + ether_addr_copy(mac, addr->mac_addr[i]); + seq_printf(s, "MAC_ADDR:%02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]); + } + } + + return 0; +} + +static int cnss_utils_mac_open(struct inode *inode, struct file *file) +{ + return single_open(file, cnss_utils_mac_show, inode->i_private); +} + +static const struct file_operations cnss_utils_mac_fops = { + .read = seq_read, + .write = cnss_utils_mac_write, + .release = single_release, + .open = cnss_utils_mac_open, + .owner = THIS_MODULE, + .llseek = seq_lseek, +}; + +static int cnss_utils_debugfs_create(struct cnss_utils_priv *priv) +{ + int ret = 0; + struct dentry *root_dentry; + + root_dentry = debugfs_create_dir("cnss_utils", NULL); + + if (IS_ERR(root_dentry)) { + ret = PTR_ERR(root_dentry); + pr_err("Unable to create debugfs %d\n", ret); + goto out; + } + priv->root_dentry = root_dentry; + debugfs_create_file("mac_address", 0600, root_dentry, priv, + &cnss_utils_mac_fops); +out: + return ret; +} + +static int __init cnss_utils_init(void) +{ + struct cnss_utils_priv *priv = NULL; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->cc_source = CNSS_UTILS_SOURCE_CORE; + + mutex_init(&priv->unsafe_channel_list_lock); + spin_lock_init(&priv->dfs_nol_info_lock); + cnss_utils_debugfs_create(priv); + cnss_utils_priv = priv; + + return 0; +} + +static void __exit cnss_utils_exit(void) +{ + kfree(cnss_utils_priv); + cnss_utils_priv = NULL; +} + +module_init(cnss_utils_init); +module_exit(cnss_utils_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION(DEVICE "CNSS Utilities Driver"); diff --git a/drivers/net/wireless/wcnss/Makefile b/drivers/net/wireless/wcnss/Makefile new file mode 100644 index 000000000000..072fef85044d --- /dev/null +++ b/drivers/net/wireless/wcnss/Makefile @@ -0,0 +1,6 @@ + +# Makefile for WCNSS triple-play driver + +wcnsscore-objs += wcnss_wlan.o wcnss_vreg.o + +obj-$(CONFIG_WCNSS_CORE) += wcnsscore.o diff --git a/drivers/net/wireless/wcnss/wcnss_vreg.c b/drivers/net/wireless/wcnss/wcnss_vreg.c new file mode 100644 index 000000000000..1be4c652f465 --- /dev/null +++ b/drivers/net/wireless/wcnss/wcnss_vreg.c @@ -0,0 +1,815 @@ +/* Copyright (c) 2011-2015, 2018 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/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/rpm-smd-regulator.h> +#include <linux/wcnss_wlan.h> +#include <linux/semaphore.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/clk.h> + + +static void __iomem *msm_wcnss_base; +static LIST_HEAD(power_on_lock_list); +static DEFINE_MUTEX(list_lock); +static DEFINE_SEMAPHORE(wcnss_power_on_lock); +static int auto_detect; +static int is_power_on; + +#define RIVA_PMU_OFFSET 0x28 + +#define RIVA_SPARE_OFFSET 0x0b4 +#define PRONTO_SPARE_OFFSET 0x1088 +#define NVBIN_DLND_BIT BIT(25) + +#define PRONTO_IRIS_REG_READ_OFFSET 0x1134 +#define PRONTO_IRIS_REG_CHIP_ID 0x04 +/* IRIS card chip ID's */ +#define WCN3660 0x0200 +#define WCN3660A 0x0300 +#define WCN3660B 0x0400 +#define WCN3620 0x5111 +#define WCN3620A 0x5112 +#define WCN3610 0x9101 +#define WCN3610V1 0x9110 + +#define WCNSS_PMU_CFG_IRIS_XO_CFG BIT(3) +#define WCNSS_PMU_CFG_IRIS_XO_EN BIT(4) +#define WCNSS_PMU_CFG_IRIS_XO_CFG_STS BIT(6) /* 1: in progress, 0: done */ + +#define WCNSS_PMU_CFG_IRIS_RESET BIT(7) +#define WCNSS_PMU_CFG_IRIS_RESET_STS BIT(8) /* 1: in progress, 0: done */ +#define WCNSS_PMU_CFG_IRIS_XO_READ BIT(9) +#define WCNSS_PMU_CFG_IRIS_XO_READ_STS BIT(10) + +#define WCNSS_PMU_CFG_IRIS_XO_MODE 0x6 +#define WCNSS_PMU_CFG_IRIS_XO_MODE_48 (3 << 1) + +#define VREG_NULL_CONFIG 0x0000 +#define VREG_GET_REGULATOR_MASK 0x0001 +#define VREG_SET_VOLTAGE_MASK 0x0002 +#define VREG_OPTIMUM_MODE_MASK 0x0004 +#define VREG_ENABLE_MASK 0x0008 +#define VDD_PA "qcom,iris-vddpa" + +#define WCNSS_INVALID_IRIS_REG 0xbaadbaad + +struct vregs_info { + const char * const name; + const char * const curr; + const char * const volt; + int state; + bool required; + struct regulator *regulator; +}; + +/* IRIS regulators for Pronto hardware */ +static struct vregs_info iris_vregs[] = { + {"qcom,iris-vddxo", "qcom,iris-vddxo-current", + "qcom,iris-vddxo-voltage-level", VREG_NULL_CONFIG, true, NULL}, + {"qcom,iris-vddrfa", "qcom,iris-vddrfa-current", + "qcom,iris-vddrfa-voltage-level", VREG_NULL_CONFIG, true, NULL}, + {"qcom,iris-vddpa", "qcom,iris-vddpa-current", + "qcom,iris-vddpa-voltage-level", VREG_NULL_CONFIG, false, NULL}, + {"qcom,iris-vdddig", "qcom,iris-vdddig-current", + "qcom,iris-vdddig-voltage-level", VREG_NULL_CONFIG, true, NULL}, +}; + +/* WCNSS regulators for Pronto hardware */ +static struct vregs_info pronto_vregs[] = { + {"qcom,pronto-vddmx", "qcom,pronto-vddmx-current", + "qcom,vddmx-voltage-level", VREG_NULL_CONFIG, true, NULL}, + {"qcom,pronto-vddcx", "qcom,pronto-vddcx-current", + "qcom,vddcx-voltage-level", VREG_NULL_CONFIG, true, NULL}, + {"qcom,pronto-vddpx", "qcom,pronto-vddpx-current", + "qcom,vddpx-voltage-level", VREG_NULL_CONFIG, true, NULL}, +}; + +struct host_driver { + char name[20]; + struct list_head list; +}; + +enum { + IRIS_3660, /* also 3660A and 3680 */ + IRIS_3620, + IRIS_3610 +}; + + +int xo_auto_detect(u32 reg) +{ + reg >>= 30; + + switch (reg) { + case IRIS_3660: + return WCNSS_XO_48MHZ; + + case IRIS_3620: + return WCNSS_XO_19MHZ; + + case IRIS_3610: + return WCNSS_XO_19MHZ; + + default: + return WCNSS_XO_INVALID; + } +} + +int wcnss_get_iris_name(char *iris_name) +{ + struct wcnss_wlan_config *cfg = NULL; + int iris_id; + + cfg = wcnss_get_wlan_config(); + + if (cfg) { + iris_id = cfg->iris_id; + iris_id = iris_id >> 16; + } else { + return 1; + } + + switch (iris_id) { + case WCN3660: + memcpy(iris_name, "WCN3660", sizeof("WCN3660")); + break; + case WCN3660A: + memcpy(iris_name, "WCN3660A", sizeof("WCN3660A")); + break; + case WCN3660B: + memcpy(iris_name, "WCN3660B", sizeof("WCN3660B")); + break; + case WCN3620: + memcpy(iris_name, "WCN3620", sizeof("WCN3620")); + break; + case WCN3620A: + memcpy(iris_name, "WCN3620A", sizeof("WCN3620A")); + break; + case WCN3610: + memcpy(iris_name, "WCN3610", sizeof("WCN3610")); + break; + case WCN3610V1: + memcpy(iris_name, "WCN3610V1", sizeof("WCN3610V1")); + break; + default: + return 1; + } + + return 0; +} +EXPORT_SYMBOL(wcnss_get_iris_name); + +int validate_iris_chip_id(u32 reg) +{ + int iris_id; + iris_id = reg >> 16; + + switch (iris_id) { + case WCN3660: + case WCN3660A: + case WCN3660B: + case WCN3620: + case WCN3620A: + case WCN3610: + case WCN3610V1: + return 0; + default: + return 1; + } +} + +static int +wcnss_dt_parse_vreg_level(struct device *dev, int index, + const char *current_vreg_name, const char *vreg_name, + struct vregs_level *vlevel) +{ + int ret = 0; + /* array used to store nominal, low and high voltage values */ + u32 voltage_levels[3], current_vreg; + + ret = of_property_read_u32_array(dev->of_node, vreg_name, + voltage_levels, + ARRAY_SIZE(voltage_levels)); + if (ret) { + dev_err(dev, "error reading %s property\n", vreg_name); + return ret; + } + + vlevel[index].nominal_min = voltage_levels[0]; + vlevel[index].low_power_min = voltage_levels[1]; + vlevel[index].max_voltage = voltage_levels[2]; + + ret = of_property_read_u32(dev->of_node, current_vreg_name, + ¤t_vreg); + if (ret) { + dev_err(dev, "error reading %s property\n", current_vreg_name); + return ret; + } + + vlevel[index].uA_load = current_vreg; + + return ret; +} + +int +wcnss_parse_voltage_regulator(struct wcnss_wlan_config *wlan_config, + struct device *dev) +{ + int rc, vreg_i; + + /* Parse pronto voltage regulators from device node */ + for (vreg_i = 0; vreg_i < PRONTO_REGULATORS; vreg_i++) { + pronto_vregs[vreg_i].regulator = + devm_regulator_get_optional(dev, + pronto_vregs[vreg_i].name); + if (IS_ERR(pronto_vregs[vreg_i].regulator)) { + if (pronto_vregs[vreg_i].required) { + rc = PTR_ERR(pronto_vregs[vreg_i].regulator); + dev_err(dev, "regulator get of %s failed (%d)\n", + pronto_vregs[vreg_i].name, rc); + return rc; + } else { + dev_dbg(dev, "Skip optional regulator configuration: %s\n", + pronto_vregs[vreg_i].name); + continue; + } + } + + rc = wcnss_dt_parse_vreg_level(dev, vreg_i, + pronto_vregs[vreg_i].curr, + pronto_vregs[vreg_i].volt, + wlan_config->pronto_vlevel); + if (rc) { + dev_err(dev, "error reading voltage-level property\n"); + return rc; + } + pronto_vregs[vreg_i].state |= VREG_GET_REGULATOR_MASK; + } + + /* Parse iris voltage regulators from device node */ + for (vreg_i = 0; vreg_i < IRIS_REGULATORS; vreg_i++) { + iris_vregs[vreg_i].regulator = + devm_regulator_get_optional(dev, + iris_vregs[vreg_i].name); + if (IS_ERR(iris_vregs[vreg_i].regulator)) { + if (iris_vregs[vreg_i].required) { + rc = PTR_ERR(iris_vregs[vreg_i].regulator); + dev_err(dev, "regulator get of %s failed (%d)\n", + iris_vregs[vreg_i].name, rc); + return rc; + } else { + dev_dbg(dev, "Skip optional regulator configuration: %s\n", + iris_vregs[vreg_i].name); + continue; + } + } + + rc = wcnss_dt_parse_vreg_level(dev, vreg_i, + iris_vregs[vreg_i].curr, + iris_vregs[vreg_i].volt, + wlan_config->iris_vlevel); + if (rc) { + dev_err(dev, "error reading voltage-level property\n"); + return rc; + } + iris_vregs[vreg_i].state |= VREG_GET_REGULATOR_MASK; + } + + return 0; +} + +void wcnss_iris_reset(u32 reg, void __iomem *pmu_conf_reg) +{ + /* Reset IRIS */ + reg |= WCNSS_PMU_CFG_IRIS_RESET; + writel_relaxed(reg, pmu_conf_reg); + + /* Wait for PMU_CFG.iris_reg_reset_sts */ + while (readl_relaxed(pmu_conf_reg) & + WCNSS_PMU_CFG_IRIS_RESET_STS) + cpu_relax(); + + /* Reset iris reset bit */ + reg &= ~WCNSS_PMU_CFG_IRIS_RESET; + writel_relaxed(reg, pmu_conf_reg); +} + +static int +configure_iris_xo(struct device *dev, + struct wcnss_wlan_config *cfg, + int on, int *iris_xo_set) +{ + u32 reg = 0, i = 0; + u32 iris_reg = WCNSS_INVALID_IRIS_REG; + int rc = 0; + int pmu_offset = 0; + int spare_offset = 0; + void __iomem *pmu_conf_reg; + void __iomem *spare_reg; + void __iomem *iris_read_reg; + struct clk *clk; + struct clk *clk_rf = NULL; + bool use_48mhz_xo; + + use_48mhz_xo = cfg->use_48mhz_xo; + + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) { + pmu_offset = PRONTO_PMU_OFFSET; + spare_offset = PRONTO_SPARE_OFFSET; + + clk = clk_get(dev, "xo"); + if (IS_ERR(clk)) { + pr_err("Couldn't get xo clock\n"); + return PTR_ERR(clk); + } + + } else { + pmu_offset = RIVA_PMU_OFFSET; + spare_offset = RIVA_SPARE_OFFSET; + + clk = clk_get(dev, "cxo"); + if (IS_ERR(clk)) { + pr_err("Couldn't get cxo clock\n"); + return PTR_ERR(clk); + } + } + + if (on) { + msm_wcnss_base = cfg->msm_wcnss_base; + if (!msm_wcnss_base) { + pr_err("ioremap wcnss physical failed\n"); + goto fail; + } + + /* Enable IRIS XO */ + rc = clk_prepare_enable(clk); + if (rc) { + pr_err("clk enable failed\n"); + goto fail; + } + + /* NV bit is set to indicate that platform driver is capable + * of doing NV download. + */ + pr_debug("wcnss: Indicate NV bin download\n"); + spare_reg = msm_wcnss_base + spare_offset; + reg = readl_relaxed(spare_reg); + reg |= NVBIN_DLND_BIT; + writel_relaxed(reg, spare_reg); + + pmu_conf_reg = msm_wcnss_base + pmu_offset; + writel_relaxed(0, pmu_conf_reg); + reg = readl_relaxed(pmu_conf_reg); + reg |= WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP | + WCNSS_PMU_CFG_IRIS_XO_EN; + writel_relaxed(reg, pmu_conf_reg); + + if (wcnss_xo_auto_detect_enabled()) { + iris_read_reg = msm_wcnss_base + + PRONTO_IRIS_REG_READ_OFFSET; + iris_reg = readl_relaxed(iris_read_reg); + } + + wcnss_iris_reset(reg, pmu_conf_reg); + + if (iris_reg != WCNSS_INVALID_IRIS_REG) { + iris_reg &= 0xffff; + iris_reg |= PRONTO_IRIS_REG_CHIP_ID; + writel_relaxed(iris_reg, iris_read_reg); + do { + /* Iris read */ + reg = readl_relaxed(pmu_conf_reg); + reg |= WCNSS_PMU_CFG_IRIS_XO_READ; + writel_relaxed(reg, pmu_conf_reg); + + /* Wait for PMU_CFG.iris_reg_read_sts */ + while (readl_relaxed(pmu_conf_reg) & + WCNSS_PMU_CFG_IRIS_XO_READ_STS) + cpu_relax(); + + iris_reg = readl_relaxed(iris_read_reg); + pr_info("wcnss: IRIS Reg: %08x\n", iris_reg); + + if (validate_iris_chip_id(iris_reg) && i >= 4) { + pr_info("wcnss: IRIS Card absent/invalid\n"); + auto_detect = WCNSS_XO_INVALID; + /* Reset iris read bit */ + reg &= ~WCNSS_PMU_CFG_IRIS_XO_READ; + /* Clear XO_MODE[b2:b1] bits. + * Clear implies 19.2 MHz TCXO + */ + reg &= ~(WCNSS_PMU_CFG_IRIS_XO_MODE); + goto xo_configure; + } else if (!validate_iris_chip_id(iris_reg)) { + pr_debug("wcnss: IRIS Card is present\n"); + break; + } + reg &= ~WCNSS_PMU_CFG_IRIS_XO_READ; + writel_relaxed(reg, pmu_conf_reg); + wcnss_iris_reset(reg, pmu_conf_reg); + } while (i++ < 5); + auto_detect = xo_auto_detect(iris_reg); + + /* Reset iris read bit */ + reg &= ~WCNSS_PMU_CFG_IRIS_XO_READ; + + } else if (wcnss_xo_auto_detect_enabled()) + /* Default to 48 MHZ */ + auto_detect = WCNSS_XO_48MHZ; + else + auto_detect = WCNSS_XO_INVALID; + + cfg->iris_id = iris_reg; + + /* Clear XO_MODE[b2:b1] bits. Clear implies 19.2 MHz TCXO */ + reg &= ~(WCNSS_PMU_CFG_IRIS_XO_MODE); + + if ((use_48mhz_xo && auto_detect == WCNSS_XO_INVALID) + || auto_detect == WCNSS_XO_48MHZ) { + reg |= WCNSS_PMU_CFG_IRIS_XO_MODE_48; + + if (iris_xo_set) + *iris_xo_set = WCNSS_XO_48MHZ; + } + +xo_configure: + writel_relaxed(reg, pmu_conf_reg); + + wcnss_iris_reset(reg, pmu_conf_reg); + + /* Start IRIS XO configuration */ + reg |= WCNSS_PMU_CFG_IRIS_XO_CFG; + writel_relaxed(reg, pmu_conf_reg); + + /* Wait for XO configuration to finish */ + while (readl_relaxed(pmu_conf_reg) & + WCNSS_PMU_CFG_IRIS_XO_CFG_STS) + cpu_relax(); + + /* Stop IRIS XO configuration */ + reg &= ~(WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP | + WCNSS_PMU_CFG_IRIS_XO_CFG); + writel_relaxed(reg, pmu_conf_reg); + clk_disable_unprepare(clk); + + if ((!use_48mhz_xo && auto_detect == WCNSS_XO_INVALID) + || auto_detect == WCNSS_XO_19MHZ) { + + clk_rf = clk_get(dev, "rf_clk"); + if (IS_ERR(clk_rf)) { + pr_err("Couldn't get rf_clk\n"); + goto fail; + } + + rc = clk_prepare_enable(clk_rf); + if (rc) { + pr_err("clk_rf enable failed\n"); + goto fail; + } + if (iris_xo_set) + *iris_xo_set = WCNSS_XO_19MHZ; + } + + } else if ((!use_48mhz_xo && auto_detect == WCNSS_XO_INVALID) + || auto_detect == WCNSS_XO_19MHZ) { + clk_rf = clk_get(dev, "rf_clk"); + if (IS_ERR(clk_rf)) { + pr_err("Couldn't get rf_clk\n"); + goto fail; + } + clk_disable_unprepare(clk_rf); + } + + /* Add some delay for XO to settle */ + msleep(20); + +fail: + clk_put(clk); + + if (clk_rf != NULL) + clk_put(clk_rf); + + return rc; +} + +/* Helper routine to turn off all WCNSS & IRIS vregs */ +static void wcnss_vregs_off(struct vregs_info regulators[], uint size, + struct vregs_level *voltage_level) +{ + int i, rc = 0; + struct wcnss_wlan_config *cfg; + + cfg = wcnss_get_wlan_config(); + + if (!cfg) { + pr_err("Failed to get WLAN configuration\n"); + return; + } + + /* Regulators need to be turned off in the reverse order */ + for (i = (size-1); i >= 0; i--) { + if (regulators[i].state == VREG_NULL_CONFIG) + continue; + + /* Remove PWM mode */ + if (regulators[i].state & VREG_OPTIMUM_MODE_MASK) { + rc = regulator_set_load(regulators[i].regulator, 0); + if (rc < 0) { + pr_err("regulator set load(%s) failed (%d)\n", + regulators[i].name, rc); + } + } + + /* Set voltage to lowest level */ + if (regulators[i].state & VREG_SET_VOLTAGE_MASK) { + if (cfg->is_pronto_vadc) { + if (cfg->vbatt < WCNSS_VBATT_THRESHOLD && + !memcmp(regulators[i].name, + VDD_PA, sizeof(VDD_PA))) { + voltage_level[i].max_voltage = + WCNSS_VBATT_LOW; + } + } + + rc = regulator_set_voltage(regulators[i].regulator, + voltage_level[i].low_power_min, + voltage_level[i].max_voltage); + + if (rc) + pr_err("regulator_set_voltage(%s) failed (%d)\n", + regulators[i].name, rc); + } + + /* Disable regulator */ + if (regulators[i].state & VREG_ENABLE_MASK) { + rc = regulator_disable(regulators[i].regulator); + if (rc < 0) + pr_err("vreg %s disable failed (%d)\n", + regulators[i].name, rc); + } + } +} + +/* Common helper routine to turn on all WCNSS & IRIS vregs */ +static int wcnss_vregs_on(struct device *dev, + struct vregs_info regulators[], uint size, + struct vregs_level *voltage_level) +{ + int i, rc = 0, reg_cnt; + struct wcnss_wlan_config *cfg; + + cfg = wcnss_get_wlan_config(); + + if (!cfg) { + pr_err("Failed to get WLAN configuration\n"); + return -EINVAL; + } + + for (i = 0; i < size; i++) { + if (regulators[i].state == VREG_NULL_CONFIG) + continue; + + reg_cnt = regulator_count_voltages(regulators[i].regulator); + /* Set voltage to nominal. Exclude swtiches e.g. LVS */ + if ((voltage_level[i].nominal_min || + voltage_level[i].max_voltage) && (reg_cnt > 0)) { + if (cfg->is_pronto_vadc) { + if (cfg->vbatt < WCNSS_VBATT_THRESHOLD && + !memcmp(regulators[i].name, + VDD_PA, sizeof(VDD_PA))) { + voltage_level[i].nominal_min = + WCNSS_VBATT_INITIAL; + voltage_level[i].max_voltage = + WCNSS_VBATT_LOW; + } + } + + rc = regulator_set_voltage(regulators[i].regulator, + voltage_level[i].nominal_min, + voltage_level[i].max_voltage); + + if (rc) { + pr_err("regulator_set_voltage(%s) failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_SET_VOLTAGE_MASK; + } + + /* Vote for PWM/PFM mode if needed */ + if (voltage_level[i].uA_load && (reg_cnt > 0)) { + rc = regulator_set_load(regulators[i].regulator, + voltage_level[i].uA_load); + if (rc < 0) { + pr_err("regulator set load(%s) failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_OPTIMUM_MODE_MASK; + } + + /* Enable the regulator */ + rc = regulator_enable(regulators[i].regulator); + if (rc) { + pr_err("vreg %s enable failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_ENABLE_MASK; + } + + return rc; + +fail: + wcnss_vregs_off(regulators, size, voltage_level); + return rc; + +} + +static void wcnss_iris_vregs_off(enum wcnss_hw_type hw_type, + struct wcnss_wlan_config *cfg) +{ + switch (hw_type) { + case WCNSS_PRONTO_HW: + wcnss_vregs_off(iris_vregs, ARRAY_SIZE(iris_vregs), + cfg->iris_vlevel); + break; + default: + pr_err("%s invalid hardware %d\n", __func__, hw_type); + + } +} + +static int wcnss_iris_vregs_on(struct device *dev, + enum wcnss_hw_type hw_type, + struct wcnss_wlan_config *cfg) +{ + int ret = -1; + + switch (hw_type) { + case WCNSS_PRONTO_HW: + ret = wcnss_vregs_on(dev, iris_vregs, ARRAY_SIZE(iris_vregs), + cfg->iris_vlevel); + break; + default: + pr_err("%s invalid hardware %d\n", __func__, hw_type); + } + return ret; +} + +static void wcnss_core_vregs_off(enum wcnss_hw_type hw_type, + struct wcnss_wlan_config *cfg) +{ + switch (hw_type) { + case WCNSS_PRONTO_HW: + wcnss_vregs_off(pronto_vregs, + ARRAY_SIZE(pronto_vregs), cfg->pronto_vlevel); + break; + default: + pr_err("%s invalid hardware %d\n", __func__, hw_type); + } + +} + +static int wcnss_core_vregs_on(struct device *dev, + enum wcnss_hw_type hw_type, + struct wcnss_wlan_config *cfg) +{ + int ret = -1; + + switch (hw_type) { + case WCNSS_PRONTO_HW: + ret = wcnss_vregs_on(dev, pronto_vregs, + ARRAY_SIZE(pronto_vregs), + cfg->pronto_vlevel); + break; + default: + pr_err("%s invalid hardware %d\n", __func__, hw_type); + } + + return ret; + +} + +int wcnss_wlan_power(struct device *dev, + struct wcnss_wlan_config *cfg, + enum wcnss_opcode on, int *iris_xo_set) +{ + int rc = 0; + enum wcnss_hw_type hw_type = wcnss_hardware_type(); + + down(&wcnss_power_on_lock); + if (on) { + /* RIVA regulator settings */ + rc = wcnss_core_vregs_on(dev, hw_type, + cfg); + if (rc) + goto fail_wcnss_on; + + /* IRIS regulator settings */ + rc = wcnss_iris_vregs_on(dev, hw_type, + cfg); + if (rc) + goto fail_iris_on; + + /* Configure IRIS XO */ + rc = configure_iris_xo(dev, cfg, + WCNSS_WLAN_SWITCH_ON, iris_xo_set); + if (rc) + goto fail_iris_xo; + + is_power_on = true; + + } else if (is_power_on) { + is_power_on = false; + configure_iris_xo(dev, cfg, + WCNSS_WLAN_SWITCH_OFF, NULL); + wcnss_iris_vregs_off(hw_type, cfg); + wcnss_core_vregs_off(hw_type, cfg); + } + + up(&wcnss_power_on_lock); + return rc; + +fail_iris_xo: + wcnss_iris_vregs_off(hw_type, cfg); + +fail_iris_on: + wcnss_core_vregs_off(hw_type, cfg); + +fail_wcnss_on: + up(&wcnss_power_on_lock); + return rc; +} +EXPORT_SYMBOL(wcnss_wlan_power); + +/* + * During SSR WCNSS should not be 'powered on' until all the host drivers + * finish their shutdown routines. Host drivers use below APIs to + * synchronize power-on. WCNSS will not be 'powered on' until all the + * requests(to lock power-on) are freed. + */ +int wcnss_req_power_on_lock(char *driver_name) +{ + struct host_driver *node; + + if (!driver_name) + goto err; + + node = kmalloc(sizeof(struct host_driver), GFP_KERNEL); + if (!node) + goto err; + strlcpy(node->name, driver_name, sizeof(node->name)); + + mutex_lock(&list_lock); + /* Lock when the first request is added */ + if (list_empty(&power_on_lock_list)) + down(&wcnss_power_on_lock); + list_add(&node->list, &power_on_lock_list); + mutex_unlock(&list_lock); + + return 0; + +err: + return -EINVAL; +} +EXPORT_SYMBOL(wcnss_req_power_on_lock); + +int wcnss_free_power_on_lock(char *driver_name) +{ + int ret = -1; + struct host_driver *node; + + mutex_lock(&list_lock); + list_for_each_entry(node, &power_on_lock_list, list) { + if (!strncmp(node->name, driver_name, sizeof(node->name))) { + list_del(&node->list); + kfree(node); + ret = 0; + break; + } + } + /* unlock when the last host driver frees the lock */ + if (list_empty(&power_on_lock_list)) + up(&wcnss_power_on_lock); + mutex_unlock(&list_lock); + + return ret; +} +EXPORT_SYMBOL(wcnss_free_power_on_lock); diff --git a/drivers/net/wireless/wcnss/wcnss_wlan.c b/drivers/net/wireless/wcnss/wcnss_wlan.c new file mode 100644 index 000000000000..13ae5c3c2471 --- /dev/null +++ b/drivers/net/wireless/wcnss/wcnss_wlan.c @@ -0,0 +1,3530 @@ +/* Copyright (c) 2011-2018, 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/module.h> +#include <linux/firmware.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/wcnss_wlan.h> +#include <linux/platform_data/qcom_wcnss_device.h> +#include <linux/workqueue.h> +#include <linux/jiffies.h> +#include <linux/gpio.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/clk.h> +#include <linux/ratelimit.h> +#include <linux/kthread.h> +#include <linux/wait.h> +#include <linux/uaccess.h> +#include <linux/suspend.h> +#include <linux/rwsem.h> +#include <linux/qpnp/qpnp-adc.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pm_qos.h> +#include <linux/bitops.h> +#include <soc/qcom/socinfo.h> + +#include <soc/qcom/subsystem_restart.h> +#include <soc/qcom/subsystem_notif.h> + +#include <soc/qcom/smd.h> + +#define DEVICE "wcnss_wlan" +#define CTRL_DEVICE "wcnss_ctrl" +#define VERSION "1.01" +#define WCNSS_PIL_DEVICE "wcnss" + +#define WCNSS_PINCTRL_STATE_DEFAULT "wcnss_default" +#define WCNSS_PINCTRL_STATE_SLEEP "wcnss_sleep" +#define WCNSS_PINCTRL_GPIO_STATE_DEFAULT "wcnss_gpio_default" + +#define WCNSS_DISABLE_PC_LATENCY 100 +#define WCNSS_ENABLE_PC_LATENCY PM_QOS_DEFAULT_VALUE +#define WCNSS_PM_QOS_TIMEOUT 15000 +#define IS_CAL_DATA_PRESENT 0 +#define WAIT_FOR_CBC_IND 2 +#define WCNSS_DUAL_BAND_CAPABILITY_OFFSET BIT(8) + +/* module params */ +#define WCNSS_CONFIG_UNSPECIFIED (-1) +#define UINT32_MAX (0xFFFFFFFFU) + +#define SUBSYS_NOTIF_MIN_INDEX 0 +#define SUBSYS_NOTIF_MAX_INDEX 9 +char *wcnss_subsys_notif_type[] = { + "SUBSYS_BEFORE_SHUTDOWN", + "SUBSYS_AFTER_SHUTDOWN", + "SUBSYS_BEFORE_POWERUP", + "SUBSYS_AFTER_POWERUP", + "SUBSYS_RAMDUMP_NOTIFICATION", + "SUBSYS_POWERUP_FAILURE", + "SUBSYS_PROXY_VOTE", + "SUBSYS_PROXY_UNVOTE", + "SUBSYS_SOC_RESET", + "SUBSYS_NOTIF_TYPE_COUNT" +}; + +static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED; +module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present"); + +static int has_calibrated_data = WCNSS_CONFIG_UNSPECIFIED; +module_param(has_calibrated_data, int, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(has_calibrated_data, "whether calibrated data file available"); + +static int has_autodetect_xo = WCNSS_CONFIG_UNSPECIFIED; +module_param(has_autodetect_xo, int, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(has_autodetect_xo, "Perform auto detect to configure IRIS XO"); + +static int do_not_cancel_vote = WCNSS_CONFIG_UNSPECIFIED; +module_param(do_not_cancel_vote, int, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(do_not_cancel_vote, "Do not cancel votes for wcnss"); + +static DEFINE_SPINLOCK(reg_spinlock); + +#define RIVA_SPARE_OFFSET 0x0b4 +#define RIVA_SUSPEND_BIT BIT(24) + +#define CCU_RIVA_INVALID_ADDR_OFFSET 0x100 +#define CCU_RIVA_LAST_ADDR0_OFFSET 0x104 +#define CCU_RIVA_LAST_ADDR1_OFFSET 0x108 +#define CCU_RIVA_LAST_ADDR2_OFFSET 0x10c + +#define PRONTO_PMU_SPARE_OFFSET 0x1088 +#define PMU_A2XB_CFG_HSPLIT_RESP_LIMIT_OFFSET 0x117C + +#define PRONTO_PMU_COM_GDSCR_OFFSET 0x0024 +#define PRONTO_PMU_COM_GDSCR_SW_COLLAPSE BIT(0) +#define PRONTO_PMU_COM_GDSCR_HW_CTRL BIT(1) + +#define PRONTO_PMU_WLAN_BCR_OFFSET 0x0050 +#define PRONTO_PMU_WLAN_BCR_BLK_ARES BIT(0) + +#define PRONTO_PMU_WLAN_GDSCR_OFFSET 0x0054 +#define PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE BIT(0) + +#define PRONTO_PMU_WDOG_CTL 0x0068 + +#define PRONTO_PMU_CBCR_OFFSET 0x0008 +#define PRONTO_PMU_CBCR_CLK_EN BIT(0) + +#define PRONTO_PMU_COM_CPU_CBCR_OFFSET 0x0030 +#define PRONTO_PMU_COM_AHB_CBCR_OFFSET 0x0034 + +#define PRONTO_PMU_WLAN_AHB_CBCR_OFFSET 0x0074 +#define PRONTO_PMU_WLAN_AHB_CBCR_CLK_EN BIT(0) +#define PRONTO_PMU_WLAN_AHB_CBCR_CLK_OFF BIT(31) + +#define PRONTO_PMU_CPU_AHB_CMD_RCGR_OFFSET 0x0120 +#define PRONTO_PMU_CPU_AHB_CMD_RCGR_ROOT_EN BIT(1) + +#define PRONTO_PMU_CFG_OFFSET 0x1004 +#define PRONTO_PMU_COM_CSR_OFFSET 0x1040 +#define PRONTO_PMU_SOFT_RESET_OFFSET 0x104C + +#define PRONTO_QFUSE_DUAL_BAND_OFFSET 0x0018 + +#define A2XB_CFG_OFFSET 0x00 +#define A2XB_INT_SRC_OFFSET 0x0c +#define A2XB_TSTBUS_CTRL_OFFSET 0x14 +#define A2XB_TSTBUS_OFFSET 0x18 +#define A2XB_ERR_INFO_OFFSET 0x1c +#define A2XB_FIFO_FILL_OFFSET 0x07 +#define A2XB_READ_FIFO_FILL_MASK 0x3F +#define A2XB_CMD_FIFO_FILL_MASK 0x0F +#define A2XB_WRITE_FIFO_FILL_MASK 0x1F +#define A2XB_FIFO_EMPTY 0x2 +#define A2XB_FIFO_COUNTER 0xA + +#define WCNSS_TSTBUS_CTRL_EN BIT(0) +#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1) +#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1) +#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1) +#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1) +#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1) +#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 8) +#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 8) +#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 28) +#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 28) + +#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08 +#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c +#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10 +#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14 + +#define CCU_PRONTO_AOWBR_ERR_ADDR_OFFSET 0x28 +#define CCU_PRONTO_AOWBR_TIMEOUT_REG_OFFSET 0xcc +#define CCU_PRONTO_AOWBR_ERR_TIMEOUT_OFFSET 0xd0 +#define CCU_PRONTO_A2AB_ERR_ADDR_OFFSET 0x18 + +#define PRONTO_SAW2_SPM_STS_OFFSET 0x0c +#define PRONTO_SAW2_SPM_CTL 0x30 +#define PRONTO_SAW2_SAW2_VERSION 0xFD0 +#define PRONTO_SAW2_MAJOR_VER_OFFSET 0x1C + +#define PRONTO_PLL_STATUS_OFFSET 0x1c +#define PRONTO_PLL_MODE_OFFSET 0x1c0 + +#define MCU_APB2PHY_STATUS_OFFSET 0xec +#define MCU_CBR_CCAHB_ERR_OFFSET 0x380 +#define MCU_CBR_CAHB_ERR_OFFSET 0x384 +#define MCU_CBR_CCAHB_TIMEOUT_OFFSET 0x388 +#define MCU_CBR_CAHB_TIMEOUT_OFFSET 0x38c +#define MCU_DBR_CDAHB_ERR_OFFSET 0x390 +#define MCU_DBR_DAHB_ERR_OFFSET 0x394 +#define MCU_DBR_CDAHB_TIMEOUT_OFFSET 0x398 +#define MCU_DBR_DAHB_TIMEOUT_OFFSET 0x39c +#define MCU_FDBR_CDAHB_ERR_OFFSET 0x3a0 +#define MCU_FDBR_FDAHB_ERR_OFFSET 0x3a4 +#define MCU_FDBR_CDAHB_TIMEOUT_OFFSET 0x3a8 +#define MCU_FDBR_FDAHB_TIMEOUT_OFFSET 0x3ac +#define PRONTO_PMU_CCPU_BOOT_REMAP_OFFSET 0x2004 + +#define WCNSS_DEF_WLAN_RX_BUFF_COUNT 1024 + +#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL" +#define WCNSS_MAX_FRAME_SIZE (4*1024) +#define WCNSS_VERSION_LEN 30 +#define WCNSS_MAX_BUILD_VER_LEN 256 +#define WCNSS_MAX_CMD_LEN (128) +#define WCNSS_MIN_CMD_LEN (3) +#define WCNSS_CMD_INFO_LEN 2 + +/* control messages from userspace */ +#define WCNSS_USR_CTRL_MSG_START 0x00000000 +#define WCNSS_USR_HAS_CAL_DATA (WCNSS_USR_CTRL_MSG_START + 2) +#define WCNSS_USR_WLAN_MAC_ADDR (WCNSS_USR_CTRL_MSG_START + 3) + +#define MAC_ADDRESS_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define WCNSS_USER_MAC_ADDR_LENGTH 18 + +/* message types */ +#define WCNSS_CTRL_MSG_START 0x01000000 +#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0) +#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1) +#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2) +#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3) +#define WCNSS_CALDATA_UPLD_REQ (WCNSS_CTRL_MSG_START + 4) +#define WCNSS_CALDATA_UPLD_RSP (WCNSS_CTRL_MSG_START + 5) +#define WCNSS_CALDATA_DNLD_REQ (WCNSS_CTRL_MSG_START + 6) +#define WCNSS_CALDATA_DNLD_RSP (WCNSS_CTRL_MSG_START + 7) +#define WCNSS_VBATT_LEVEL_IND (WCNSS_CTRL_MSG_START + 8) +#define WCNSS_BUILD_VER_REQ (WCNSS_CTRL_MSG_START + 9) +#define WCNSS_BUILD_VER_RSP (WCNSS_CTRL_MSG_START + 10) +#define WCNSS_PM_CONFIG_REQ (WCNSS_CTRL_MSG_START + 11) +#define WCNSS_CBC_COMPLETE_IND (WCNSS_CTRL_MSG_START + 12) + +/* max 20mhz channel count */ +#define WCNSS_MAX_CH_NUM 45 +#define WCNSS_MAX_PIL_RETRY 2 + +#define VALID_VERSION(version) \ + ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0) + +#define FW_CALDATA_CAPABLE() \ + ((penv->fw_major >= 1) && (penv->fw_minor >= 5) ? 1 : 0) + +static int wcnss_pinctrl_set_state(bool active); + +struct smd_msg_hdr { + unsigned int msg_type; + unsigned int msg_len; +}; + +struct wcnss_version { + struct smd_msg_hdr hdr; + unsigned char major; + unsigned char minor; + unsigned char version; + unsigned char revision; +}; + +struct wcnss_pmic_dump { + char reg_name[10]; + u16 reg_addr; +}; + +static int wcnss_notif_cb(struct notifier_block *this, unsigned long code, + void *ss_handle); + +static struct notifier_block wnb = { + .notifier_call = wcnss_notif_cb, +}; + +#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin" + +/* On SMD channel 4K of maximum data can be transferred, including message + * header, so NV fragment size as next multiple of 1Kb is 3Kb. + */ +#define NV_FRAGMENT_SIZE 3072 +#define MAX_CALIBRATED_DATA_SIZE (64*1024) +#define LAST_FRAGMENT (1 << 0) +#define MESSAGE_TO_FOLLOW (1 << 1) +#define CAN_RECEIVE_CALDATA (1 << 15) +#define WCNSS_RESP_SUCCESS 1 +#define WCNSS_RESP_FAIL 0 + + +/* Macro to find the total number fragments of the NV bin Image */ +#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \ + (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1)) + +struct nvbin_dnld_req_params { + /* Fragment sequence number of the NV bin Image. NV Bin Image + * might not fit into one message due to size limitation of + * the SMD channel FIFO so entire NV blob is chopped into + * multiple fragments starting with seqeunce number 0. The + * last fragment is indicated by marking is_last_fragment field + * to 1. At receiving side, NV blobs would be concatenated + * together without any padding bytes in between. + */ + unsigned short frag_number; + + /* bit 0: When set to 1 it indicates that no more fragments will + * be sent. + * bit 1: When set, a new message will be followed by this message + * bit 2- bit 14: Reserved + * bit 15: when set, it indicates that the sender is capable of + * receiving Calibrated data. + */ + unsigned short msg_flags; + + /* NV Image size (number of bytes) */ + unsigned int nvbin_buffer_size; + + /* Following the 'nvbin_buffer_size', there should be + * nvbin_buffer_size bytes of NV bin Image i.e. + * uint8[nvbin_buffer_size]. + */ +}; + + +struct nvbin_dnld_req_msg { + /* Note: The length specified in nvbin_dnld_req_msg messages + * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) + + * nvbin_buffer_size. + */ + struct smd_msg_hdr hdr; + struct nvbin_dnld_req_params dnld_req_params; +}; + +struct cal_data_params { + + /* The total size of the calibrated data, including all the + * fragments. + */ + unsigned int total_size; + unsigned short frag_number; + /* bit 0: When set to 1 it indicates that no more fragments will + * be sent. + * bit 1: When set, a new message will be followed by this message + * bit 2- bit 15: Reserved + */ + unsigned short msg_flags; + /* fragment size + */ + unsigned int frag_size; + /* Following the frag_size, frag_size of fragmented + * data will be followed. + */ +}; + +struct cal_data_msg { + /* The length specified in cal_data_msg should be + * hdr.msg_len = sizeof(cal_data_msg) + frag_size + */ + struct smd_msg_hdr hdr; + struct cal_data_params cal_params; +}; + +struct vbatt_level { + u32 curr_volt; + u32 threshold; +}; + +struct vbatt_message { + struct smd_msg_hdr hdr; + struct vbatt_level vbatt; +}; + +static struct { + struct platform_device *pdev; + void *pil; + struct resource *mmio_res; + struct resource *tx_irq_res; + struct resource *rx_irq_res; + struct resource *gpios_5wire; + const struct dev_pm_ops *pm_ops; + int triggered; + int smd_channel_ready; + u32 wlan_rx_buff_count; + int is_vsys_adc_channel; + int is_a2xb_split_reg; + smd_channel_t *smd_ch; + unsigned char wcnss_version[WCNSS_VERSION_LEN]; + unsigned char fw_major; + unsigned char fw_minor; + unsigned int serial_number; + int thermal_mitigation; + enum wcnss_hw_type wcnss_hw_type; + void (*tm_notify)(struct device *, int); + struct wcnss_wlan_config wlan_config; + struct delayed_work wcnss_work; + struct delayed_work vbatt_work; + struct work_struct wcnssctrl_version_work; + struct work_struct wcnss_pm_config_work; + struct work_struct wcnssctrl_nvbin_dnld_work; + struct work_struct wcnssctrl_rx_work; + struct work_struct wcnss_vadc_work; + struct wake_lock wcnss_wake_lock; + void __iomem *msm_wcnss_base; + void __iomem *riva_ccu_base; + void __iomem *pronto_a2xb_base; + void __iomem *pronto_ccpu_base; + void __iomem *pronto_saw2_base; + void __iomem *pronto_pll_base; + void __iomem *pronto_mcu_base; + void __iomem *pronto_qfuse; + void __iomem *wlan_tx_status; + void __iomem *wlan_tx_phy_aborts; + void __iomem *wlan_brdg_err_source; + void __iomem *alarms_txctl; + void __iomem *alarms_tactl; + void __iomem *fiq_reg; + int nv_downloaded; + int is_cbc_done; + unsigned char *fw_cal_data; + unsigned char *user_cal_data; + int fw_cal_rcvd; + int fw_cal_exp_frag; + int fw_cal_available; + int user_cal_read; + int user_cal_available; + u32 user_cal_rcvd; + u32 user_cal_exp_size; + int iris_xo_mode_set; + int fw_vbatt_state; + char wlan_nv_macAddr[WLAN_MAC_ADDR_SIZE]; + int ctrl_device_opened; + struct mutex dev_lock; + struct mutex ctrl_lock; + wait_queue_head_t read_wait; + struct qpnp_adc_tm_btm_param vbat_monitor_params; + struct qpnp_adc_tm_chip *adc_tm_dev; + struct qpnp_vadc_chip *vadc_dev; + struct mutex vbat_monitor_mutex; + u16 unsafe_ch_count; + u16 unsafe_ch_list[WCNSS_MAX_CH_NUM]; + void *wcnss_notif_hdle; + struct pinctrl *pinctrl; + struct pinctrl_state *wcnss_5wire_active; + struct pinctrl_state *wcnss_5wire_suspend; + struct pinctrl_state *wcnss_gpio_active; + int gpios[WCNSS_WLAN_MAX_GPIO]; + int use_pinctrl; + u8 is_shutdown; + struct pm_qos_request wcnss_pm_qos_request; + int pc_disabled; + struct delayed_work wcnss_pm_qos_del_req; + struct mutex pm_qos_mutex; + struct clk *snoc_wcnss; + unsigned int snoc_wcnss_clock_freq; + bool is_dual_band_disabled; +} *penv = NULL; + +static ssize_t wcnss_wlan_macaddr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int index; + int macAddr[WLAN_MAC_ADDR_SIZE]; + + if (!penv) + return -ENODEV; + + if (strlen(buf) != WCNSS_USER_MAC_ADDR_LENGTH) { + dev_err(dev, "%s: Invalid MAC addr length\n", __func__); + return -EINVAL; + } + + if (WLAN_MAC_ADDR_SIZE != sscanf(buf, MAC_ADDRESS_STR, + &macAddr[0], &macAddr[1], &macAddr[2], + &macAddr[3], &macAddr[4], &macAddr[5])) { + pr_err("%s: Failed to Copy MAC\n", __func__); + return -EINVAL; + } + + for (index = 0; index < WLAN_MAC_ADDR_SIZE; index++) { + memcpy(&penv->wlan_nv_macAddr[index], + (char *)&macAddr[index], sizeof(char)); + } + + pr_info("%s: Write MAC Addr: %pM\n", __func__, penv->wlan_nv_macAddr); + return count; +} + +static ssize_t wcnss_wlan_macaddr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%pM\n", penv->wlan_nv_macAddr); +} + +static DEVICE_ATTR(wcnss_mac_addr, S_IRUSR | S_IWUSR, + wcnss_wlan_macaddr_show, wcnss_wlan_macaddr_store); + +static ssize_t wcnss_thermal_mitigation_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation); +} + +static ssize_t wcnss_thermal_mitigation_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + if (!penv) + return -ENODEV; + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + penv->thermal_mitigation = value; + if (penv->tm_notify) + (penv->tm_notify)(dev, value); + return count; +} + +static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR, + wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store); + + +static ssize_t wcnss_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!penv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version); +} + +static DEVICE_ATTR(wcnss_version, S_IRUSR, + wcnss_version_show, NULL); + +/* wcnss_reset_fiq() is invoked when host drivers fails to + * communicate with WCNSS over SMD; so logging these registers + * helps to know WCNSS failure reason + */ +void wcnss_riva_log_debug_regs(void) +{ + void __iomem *ccu_reg; + u32 reg = 0; + + ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_OFFSET; + reg = readl_relaxed(ccu_reg); + pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg); + + ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR0_OFFSET; + reg = readl_relaxed(ccu_reg); + pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg); + + ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_OFFSET; + reg = readl_relaxed(ccu_reg); + pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg); + + ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR2_OFFSET; + reg = readl_relaxed(ccu_reg); + pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg); + +} +EXPORT_SYMBOL(wcnss_riva_log_debug_regs); + +void wcnss_pronto_is_a2xb_bus_stall(void *tst_addr, u32 fifo_mask, char *type) +{ + u32 iter = 0, reg = 0; + u32 axi_fifo_count = 0, axi_fifo_count_last = 0; + + reg = readl_relaxed(tst_addr); + axi_fifo_count = (reg >> A2XB_FIFO_FILL_OFFSET) & fifo_mask; + while ((++iter < A2XB_FIFO_COUNTER) && axi_fifo_count) { + axi_fifo_count_last = axi_fifo_count; + reg = readl_relaxed(tst_addr); + axi_fifo_count = (reg >> A2XB_FIFO_FILL_OFFSET) & fifo_mask; + if (axi_fifo_count < axi_fifo_count_last) + break; + } + + if (iter == A2XB_FIFO_COUNTER) { + pr_err("%s data FIFO testbus possibly stalled reg%08x\n", + type, reg); + } else { + pr_err("%s data FIFO tstbus not stalled reg%08x\n", + type, reg); + } +} + +int wcnss_get_dual_band_capability_info(struct platform_device *pdev) +{ + u32 reg = 0; + struct resource *res; + + res = platform_get_resource_byname( + pdev, IORESOURCE_MEM, "pronto_qfuse"); + if (!res) + return -EINVAL; + + penv->pronto_qfuse = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(penv->pronto_qfuse)) + return -ENOMEM; + + reg = readl_relaxed(penv->pronto_qfuse + + PRONTO_QFUSE_DUAL_BAND_OFFSET); + if (reg & WCNSS_DUAL_BAND_CAPABILITY_OFFSET) + penv->is_dual_band_disabled = true; + else + penv->is_dual_band_disabled = false; + + return 0; +} + +/* Log pronto debug registers during SSR Timeout CB */ +void wcnss_pronto_log_debug_regs(void) +{ + void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr; + u32 reg = 0, reg2 = 0, reg3 = 0, reg4 = 0; + + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SPARE_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_SPARE %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_CPU_CBCR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_COM_CPU_CBCR %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_AHB_CBCR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_COM_AHB_CBCR %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CFG_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_CFG %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_CSR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_COM_CSR %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SOFT_RESET_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_SOFT_RESET %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WDOG_CTL; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_WDOG_CTL %08x\n", reg); + + reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SPM_STS_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_SAW2_SPM_STS %08x\n", reg); + + reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SPM_CTL; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_SAW2_SPM_CTL %08x\n", reg); + + if (penv->is_a2xb_split_reg) { + reg_addr = penv->msm_wcnss_base + + PMU_A2XB_CFG_HSPLIT_RESP_LIMIT_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PMU_A2XB_CFG_HSPLIT_RESP_LIMIT %08x\n", reg); + } + + reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SAW2_VERSION; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_SAW2_SAW2_VERSION %08x\n", reg); + reg >>= PRONTO_SAW2_MAJOR_VER_OFFSET; + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CCPU_BOOT_REMAP_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_CCPU_BOOT_REMAP %08x\n", reg); + + reg_addr = penv->pronto_pll_base + PRONTO_PLL_STATUS_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PLL_STATUS %08x\n", reg); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CPU_AHB_CMD_RCGR_OFFSET; + reg4 = readl_relaxed(reg_addr); + pr_err("PMU_CPU_CMD_RCGR %08x\n", reg4); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_GDSCR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("PRONTO_PMU_COM_GDSCR %08x\n", reg); + reg >>= 31; + + if (!reg) { + pr_err("Cannot log, Pronto common SS is power collapsed\n"); + return; + } + reg &= ~(PRONTO_PMU_COM_GDSCR_SW_COLLAPSE + | PRONTO_PMU_COM_GDSCR_HW_CTRL); + writel_relaxed(reg, reg_addr); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CBCR_OFFSET; + reg = readl_relaxed(reg_addr); + reg |= PRONTO_PMU_CBCR_CLK_EN; + writel_relaxed(reg, reg_addr); + + reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("A2XB_CFG_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("A2XB_INT_SRC_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("A2XB_ERR_INFO_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("CCU_CCPU_INVALID_ADDR %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("CCU_CCPU_LAST_ADDR0 %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("CCU_CCPU_LAST_ADDR1 %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("CCU_CCPU_LAST_ADDR2 %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_AOWBR_ERR_ADDR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("CCU_PRONTO_AOWBR_ERR_ADDR_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_AOWBR_TIMEOUT_REG_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("CCU_PRONTO_AOWBR_TIMEOUT_REG_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_AOWBR_ERR_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("CCU_PRONTO_AOWBR_ERR_TIMEOUT_OFFSET %08x\n", reg); + + reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_A2AB_ERR_ADDR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("CCU_PRONTO_A2AB_ERR_ADDR_OFFSET %08x\n", reg); + + tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET; + tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET; + + /* read data FIFO */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + if (!(reg & A2XB_FIFO_EMPTY)) { + wcnss_pronto_is_a2xb_bus_stall(tst_addr, + A2XB_READ_FIFO_FILL_MASK, "Read"); + } else { + pr_err("Read data FIFO testbus %08x\n", reg); + } + /* command FIFO */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + if (!(reg & A2XB_FIFO_EMPTY)) { + wcnss_pronto_is_a2xb_bus_stall(tst_addr, + A2XB_CMD_FIFO_FILL_MASK, "Cmd"); + } else { + pr_err("Command FIFO testbus %08x\n", reg); + } + + + /* write data FIFO */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + if (!(reg & A2XB_FIFO_EMPTY)) { + wcnss_pronto_is_a2xb_bus_stall(tst_addr, + A2XB_WRITE_FIFO_FILL_MASK, "Write"); + } else { + pr_err("Write data FIFO testbus %08x\n", reg); + } + + /* AXIM SEL CFG0 */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM | + WCNSS_TSTBUS_CTRL_AXIM_CFG0; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + pr_err("AXIM SEL CFG0 testbus %08x\n", reg); + + /* AXIM SEL CFG1 */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM | + WCNSS_TSTBUS_CTRL_AXIM_CFG1; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + pr_err("AXIM SEL CFG1 testbus %08x\n", reg); + + /* CTRL SEL CFG0 */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL | + WCNSS_TSTBUS_CTRL_CTRL_CFG0; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + pr_err("CTRL SEL CFG0 testbus %08x\n", reg); + + /* CTRL SEL CFG1 */ + reg = 0; + reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL | + WCNSS_TSTBUS_CTRL_CTRL_CFG1; + writel_relaxed(reg, tst_ctrl_addr); + reg = readl_relaxed(tst_addr); + pr_err("CTRL SEL CFG1 testbus %08x\n", reg); + + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_BCR_OFFSET; + reg = readl_relaxed(reg_addr); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_GDSCR_OFFSET; + reg2 = readl_relaxed(reg_addr); + + reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_AHB_CBCR_OFFSET; + reg3 = readl_relaxed(reg_addr); + pr_err("PMU_WLAN_AHB_CBCR %08x\n", reg3); + + msleep(50); + + if ((reg & PRONTO_PMU_WLAN_BCR_BLK_ARES) || + (reg2 & PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE) || + (!(reg4 & PRONTO_PMU_CPU_AHB_CMD_RCGR_ROOT_EN)) || + (reg3 & PRONTO_PMU_WLAN_AHB_CBCR_CLK_OFF) || + (!(reg3 & PRONTO_PMU_WLAN_AHB_CBCR_CLK_EN))) { + pr_err("Cannot log, wlan domain is power collapsed\n"); + return; + } + + reg = readl_relaxed(penv->wlan_tx_phy_aborts); + pr_err("WLAN_TX_PHY_ABORTS %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_APB2PHY_STATUS_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_APB2PHY_STATUS %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_CBR_CCAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_CBR_CCAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_CBR_CAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_CBR_CAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_CBR_CCAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_CBR_CCAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_CBR_CAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_CBR_CAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_DBR_CDAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_DBR_CDAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_DBR_DAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_DBR_DAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_DBR_CDAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_DBR_CDAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_DBR_DAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_DBR_DAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_FDBR_CDAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_FDBR_CDAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_FDBR_FDAHB_ERR_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_FDBR_FDAHB_ERR %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_FDBR_CDAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_FDBR_CDAHB_TIMEOUT %08x\n", reg); + + reg_addr = penv->pronto_mcu_base + MCU_FDBR_FDAHB_TIMEOUT_OFFSET; + reg = readl_relaxed(reg_addr); + pr_err("MCU_FDBR_FDAHB_TIMEOUT %08x\n", reg); + + reg = readl_relaxed(penv->wlan_brdg_err_source); + pr_err("WLAN_BRDG_ERR_SOURCE %08x\n", reg); + + reg = readl_relaxed(penv->wlan_tx_status); + pr_err("WLAN_TXP_STATUS %08x\n", reg); + + reg = readl_relaxed(penv->alarms_txctl); + pr_err("ALARMS_TXCTL %08x\n", reg); + + reg = readl_relaxed(penv->alarms_tactl); + pr_err("ALARMS_TACTL %08x\n", reg); +} +EXPORT_SYMBOL(wcnss_pronto_log_debug_regs); + +#ifdef CONFIG_WCNSS_REGISTER_DUMP_ON_BITE + +static int wcnss_gpio_set_state(bool is_enable) +{ + struct pinctrl_state *pin_state; + int ret; + int i; + + if (!is_enable) { + for (i = 0; i < WCNSS_WLAN_MAX_GPIO; i++) { + if (gpio_is_valid(penv->gpios[i])) + gpio_free(penv->gpios[i]); + } + + return 0; + } + + pin_state = penv->wcnss_gpio_active; + if (!IS_ERR_OR_NULL(pin_state)) { + ret = pinctrl_select_state(penv->pinctrl, pin_state); + if (ret < 0) { + pr_err("%s: can not set gpio pins err: %d\n", + __func__, ret); + goto pinctrl_set_err; + } + + } else { + pr_err("%s: invalid gpio pinstate err: %lu\n", + __func__, PTR_ERR(pin_state)); + goto pinctrl_set_err; + } + + for (i = WCNSS_WLAN_DATA2; i <= WCNSS_WLAN_DATA0; i++) { + ret = gpio_request_one(penv->gpios[i], + GPIOF_DIR_IN, NULL); + if (ret) { + pr_err("%s: request failed for gpio:%d\n", + __func__, penv->gpios[i]); + i--; + goto gpio_req_err; + } + } + + for (i = WCNSS_WLAN_SET; i <= WCNSS_WLAN_CLK; i++) { + ret = gpio_request_one(penv->gpios[i], + GPIOF_OUT_INIT_LOW, NULL); + if (ret) { + pr_err("%s: request failed for gpio:%d\n", + __func__, penv->gpios[i]); + i--; + goto gpio_req_err; + } + } + + return 0; + +gpio_req_err: + for (; i >= WCNSS_WLAN_DATA2; --i) + gpio_free(penv->gpios[i]); + +pinctrl_set_err: + return -EINVAL; +} + +static u32 wcnss_rf_read_reg(u32 rf_reg_addr) +{ + int count = 0; + u32 rf_cmd_and_addr = 0; + u32 rf_data_received = 0; + u32 rf_bit = 0; + + if (wcnss_gpio_set_state(true)) + return 0; + + /* Reset the signal if it is already being used. */ + gpio_set_value(penv->gpios[WCNSS_WLAN_SET], 0); + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + + /* We start with cmd_set high penv->gpio_base + WCNSS_WLAN_SET = 1. */ + gpio_set_value(penv->gpios[WCNSS_WLAN_SET], 1); + + gpio_direction_output(penv->gpios[WCNSS_WLAN_DATA0], 1); + gpio_direction_output(penv->gpios[WCNSS_WLAN_DATA1], 1); + gpio_direction_output(penv->gpios[WCNSS_WLAN_DATA2], 1); + + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA0], 0); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA1], 0); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA2], 0); + + /* Prepare command and RF register address that need to sent out. */ + rf_cmd_and_addr = (((WLAN_RF_READ_REG_CMD) | + (rf_reg_addr << WLAN_RF_REG_ADDR_START_OFFSET)) & + WLAN_RF_READ_CMD_MASK); + /* Send 15 bit RF register address */ + for (count = 0; count < WLAN_RF_PREPARE_CMD_DATA; count++) { + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + + rf_bit = (rf_cmd_and_addr & 0x1); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA0], + rf_bit ? 1 : 0); + rf_cmd_and_addr = (rf_cmd_and_addr >> 1); + + rf_bit = (rf_cmd_and_addr & 0x1); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA1], rf_bit ? 1 : 0); + rf_cmd_and_addr = (rf_cmd_and_addr >> 1); + + rf_bit = (rf_cmd_and_addr & 0x1); + gpio_set_value(penv->gpios[WCNSS_WLAN_DATA2], rf_bit ? 1 : 0); + rf_cmd_and_addr = (rf_cmd_and_addr >> 1); + + /* Send the data out penv->gpio_base + WCNSS_WLAN_CLK = 1 */ + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 1); + } + + /* Pull down the clock signal */ + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + + /* Configure data pins to input IO pins */ + gpio_direction_input(penv->gpios[WCNSS_WLAN_DATA0]); + gpio_direction_input(penv->gpios[WCNSS_WLAN_DATA1]); + gpio_direction_input(penv->gpios[WCNSS_WLAN_DATA2]); + + for (count = 0; count < WLAN_RF_CLK_WAIT_CYCLE; count++) { + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 1); + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + } + + rf_bit = 0; + /* Read 16 bit RF register value */ + for (count = 0; count < WLAN_RF_READ_DATA; count++) { + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 1); + gpio_set_value(penv->gpios[WCNSS_WLAN_CLK], 0); + + rf_bit = gpio_get_value(penv->gpios[WCNSS_WLAN_DATA0]); + rf_data_received |= (rf_bit << (count * WLAN_RF_DATA_LEN + + WLAN_RF_DATA0_SHIFT)); + + if (count != 5) { + rf_bit = gpio_get_value(penv->gpios[WCNSS_WLAN_DATA1]); + rf_data_received |= (rf_bit << (count * WLAN_RF_DATA_LEN + + WLAN_RF_DATA1_SHIFT)); + + rf_bit = gpio_get_value(penv->gpios[WCNSS_WLAN_DATA2]); + rf_data_received |= (rf_bit << (count * WLAN_RF_DATA_LEN + + WLAN_RF_DATA2_SHIFT)); + } + } + + gpio_set_value(penv->gpios[WCNSS_WLAN_SET], 0); + wcnss_gpio_set_state(false); + wcnss_pinctrl_set_state(true); + + return rf_data_received; +} + +static void wcnss_log_iris_regs(void) +{ + int i; + u32 reg_val; + u32 regs_array[] = { + 0x04, 0x05, 0x11, 0x1e, 0x40, 0x48, + 0x49, 0x4b, 0x00, 0x01, 0x4d}; + + pr_info("%s: IRIS Registers [address] : value\n", __func__); + + for (i = 0; i < ARRAY_SIZE(regs_array); i++) { + reg_val = wcnss_rf_read_reg(regs_array[i]); + + pr_info("[0x%08x] : 0x%08x\n", regs_array[i], reg_val); + } +} + +int wcnss_get_mux_control(void) +{ + void __iomem *pmu_conf_reg; + u32 reg = 0; + + if (NULL == penv) + return 0; + + pmu_conf_reg = penv->msm_wcnss_base + PRONTO_PMU_OFFSET; + reg = readl_relaxed(pmu_conf_reg); + reg |= WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP; + writel_relaxed(reg, pmu_conf_reg); + return 1; +} + +void wcnss_log_debug_regs_on_bite(void) +{ + struct platform_device *pdev = wcnss_get_platform_device(); + struct clk *measure; + struct clk *wcnss_debug_mux; + unsigned long clk_rate; + + if (wcnss_hardware_type() != WCNSS_PRONTO_HW) + return; + + measure = clk_get(&pdev->dev, "measure"); + wcnss_debug_mux = clk_get(&pdev->dev, "wcnss_debug"); + + if (!IS_ERR(measure) && !IS_ERR(wcnss_debug_mux)) { + if (clk_set_parent(measure, wcnss_debug_mux)) { + pr_err("Setting measure clk parent failed\n"); + return; + } + + if (clk_prepare_enable(measure)) { + pr_err("measure clk enable failed\n"); + return; + } + + clk_rate = clk_get_rate(measure); + pr_debug("wcnss: clock frequency is: %luHz\n", clk_rate); + + if (clk_rate) { + wcnss_pronto_log_debug_regs(); + if (wcnss_get_mux_control()) + wcnss_log_iris_regs(); + } else { + pr_err("clock frequency is zero, cannot access PMU or other registers\n"); + wcnss_log_iris_regs(); + } + + clk_disable_unprepare(measure); + } +} +#endif + +/* interface to reset wcnss by sending the reset interrupt */ +void wcnss_reset_fiq(bool clk_chk_en) +{ + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) { + if (clk_chk_en) { + wcnss_log_debug_regs_on_bite(); + } else { + wcnss_pronto_log_debug_regs(); + if (wcnss_get_mux_control()) + wcnss_log_iris_regs(); + } + if (!wcnss_device_is_shutdown()) { + /* Insert memory barrier before writing fiq register */ + wmb(); + __raw_writel(1 << 16, penv->fiq_reg); + } else { + pr_info("%s: Block FIQ during power up sequence\n", + __func__); + } + } else { + wcnss_riva_log_debug_regs(); + } +} +EXPORT_SYMBOL(wcnss_reset_fiq); + +static int wcnss_create_sysfs(struct device *dev) +{ + int ret; + + if (!dev) + return -ENODEV; + + ret = device_create_file(dev, &dev_attr_thermal_mitigation); + if (ret) + return ret; + + ret = device_create_file(dev, &dev_attr_wcnss_version); + if (ret) + goto remove_thermal; + + ret = device_create_file(dev, &dev_attr_wcnss_mac_addr); + if (ret) + goto remove_version; + + return 0; + +remove_version: + device_remove_file(dev, &dev_attr_wcnss_version); +remove_thermal: + device_remove_file(dev, &dev_attr_thermal_mitigation); + + return ret; +} + +static void wcnss_remove_sysfs(struct device *dev) +{ + if (dev) { + device_remove_file(dev, &dev_attr_thermal_mitigation); + device_remove_file(dev, &dev_attr_wcnss_version); + device_remove_file(dev, &dev_attr_wcnss_mac_addr); + } +} + +static void wcnss_pm_qos_add_request(void) +{ + pr_info("%s: add request\n", __func__); + pm_qos_add_request(&penv->wcnss_pm_qos_request, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); +} + +static void wcnss_pm_qos_remove_request(void) +{ + pr_info("%s: remove request\n", __func__); + pm_qos_remove_request(&penv->wcnss_pm_qos_request); +} + +void wcnss_pm_qos_update_request(int val) +{ + pr_info("%s: update request %d\n", __func__, val); + pm_qos_update_request(&penv->wcnss_pm_qos_request, val); +} + +void wcnss_disable_pc_remove_req(void) +{ + mutex_lock(&penv->pm_qos_mutex); + if (penv->pc_disabled) { + penv->pc_disabled = 0; + wcnss_pm_qos_update_request(WCNSS_ENABLE_PC_LATENCY); + wcnss_pm_qos_remove_request(); + wcnss_allow_suspend(); + } + mutex_unlock(&penv->pm_qos_mutex); +} + +void wcnss_disable_pc_add_req(void) +{ + mutex_lock(&penv->pm_qos_mutex); + if (!penv->pc_disabled) { + wcnss_pm_qos_add_request(); + wcnss_prevent_suspend(); + wcnss_pm_qos_update_request(WCNSS_DISABLE_PC_LATENCY); + penv->pc_disabled = 1; + } + mutex_unlock(&penv->pm_qos_mutex); +} + +static void wcnss_smd_notify_event(void *data, unsigned int event) +{ + int len = 0; + + if (penv != data) { + pr_err("wcnss: invalid env pointer in smd callback\n"); + return; + } + switch (event) { + case SMD_EVENT_DATA: + len = smd_read_avail(penv->smd_ch); + if (len < 0) { + pr_err("wcnss: failed to read from smd %d\n", len); + return; + } + schedule_work(&penv->wcnssctrl_rx_work); + break; + + case SMD_EVENT_OPEN: + pr_debug("wcnss: opening WCNSS SMD channel :%s", + WCNSS_CTRL_CHANNEL); + schedule_work(&penv->wcnssctrl_version_work); + schedule_work(&penv->wcnss_pm_config_work); + cancel_delayed_work(&penv->wcnss_pm_qos_del_req); + schedule_delayed_work(&penv->wcnss_pm_qos_del_req, 0); + if (penv->wlan_config.is_pronto_vadc && (penv->vadc_dev)) + schedule_work(&penv->wcnss_vadc_work); + break; + + case SMD_EVENT_CLOSE: + pr_debug("wcnss: closing WCNSS SMD channel :%s", + WCNSS_CTRL_CHANNEL); + penv->nv_downloaded = 0; + penv->is_cbc_done = 0; + break; + + default: + break; + } +} + +static int +wcnss_pinctrl_set_state(bool active) +{ + struct pinctrl_state *pin_state; + int ret; + + pr_debug("%s: Set GPIO state : %d\n", __func__, active); + + pin_state = active ? penv->wcnss_5wire_active + : penv->wcnss_5wire_suspend; + + if (!IS_ERR_OR_NULL(pin_state)) { + ret = pinctrl_select_state(penv->pinctrl, pin_state); + if (ret < 0) { + pr_err("%s: can not set %s pins\n", __func__, + active ? WCNSS_PINCTRL_STATE_DEFAULT + : WCNSS_PINCTRL_STATE_SLEEP); + return ret; + } + } else { + pr_err("%s: invalid '%s' pinstate\n", __func__, + active ? WCNSS_PINCTRL_STATE_DEFAULT + : WCNSS_PINCTRL_STATE_SLEEP); + return PTR_ERR(pin_state); + } + + return 0; +} + +static int +wcnss_pinctrl_init(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + int i; + + /* Get pinctrl if target uses pinctrl */ + penv->pinctrl = devm_pinctrl_get(&pdev->dev); + + if (IS_ERR_OR_NULL(penv->pinctrl)) { + pr_err("%s: failed to get pinctrl\n", __func__); + return PTR_ERR(penv->pinctrl); + } + + penv->wcnss_5wire_active + = pinctrl_lookup_state(penv->pinctrl, + WCNSS_PINCTRL_STATE_DEFAULT); + + if (IS_ERR_OR_NULL(penv->wcnss_5wire_active)) { + pr_err("%s: can not get default pinstate\n", __func__); + return PTR_ERR(penv->wcnss_5wire_active); + } + + penv->wcnss_5wire_suspend + = pinctrl_lookup_state(penv->pinctrl, + WCNSS_PINCTRL_STATE_SLEEP); + + if (IS_ERR_OR_NULL(penv->wcnss_5wire_suspend)) { + pr_warn("%s: can not get sleep pinstate\n", __func__); + return PTR_ERR(penv->wcnss_5wire_suspend); + } + + penv->wcnss_gpio_active = pinctrl_lookup_state(penv->pinctrl, + WCNSS_PINCTRL_GPIO_STATE_DEFAULT); + if (IS_ERR_OR_NULL(penv->wcnss_gpio_active)) + pr_warn("%s: can not get gpio default pinstate\n", __func__); + + for (i = 0; i < WCNSS_WLAN_MAX_GPIO; i++) { + penv->gpios[i] = of_get_gpio(node, i); + if (penv->gpios[i] < 0) + pr_warn("%s: Fail to get 5wire gpio: %d\n", + __func__, i); + } + + return 0; +} + +static int +wcnss_pronto_gpios_config(struct platform_device *pdev, bool enable) +{ + int rc = 0; + int i, j; + int WCNSS_WLAN_NUM_GPIOS = 5; + + /* Use Pinctrl to configure 5 wire GPIOs */ + rc = wcnss_pinctrl_init(pdev); + if (rc) { + pr_err("%s: failed to get pin resources\n", __func__); + penv->pinctrl = NULL; + goto gpio_probe; + } else { + rc = wcnss_pinctrl_set_state(true); + if (rc) + pr_err("%s: failed to set pin state\n", + __func__); + penv->use_pinctrl = true; + return rc; + } + +gpio_probe: + for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) { + int gpio = of_get_gpio(pdev->dev.of_node, i); + if (enable) { + rc = gpio_request(gpio, "wcnss_wlan"); + if (rc) { + pr_err("WCNSS gpio_request %d err %d\n", + gpio, rc); + goto fail; + } + } else + gpio_free(gpio); + } + return rc; + +fail: + for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) { + int gpio = of_get_gpio(pdev->dev.of_node, i); + gpio_free(gpio); + } + return rc; +} + +static int +wcnss_gpios_config(struct resource *gpios_5wire, bool enable) +{ + int i, j; + int rc = 0; + + for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) { + if (enable) { + rc = gpio_request(i, gpios_5wire->name); + if (rc) { + pr_err("WCNSS gpio_request %d err %d\n", i, rc); + goto fail; + } + } else + gpio_free(i); + } + + return rc; + +fail: + for (j = i-1; j >= gpios_5wire->start; j--) + gpio_free(j); + return rc; +} + +static int +wcnss_wlan_ctrl_probe(struct platform_device *pdev) +{ + if (!penv || !penv->triggered) + return -ENODEV; + + penv->smd_channel_ready = 1; + + pr_info("%s: SMD ctrl channel up\n", __func__); + return 0; +} + +static int +wcnss_wlan_ctrl_remove(struct platform_device *pdev) +{ + if (penv) + penv->smd_channel_ready = 0; + + pr_info("%s: SMD ctrl channel down\n", __func__); + + return 0; +} + + +static struct platform_driver wcnss_wlan_ctrl_driver = { + .driver = { + .name = "WLAN_CTRL", + .owner = THIS_MODULE, + }, + .probe = wcnss_wlan_ctrl_probe, + .remove = wcnss_wlan_ctrl_remove, +}; + +static int +wcnss_ctrl_remove(struct platform_device *pdev) +{ + if (penv && penv->smd_ch) + smd_close(penv->smd_ch); + + return 0; +} + +static int +wcnss_ctrl_probe(struct platform_device *pdev) +{ + int ret = 0; + + if (!penv || !penv->triggered) + return -ENODEV; + + ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS, + &penv->smd_ch, penv, wcnss_smd_notify_event); + if (ret < 0) { + pr_err("wcnss: cannot open the smd command channel %s: %d\n", + WCNSS_CTRL_CHANNEL, ret); + return -ENODEV; + } + smd_disable_read_intr(penv->smd_ch); + + return 0; +} + +/* platform device for WCNSS_CTRL SMD channel */ +static struct platform_driver wcnss_ctrl_driver = { + .driver = { + .name = "WCNSS_CTRL", + .owner = THIS_MODULE, + }, + .probe = wcnss_ctrl_probe, + .remove = wcnss_ctrl_remove, +}; + +struct device *wcnss_wlan_get_device(void) +{ + if (penv && penv->pdev && penv->smd_channel_ready) + return &penv->pdev->dev; + return NULL; +} +EXPORT_SYMBOL(wcnss_wlan_get_device); + +void wcnss_get_monotonic_boottime(struct timespec *ts) +{ + get_monotonic_boottime(ts); +} +EXPORT_SYMBOL(wcnss_get_monotonic_boottime); + +struct platform_device *wcnss_get_platform_device(void) +{ + if (penv && penv->pdev) + return penv->pdev; + return NULL; +} +EXPORT_SYMBOL(wcnss_get_platform_device); + +struct wcnss_wlan_config *wcnss_get_wlan_config(void) +{ + if (penv && penv->pdev) + return &penv->wlan_config; + return NULL; +} +EXPORT_SYMBOL(wcnss_get_wlan_config); + +int wcnss_is_hw_pronto_ver3(void) +{ + if (penv && penv->pdev) { + if (penv->wlan_config.is_pronto_v3) + return penv->wlan_config.is_pronto_v3; + } + return 0; +} +EXPORT_SYMBOL(wcnss_is_hw_pronto_ver3); + +int wcnss_device_ready(void) +{ + if (penv && penv->pdev && penv->nv_downloaded && + !wcnss_device_is_shutdown()) + return 1; + return 0; +} +EXPORT_SYMBOL(wcnss_device_ready); + +bool wcnss_cbc_complete(void) +{ + if (penv && penv->pdev && penv->is_cbc_done && + !wcnss_device_is_shutdown()) + return true; + return false; +} +EXPORT_SYMBOL(wcnss_cbc_complete); + +int wcnss_device_is_shutdown(void) +{ + if (penv && penv->is_shutdown) + return 1; + return 0; +} +EXPORT_SYMBOL(wcnss_device_is_shutdown); + +struct resource *wcnss_wlan_get_memory_map(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready) + return penv->mmio_res; + return NULL; +} +EXPORT_SYMBOL(wcnss_wlan_get_memory_map); + +int wcnss_wlan_get_dxe_tx_irq(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->tx_irq_res && penv->smd_channel_ready) + return penv->tx_irq_res->start; + return WCNSS_WLAN_IRQ_INVALID; +} +EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq); + +int wcnss_wlan_get_dxe_rx_irq(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->rx_irq_res && penv->smd_channel_ready) + return penv->rx_irq_res->start; + return WCNSS_WLAN_IRQ_INVALID; +} +EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq); + +void wcnss_wlan_register_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops) +{ + if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) + penv->pm_ops = pm_ops; +} +EXPORT_SYMBOL(wcnss_wlan_register_pm_ops); + +void wcnss_wlan_unregister_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops) +{ + if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) { + if (penv->pm_ops == NULL) { + pr_err("%s: pm_ops is already unregistered.\n", + __func__); + return; + } + + if (pm_ops->suspend != penv->pm_ops->suspend || + pm_ops->resume != penv->pm_ops->resume) + pr_err("PM APIs dont match with registered APIs\n"); + penv->pm_ops = NULL; + } +} +EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops); + +void wcnss_register_thermal_mitigation(struct device *dev, + void (*tm_notify)(struct device *, int)) +{ + if (penv && dev && tm_notify) + penv->tm_notify = tm_notify; +} +EXPORT_SYMBOL(wcnss_register_thermal_mitigation); + +void wcnss_unregister_thermal_mitigation( + void (*tm_notify)(struct device *, int)) +{ + if (penv && tm_notify) { + if (tm_notify != penv->tm_notify) + pr_err("tm_notify doesn't match registered\n"); + penv->tm_notify = NULL; + } +} +EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation); + +unsigned int wcnss_get_serial_number(void) +{ + if (penv) { + penv->serial_number = socinfo_get_serial_number(); + pr_info("%s: Device serial number: %u\n", + __func__, penv->serial_number); + return penv->serial_number; + } + + return 0; +} +EXPORT_SYMBOL(wcnss_get_serial_number); + +int wcnss_get_wlan_mac_address(char mac_addr[WLAN_MAC_ADDR_SIZE]) +{ + if (!penv) + return -ENODEV; + + memcpy(mac_addr, penv->wlan_nv_macAddr, WLAN_MAC_ADDR_SIZE); + pr_debug("%s: Get MAC Addr: %pM\n", __func__, penv->wlan_nv_macAddr); + + return 0; +} +EXPORT_SYMBOL(wcnss_get_wlan_mac_address); + +static int enable_wcnss_suspend_notify; + +static int enable_wcnss_suspend_notify_set(const char *val, + struct kernel_param *kp) +{ + int ret; + + ret = param_set_int(val, kp); + if (ret) + return ret; + + if (enable_wcnss_suspend_notify) + pr_debug("Suspend notification activated for wcnss\n"); + + return 0; +} +module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set, + param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR); + +int wcnss_xo_auto_detect_enabled(void) +{ + return (has_autodetect_xo == 1 ? 1 : 0); +} + +void wcnss_set_iris_xo_mode(int iris_xo_mode_set) +{ + penv->iris_xo_mode_set = iris_xo_mode_set; +} +EXPORT_SYMBOL(wcnss_set_iris_xo_mode); + +int wcnss_wlan_iris_xo_mode(void) +{ + if (penv && penv->pdev && penv->smd_channel_ready) + return penv->iris_xo_mode_set; + return -ENODEV; +} +EXPORT_SYMBOL(wcnss_wlan_iris_xo_mode); + +int wcnss_wlan_dual_band_disabled(void) +{ + if (penv && penv->pdev) + return penv->is_dual_band_disabled; + + return -EINVAL; +} +EXPORT_SYMBOL(wcnss_wlan_dual_band_disabled); + +void wcnss_suspend_notify(void) +{ + void __iomem *pmu_spare_reg; + u32 reg = 0; + unsigned long flags; + + if (!enable_wcnss_suspend_notify) + return; + + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) + return; + + /* For Riva */ + pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET; + spin_lock_irqsave(®_spinlock, flags); + reg = readl_relaxed(pmu_spare_reg); + reg |= RIVA_SUSPEND_BIT; + writel_relaxed(reg, pmu_spare_reg); + spin_unlock_irqrestore(®_spinlock, flags); +} +EXPORT_SYMBOL(wcnss_suspend_notify); + +void wcnss_resume_notify(void) +{ + void __iomem *pmu_spare_reg; + u32 reg = 0; + unsigned long flags; + + if (!enable_wcnss_suspend_notify) + return; + + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) + return; + + /* For Riva */ + pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET; + + spin_lock_irqsave(®_spinlock, flags); + reg = readl_relaxed(pmu_spare_reg); + reg &= ~RIVA_SUSPEND_BIT; + writel_relaxed(reg, pmu_spare_reg); + spin_unlock_irqrestore(®_spinlock, flags); +} +EXPORT_SYMBOL(wcnss_resume_notify); + +static int wcnss_wlan_suspend(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->smd_channel_ready && + penv->pm_ops && penv->pm_ops->suspend) + return penv->pm_ops->suspend(dev); + return 0; +} + +static int wcnss_wlan_resume(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->smd_channel_ready && + penv->pm_ops && penv->pm_ops->resume) + return penv->pm_ops->resume(dev); + return 0; +} + +void wcnss_prevent_suspend() +{ + if (penv) + wake_lock(&penv->wcnss_wake_lock); +} +EXPORT_SYMBOL(wcnss_prevent_suspend); + +void wcnss_allow_suspend() +{ + if (penv) + wake_unlock(&penv->wcnss_wake_lock); +} +EXPORT_SYMBOL(wcnss_allow_suspend); + +int wcnss_hardware_type(void) +{ + if (penv) + return penv->wcnss_hw_type; + else + return -ENODEV; +} +EXPORT_SYMBOL(wcnss_hardware_type); + +int fw_cal_data_available(void) +{ + if (penv) + return penv->fw_cal_available; + else + return -ENODEV; +} + +u32 wcnss_get_wlan_rx_buff_count(void) +{ + if (penv) + return penv->wlan_rx_buff_count; + else + return WCNSS_DEF_WLAN_RX_BUFF_COUNT; + +} +EXPORT_SYMBOL(wcnss_get_wlan_rx_buff_count); + +int wcnss_set_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 ch_count) +{ + if (penv && unsafe_ch_list && + (ch_count <= WCNSS_MAX_CH_NUM)) { + memcpy((char *)penv->unsafe_ch_list, + (char *)unsafe_ch_list, ch_count * sizeof(u16)); + penv->unsafe_ch_count = ch_count; + return 0; + } else + return -ENODEV; +} +EXPORT_SYMBOL(wcnss_set_wlan_unsafe_channel); + +int wcnss_get_wlan_unsafe_channel(u16 *unsafe_ch_list, u16 buffer_size, + u16 *ch_count) +{ + if (penv) { + if (buffer_size < penv->unsafe_ch_count * sizeof(u16)) + return -ENODEV; + memcpy((char *)unsafe_ch_list, + (char *)penv->unsafe_ch_list, + penv->unsafe_ch_count * sizeof(u16)); + *ch_count = penv->unsafe_ch_count; + return 0; + } else + return -ENODEV; +} +EXPORT_SYMBOL(wcnss_get_wlan_unsafe_channel); + +static int wcnss_smd_tx(void *data, int len) +{ + int ret = 0; + + ret = smd_write_avail(penv->smd_ch); + if (ret < len) { + pr_err("wcnss: no space available for smd frame\n"); + return -ENOSPC; + } + ret = smd_write(penv->smd_ch, data, len); + if (ret < len) { + pr_err("wcnss: failed to write Command %d", len); + ret = -ENODEV; + } + return ret; +} + +static int wcnss_get_battery_volt(int *result_uv) +{ + int rc = -1; + struct qpnp_vadc_result adc_result; + + if (!penv->vadc_dev) { + pr_err("wcnss: not setting up vadc\n"); + return rc; + } + + rc = qpnp_vadc_read(penv->vadc_dev, VBAT_SNS, &adc_result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + VBAT_SNS, rc); + return rc; + } + + pr_info("Battery mvolts phy=%lld meas=0x%llx\n", adc_result.physical, + adc_result.measurement); + *result_uv = (int)adc_result.physical; + + return 0; +} + +static void wcnss_notify_vbat(enum qpnp_tm_state state, void *ctx) +{ + int rc = 0; + + mutex_lock(&penv->vbat_monitor_mutex); + cancel_delayed_work_sync(&penv->vbatt_work); + + if (state == ADC_TM_LOW_STATE) { + pr_debug("wcnss: low voltage notification triggered\n"); + penv->vbat_monitor_params.state_request = + ADC_TM_HIGH_THR_ENABLE; + penv->vbat_monitor_params.high_thr = WCNSS_VBATT_THRESHOLD + + WCNSS_VBATT_GUARD; + penv->vbat_monitor_params.low_thr = 0; + } else if (state == ADC_TM_HIGH_STATE) { + penv->vbat_monitor_params.state_request = + ADC_TM_LOW_THR_ENABLE; + penv->vbat_monitor_params.low_thr = WCNSS_VBATT_THRESHOLD - + WCNSS_VBATT_GUARD; + penv->vbat_monitor_params.high_thr = 0; + pr_debug("wcnss: high voltage notification triggered\n"); + } else { + pr_debug("wcnss: unknown voltage notification state: %d\n", + state); + mutex_unlock(&penv->vbat_monitor_mutex); + return; + } + pr_debug("wcnss: set low thr to %d and high to %d\n", + penv->vbat_monitor_params.low_thr, + penv->vbat_monitor_params.high_thr); + + rc = qpnp_adc_tm_channel_measure(penv->adc_tm_dev, + &penv->vbat_monitor_params); + + if (rc) + pr_err("%s: tm setup failed: %d\n", __func__, rc); + else + schedule_delayed_work(&penv->vbatt_work, + msecs_to_jiffies(2000)); + + mutex_unlock(&penv->vbat_monitor_mutex); +} + +static int wcnss_setup_vbat_monitoring(void) +{ + int rc = -1; + + if (!penv->adc_tm_dev) { + pr_err("wcnss: not setting up vbatt\n"); + return rc; + } + penv->vbat_monitor_params.low_thr = WCNSS_VBATT_THRESHOLD; + penv->vbat_monitor_params.high_thr = WCNSS_VBATT_THRESHOLD; + penv->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; + + if (penv->is_vsys_adc_channel) + penv->vbat_monitor_params.channel = VSYS; + else + penv->vbat_monitor_params.channel = VBAT_SNS; + + penv->vbat_monitor_params.btm_ctx = (void *)penv; + penv->vbat_monitor_params.timer_interval = ADC_MEAS1_INTERVAL_1S; + penv->vbat_monitor_params.threshold_notification = &wcnss_notify_vbat; + pr_debug("wcnss: set low thr to %d and high to %d\n", + penv->vbat_monitor_params.low_thr, + penv->vbat_monitor_params.high_thr); + + rc = qpnp_adc_tm_channel_measure(penv->adc_tm_dev, + &penv->vbat_monitor_params); + if (rc) + pr_err("%s: tm setup failed: %d\n", __func__, rc); + + return rc; +} + +static void wcnss_send_vbatt_indication(struct work_struct *work) +{ + struct vbatt_message vbatt_msg; + int ret = 0; + + vbatt_msg.hdr.msg_type = WCNSS_VBATT_LEVEL_IND; + vbatt_msg.hdr.msg_len = sizeof(struct vbatt_message); + vbatt_msg.vbatt.threshold = WCNSS_VBATT_THRESHOLD; + + mutex_lock(&penv->vbat_monitor_mutex); + vbatt_msg.vbatt.curr_volt = penv->wlan_config.vbatt; + mutex_unlock(&penv->vbat_monitor_mutex); + pr_debug("wcnss: send curr_volt: %d to FW\n", + vbatt_msg.vbatt.curr_volt); + + ret = wcnss_smd_tx(&vbatt_msg, vbatt_msg.hdr.msg_len); + if (ret < 0) + pr_err("wcnss: smd tx failed\n"); +} + +static void wcnss_update_vbatt(struct work_struct *work) +{ + struct vbatt_message vbatt_msg; + int ret = 0; + + vbatt_msg.hdr.msg_type = WCNSS_VBATT_LEVEL_IND; + vbatt_msg.hdr.msg_len = sizeof(struct vbatt_message); + vbatt_msg.vbatt.threshold = WCNSS_VBATT_THRESHOLD; + + mutex_lock(&penv->vbat_monitor_mutex); + if (penv->vbat_monitor_params.low_thr && + (penv->fw_vbatt_state == WCNSS_VBATT_LOW || + penv->fw_vbatt_state == WCNSS_CONFIG_UNSPECIFIED)) { + vbatt_msg.vbatt.curr_volt = WCNSS_VBATT_HIGH; + penv->fw_vbatt_state = WCNSS_VBATT_HIGH; + pr_debug("wcnss: send HIGH BATT to FW\n"); + } else if (!penv->vbat_monitor_params.low_thr && + (penv->fw_vbatt_state == WCNSS_VBATT_HIGH || + penv->fw_vbatt_state == WCNSS_CONFIG_UNSPECIFIED)){ + vbatt_msg.vbatt.curr_volt = WCNSS_VBATT_LOW; + penv->fw_vbatt_state = WCNSS_VBATT_LOW; + pr_debug("wcnss: send LOW BATT to FW\n"); + } else { + mutex_unlock(&penv->vbat_monitor_mutex); + return; + } + mutex_unlock(&penv->vbat_monitor_mutex); + ret = wcnss_smd_tx(&vbatt_msg, vbatt_msg.hdr.msg_len); + if (ret < 0) + pr_err("wcnss: smd tx failed\n"); + return; +} + + +static unsigned char wcnss_fw_status(void) +{ + int len = 0; + int rc = 0; + + unsigned char fw_status = 0xFF; + + len = smd_read_avail(penv->smd_ch); + if (len < 1) { + pr_err("%s: invalid firmware status", __func__); + return fw_status; + } + + rc = smd_read(penv->smd_ch, &fw_status, 1); + if (rc < 0) { + pr_err("%s: incomplete data read from smd\n", __func__); + return fw_status; + } + return fw_status; +} + +static void wcnss_send_cal_rsp(unsigned char fw_status) +{ + struct smd_msg_hdr *rsphdr; + unsigned char *msg = NULL; + int rc; + + msg = kmalloc((sizeof(struct smd_msg_hdr) + 1), GFP_KERNEL); + if (NULL == msg) { + pr_err("wcnss: %s: failed to get memory\n", __func__); + return; + } + + rsphdr = (struct smd_msg_hdr *)msg; + rsphdr->msg_type = WCNSS_CALDATA_UPLD_RSP; + rsphdr->msg_len = sizeof(struct smd_msg_hdr) + 1; + memcpy(msg+sizeof(struct smd_msg_hdr), &fw_status, 1); + + rc = wcnss_smd_tx(msg, rsphdr->msg_len); + if (rc < 0) + pr_err("wcnss: smd tx failed\n"); + + kfree(msg); +} + +/* Collect calibrated data from WCNSS */ +void extract_cal_data(int len) +{ + int rc; + struct cal_data_params calhdr; + unsigned char fw_status = WCNSS_RESP_FAIL; + + if (len < sizeof(struct cal_data_params)) { + pr_err("wcnss: incomplete cal header length\n"); + return; + } + + mutex_lock(&penv->dev_lock); + rc = smd_read(penv->smd_ch, (unsigned char *)&calhdr, + sizeof(struct cal_data_params)); + if (rc < sizeof(struct cal_data_params)) { + pr_err("wcnss: incomplete cal header read from smd\n"); + mutex_unlock(&penv->dev_lock); + return; + } + + if (penv->fw_cal_exp_frag != calhdr.frag_number) { + pr_err("wcnss: Invalid frgament"); + goto unlock_exit; + } + + if (calhdr.frag_size > WCNSS_MAX_FRAME_SIZE) { + pr_err("wcnss: Invalid fragment size"); + goto unlock_exit; + } + + if (penv->fw_cal_available) { + /* ignore cal upload from SSR */ + smd_read(penv->smd_ch, NULL, calhdr.frag_size); + penv->fw_cal_exp_frag++; + if (calhdr.msg_flags & LAST_FRAGMENT) { + penv->fw_cal_exp_frag = 0; + goto unlock_exit; + } + mutex_unlock(&penv->dev_lock); + return; + } + + if (0 == calhdr.frag_number) { + if (calhdr.total_size > MAX_CALIBRATED_DATA_SIZE) { + pr_err("wcnss: Invalid cal data size %d", + calhdr.total_size); + goto unlock_exit; + } + kfree(penv->fw_cal_data); + penv->fw_cal_rcvd = 0; + penv->fw_cal_data = kmalloc(calhdr.total_size, + GFP_KERNEL); + if (penv->fw_cal_data == NULL) { + smd_read(penv->smd_ch, NULL, calhdr.frag_size); + goto unlock_exit; + } + } + + if (penv->fw_cal_rcvd + calhdr.frag_size > + MAX_CALIBRATED_DATA_SIZE) { + pr_err("calibrated data size is more than expected %d", + penv->fw_cal_rcvd + calhdr.frag_size); + penv->fw_cal_exp_frag = 0; + penv->fw_cal_rcvd = 0; + smd_read(penv->smd_ch, NULL, calhdr.frag_size); + goto unlock_exit; + } + + rc = smd_read(penv->smd_ch, penv->fw_cal_data + penv->fw_cal_rcvd, + calhdr.frag_size); + if (rc < calhdr.frag_size) + goto unlock_exit; + + penv->fw_cal_exp_frag++; + penv->fw_cal_rcvd += calhdr.frag_size; + + if (calhdr.msg_flags & LAST_FRAGMENT) { + penv->fw_cal_exp_frag = 0; + penv->fw_cal_available = true; + pr_info("wcnss: cal data collection completed\n"); + } + mutex_unlock(&penv->dev_lock); + wake_up(&penv->read_wait); + + if (penv->fw_cal_available) { + fw_status = WCNSS_RESP_SUCCESS; + wcnss_send_cal_rsp(fw_status); + } + return; + +unlock_exit: + mutex_unlock(&penv->dev_lock); + wcnss_send_cal_rsp(fw_status); + return; +} + +static void wcnssctrl_rx_handler(struct work_struct *worker) +{ + int len = 0; + int rc = 0; + unsigned char buf[sizeof(struct wcnss_version)]; + unsigned char build[WCNSS_MAX_BUILD_VER_LEN+1]; + struct smd_msg_hdr *phdr; + struct smd_msg_hdr smd_msg; + struct wcnss_version *pversion; + int hw_type; + unsigned char fw_status = 0; + + len = smd_read_avail(penv->smd_ch); + if (len > WCNSS_MAX_FRAME_SIZE) { + pr_err("wcnss: frame larger than the allowed size\n"); + smd_read(penv->smd_ch, NULL, len); + return; + } + if (len < sizeof(struct smd_msg_hdr)) { + pr_debug("wcnss: incomplete header available len = %d\n", len); + return; + } + + rc = smd_read(penv->smd_ch, buf, sizeof(struct smd_msg_hdr)); + if (rc < sizeof(struct smd_msg_hdr)) { + pr_err("wcnss: incomplete header read from smd\n"); + return; + } + len -= sizeof(struct smd_msg_hdr); + + phdr = (struct smd_msg_hdr *)buf; + + switch (phdr->msg_type) { + + case WCNSS_VERSION_RSP: + if (len != sizeof(struct wcnss_version) + - sizeof(struct smd_msg_hdr)) { + pr_err("wcnss: invalid version data from wcnss %d\n", + len); + return; + } + rc = smd_read(penv->smd_ch, buf+sizeof(struct smd_msg_hdr), + len); + if (rc < len) { + pr_err("wcnss: incomplete data read from smd\n"); + return; + } + pversion = (struct wcnss_version *)buf; + penv->fw_major = pversion->major; + penv->fw_minor = pversion->minor; + snprintf(penv->wcnss_version, WCNSS_VERSION_LEN, + "%02x%02x%02x%02x", pversion->major, pversion->minor, + pversion->version, pversion->revision); + pr_info("wcnss: version %s\n", penv->wcnss_version); + /* schedule work to download nvbin to ccpu */ + hw_type = wcnss_hardware_type(); + switch (hw_type) { + case WCNSS_RIVA_HW: + /* supported only if riva major >= 1 and minor >= 4 */ + if ((pversion->major >= 1) && (pversion->minor >= 4)) { + pr_info("wcnss: schedule dnld work for riva\n"); + schedule_work(&penv->wcnssctrl_nvbin_dnld_work); + } + break; + + case WCNSS_PRONTO_HW: + smd_msg.msg_type = WCNSS_BUILD_VER_REQ; + smd_msg.msg_len = sizeof(smd_msg); + rc = wcnss_smd_tx(&smd_msg, smd_msg.msg_len); + if (rc < 0) + pr_err("wcnss: smd tx failed: %s\n", __func__); + + /* supported only if pronto major >= 1 and minor >= 4 */ + if ((pversion->major >= 1) && (pversion->minor >= 4)) { + pr_info("wcnss: schedule dnld work for pronto\n"); + schedule_work(&penv->wcnssctrl_nvbin_dnld_work); + } + break; + + default: + pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n", + hw_type); + break; + } + break; + + case WCNSS_BUILD_VER_RSP: + if (len > WCNSS_MAX_BUILD_VER_LEN) { + pr_err("wcnss: invalid build version data from wcnss %d\n", + len); + return; + } + rc = smd_read(penv->smd_ch, build, len); + if (rc < len) { + pr_err("wcnss: incomplete data read from smd\n"); + return; + } + build[len] = 0; + pr_info("wcnss: build version %s\n", build); + break; + + case WCNSS_NVBIN_DNLD_RSP: + penv->nv_downloaded = true; + fw_status = wcnss_fw_status(); + pr_debug("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu %u\n", + fw_status); + if (fw_status != WAIT_FOR_CBC_IND) + penv->is_cbc_done = 1; + wcnss_setup_vbat_monitoring(); + break; + + case WCNSS_CALDATA_DNLD_RSP: + penv->nv_downloaded = true; + fw_status = wcnss_fw_status(); + pr_debug("wcnss: received WCNSS_CALDATA_DNLD_RSP from ccpu %u\n", + fw_status); + break; + case WCNSS_CBC_COMPLETE_IND: + penv->is_cbc_done = 1; + pr_debug("wcnss: received WCNSS_CBC_COMPLETE_IND from FW\n"); + break; + + case WCNSS_CALDATA_UPLD_REQ: + extract_cal_data(len); + break; + + default: + pr_err("wcnss: invalid message type %d\n", phdr->msg_type); + } + return; +} + +static void wcnss_send_version_req(struct work_struct *worker) +{ + struct smd_msg_hdr smd_msg; + int ret = 0; + + smd_msg.msg_type = WCNSS_VERSION_REQ; + smd_msg.msg_len = sizeof(smd_msg); + ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len); + if (ret < 0) + pr_err("wcnss: smd tx failed\n"); + + return; +} + +static void wcnss_send_pm_config(struct work_struct *worker) +{ + struct smd_msg_hdr *hdr; + unsigned char *msg = NULL; + int rc, prop_len; + u32 *payload; + + if (!of_find_property(penv->pdev->dev.of_node, + "qcom,wcnss-pm", &prop_len)) + return; + + msg = kmalloc((sizeof(struct smd_msg_hdr) + prop_len), GFP_KERNEL); + + if (NULL == msg) { + pr_err("wcnss: %s: failed to allocate memory\n", __func__); + return; + } + + payload = (u32 *)(msg + sizeof(struct smd_msg_hdr)); + + prop_len /= sizeof(int); + + rc = of_property_read_u32_array(penv->pdev->dev.of_node, + "qcom,wcnss-pm", payload, prop_len); + if (rc < 0) { + pr_err("wcnss: property read failed\n"); + kfree(msg); + return; + } + + pr_debug("%s:size=%d: <%d, %d, %d, %d, %d %d>\n", __func__, + prop_len, *payload, *(payload+1), *(payload+2), + *(payload+3), *(payload+4), *(payload+5)); + + hdr = (struct smd_msg_hdr *)msg; + hdr->msg_type = WCNSS_PM_CONFIG_REQ; + hdr->msg_len = sizeof(struct smd_msg_hdr) + (prop_len * sizeof(int)); + + rc = wcnss_smd_tx(msg, hdr->msg_len); + if (rc < 0) + pr_err("wcnss: smd tx failed\n"); + + kfree(msg); + return; +} + +static void wcnss_pm_qos_enable_pc(struct work_struct *worker) +{ + wcnss_disable_pc_remove_req(); + return; +} + +static DECLARE_RWSEM(wcnss_pm_sem); + +static void wcnss_nvbin_dnld(void) +{ + int ret = 0; + struct nvbin_dnld_req_msg *dnld_req_msg; + unsigned short total_fragments = 0; + unsigned short count = 0; + unsigned short retry_count = 0; + unsigned short cur_frag_size = 0; + unsigned char *outbuffer = NULL; + const void *nv_blob_addr = NULL; + unsigned int nv_blob_size = 0; + const struct firmware *nv = NULL; + struct device *dev = &penv->pdev->dev; + + down_read(&wcnss_pm_sem); + + ret = request_firmware(&nv, NVBIN_FILE, dev); + + if (ret || !nv || !nv->data || !nv->size) { + pr_err("wcnss: %s: request_firmware failed for %s (ret = %d)\n", + __func__, NVBIN_FILE, ret); + goto out; + } + + /* First 4 bytes in nv blob is validity bitmap. + * We cannot validate nv, so skip those 4 bytes. + */ + nv_blob_addr = nv->data + 4; + nv_blob_size = nv->size - 4; + + total_fragments = TOTALFRAGMENTS(nv_blob_size); + + pr_info("wcnss: NV bin size: %d, total_fragments: %d\n", + nv_blob_size, total_fragments); + + /* get buffer for nv bin dnld req message */ + outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) + + NV_FRAGMENT_SIZE), GFP_KERNEL); + + if (NULL == outbuffer) { + pr_err("wcnss: %s: failed to get buffer\n", __func__); + goto err_free_nv; + } + + dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer; + + dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ; + dnld_req_msg->dnld_req_params.msg_flags = 0; + + for (count = 0; count < total_fragments; count++) { + dnld_req_msg->dnld_req_params.frag_number = count; + + if (count == (total_fragments - 1)) { + /* last fragment, take care of boundry condition */ + cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE; + if (!cur_frag_size) + cur_frag_size = NV_FRAGMENT_SIZE; + + dnld_req_msg->dnld_req_params.msg_flags |= + LAST_FRAGMENT; + dnld_req_msg->dnld_req_params.msg_flags |= + CAN_RECEIVE_CALDATA; + } else { + cur_frag_size = NV_FRAGMENT_SIZE; + dnld_req_msg->dnld_req_params.msg_flags &= + ~LAST_FRAGMENT; + } + + dnld_req_msg->dnld_req_params.nvbin_buffer_size = + cur_frag_size; + + dnld_req_msg->hdr.msg_len = + sizeof(struct nvbin_dnld_req_msg) + cur_frag_size; + + /* copy NV fragment */ + memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)), + (nv_blob_addr + count * NV_FRAGMENT_SIZE), + cur_frag_size); + + ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len); + + retry_count = 0; + while ((ret == -ENOSPC) && (retry_count <= 3)) { + pr_debug("wcnss: %s: smd tx failed, ENOSPC\n", + __func__); + pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n", + count, dnld_req_msg->hdr.msg_len, + total_fragments, retry_count); + + /* wait and try again */ + msleep(20); + retry_count++; + ret = wcnss_smd_tx(outbuffer, + dnld_req_msg->hdr.msg_len); + } + + if (ret < 0) { + pr_err("wcnss: %s: smd tx failed\n", __func__); + pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n", + count, dnld_req_msg->hdr.msg_len, + total_fragments, retry_count); + goto err_dnld; + } + } + +err_dnld: + /* free buffer */ + kfree(outbuffer); + +err_free_nv: + /* release firmware */ + release_firmware(nv); + +out: + up_read(&wcnss_pm_sem); + + return; +} + + +static void wcnss_caldata_dnld(const void *cal_data, + unsigned int cal_data_size, bool msg_to_follow) +{ + int ret = 0; + struct cal_data_msg *cal_msg; + unsigned short total_fragments = 0; + unsigned short count = 0; + unsigned short retry_count = 0; + unsigned short cur_frag_size = 0; + unsigned char *outbuffer = NULL; + + total_fragments = TOTALFRAGMENTS(cal_data_size); + + outbuffer = kmalloc((sizeof(struct cal_data_msg) + + NV_FRAGMENT_SIZE), GFP_KERNEL); + + if (NULL == outbuffer) { + pr_err("wcnss: %s: failed to get buffer\n", __func__); + return; + } + + cal_msg = (struct cal_data_msg *)outbuffer; + + cal_msg->hdr.msg_type = WCNSS_CALDATA_DNLD_REQ; + cal_msg->cal_params.msg_flags = 0; + + for (count = 0; count < total_fragments; count++) { + cal_msg->cal_params.frag_number = count; + + if (count == (total_fragments - 1)) { + cur_frag_size = cal_data_size % NV_FRAGMENT_SIZE; + if (!cur_frag_size) + cur_frag_size = NV_FRAGMENT_SIZE; + + cal_msg->cal_params.msg_flags + |= LAST_FRAGMENT; + if (msg_to_follow) + cal_msg->cal_params.msg_flags |= + MESSAGE_TO_FOLLOW; + } else { + cur_frag_size = NV_FRAGMENT_SIZE; + cal_msg->cal_params.msg_flags &= + ~LAST_FRAGMENT; + } + + cal_msg->cal_params.total_size = cal_data_size; + cal_msg->cal_params.frag_size = + cur_frag_size; + + cal_msg->hdr.msg_len = + sizeof(struct cal_data_msg) + cur_frag_size; + + memcpy((outbuffer + sizeof(struct cal_data_msg)), + (cal_data + count * NV_FRAGMENT_SIZE), + cur_frag_size); + + ret = wcnss_smd_tx(outbuffer, cal_msg->hdr.msg_len); + + retry_count = 0; + while ((ret == -ENOSPC) && (retry_count <= 3)) { + pr_debug("wcnss: %s: smd tx failed, ENOSPC\n", + __func__); + pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n", + count, cal_msg->hdr.msg_len, + total_fragments, retry_count); + + /* wait and try again */ + msleep(20); + retry_count++; + ret = wcnss_smd_tx(outbuffer, + cal_msg->hdr.msg_len); + } + + if (ret < 0) { + pr_err("wcnss: %s: smd tx failed\n", __func__); + pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n", + count, cal_msg->hdr.msg_len, + total_fragments, retry_count); + goto err_dnld; + } + } + + +err_dnld: + /* free buffer */ + kfree(outbuffer); + + return; +} + + +static void wcnss_nvbin_dnld_main(struct work_struct *worker) +{ + int retry = 0; + + if (!FW_CALDATA_CAPABLE()) + goto nv_download; + + if (!penv->fw_cal_available && IS_CAL_DATA_PRESENT + != has_calibrated_data && !penv->user_cal_available) { + while (!penv->user_cal_available && retry++ < 5) + msleep(500); + } + if (penv->fw_cal_available) { + pr_info_ratelimited("wcnss: cal download, using fw cal"); + wcnss_caldata_dnld(penv->fw_cal_data, penv->fw_cal_rcvd, true); + + } else if (penv->user_cal_available) { + pr_info_ratelimited("wcnss: cal download, using user cal"); + wcnss_caldata_dnld(penv->user_cal_data, + penv->user_cal_rcvd, true); + } + +nv_download: + pr_info_ratelimited("wcnss: NV download"); + wcnss_nvbin_dnld(); + + return; +} + +static int wcnss_pm_notify(struct notifier_block *b, + unsigned long event, void *p) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + down_write(&wcnss_pm_sem); + break; + + case PM_POST_SUSPEND: + up_write(&wcnss_pm_sem); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block wcnss_pm_notifier = { + .notifier_call = wcnss_pm_notify, +}; + +static int wcnss_ctrl_open(struct inode *inode, struct file *file) +{ + int rc = 0; + + if (!penv || penv->ctrl_device_opened) + return -EFAULT; + + penv->ctrl_device_opened = 1; + + return rc; +} + +static ssize_t wcnss_ctrl_write(struct file *fp, const char __user + *user_buffer, size_t count, loff_t *position) +{ + int rc = 0; + u16 cmd; + u8 buf[WCNSS_MAX_CMD_LEN]; + + if (!penv || !penv->ctrl_device_opened || + WCNSS_MAX_CMD_LEN < count || WCNSS_MIN_CMD_LEN > count) + return -EFAULT; + + mutex_lock(&penv->ctrl_lock); + rc = copy_from_user(buf, user_buffer, count); + if (rc) { + pr_err("%s: Failed to copy ctrl data\n", __func__); + goto exit; + } + + cmd = buf[0] << 8 | buf[1]; + switch (cmd) { + case WCNSS_USR_HAS_CAL_DATA: + if (buf[2] > 1) { + pr_err("%s: Invalid cal data %d\n", __func__, buf[2]); + rc = -EINVAL; + goto exit; + } + has_calibrated_data = buf[2]; + break; + + case WCNSS_USR_WLAN_MAC_ADDR: + if ((count - WCNSS_CMD_INFO_LEN) != WLAN_MAC_ADDR_SIZE) { + pr_err("%s: Invalid Mac addr %d\n", __func__, buf[2]); + rc = -EINVAL; + goto exit; + } + + memcpy(&penv->wlan_nv_macAddr, &buf[2], + sizeof(penv->wlan_nv_macAddr)); + pr_debug("%s:MAC Addr: %pM\n", __func__, penv->wlan_nv_macAddr); + break; + default: + pr_err("%s: Invalid command %d\n", __func__, cmd); + rc = -EINVAL; + break; + } + +exit: + mutex_unlock(&penv->ctrl_lock); + return rc; +} + +static const struct file_operations wcnss_ctrl_fops = { + .owner = THIS_MODULE, + .open = wcnss_ctrl_open, + .write = wcnss_ctrl_write, +}; + +static struct miscdevice wcnss_usr_ctrl = { + .minor = MISC_DYNAMIC_MINOR, + .name = CTRL_DEVICE, + .fops = &wcnss_ctrl_fops, +}; + +static int +wcnss_trigger_config(struct platform_device *pdev) +{ + int ret; + int rc; + struct qcom_wcnss_opts *pdata; + struct resource *res; + int is_pronto_vadc; + int is_pronto_v3; + int pil_retry = 0; + struct device_node *node = (&pdev->dev)->of_node; + int has_pronto_hw = of_property_read_bool(node, "qcom,has-pronto-hw"); + + is_pronto_vadc = of_property_read_bool(node, "qcom,is-pronto-vadc"); + is_pronto_v3 = of_property_read_bool(node, "qcom,is-pronto-v3"); + + penv->is_vsys_adc_channel = + of_property_read_bool(node, "qcom,has-vsys-adc-channel"); + penv->is_a2xb_split_reg = + of_property_read_bool(node, "qcom,has-a2xb-split-reg"); + + if (of_property_read_u32(node, "qcom,wlan-rx-buff-count", + &penv->wlan_rx_buff_count)) { + penv->wlan_rx_buff_count = WCNSS_DEF_WLAN_RX_BUFF_COUNT; + } + + rc = wcnss_parse_voltage_regulator(&penv->wlan_config, &pdev->dev); + if (rc) { + dev_err(&pdev->dev, "Failed to parse voltage regulators\n"); + goto fail; + } + + /* make sure we are only triggered once */ + if (penv->triggered) + return 0; + penv->triggered = 1; + + /* initialize the WCNSS device configuration */ + pdata = pdev->dev.platform_data; + if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) { + if (has_pronto_hw) { + has_48mhz_xo = + of_property_read_bool(node, "qcom,has-48mhz-xo"); + } else { + has_48mhz_xo = pdata->has_48mhz_xo; + } + } + penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW; + penv->wlan_config.use_48mhz_xo = has_48mhz_xo; + penv->wlan_config.is_pronto_vadc = is_pronto_vadc; + penv->wlan_config.is_pronto_v3 = is_pronto_v3; + + if (WCNSS_CONFIG_UNSPECIFIED == has_autodetect_xo && has_pronto_hw) { + has_autodetect_xo = + of_property_read_bool(node, "qcom,has-autodetect-xo"); + } + + penv->thermal_mitigation = 0; + strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN); + + /* Configure 5 wire GPIOs */ + if (!has_pronto_hw) { + penv->gpios_5wire = platform_get_resource_byname(pdev, + IORESOURCE_IO, "wcnss_gpios_5wire"); + + /* allocate 5-wire GPIO resources */ + if (!penv->gpios_5wire) { + dev_err(&pdev->dev, "insufficient IO resources\n"); + ret = -ENOENT; + goto fail_gpio_res; + } + ret = wcnss_gpios_config(penv->gpios_5wire, true); + } else + ret = wcnss_pronto_gpios_config(pdev, true); + + if (ret) { + dev_err(&pdev->dev, "WCNSS gpios config failed.\n"); + goto fail_gpio_res; + } + + /* allocate resources */ + penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "wcnss_mmio"); + penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "wcnss_wlantx_irq"); + penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "wcnss_wlanrx_irq"); + + if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) { + dev_err(&pdev->dev, "insufficient resources\n"); + ret = -ENOENT; + goto fail_res; + } + INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler); + INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req); + INIT_WORK(&penv->wcnss_pm_config_work, wcnss_send_pm_config); + INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main); + INIT_DELAYED_WORK(&penv->wcnss_pm_qos_del_req, wcnss_pm_qos_enable_pc); + + wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss"); + /* Add pm_qos request to disable power collapse for DDR */ + wcnss_disable_pc_add_req(); + + if (wcnss_hardware_type() == WCNSS_PRONTO_HW) { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "pronto_phy_base"); + if (!res) { + ret = -EIO; + pr_err("%s: resource pronto_phy_base failed\n", + __func__); + goto fail_ioremap; + } + penv->msm_wcnss_base = + devm_ioremap_resource(&pdev->dev, res); + } else { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "riva_phy_base"); + if (!res) { + ret = -EIO; + pr_err("%s: resource riva_phy_base failed\n", + __func__); + goto fail_ioremap; + } + penv->msm_wcnss_base = + devm_ioremap_resource(&pdev->dev, res); + } + + if (!penv->msm_wcnss_base) { + ret = -ENOMEM; + pr_err("%s: ioremap wcnss physical failed\n", __func__); + goto fail_ioremap; + } + + penv->wlan_config.msm_wcnss_base = penv->msm_wcnss_base; + if (wcnss_hardware_type() == WCNSS_RIVA_HW) { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "riva_ccu_base"); + if (!res) { + ret = -EIO; + pr_err("%s: resource riva_ccu_base failed\n", + __func__); + goto fail_ioremap; + } + penv->riva_ccu_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->riva_ccu_base) { + ret = -ENOMEM; + pr_err("%s: ioremap riva ccu physical failed\n", + __func__); + goto fail_ioremap; + } + } else { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "pronto_a2xb_base"); + if (!res) { + ret = -EIO; + pr_err("%s: resource pronto_a2xb_base failed\n", + __func__); + goto fail_ioremap; + } + penv->pronto_a2xb_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->pronto_a2xb_base) { + ret = -ENOMEM; + pr_err("%s: ioremap pronto a2xb physical failed\n", + __func__); + goto fail_ioremap; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "pronto_ccpu_base"); + if (!res) { + ret = -EIO; + pr_err("%s: resource pronto_ccpu_base failed\n", + __func__); + goto fail_ioremap; + } + penv->pronto_ccpu_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->pronto_ccpu_base) { + ret = -ENOMEM; + pr_err("%s: ioremap pronto ccpu physical failed\n", + __func__); + goto fail_ioremap; + } + + /* for reset FIQ */ + res = platform_get_resource_byname(penv->pdev, + IORESOURCE_MEM, "wcnss_fiq"); + if (!res) { + dev_err(&pdev->dev, "insufficient irq mem resources\n"); + ret = -ENOENT; + goto fail_ioremap; + } + penv->fiq_reg = ioremap_nocache(res->start, resource_size(res)); + if (!penv->fiq_reg) { + pr_err("wcnss: %s: ioremap_nocache() failed fiq_reg addr:%pr\n", + __func__, &res->start); + ret = -ENOMEM; + goto fail_ioremap; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "pronto_saw2_base"); + if (!res) { + ret = -EIO; + pr_err("%s: resource pronto_saw2_base failed\n", + __func__); + goto fail_ioremap2; + } + penv->pronto_saw2_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->pronto_saw2_base) { + pr_err("%s: ioremap wcnss physical(saw2) failed\n", + __func__); + ret = -ENOMEM; + goto fail_ioremap2; + } + + penv->pronto_pll_base = + penv->msm_wcnss_base + PRONTO_PLL_MODE_OFFSET; + if (!penv->pronto_pll_base) { + pr_err("%s: ioremap wcnss physical(pll) failed\n", + __func__); + ret = -ENOMEM; + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "wlan_tx_phy_aborts"); + if (!res) { + ret = -EIO; + pr_err("%s: resource wlan_tx_phy_aborts failed\n", + __func__); + goto fail_ioremap2; + } + penv->wlan_tx_phy_aborts = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->wlan_tx_phy_aborts) { + ret = -ENOMEM; + pr_err("%s: ioremap wlan TX PHY failed\n", __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "wlan_brdg_err_source"); + if (!res) { + ret = -EIO; + pr_err("%s: resource wlan_brdg_err_source failed\n", + __func__); + goto fail_ioremap2; + } + penv->wlan_brdg_err_source = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->wlan_brdg_err_source) { + ret = -ENOMEM; + pr_err("%s: ioremap wlan BRDG ERR failed\n", __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "wlan_tx_status"); + if (!res) { + ret = -EIO; + pr_err("%s: resource wlan_tx_status failed\n", + __func__); + goto fail_ioremap2; + } + penv->wlan_tx_status = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->wlan_tx_status) { + ret = -ENOMEM; + pr_err("%s: ioremap wlan TX STATUS failed\n", __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "alarms_txctl"); + if (!res) { + ret = -EIO; + pr_err("%s: resource alarms_txctl failed\n", + __func__); + goto fail_ioremap2; + } + penv->alarms_txctl = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->alarms_txctl) { + ret = -ENOMEM; + pr_err("%s: ioremap alarms TXCTL failed\n", __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "alarms_tactl"); + if (!res) { + ret = -EIO; + pr_err("%s: resource alarms_tactl failed\n", + __func__); + goto fail_ioremap2; + } + penv->alarms_tactl = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->alarms_tactl) { + ret = -ENOMEM; + pr_err("%s: ioremap alarms TACTL failed\n", __func__); + goto fail_ioremap2; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "pronto_mcu_base"); + if (!res) { + ret = -EIO; + pr_err("%s: resource pronto_mcu_base failed\n", + __func__); + goto fail_ioremap2; + } + penv->pronto_mcu_base = + devm_ioremap_resource(&pdev->dev, res); + + if (!penv->pronto_mcu_base) { + ret = -ENOMEM; + pr_err("%s: ioremap pronto mcu physical failed\n", + __func__); + goto fail_ioremap2; + } + + if (of_property_read_bool(node, + "qcom,is-dual-band-disabled")) { + ret = wcnss_get_dual_band_capability_info(pdev); + if (ret) { + pr_err( + "%s: failed to get dual band info\n", __func__); + goto fail_ioremap2; + } + } + } + + penv->adc_tm_dev = qpnp_get_adc_tm(&penv->pdev->dev, "wcnss"); + if (IS_ERR(penv->adc_tm_dev)) { + pr_err("%s: adc get failed\n", __func__); + penv->adc_tm_dev = NULL; + } else { + INIT_DELAYED_WORK(&penv->vbatt_work, wcnss_update_vbatt); + penv->fw_vbatt_state = WCNSS_CONFIG_UNSPECIFIED; + } + + penv->snoc_wcnss = devm_clk_get(&penv->pdev->dev, "snoc_wcnss"); + if (IS_ERR(penv->snoc_wcnss)) { + pr_err("%s: couldn't get snoc_wcnss\n", __func__); + penv->snoc_wcnss = NULL; + } else { + if (of_property_read_u32(pdev->dev.of_node, + "qcom,snoc-wcnss-clock-freq", + &penv->snoc_wcnss_clock_freq)) { + pr_debug("%s: wcnss snoc clock frequency is not defined\n", + __func__); + devm_clk_put(&penv->pdev->dev, penv->snoc_wcnss); + penv->snoc_wcnss = NULL; + } + } + + if (penv->wlan_config.is_pronto_vadc) { + penv->vadc_dev = qpnp_get_vadc(&penv->pdev->dev, "wcnss"); + + if (IS_ERR(penv->vadc_dev)) { + pr_debug("%s: vadc get failed\n", __func__); + penv->vadc_dev = NULL; + } else { + rc = wcnss_get_battery_volt(&penv->wlan_config.vbatt); + INIT_WORK(&penv->wcnss_vadc_work, + wcnss_send_vbatt_indication); + + if (rc < 0) + pr_err("Failed to get battery voltage with error= %d\n", + rc); + } + } + + do { + /* trigger initialization of the WCNSS */ + penv->pil = subsystem_get(WCNSS_PIL_DEVICE); + if (IS_ERR(penv->pil)) { + dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n"); + ret = PTR_ERR(penv->pil); + wcnss_disable_pc_add_req(); + wcnss_pronto_log_debug_regs(); + } + } while (pil_retry++ < WCNSS_MAX_PIL_RETRY && IS_ERR(penv->pil)); + + if (IS_ERR(penv->pil)) { + wcnss_reset_fiq(false); + if (penv->wcnss_notif_hdle) + subsys_notif_unregister_notifier(penv->wcnss_notif_hdle, + &wnb); + penv->pil = NULL; + goto fail_ioremap2; + } + /* Remove pm_qos request */ + wcnss_disable_pc_remove_req(); + + return 0; + +fail_ioremap2: + if (penv->fiq_reg) + iounmap(penv->fiq_reg); +fail_ioremap: + wake_lock_destroy(&penv->wcnss_wake_lock); +fail_res: + if (!has_pronto_hw) + wcnss_gpios_config(penv->gpios_5wire, false); + else if (penv->use_pinctrl) + wcnss_pinctrl_set_state(false); + else + wcnss_pronto_gpios_config(pdev, false); +fail_gpio_res: + wcnss_disable_pc_remove_req(); +fail: + if (penv->wcnss_notif_hdle) + subsys_notif_unregister_notifier(penv->wcnss_notif_hdle, &wnb); + penv = NULL; + return ret; +} + +/* Driver requires to directly vote the snoc clocks + * To enable and disable snoc clock, it call + * wcnss_snoc_vote function + */ +void wcnss_snoc_vote(bool clk_chk_en) +{ + int rc; + + if (!penv->snoc_wcnss) { + pr_err("%s: couldn't get clk snoc_wcnss\n", __func__); + return; + } + + if (clk_chk_en) { + rc = clk_set_rate(penv->snoc_wcnss, + penv->snoc_wcnss_clock_freq); + if (rc) { + pr_err("%s: snoc_wcnss_clk-clk_set_rate failed =%d\n", + __func__, rc); + return; + } + + if (clk_prepare_enable(penv->snoc_wcnss)) { + pr_err("%s: snoc_wcnss clk enable failed\n", __func__); + return; + } + } else { + clk_disable_unprepare(penv->snoc_wcnss); + } +} +EXPORT_SYMBOL(wcnss_snoc_vote); + +/* wlan prop driver cannot invoke cancel_work_sync + * function directly, so to invoke this function it + * call wcnss_flush_work function + */ +void wcnss_flush_work(struct work_struct *work) +{ + struct work_struct *cnss_work = work; + if (cnss_work != NULL) + cancel_work_sync(cnss_work); +} +EXPORT_SYMBOL(wcnss_flush_work); + +/* wlan prop driver cannot invoke show_stack + * function directly, so to invoke this function it + * call wcnss_dump_stack function + */ +void wcnss_dump_stack(struct task_struct *task) +{ + show_stack(task, NULL); +} +EXPORT_SYMBOL(wcnss_dump_stack); + +/* wlan prop driver cannot invoke cancel_delayed_work_sync + * function directly, so to invoke this function it call + * wcnss_flush_delayed_work function + */ +void wcnss_flush_delayed_work(struct delayed_work *dwork) +{ + struct delayed_work *cnss_dwork = dwork; + if (cnss_dwork != NULL) + cancel_delayed_work_sync(cnss_dwork); +} +EXPORT_SYMBOL(wcnss_flush_delayed_work); + +/* wlan prop driver cannot invoke INIT_WORK function + * directly, so to invoke this function call + * wcnss_init_work function. + */ +void wcnss_init_work(struct work_struct *work , void *callbackptr) +{ + if (work && callbackptr) + INIT_WORK(work, callbackptr); +} +EXPORT_SYMBOL(wcnss_init_work); + +/* wlan prop driver cannot invoke INIT_DELAYED_WORK + * function directly, so to invoke this function + * call wcnss_init_delayed_work function. + */ +void wcnss_init_delayed_work(struct delayed_work *dwork , void *callbackptr) +{ + if (dwork && callbackptr) + INIT_DELAYED_WORK(dwork, callbackptr); +} +EXPORT_SYMBOL(wcnss_init_delayed_work); + +static int wcnss_node_open(struct inode *inode, struct file *file) +{ + struct platform_device *pdev; + int rc = 0; + + if (!penv) + return -EFAULT; + + if (!penv->triggered) { + pr_info(DEVICE " triggered by userspace\n"); + pdev = penv->pdev; + rc = wcnss_trigger_config(pdev); + if (rc) + return -EFAULT; + } + + return rc; +} + +static ssize_t wcnss_wlan_read(struct file *fp, char __user + *buffer, size_t count, loff_t *position) +{ + int rc = 0; + + if (!penv) + return -EFAULT; + + rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd + > penv->user_cal_read || penv->fw_cal_available); + + if (rc < 0) + return rc; + + mutex_lock(&penv->dev_lock); + + if (penv->fw_cal_available && penv->fw_cal_rcvd + == penv->user_cal_read) { + rc = 0; + goto exit; + } + + if (count > penv->fw_cal_rcvd - penv->user_cal_read) + count = penv->fw_cal_rcvd - penv->user_cal_read; + + rc = copy_to_user(buffer, penv->fw_cal_data + + penv->user_cal_read, count); + if (rc == 0) { + penv->user_cal_read += count; + rc = count; + } + +exit: + mutex_unlock(&penv->dev_lock); + return rc; +} + +/* first (valid) write to this device should be 4 bytes cal file size */ +static ssize_t wcnss_wlan_write(struct file *fp, const char __user + *user_buffer, size_t count, loff_t *position) +{ + int rc = 0; + char *cal_data = NULL; + + if (!penv || penv->user_cal_available) + return -EFAULT; + + if (!penv->user_cal_rcvd && count >= 4 && !penv->user_cal_exp_size) { + mutex_lock(&penv->dev_lock); + rc = copy_from_user((void *)&penv->user_cal_exp_size, + user_buffer, 4); + if (!penv->user_cal_exp_size || + penv->user_cal_exp_size > MAX_CALIBRATED_DATA_SIZE) { + pr_err(DEVICE " invalid size to write %d\n", + penv->user_cal_exp_size); + penv->user_cal_exp_size = 0; + mutex_unlock(&penv->dev_lock); + return -EFAULT; + } + mutex_unlock(&penv->dev_lock); + return count; + } else if (!penv->user_cal_rcvd && count < 4) { + return -EFAULT; + } + + mutex_lock(&penv->dev_lock); + if ((UINT32_MAX - count < penv->user_cal_rcvd) || + (penv->user_cal_exp_size < count + penv->user_cal_rcvd)) { + pr_err(DEVICE " invalid size to write %zu\n", count + + penv->user_cal_rcvd); + mutex_unlock(&penv->dev_lock); + return -ENOMEM; + } + + cal_data = kmalloc(count, GFP_KERNEL); + if (!cal_data) { + mutex_unlock(&penv->dev_lock); + return -ENOMEM; + } + + rc = copy_from_user(cal_data, user_buffer, count); + if (!rc) { + memcpy(penv->user_cal_data + penv->user_cal_rcvd, + cal_data, count); + penv->user_cal_rcvd += count; + rc += count; + } + + kfree(cal_data); + if (penv->user_cal_rcvd == penv->user_cal_exp_size) { + penv->user_cal_available = true; + pr_info_ratelimited("wcnss: user cal written"); + } + mutex_unlock(&penv->dev_lock); + + return rc; +} + +static int wcnss_node_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int wcnss_notif_cb(struct notifier_block *this, unsigned long code, + void *ss_handle) +{ + struct platform_device *pdev = wcnss_get_platform_device(); + struct wcnss_wlan_config *pwlanconfig = wcnss_get_wlan_config(); + struct notif_data *data = (struct notif_data *)ss_handle; + int ret, xo_mode; + + if (!(code >= SUBSYS_NOTIF_MIN_INDEX) && + (code <= SUBSYS_NOTIF_MAX_INDEX)) { + pr_debug("%s: Invaild subsystem notification code: %lu\n", + __func__, code); + return NOTIFY_DONE; + } + + pr_info("%s: wcnss notification event: %lu : %s\n", + __func__, code, wcnss_subsys_notif_type[code]); + + if (code == SUBSYS_PROXY_VOTE) { + if (pdev && pwlanconfig) { + ret = wcnss_wlan_power(&pdev->dev, pwlanconfig, + WCNSS_WLAN_SWITCH_ON, &xo_mode); + wcnss_set_iris_xo_mode(xo_mode); + if (ret) + pr_err("Failed to execute wcnss_wlan_power\n"); + } + } else if (code == SUBSYS_PROXY_UNVOTE) { + if (pdev && pwlanconfig) { + /* Temporary workaround as some pronto images have an + * issue of sending an interrupt that it is capable of + * voting for it's resources too early. + */ + msleep(20); + wcnss_wlan_power(&pdev->dev, pwlanconfig, + WCNSS_WLAN_SWITCH_OFF, NULL); + } + } else if ((code == SUBSYS_BEFORE_SHUTDOWN && data && data->crashed) || + code == SUBSYS_SOC_RESET) { + wcnss_disable_pc_add_req(); + schedule_delayed_work(&penv->wcnss_pm_qos_del_req, + msecs_to_jiffies(WCNSS_PM_QOS_TIMEOUT)); + penv->is_shutdown = 1; + wcnss_log_debug_regs_on_bite(); + } else if (code == SUBSYS_POWERUP_FAILURE) { + if (pdev && pwlanconfig) + wcnss_wlan_power(&pdev->dev, pwlanconfig, + WCNSS_WLAN_SWITCH_OFF, NULL); + wcnss_pronto_log_debug_regs(); + wcnss_disable_pc_remove_req(); + } else if (SUBSYS_BEFORE_SHUTDOWN == code) { + wcnss_disable_pc_add_req(); + schedule_delayed_work(&penv->wcnss_pm_qos_del_req, + msecs_to_jiffies(WCNSS_PM_QOS_TIMEOUT)); + penv->is_shutdown = 1; + } else if (SUBSYS_AFTER_POWERUP == code) + penv->is_shutdown = 0; + + return NOTIFY_DONE; +} + +static const struct file_operations wcnss_node_fops = { + .owner = THIS_MODULE, + .open = wcnss_node_open, + .read = wcnss_wlan_read, + .write = wcnss_wlan_write, + .release = wcnss_node_release, +}; + +static struct miscdevice wcnss_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = DEVICE, + .fops = &wcnss_node_fops, +}; + +static int +wcnss_wlan_probe(struct platform_device *pdev) +{ + int ret = 0; + + /* verify we haven't been called more than once */ + if (penv) { + dev_err(&pdev->dev, "cannot handle multiple devices.\n"); + return -ENODEV; + } + + /* create an environment to track the device */ + penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL); + if (!penv) { + dev_err(&pdev->dev, "cannot allocate device memory.\n"); + return -ENOMEM; + } + penv->pdev = pdev; + + penv->user_cal_data = + devm_kzalloc(&pdev->dev, MAX_CALIBRATED_DATA_SIZE, GFP_KERNEL); + if (!penv->user_cal_data) { + dev_err(&pdev->dev, "Failed to alloc memory for cal data.\n"); + return -ENOMEM; + } + + /* register sysfs entries */ + ret = wcnss_create_sysfs(&pdev->dev); + if (ret) { + penv = NULL; + return -ENOENT; + } + + /* register wcnss event notification */ + penv->wcnss_notif_hdle = subsys_notif_register_notifier("wcnss", &wnb); + if (IS_ERR(penv->wcnss_notif_hdle)) { + pr_err("wcnss: register event notification failed!\n"); + return PTR_ERR(penv->wcnss_notif_hdle); + } + + mutex_init(&penv->dev_lock); + mutex_init(&penv->ctrl_lock); + mutex_init(&penv->vbat_monitor_mutex); + mutex_init(&penv->pm_qos_mutex); + init_waitqueue_head(&penv->read_wait); + + penv->user_cal_rcvd = 0; + penv->user_cal_read = 0; + penv->user_cal_exp_size = 0; + penv->user_cal_available = false; + + /* Since we were built into the kernel we'll be called as part + * of kernel initialization. We don't know if userspace + * applications are available to service PIL at this time + * (they probably are not), so we simply create a device node + * here. When userspace is available it should touch the + * device so that we know that WCNSS configuration can take + * place + */ + pr_info(DEVICE " probed in built-in mode\n"); + + misc_register(&wcnss_usr_ctrl); + + return misc_register(&wcnss_misc); + +} + +static int +wcnss_wlan_remove(struct platform_device *pdev) +{ + if (penv->wcnss_notif_hdle) + subsys_notif_unregister_notifier(penv->wcnss_notif_hdle, &wnb); + wcnss_remove_sysfs(&pdev->dev); + penv = NULL; + return 0; +} + + +static const struct dev_pm_ops wcnss_wlan_pm_ops = { + .suspend = wcnss_wlan_suspend, + .resume = wcnss_wlan_resume, +}; + +#ifdef CONFIG_WCNSS_CORE_PRONTO +static struct of_device_id msm_wcnss_pronto_match[] = { + {.compatible = "qcom,wcnss_wlan"}, + {} +}; +#endif + +static struct platform_driver wcnss_wlan_driver = { + .driver = { + .name = DEVICE, + .owner = THIS_MODULE, + .pm = &wcnss_wlan_pm_ops, +#ifdef CONFIG_WCNSS_CORE_PRONTO + .of_match_table = msm_wcnss_pronto_match, +#endif + }, + .probe = wcnss_wlan_probe, + .remove = wcnss_wlan_remove, +}; + +static int __init wcnss_wlan_init(void) +{ + platform_driver_register(&wcnss_wlan_driver); + platform_driver_register(&wcnss_wlan_ctrl_driver); + platform_driver_register(&wcnss_ctrl_driver); + register_pm_notifier(&wcnss_pm_notifier); + + return 0; +} + +static void __exit wcnss_wlan_exit(void) +{ + if (penv) { + if (penv->pil) + subsystem_put(penv->pil); + penv = NULL; + } + + unregister_pm_notifier(&wcnss_pm_notifier); + platform_driver_unregister(&wcnss_ctrl_driver); + platform_driver_unregister(&wcnss_wlan_ctrl_driver); + platform_driver_unregister(&wcnss_wlan_driver); +} + +module_init(wcnss_wlan_init); +module_exit(wcnss_wlan_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(VERSION); +MODULE_DESCRIPTION(DEVICE "Driver"); |