274 lines
7.0 KiB
C
274 lines
7.0 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
/*
|
||
|
* Copyright (c) 2021, MediaTek Inc.
|
||
|
* Copyright (c) 2021-2022, Intel Corporation.
|
||
|
*
|
||
|
* Authors:
|
||
|
* Haijun Liu <haijun.liu@mediatek.com>
|
||
|
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
||
|
* Moises Veleta <moises.veleta@intel.com>
|
||
|
*
|
||
|
* Contributors:
|
||
|
* Amir Hanania <amir.hanania@intel.com>
|
||
|
* Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
|
||
|
* Eliot Lee <eliot.lee@intel.com>
|
||
|
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
||
|
*/
|
||
|
|
||
|
#include <linux/bitfield.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/netdevice.h>
|
||
|
#include <linux/skbuff.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
|
||
|
#include "t7xx_port.h"
|
||
|
#include "t7xx_port_proxy.h"
|
||
|
#include "t7xx_state_monitor.h"
|
||
|
|
||
|
#define PORT_MSG_VERSION GENMASK(31, 16)
|
||
|
#define PORT_MSG_PRT_CNT GENMASK(15, 0)
|
||
|
|
||
|
struct port_msg {
|
||
|
__le32 head_pattern;
|
||
|
__le32 info;
|
||
|
__le32 tail_pattern;
|
||
|
__le32 data[];
|
||
|
};
|
||
|
|
||
|
static int port_ctl_send_msg_to_md(struct t7xx_port *port, unsigned int msg, unsigned int ex_msg)
|
||
|
{
|
||
|
struct sk_buff *skb;
|
||
|
int ret;
|
||
|
|
||
|
skb = t7xx_ctrl_alloc_skb(0);
|
||
|
if (!skb)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
ret = t7xx_port_send_ctl_skb(port, skb, msg, ex_msg);
|
||
|
if (ret)
|
||
|
dev_kfree_skb_any(skb);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int fsm_ee_message_handler(struct t7xx_port *port, struct t7xx_fsm_ctl *ctl,
|
||
|
struct sk_buff *skb)
|
||
|
{
|
||
|
struct ctrl_msg_header *ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
|
||
|
struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
|
||
|
enum md_state md_state;
|
||
|
int ret = -EINVAL;
|
||
|
|
||
|
md_state = t7xx_fsm_get_md_state(ctl);
|
||
|
if (md_state != MD_STATE_EXCEPTION) {
|
||
|
dev_err(dev, "Receive invalid MD_EX %x when MD state is %d\n",
|
||
|
ctrl_msg_h->ex_msg, md_state);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
switch (le32_to_cpu(ctrl_msg_h->ctrl_msg_id)) {
|
||
|
case CTL_ID_MD_EX:
|
||
|
if (le32_to_cpu(ctrl_msg_h->ex_msg) != MD_EX_CHK_ID) {
|
||
|
dev_err(dev, "Receive invalid MD_EX %x\n", ctrl_msg_h->ex_msg);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ret = port_ctl_send_msg_to_md(port, CTL_ID_MD_EX, MD_EX_CHK_ID);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Failed to send exception message to modem\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ret = t7xx_fsm_append_event(ctl, FSM_EVENT_MD_EX, NULL, 0);
|
||
|
if (ret)
|
||
|
dev_err(dev, "Failed to append Modem Exception event");
|
||
|
|
||
|
break;
|
||
|
|
||
|
case CTL_ID_MD_EX_ACK:
|
||
|
if (le32_to_cpu(ctrl_msg_h->ex_msg) != MD_EX_CHK_ACK_ID) {
|
||
|
dev_err(dev, "Receive invalid MD_EX_ACK %x\n", ctrl_msg_h->ex_msg);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ret = t7xx_fsm_append_event(ctl, FSM_EVENT_MD_EX_REC_OK, NULL, 0);
|
||
|
if (ret)
|
||
|
dev_err(dev, "Failed to append Modem Exception Received event");
|
||
|
|
||
|
break;
|
||
|
|
||
|
case CTL_ID_MD_EX_PASS:
|
||
|
ret = t7xx_fsm_append_event(ctl, FSM_EVENT_MD_EX_PASS, NULL, 0);
|
||
|
if (ret)
|
||
|
dev_err(dev, "Failed to append Modem Exception Passed event");
|
||
|
|
||
|
break;
|
||
|
|
||
|
case CTL_ID_DRV_VER_ERROR:
|
||
|
dev_err(dev, "AP/MD driver version mismatch\n");
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* t7xx_port_enum_msg_handler() - Parse the port enumeration message to create/remove nodes.
|
||
|
* @md: Modem context.
|
||
|
* @msg: Message.
|
||
|
*
|
||
|
* Used to control create/remove device node.
|
||
|
*
|
||
|
* Return:
|
||
|
* * 0 - Success.
|
||
|
* * -EFAULT - Message check failure.
|
||
|
*/
|
||
|
int t7xx_port_enum_msg_handler(struct t7xx_modem *md, void *msg)
|
||
|
{
|
||
|
struct device *dev = &md->t7xx_dev->pdev->dev;
|
||
|
unsigned int version, port_count, i;
|
||
|
struct port_msg *port_msg = msg;
|
||
|
|
||
|
version = FIELD_GET(PORT_MSG_VERSION, le32_to_cpu(port_msg->info));
|
||
|
if (version != PORT_ENUM_VER ||
|
||
|
le32_to_cpu(port_msg->head_pattern) != PORT_ENUM_HEAD_PATTERN ||
|
||
|
le32_to_cpu(port_msg->tail_pattern) != PORT_ENUM_TAIL_PATTERN) {
|
||
|
dev_err(dev, "Invalid port control message %x:%x:%x\n",
|
||
|
version, le32_to_cpu(port_msg->head_pattern),
|
||
|
le32_to_cpu(port_msg->tail_pattern));
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
|
||
|
port_count = FIELD_GET(PORT_MSG_PRT_CNT, le32_to_cpu(port_msg->info));
|
||
|
for (i = 0; i < port_count; i++) {
|
||
|
u32 port_info = le32_to_cpu(port_msg->data[i]);
|
||
|
unsigned int ch_id;
|
||
|
bool en_flag;
|
||
|
|
||
|
ch_id = FIELD_GET(PORT_INFO_CH_ID, port_info);
|
||
|
en_flag = port_info & PORT_INFO_ENFLG;
|
||
|
if (t7xx_port_proxy_chl_enable_disable(md->port_prox, ch_id, en_flag))
|
||
|
dev_dbg(dev, "Port:%x not found\n", ch_id);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int control_msg_handler(struct t7xx_port *port, struct sk_buff *skb)
|
||
|
{
|
||
|
const struct t7xx_port_conf *port_conf = port->port_conf;
|
||
|
struct t7xx_fsm_ctl *ctl = port->t7xx_dev->md->fsm_ctl;
|
||
|
struct ctrl_msg_header *ctrl_msg_h;
|
||
|
int ret = 0;
|
||
|
|
||
|
ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
|
||
|
switch (le32_to_cpu(ctrl_msg_h->ctrl_msg_id)) {
|
||
|
case CTL_ID_HS2_MSG:
|
||
|
skb_pull(skb, sizeof(*ctrl_msg_h));
|
||
|
|
||
|
if (port_conf->rx_ch == PORT_CH_CONTROL_RX) {
|
||
|
ret = t7xx_fsm_append_event(ctl, FSM_EVENT_MD_HS2, skb->data,
|
||
|
le32_to_cpu(ctrl_msg_h->data_length));
|
||
|
if (ret)
|
||
|
dev_err(port->dev, "Failed to append Handshake 2 event");
|
||
|
}
|
||
|
|
||
|
dev_kfree_skb_any(skb);
|
||
|
break;
|
||
|
|
||
|
case CTL_ID_MD_EX:
|
||
|
case CTL_ID_MD_EX_ACK:
|
||
|
case CTL_ID_MD_EX_PASS:
|
||
|
case CTL_ID_DRV_VER_ERROR:
|
||
|
ret = fsm_ee_message_handler(port, ctl, skb);
|
||
|
dev_kfree_skb_any(skb);
|
||
|
break;
|
||
|
|
||
|
case CTL_ID_PORT_ENUM:
|
||
|
skb_pull(skb, sizeof(*ctrl_msg_h));
|
||
|
ret = t7xx_port_enum_msg_handler(ctl->md, (struct port_msg *)skb->data);
|
||
|
if (!ret)
|
||
|
ret = port_ctl_send_msg_to_md(port, CTL_ID_PORT_ENUM, 0);
|
||
|
else
|
||
|
ret = port_ctl_send_msg_to_md(port, CTL_ID_PORT_ENUM,
|
||
|
PORT_ENUM_VER_MISMATCH);
|
||
|
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
ret = -EINVAL;
|
||
|
dev_err(port->dev, "Unknown control message ID to FSM %x\n",
|
||
|
le32_to_cpu(ctrl_msg_h->ctrl_msg_id));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (ret)
|
||
|
dev_err(port->dev, "%s control message handle error: %d\n", port_conf->name, ret);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int port_ctl_rx_thread(void *arg)
|
||
|
{
|
||
|
while (!kthread_should_stop()) {
|
||
|
struct t7xx_port *port = arg;
|
||
|
struct sk_buff *skb;
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&port->rx_wq.lock, flags);
|
||
|
if (skb_queue_empty(&port->rx_skb_list) &&
|
||
|
wait_event_interruptible_locked_irq(port->rx_wq,
|
||
|
!skb_queue_empty(&port->rx_skb_list) ||
|
||
|
kthread_should_stop())) {
|
||
|
spin_unlock_irqrestore(&port->rx_wq.lock, flags);
|
||
|
continue;
|
||
|
}
|
||
|
if (kthread_should_stop()) {
|
||
|
spin_unlock_irqrestore(&port->rx_wq.lock, flags);
|
||
|
break;
|
||
|
}
|
||
|
skb = __skb_dequeue(&port->rx_skb_list);
|
||
|
spin_unlock_irqrestore(&port->rx_wq.lock, flags);
|
||
|
|
||
|
control_msg_handler(port, skb);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int port_ctl_init(struct t7xx_port *port)
|
||
|
{
|
||
|
const struct t7xx_port_conf *port_conf = port->port_conf;
|
||
|
|
||
|
port->thread = kthread_run(port_ctl_rx_thread, port, "%s", port_conf->name);
|
||
|
if (IS_ERR(port->thread)) {
|
||
|
dev_err(port->dev, "Failed to start port control thread\n");
|
||
|
return PTR_ERR(port->thread);
|
||
|
}
|
||
|
|
||
|
port->rx_length_th = CTRL_QUEUE_MAXLEN;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void port_ctl_uninit(struct t7xx_port *port)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
struct sk_buff *skb;
|
||
|
|
||
|
if (port->thread)
|
||
|
kthread_stop(port->thread);
|
||
|
|
||
|
spin_lock_irqsave(&port->rx_wq.lock, flags);
|
||
|
port->rx_length_th = 0;
|
||
|
while ((skb = __skb_dequeue(&port->rx_skb_list)) != NULL)
|
||
|
dev_kfree_skb_any(skb);
|
||
|
spin_unlock_irqrestore(&port->rx_wq.lock, flags);
|
||
|
}
|
||
|
|
||
|
struct port_ops ctl_port_ops = {
|
||
|
.init = port_ctl_init,
|
||
|
.recv_skb = t7xx_port_enqueue_skb,
|
||
|
.uninit = port_ctl_uninit,
|
||
|
};
|