Runtime power management support breaks Intel LTE modem where dmesg dump showes timeout errors: ``` [ 72.027442] iosm 0000:01:00.0: msg timeout [ 72.531638] iosm 0000:01:00.0: msg timeout [ 73.035414] iosm 0000:01:00.0: msg timeout [ 73.540359] iosm 0000:01:00.0: msg timeout ``` Furthermore, when shutting down with `poweroff` and modem attached, the system rebooted instead of powering down as expected. The modem works again only after power cycling. Revert runtime power management support for IOSM driver as introduced by commite4f5073d53
("net: wwan: iosm: enable runtime pm support for 7560"). Fixes:e4f5073d53
("net: wwan: iosm: enable runtime pm support for 7560") Reported-by: Martin <mwolf@adiumentum.com> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=217996 Link: https://lore.kernel.org/r/267abf02-4b60-4a2e-92cd-709e3da6f7d3@gmail.com/ Signed-off-by: Bagas Sanjaya <bagasdotme@gmail.com> Reviewed-by: Loic Poulain <loic.poulain@linaro.org> Signed-off-by: David S. Miller <davem@davemloft.net>
316 lines
7.4 KiB
C
316 lines
7.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2020-21 Intel Corporation.
|
|
*/
|
|
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/if_link.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/wwan.h>
|
|
#include <net/pkt_sched.h>
|
|
|
|
#include "iosm_ipc_chnl_cfg.h"
|
|
#include "iosm_ipc_imem_ops.h"
|
|
#include "iosm_ipc_wwan.h"
|
|
|
|
#define IOSM_IP_TYPE_MASK 0xF0
|
|
#define IOSM_IP_TYPE_IPV4 0x40
|
|
#define IOSM_IP_TYPE_IPV6 0x60
|
|
|
|
/**
|
|
* struct iosm_netdev_priv - netdev WWAN driver specific private data
|
|
* @ipc_wwan: Pointer to iosm_wwan struct
|
|
* @netdev: Pointer to network interface device structure
|
|
* @if_id: Interface id for device.
|
|
* @ch_id: IPC channel number for which interface device is created.
|
|
*/
|
|
struct iosm_netdev_priv {
|
|
struct iosm_wwan *ipc_wwan;
|
|
struct net_device *netdev;
|
|
int if_id;
|
|
int ch_id;
|
|
};
|
|
|
|
/**
|
|
* struct iosm_wwan - This structure contains information about WWAN root device
|
|
* and interface to the IPC layer.
|
|
* @ipc_imem: Pointer to imem data-struct
|
|
* @sub_netlist: List of active netdevs
|
|
* @dev: Pointer device structure
|
|
*/
|
|
struct iosm_wwan {
|
|
struct iosm_imem *ipc_imem;
|
|
struct iosm_netdev_priv __rcu *sub_netlist[IP_MUX_SESSION_END + 1];
|
|
struct device *dev;
|
|
};
|
|
|
|
/* Bring-up the wwan net link */
|
|
static int ipc_wwan_link_open(struct net_device *netdev)
|
|
{
|
|
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
|
|
struct iosm_wwan *ipc_wwan = priv->ipc_wwan;
|
|
int if_id = priv->if_id;
|
|
|
|
if (if_id < IP_MUX_SESSION_START ||
|
|
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))
|
|
return -EINVAL;
|
|
|
|
/* get channel id */
|
|
priv->ch_id = ipc_imem_sys_wwan_open(ipc_wwan->ipc_imem, if_id);
|
|
|
|
if (priv->ch_id < 0) {
|
|
dev_err(ipc_wwan->dev,
|
|
"cannot connect wwan0 & id %d to the IPC mem layer",
|
|
if_id);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* enable tx path, DL data may follow */
|
|
netif_start_queue(netdev);
|
|
|
|
dev_dbg(ipc_wwan->dev, "Channel id %d allocated to if_id %d",
|
|
priv->ch_id, priv->if_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Bring-down the wwan net link */
|
|
static int ipc_wwan_link_stop(struct net_device *netdev)
|
|
{
|
|
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
|
|
|
|
netif_stop_queue(netdev);
|
|
|
|
ipc_imem_sys_wwan_close(priv->ipc_wwan->ipc_imem, priv->if_id,
|
|
priv->ch_id);
|
|
priv->ch_id = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Transmit a packet */
|
|
static netdev_tx_t ipc_wwan_link_transmit(struct sk_buff *skb,
|
|
struct net_device *netdev)
|
|
{
|
|
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev);
|
|
struct iosm_wwan *ipc_wwan = priv->ipc_wwan;
|
|
unsigned int len = skb->len;
|
|
int if_id = priv->if_id;
|
|
int ret;
|
|
|
|
/* Interface IDs from 1 to 8 are for IP data
|
|
* & from 257 to 261 are for non-IP data
|
|
*/
|
|
if (if_id < IP_MUX_SESSION_START ||
|
|
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))
|
|
return -EINVAL;
|
|
|
|
/* Send the SKB to device for transmission */
|
|
ret = ipc_imem_sys_wwan_transmit(ipc_wwan->ipc_imem,
|
|
if_id, priv->ch_id, skb);
|
|
|
|
/* Return code of zero is success */
|
|
if (ret == 0) {
|
|
netdev->stats.tx_packets++;
|
|
netdev->stats.tx_bytes += len;
|
|
ret = NETDEV_TX_OK;
|
|
} else if (ret == -EBUSY) {
|
|
ret = NETDEV_TX_BUSY;
|
|
dev_err(ipc_wwan->dev, "unable to push packets");
|
|
} else {
|
|
goto exit;
|
|
}
|
|
|
|
return ret;
|
|
|
|
exit:
|
|
/* Log any skb drop */
|
|
if (if_id)
|
|
dev_dbg(ipc_wwan->dev, "skb dropped. IF_ID: %d, ret: %d", if_id,
|
|
ret);
|
|
|
|
dev_kfree_skb_any(skb);
|
|
netdev->stats.tx_dropped++;
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
/* Ops structure for wwan net link */
|
|
static const struct net_device_ops ipc_inm_ops = {
|
|
.ndo_open = ipc_wwan_link_open,
|
|
.ndo_stop = ipc_wwan_link_stop,
|
|
.ndo_start_xmit = ipc_wwan_link_transmit,
|
|
};
|
|
|
|
/* Setup function for creating new net link */
|
|
static void ipc_wwan_setup(struct net_device *iosm_dev)
|
|
{
|
|
iosm_dev->header_ops = NULL;
|
|
iosm_dev->hard_header_len = 0;
|
|
iosm_dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
|
|
|
|
iosm_dev->type = ARPHRD_NONE;
|
|
iosm_dev->mtu = ETH_DATA_LEN;
|
|
iosm_dev->min_mtu = ETH_MIN_MTU;
|
|
iosm_dev->max_mtu = ETH_MAX_MTU;
|
|
|
|
iosm_dev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
|
iosm_dev->needs_free_netdev = true;
|
|
|
|
iosm_dev->netdev_ops = &ipc_inm_ops;
|
|
}
|
|
|
|
/* Create new wwan net link */
|
|
static int ipc_wwan_newlink(void *ctxt, struct net_device *dev,
|
|
u32 if_id, struct netlink_ext_ack *extack)
|
|
{
|
|
struct iosm_wwan *ipc_wwan = ctxt;
|
|
struct iosm_netdev_priv *priv;
|
|
int err;
|
|
|
|
if (if_id < IP_MUX_SESSION_START ||
|
|
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))
|
|
return -EINVAL;
|
|
|
|
priv = wwan_netdev_drvpriv(dev);
|
|
priv->if_id = if_id;
|
|
priv->netdev = dev;
|
|
priv->ipc_wwan = ipc_wwan;
|
|
|
|
if (rcu_access_pointer(ipc_wwan->sub_netlist[if_id]))
|
|
return -EBUSY;
|
|
|
|
err = register_netdevice(dev);
|
|
if (err)
|
|
return err;
|
|
|
|
rcu_assign_pointer(ipc_wwan->sub_netlist[if_id], priv);
|
|
netif_device_attach(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ipc_wwan_dellink(void *ctxt, struct net_device *dev,
|
|
struct list_head *head)
|
|
{
|
|
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(dev);
|
|
struct iosm_wwan *ipc_wwan = ctxt;
|
|
int if_id = priv->if_id;
|
|
|
|
if (WARN_ON(if_id < IP_MUX_SESSION_START ||
|
|
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)))
|
|
return;
|
|
|
|
if (WARN_ON(rcu_access_pointer(ipc_wwan->sub_netlist[if_id]) != priv))
|
|
return;
|
|
|
|
RCU_INIT_POINTER(ipc_wwan->sub_netlist[if_id], NULL);
|
|
/* unregistering includes synchronize_net() */
|
|
unregister_netdevice_queue(dev, head);
|
|
}
|
|
|
|
static const struct wwan_ops iosm_wwan_ops = {
|
|
.priv_size = sizeof(struct iosm_netdev_priv),
|
|
.setup = ipc_wwan_setup,
|
|
.newlink = ipc_wwan_newlink,
|
|
.dellink = ipc_wwan_dellink,
|
|
};
|
|
|
|
int ipc_wwan_receive(struct iosm_wwan *ipc_wwan, struct sk_buff *skb_arg,
|
|
bool dss, int if_id)
|
|
{
|
|
struct sk_buff *skb = skb_arg;
|
|
struct net_device_stats *stats;
|
|
struct iosm_netdev_priv *priv;
|
|
int ret;
|
|
|
|
if ((skb->data[0] & IOSM_IP_TYPE_MASK) == IOSM_IP_TYPE_IPV4)
|
|
skb->protocol = htons(ETH_P_IP);
|
|
else if ((skb->data[0] & IOSM_IP_TYPE_MASK) ==
|
|
IOSM_IP_TYPE_IPV6)
|
|
skb->protocol = htons(ETH_P_IPV6);
|
|
|
|
skb->pkt_type = PACKET_HOST;
|
|
|
|
if (if_id < IP_MUX_SESSION_START ||
|
|
if_id > IP_MUX_SESSION_END) {
|
|
ret = -EINVAL;
|
|
goto free;
|
|
}
|
|
|
|
rcu_read_lock();
|
|
priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]);
|
|
if (!priv) {
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
skb->dev = priv->netdev;
|
|
stats = &priv->netdev->stats;
|
|
stats->rx_packets++;
|
|
stats->rx_bytes += skb->len;
|
|
|
|
ret = netif_rx(skb);
|
|
skb = NULL;
|
|
unlock:
|
|
rcu_read_unlock();
|
|
free:
|
|
dev_kfree_skb(skb);
|
|
return ret;
|
|
}
|
|
|
|
void ipc_wwan_tx_flowctrl(struct iosm_wwan *ipc_wwan, int if_id, bool on)
|
|
{
|
|
struct net_device *netdev;
|
|
struct iosm_netdev_priv *priv;
|
|
bool is_tx_blk;
|
|
|
|
rcu_read_lock();
|
|
priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]);
|
|
if (!priv) {
|
|
rcu_read_unlock();
|
|
return;
|
|
}
|
|
|
|
netdev = priv->netdev;
|
|
|
|
is_tx_blk = netif_queue_stopped(netdev);
|
|
|
|
if (on)
|
|
dev_dbg(ipc_wwan->dev, "session id[%d]: flowctrl enable",
|
|
if_id);
|
|
|
|
if (on && !is_tx_blk)
|
|
netif_stop_queue(netdev);
|
|
else if (!on && is_tx_blk)
|
|
netif_wake_queue(netdev);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev)
|
|
{
|
|
struct iosm_wwan *ipc_wwan;
|
|
|
|
ipc_wwan = kzalloc(sizeof(*ipc_wwan), GFP_KERNEL);
|
|
if (!ipc_wwan)
|
|
return NULL;
|
|
|
|
ipc_wwan->dev = dev;
|
|
ipc_wwan->ipc_imem = ipc_imem;
|
|
|
|
/* WWAN core will create a netdev for the default IP MUX channel */
|
|
if (wwan_register_ops(ipc_wwan->dev, &iosm_wwan_ops, ipc_wwan,
|
|
IP_MUX_SESSION_DEFAULT)) {
|
|
kfree(ipc_wwan);
|
|
return NULL;
|
|
}
|
|
|
|
return ipc_wwan;
|
|
}
|
|
|
|
void ipc_wwan_deinit(struct iosm_wwan *ipc_wwan)
|
|
{
|
|
/* This call will remove all child netdev(s) */
|
|
wwan_unregister_ops(ipc_wwan->dev);
|
|
|
|
kfree(ipc_wwan);
|
|
}
|