Merge branch 'RPMSG-WWAN-CTRL-driver'
Stephan Gerhold says: ==================== net: wwan: Add RPMSG WWAN CTRL driver This patch series adds a WWAN "control" driver for the remote processor messaging (rpmsg) subsystem. This subsystem allows communicating with an integrated modem DSP on many Qualcomm SoCs, e.g. MSM8916 or MSM8974. The driver is a fairly simple glue layer between WWAN and RPMSG and is mostly based on the existing mhi_wwan_ctrl.c and rpmsg_char.c. For more information, see commit message in PATCH 2/3. I already posted a RFC for this a while ago: https://lore.kernel.org/linux-arm-msm/YLfL9Q+4860uqS8f@gerhold.net/ and now I'm looking for some feedback for the actual changes. :) Changes in v3: - PATCH 2/3: Clarify commit message - PATCH 3/3: Fix build error for cdc-wdm.c, use extra tx_blocking() op instead v2: https://lore.kernel.org/netdev/20210618075243.42046-1-stephan@gerhold.net/ Changes in v2: Only in PATCH 3/3 - Fix EPOLLOUT being always set even if poll op is defined - Rename poll() op -> tx_poll() since it should be only used for TX v1: https://lore.kernel.org/netdev/20210615133229.213064-1-stephan@gerhold.net/ ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Коммит
4bea7207a8
|
@ -15587,6 +15587,13 @@ F: include/linux/rpmsg/
|
|||
F: include/uapi/linux/rpmsg.h
|
||||
F: samples/rpmsg/
|
||||
|
||||
REMOTE PROCESSOR MESSAGING (RPMSG) WWAN CONTROL DRIVER
|
||||
M: Stephan Gerhold <stephan@gerhold.net>
|
||||
L: netdev@vger.kernel.org
|
||||
L: linux-remoteproc@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/net/wwan/rpmsg_wwan_ctrl.c
|
||||
|
||||
RENESAS CLOCK DRIVERS
|
||||
M: Geert Uytterhoeven <geert+renesas@glider.be>
|
||||
L: linux-renesas-soc@vger.kernel.org
|
||||
|
|
|
@ -38,6 +38,24 @@ config MHI_WWAN_CTRL
|
|||
To compile this driver as a module, choose M here: the module will be
|
||||
called mhi_wwan_ctrl.
|
||||
|
||||
config RPMSG_WWAN_CTRL
|
||||
tristate "RPMSG WWAN control driver"
|
||||
depends on RPMSG
|
||||
help
|
||||
RPMSG WWAN CTRL allows modems available via RPMSG channels to expose
|
||||
different modem protocols/ports to userspace, including AT and QMI.
|
||||
These protocols can be accessed directly from userspace
|
||||
(e.g. AT commands) or via libraries/tools (e.g. libqmi, libqcdm...).
|
||||
|
||||
This is mainly used for modems integrated into many Qualcomm SoCs,
|
||||
e.g. for AT and QMI on Qualcomm MSM8916 or MSM8974. Note that many
|
||||
newer Qualcomm SoCs (e.g. SDM845) still provide an AT port through
|
||||
this driver but the QMI messages can only be sent through
|
||||
QRTR network sockets (CONFIG_QRTR).
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called rpmsg_wwan_ctrl.
|
||||
|
||||
config IOSM
|
||||
tristate "IOSM Driver for Intel M.2 WWAN Device"
|
||||
depends on INTEL_IOMMU
|
||||
|
|
|
@ -9,4 +9,5 @@ wwan-objs += wwan_core.o
|
|||
obj-$(CONFIG_WWAN_HWSIM) += wwan_hwsim.o
|
||||
|
||||
obj-$(CONFIG_MHI_WWAN_CTRL) += mhi_wwan_ctrl.o
|
||||
obj-$(CONFIG_RPMSG_WWAN_CTRL) += rpmsg_wwan_ctrl.o
|
||||
obj-$(CONFIG_IOSM) += iosm/
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2021, Stephan Gerhold <stephan@gerhold.net> */
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rpmsg.h>
|
||||
#include <linux/wwan.h>
|
||||
|
||||
struct rpmsg_wwan_dev {
|
||||
/* Lower level is a rpmsg dev, upper level is a wwan port */
|
||||
struct rpmsg_device *rpdev;
|
||||
struct wwan_port *wwan_port;
|
||||
struct rpmsg_endpoint *ept;
|
||||
};
|
||||
|
||||
static int rpmsg_wwan_ctrl_callback(struct rpmsg_device *rpdev,
|
||||
void *buf, int len, void *priv, u32 src)
|
||||
{
|
||||
struct rpmsg_wwan_dev *rpwwan = priv;
|
||||
struct sk_buff *skb;
|
||||
|
||||
skb = alloc_skb(len, GFP_ATOMIC);
|
||||
if (!skb)
|
||||
return -ENOMEM;
|
||||
|
||||
skb_put_data(skb, buf, len);
|
||||
wwan_port_rx(rpwwan->wwan_port, skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rpmsg_wwan_ctrl_start(struct wwan_port *port)
|
||||
{
|
||||
struct rpmsg_wwan_dev *rpwwan = wwan_port_get_drvdata(port);
|
||||
struct rpmsg_channel_info chinfo = {
|
||||
.src = rpwwan->rpdev->src,
|
||||
.dst = RPMSG_ADDR_ANY,
|
||||
};
|
||||
|
||||
strncpy(chinfo.name, rpwwan->rpdev->id.name, RPMSG_NAME_SIZE);
|
||||
rpwwan->ept = rpmsg_create_ept(rpwwan->rpdev, rpmsg_wwan_ctrl_callback,
|
||||
rpwwan, chinfo);
|
||||
if (!rpwwan->ept)
|
||||
return -EREMOTEIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rpmsg_wwan_ctrl_stop(struct wwan_port *port)
|
||||
{
|
||||
struct rpmsg_wwan_dev *rpwwan = wwan_port_get_drvdata(port);
|
||||
|
||||
rpmsg_destroy_ept(rpwwan->ept);
|
||||
rpwwan->ept = NULL;
|
||||
}
|
||||
|
||||
static int rpmsg_wwan_ctrl_tx(struct wwan_port *port, struct sk_buff *skb)
|
||||
{
|
||||
struct rpmsg_wwan_dev *rpwwan = wwan_port_get_drvdata(port);
|
||||
int ret;
|
||||
|
||||
ret = rpmsg_trysend(rpwwan->ept, skb->data, skb->len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
consume_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rpmsg_wwan_ctrl_tx_blocking(struct wwan_port *port, struct sk_buff *skb)
|
||||
{
|
||||
struct rpmsg_wwan_dev *rpwwan = wwan_port_get_drvdata(port);
|
||||
int ret;
|
||||
|
||||
ret = rpmsg_send(rpwwan->ept, skb->data, skb->len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
consume_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __poll_t rpmsg_wwan_ctrl_tx_poll(struct wwan_port *port,
|
||||
struct file *filp, poll_table *wait)
|
||||
{
|
||||
struct rpmsg_wwan_dev *rpwwan = wwan_port_get_drvdata(port);
|
||||
|
||||
return rpmsg_poll(rpwwan->ept, filp, wait);
|
||||
}
|
||||
|
||||
static const struct wwan_port_ops rpmsg_wwan_pops = {
|
||||
.start = rpmsg_wwan_ctrl_start,
|
||||
.stop = rpmsg_wwan_ctrl_stop,
|
||||
.tx = rpmsg_wwan_ctrl_tx,
|
||||
.tx_blocking = rpmsg_wwan_ctrl_tx_blocking,
|
||||
.tx_poll = rpmsg_wwan_ctrl_tx_poll,
|
||||
};
|
||||
|
||||
static struct device *rpmsg_wwan_find_parent(struct device *dev)
|
||||
{
|
||||
/* Select first platform device as parent for the WWAN ports.
|
||||
* On Qualcomm platforms this is usually the platform device that
|
||||
* represents the modem remote processor. This might need to be
|
||||
* adjusted when adding device IDs for other platforms.
|
||||
*/
|
||||
for (dev = dev->parent; dev; dev = dev->parent) {
|
||||
if (dev_is_platform(dev))
|
||||
return dev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int rpmsg_wwan_ctrl_probe(struct rpmsg_device *rpdev)
|
||||
{
|
||||
struct rpmsg_wwan_dev *rpwwan;
|
||||
struct wwan_port *port;
|
||||
struct device *parent;
|
||||
|
||||
parent = rpmsg_wwan_find_parent(&rpdev->dev);
|
||||
if (!parent)
|
||||
return -ENODEV;
|
||||
|
||||
rpwwan = devm_kzalloc(&rpdev->dev, sizeof(*rpwwan), GFP_KERNEL);
|
||||
if (!rpwwan)
|
||||
return -ENOMEM;
|
||||
|
||||
rpwwan->rpdev = rpdev;
|
||||
dev_set_drvdata(&rpdev->dev, rpwwan);
|
||||
|
||||
/* Register as a wwan port, id.driver_data contains wwan port type */
|
||||
port = wwan_create_port(parent, rpdev->id.driver_data,
|
||||
&rpmsg_wwan_pops, rpwwan);
|
||||
if (IS_ERR(port))
|
||||
return PTR_ERR(port);
|
||||
|
||||
rpwwan->wwan_port = port;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
static void rpmsg_wwan_ctrl_remove(struct rpmsg_device *rpdev)
|
||||
{
|
||||
struct rpmsg_wwan_dev *rpwwan = dev_get_drvdata(&rpdev->dev);
|
||||
|
||||
wwan_remove_port(rpwwan->wwan_port);
|
||||
}
|
||||
|
||||
static const struct rpmsg_device_id rpmsg_wwan_ctrl_id_table[] = {
|
||||
/* RPMSG channels for Qualcomm SoCs with integrated modem */
|
||||
{ .name = "DATA5_CNTL", .driver_data = WWAN_PORT_QMI },
|
||||
{ .name = "DATA4", .driver_data = WWAN_PORT_AT },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(rpmsg, rpmsg_wwan_ctrl_id_table);
|
||||
|
||||
static struct rpmsg_driver rpmsg_wwan_ctrl_driver = {
|
||||
.drv.name = "rpmsg_wwan_ctrl",
|
||||
.id_table = rpmsg_wwan_ctrl_id_table,
|
||||
.probe = rpmsg_wwan_ctrl_probe,
|
||||
.remove = rpmsg_wwan_ctrl_remove,
|
||||
};
|
||||
module_rpmsg_driver(rpmsg_wwan_ctrl_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("RPMSG WWAN CTRL Driver");
|
||||
MODULE_AUTHOR("Stephan Gerhold <stephan@gerhold.net>");
|
|
@ -500,7 +500,8 @@ static void wwan_port_op_stop(struct wwan_port *port)
|
|||
mutex_unlock(&port->ops_lock);
|
||||
}
|
||||
|
||||
static int wwan_port_op_tx(struct wwan_port *port, struct sk_buff *skb)
|
||||
static int wwan_port_op_tx(struct wwan_port *port, struct sk_buff *skb,
|
||||
bool nonblock)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -510,7 +511,10 @@ static int wwan_port_op_tx(struct wwan_port *port, struct sk_buff *skb)
|
|||
goto out_unlock;
|
||||
}
|
||||
|
||||
ret = port->ops->tx(port, skb);
|
||||
if (nonblock || !port->ops->tx_blocking)
|
||||
ret = port->ops->tx(port, skb);
|
||||
else
|
||||
ret = port->ops->tx_blocking(port, skb);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&port->ops_lock);
|
||||
|
@ -637,7 +641,7 @@ static ssize_t wwan_port_fops_write(struct file *filp, const char __user *buf,
|
|||
return -EFAULT;
|
||||
}
|
||||
|
||||
ret = wwan_port_op_tx(port, skb);
|
||||
ret = wwan_port_op_tx(port, skb, !!(filp->f_flags & O_NONBLOCK));
|
||||
if (ret) {
|
||||
kfree_skb(skb);
|
||||
return ret;
|
||||
|
@ -653,12 +657,16 @@ static __poll_t wwan_port_fops_poll(struct file *filp, poll_table *wait)
|
|||
|
||||
poll_wait(filp, &port->waitqueue, wait);
|
||||
|
||||
if (!is_write_blocked(port))
|
||||
mutex_lock(&port->ops_lock);
|
||||
if (port->ops && port->ops->tx_poll)
|
||||
mask |= port->ops->tx_poll(port, filp, wait);
|
||||
else if (!is_write_blocked(port))
|
||||
mask |= EPOLLOUT | EPOLLWRNORM;
|
||||
if (!is_read_blocked(port))
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
if (!port->ops)
|
||||
mask |= EPOLLHUP | EPOLLERR;
|
||||
mutex_unlock(&port->ops_lock);
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
|
|
@ -459,8 +459,10 @@ static int rpmsg_dev_match(struct device *dev, struct device_driver *drv)
|
|||
|
||||
if (ids)
|
||||
for (i = 0; ids[i].name[0]; i++)
|
||||
if (rpmsg_id_match(rpdev, &ids[i]))
|
||||
if (rpmsg_id_match(rpdev, &ids[i])) {
|
||||
rpdev->id.driver_data = ids[i].driver_data;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return of_driver_match_device(dev, drv);
|
||||
}
|
||||
|
|
|
@ -447,6 +447,7 @@ struct hv_vmbus_device_id {
|
|||
|
||||
struct rpmsg_device_id {
|
||||
char name[RPMSG_NAME_SIZE];
|
||||
kernel_ulong_t driver_data;
|
||||
};
|
||||
|
||||
/* i2c */
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/netlink.h>
|
||||
|
||||
|
@ -40,15 +41,23 @@ struct wwan_port;
|
|||
/** struct wwan_port_ops - The WWAN port operations
|
||||
* @start: The routine for starting the WWAN port device.
|
||||
* @stop: The routine for stopping the WWAN port device.
|
||||
* @tx: The routine that sends WWAN port protocol data to the device.
|
||||
* @tx: Non-blocking routine that sends WWAN port protocol data to the device.
|
||||
* @tx_blocking: Optional blocking routine that sends WWAN port protocol data
|
||||
* to the device.
|
||||
* @tx_poll: Optional routine that sets additional TX poll flags.
|
||||
*
|
||||
* The wwan_port_ops structure contains a list of low-level operations
|
||||
* that control a WWAN port device. All functions are mandatory.
|
||||
* that control a WWAN port device. All functions are mandatory unless specified.
|
||||
*/
|
||||
struct wwan_port_ops {
|
||||
int (*start)(struct wwan_port *port);
|
||||
void (*stop)(struct wwan_port *port);
|
||||
int (*tx)(struct wwan_port *port, struct sk_buff *skb);
|
||||
|
||||
/* Optional operations */
|
||||
int (*tx_blocking)(struct wwan_port *port, struct sk_buff *skb);
|
||||
__poll_t (*tx_poll)(struct wwan_port *port, struct file *filp,
|
||||
poll_table *wait);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче