You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
442 lines
11 KiB
442 lines
11 KiB
/* Copyright (c) 2020, 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/mod_devicetable.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/spinlock.h>
|
|
#include <net/sock.h>
|
|
#include <uapi/linux/sched/types.h>
|
|
|
|
#include <soc/qcom/qrtr_ethernet.h>
|
|
#include "qrtr.h"
|
|
|
|
struct qrtr_ethernet_dl_buf {
|
|
void *buf;
|
|
struct mutex buf_lock; /* lock to protect buf */
|
|
size_t saved;
|
|
size_t needed;
|
|
size_t pkt_len;
|
|
};
|
|
|
|
struct qrtr_ethernet_dev {
|
|
struct qrtr_endpoint ep;
|
|
struct device *dev;
|
|
spinlock_t ul_lock; /* lock to protect ul_pkts */
|
|
struct list_head ul_pkts;
|
|
atomic_t in_reset;
|
|
u32 net_id;
|
|
bool rt;
|
|
struct qrtr_ethernet_cb_info *cb_info;
|
|
|
|
struct kthread_worker kworker;
|
|
struct task_struct *task;
|
|
struct kthread_work send_data;
|
|
|
|
struct qrtr_ethernet_dl_buf dlbuf;
|
|
};
|
|
|
|
struct qrtr_ethernet_pkt {
|
|
struct list_head node;
|
|
struct sk_buff *skb;
|
|
struct kref refcount;
|
|
};
|
|
|
|
/* Buffer to parse packets from ethernet adaption layer to qrtr */
|
|
#define MAX_BUFSIZE SZ_64K
|
|
|
|
struct qrtr_ethernet_dev *qrtr_ethernet_device_endpoint;
|
|
|
|
static void qrtr_ethernet_link_up(void)
|
|
{
|
|
struct qrtr_ethernet_dev *qdev = qrtr_ethernet_device_endpoint;
|
|
int rc;
|
|
|
|
if (!qdev) {
|
|
pr_err("%s: qrtr ep dev ptr not found\n", __func__);
|
|
return;
|
|
}
|
|
|
|
atomic_set(&qdev->in_reset, 0);
|
|
|
|
mutex_lock(&qdev->dlbuf.buf_lock);
|
|
memset(qdev->dlbuf.buf, 0, MAX_BUFSIZE);
|
|
qdev->dlbuf.saved = 0;
|
|
qdev->dlbuf.needed = 0;
|
|
qdev->dlbuf.pkt_len = 0;
|
|
mutex_unlock(&qdev->dlbuf.buf_lock);
|
|
|
|
rc = qrtr_endpoint_register(&qdev->ep, qdev->net_id, qdev->rt);
|
|
if (rc) {
|
|
dev_err(qdev->dev, "%s: EP register fail: %d\n", __func__, rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void qrtr_ethernet_link_down(void)
|
|
{
|
|
struct qrtr_ethernet_dev *qdev = qrtr_ethernet_device_endpoint;
|
|
|
|
atomic_inc(&qdev->in_reset);
|
|
|
|
kthread_flush_work(&qdev->send_data);
|
|
|
|
mutex_lock(&qdev->dlbuf.buf_lock);
|
|
memset(qdev->dlbuf.buf, 0, MAX_BUFSIZE);
|
|
qdev->dlbuf.saved = 0;
|
|
qdev->dlbuf.needed = 0;
|
|
qdev->dlbuf.pkt_len = 0;
|
|
mutex_unlock(&qdev->dlbuf.buf_lock);
|
|
|
|
qrtr_endpoint_unregister(&qdev->ep);
|
|
}
|
|
|
|
/**
|
|
* qcom_ethernet_qrtr_status_cb() - Notify qrtr-ethernet of status changes
|
|
* @event: Event type
|
|
*
|
|
* NETDEV_UP is posted when ethernet link is setup and NETDEV_DOWN is posted
|
|
* when the ethernet link goes down by the transport layer.
|
|
*
|
|
* Return: None
|
|
*/
|
|
void qcom_ethernet_qrtr_status_cb(unsigned int event)
|
|
{
|
|
if (event == NETDEV_UP)
|
|
qrtr_ethernet_link_up();
|
|
else if (event == NETDEV_DOWN)
|
|
qrtr_ethernet_link_down();
|
|
else
|
|
pr_err("%s: Unknown state: %d\n", __func__, event);
|
|
}
|
|
EXPORT_SYMBOL(qcom_ethernet_qrtr_status_cb);
|
|
|
|
static size_t set_cp_size(size_t len)
|
|
{
|
|
return ((len > MAX_BUFSIZE) ? MAX_BUFSIZE : len);
|
|
}
|
|
|
|
/**
|
|
* qcom_ethernet_qrtr_dl_cb() - Post incoming stream to qrtr
|
|
* @eth_res: Pointer that holds the incoming data stream
|
|
*
|
|
* Transport layer posts the data from external AP to qrtr.
|
|
* This is then posted to the qrtr endpoint.
|
|
*
|
|
* Return: None
|
|
*/
|
|
void qcom_ethernet_qrtr_dl_cb(struct eth_adapt_result *eth_res)
|
|
{
|
|
struct qrtr_ethernet_dev *qdev = qrtr_ethernet_device_endpoint;
|
|
struct qrtr_ethernet_dl_buf *dlbuf;
|
|
size_t pkt_len, len;
|
|
void *src;
|
|
int rc;
|
|
|
|
if (!eth_res)
|
|
return;
|
|
|
|
if (!qdev) {
|
|
pr_err("%s: qrtr ep dev ptr not found\n", __func__);
|
|
return;
|
|
}
|
|
|
|
dlbuf = &qdev->dlbuf;
|
|
|
|
src = eth_res->buf_addr;
|
|
if (!src) {
|
|
dev_err(qdev->dev, "%s: Invalid input buffer\n", __func__);
|
|
return;
|
|
}
|
|
|
|
len = eth_res->bytes_xferd;
|
|
if (len > MAX_BUFSIZE) {
|
|
dev_err(qdev->dev, "%s: Pkt len, 0x%x > MAX_BUFSIZE\n",
|
|
__func__, len);
|
|
return;
|
|
}
|
|
|
|
if (atomic_read(&qdev->in_reset) > 0) {
|
|
dev_err(qdev->dev, "%s: link in reset\n", __func__);
|
|
return;
|
|
}
|
|
|
|
mutex_lock(&dlbuf->buf_lock);
|
|
while (len > 0) {
|
|
if (dlbuf->needed > 0) {
|
|
pkt_len = dlbuf->pkt_len;
|
|
if (len >= dlbuf->needed) {
|
|
dlbuf->needed = set_cp_size(dlbuf->needed);
|
|
memcpy((dlbuf->buf + dlbuf->saved),
|
|
src, dlbuf->needed);
|
|
rc = qrtr_endpoint_post(&qdev->ep, dlbuf->buf,
|
|
pkt_len);
|
|
if (rc == -EINVAL) {
|
|
dev_err(qdev->dev,
|
|
"Invalid qrtr packet\n");
|
|
goto exit;
|
|
}
|
|
memset(dlbuf->buf, 0, MAX_BUFSIZE);
|
|
len = len - dlbuf->needed;
|
|
src = src + dlbuf->needed;
|
|
dlbuf->needed = 0;
|
|
dlbuf->pkt_len = 0;
|
|
} else {
|
|
/* Partial packet */
|
|
len = set_cp_size(len);
|
|
memcpy(dlbuf->buf + dlbuf->saved, src, len);
|
|
dlbuf->saved = dlbuf->saved + len;
|
|
dlbuf->needed = dlbuf->needed - len;
|
|
break;
|
|
}
|
|
} else {
|
|
pkt_len = qrtr_peek_pkt_size(src);
|
|
if ((int)pkt_len < 0) {
|
|
dev_err(qdev->dev,
|
|
"Invalid pkt_len %zu\n", pkt_len);
|
|
break;
|
|
}
|
|
|
|
if ((int)pkt_len == 0) {
|
|
dlbuf->needed = 0;
|
|
dlbuf->pkt_len = 0;
|
|
break;
|
|
}
|
|
|
|
if (pkt_len > MAX_BUFSIZE) {
|
|
dev_err(qdev->dev,
|
|
"Unsupported pkt_len %zu\n", pkt_len);
|
|
break;
|
|
}
|
|
|
|
if (pkt_len > len) {
|
|
/* Partial packet */
|
|
dlbuf->needed = pkt_len - len;
|
|
dlbuf->pkt_len = pkt_len;
|
|
dlbuf->saved = len;
|
|
dlbuf->saved = set_cp_size(dlbuf->saved);
|
|
memcpy(dlbuf->buf, src, dlbuf->saved);
|
|
break;
|
|
}
|
|
pkt_len = set_cp_size(pkt_len);
|
|
memcpy(dlbuf->buf, src, pkt_len);
|
|
rc = qrtr_endpoint_post(&qdev->ep, dlbuf->buf, pkt_len);
|
|
if (rc == -EINVAL) {
|
|
dev_err(qdev->dev, "Invalid qrtr packet\n");
|
|
goto exit;
|
|
}
|
|
memset(dlbuf->buf, 0, MAX_BUFSIZE);
|
|
len = len - pkt_len;
|
|
src = src + pkt_len;
|
|
dlbuf->needed = 0;
|
|
dlbuf->pkt_len = 0;
|
|
}
|
|
}
|
|
exit:
|
|
mutex_unlock(&dlbuf->buf_lock);
|
|
}
|
|
EXPORT_SYMBOL(qcom_ethernet_qrtr_dl_cb);
|
|
|
|
static void qrtr_ethernet_pkt_release(struct kref *ref)
|
|
{
|
|
struct qrtr_ethernet_pkt *pkt = container_of(ref,
|
|
struct qrtr_ethernet_pkt,
|
|
refcount);
|
|
struct sock *sk = pkt->skb->sk;
|
|
|
|
consume_skb(pkt->skb);
|
|
if (sk)
|
|
sock_put(sk);
|
|
kfree(pkt);
|
|
}
|
|
|
|
static void eth_tx_data(struct kthread_work *work)
|
|
{
|
|
struct qrtr_ethernet_dev *qdev = container_of(work,
|
|
struct qrtr_ethernet_dev,
|
|
send_data);
|
|
struct qrtr_ethernet_pkt *pkt, *temp;
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
if (atomic_read(&qdev->in_reset) > 0) {
|
|
dev_err(qdev->dev, "%s: link in reset\n", __func__);
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&qdev->ul_lock, flags);
|
|
list_for_each_entry_safe(pkt, temp, &qdev->ul_pkts, node) {
|
|
/* unlock before calling eth_send as tcp_sendmsg could sleep */
|
|
list_del(&pkt->node);
|
|
spin_unlock_irqrestore(&qdev->ul_lock, flags);
|
|
|
|
rc = qdev->cb_info->eth_send(pkt->skb);
|
|
if (rc)
|
|
dev_err(qdev->dev, "%s: eth_send failed: %d\n",
|
|
__func__, rc);
|
|
|
|
spin_lock_irqsave(&qdev->ul_lock, flags);
|
|
kref_put(&pkt->refcount, qrtr_ethernet_pkt_release);
|
|
}
|
|
spin_unlock_irqrestore(&qdev->ul_lock, flags);
|
|
}
|
|
|
|
/* from qrtr to ethernet adaption layer */
|
|
static int qcom_ethernet_qrtr_send(struct qrtr_endpoint *ep,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct qrtr_ethernet_dev *qdev = container_of(ep,
|
|
struct qrtr_ethernet_dev,
|
|
ep);
|
|
struct qrtr_ethernet_pkt *pkt;
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
rc = skb_linearize(skb);
|
|
if (rc) {
|
|
kfree_skb(skb);
|
|
dev_err(qdev->dev, "%s: skb_linearize failed: %d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
pkt = kzalloc(sizeof(*pkt), GFP_ATOMIC);
|
|
if (!pkt) {
|
|
kfree_skb(skb);
|
|
dev_err(qdev->dev, "%s: kzalloc failed: %d\n", __func__, rc);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pkt->skb = skb;
|
|
|
|
kref_init(&pkt->refcount);
|
|
kref_get(&pkt->refcount);
|
|
|
|
spin_lock_irqsave(&qdev->ul_lock, flags);
|
|
list_add_tail(&pkt->node, &qdev->ul_pkts);
|
|
spin_unlock_irqrestore(&qdev->ul_lock, flags);
|
|
|
|
kthread_queue_work(&qdev->kworker, &qdev->send_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* qcom_ethernet_init_cb() - Pass callback pointer to qrtr-ethernet
|
|
* @cbinfo: qrtr_ethernet_cb_info pointer providing the callback
|
|
* function for outgoing packets.
|
|
*
|
|
* Pass in a pointer to be used by this module to communicate with
|
|
* eth-adaption layer. This needs to be called after the ethernet
|
|
* link is up.
|
|
*
|
|
* Return: None
|
|
*/
|
|
void qcom_ethernet_init_cb(struct qrtr_ethernet_cb_info *cbinfo)
|
|
{
|
|
struct qrtr_ethernet_dev *qdev = qrtr_ethernet_device_endpoint;
|
|
|
|
if (!qdev) {
|
|
pr_err("%s: qrtr ep dev ptr not found\n", __func__);
|
|
return;
|
|
}
|
|
|
|
qdev->cb_info = cbinfo;
|
|
|
|
qrtr_ethernet_link_up();
|
|
}
|
|
EXPORT_SYMBOL(qcom_ethernet_init_cb);
|
|
|
|
static int qcom_ethernet_qrtr_probe(struct platform_device *pdev)
|
|
{
|
|
struct sched_param param = {.sched_priority = 1};
|
|
struct device_node *node = pdev->dev.of_node;
|
|
struct qrtr_ethernet_dev *qdev;
|
|
int rc;
|
|
|
|
qdev = devm_kzalloc(&pdev->dev, sizeof(*qdev), GFP_KERNEL);
|
|
if (!qdev)
|
|
return -ENOMEM;
|
|
|
|
qdev->dlbuf.buf = devm_kzalloc(&pdev->dev, MAX_BUFSIZE, GFP_KERNEL);
|
|
if (!qdev->dlbuf.buf)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&qdev->dlbuf.buf_lock);
|
|
qdev->dlbuf.saved = 0;
|
|
qdev->dlbuf.needed = 0;
|
|
qdev->dlbuf.pkt_len = 0;
|
|
|
|
qdev->dev = &pdev->dev;
|
|
dev_set_drvdata(&pdev->dev, qdev);
|
|
|
|
qdev->ep.xmit = qcom_ethernet_qrtr_send;
|
|
atomic_set(&qdev->in_reset, 0);
|
|
|
|
INIT_LIST_HEAD(&qdev->ul_pkts);
|
|
spin_lock_init(&qdev->ul_lock);
|
|
|
|
rc = of_property_read_u32(node, "qcom,net-id", &qdev->net_id);
|
|
if (rc < 0)
|
|
qdev->net_id = QRTR_EP_NET_ID_AUTO;
|
|
|
|
qdev->rt = of_property_read_bool(node, "qcom,low-latency");
|
|
|
|
kthread_init_work(&qdev->send_data, eth_tx_data);
|
|
kthread_init_worker(&qdev->kworker);
|
|
qdev->task = kthread_run(kthread_worker_fn, &qdev->kworker, "eth_tx");
|
|
if (IS_ERR(qdev->task)) {
|
|
dev_err(qdev->dev, "%s: Error starting eth_tx\n", __func__);
|
|
kfree(qdev);
|
|
return PTR_ERR(qdev->task);
|
|
}
|
|
|
|
if (qdev->rt)
|
|
sched_setscheduler(qdev->task, SCHED_FIFO, ¶m);
|
|
|
|
qrtr_ethernet_device_endpoint = qdev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qcom_ethernet_qrtr_remove(struct platform_device *pdev)
|
|
{
|
|
struct qrtr_ethernet_dev *qdev = dev_get_drvdata(&pdev->dev);
|
|
|
|
kthread_cancel_work_sync(&qdev->send_data);
|
|
|
|
dev_set_drvdata(&pdev->dev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id qcom_qrtr_ethernet_match[] = {
|
|
{ .compatible = "qcom,qrtr-ethernet-dev" },
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver qrtr_ethernet_dev_driver = {
|
|
.probe = qcom_ethernet_qrtr_probe,
|
|
.remove = qcom_ethernet_qrtr_remove,
|
|
.driver = {
|
|
.name = "qcom_ethernet_qrtr",
|
|
.of_match_table = qcom_qrtr_ethernet_match,
|
|
},
|
|
};
|
|
module_platform_driver(qrtr_ethernet_dev_driver);
|
|
|
|
MODULE_DESCRIPTION("QTI IPC-Router Ethernet interface driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
|