60e099974d
Validate the incoming packet size when trying to process the message received on the transport. Change-Id: If7ea95180a2b04be4832e771013b9e4342118b4c Signed-off-by: Tao Zhang <quic_taozhan@quicinc.com>
890 lines
20 KiB
C
890 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
|
|
*/
|
|
#include <linux/gunyah/gh_msgq.h>
|
|
#include <linux/gunyah/gh_rm_drv.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/socket.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/types.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <net/sock.h>
|
|
#include <net/af_vsock.h>
|
|
|
|
/* auto-bind range */
|
|
#define GHVST_MIN_SOCKET 0x4000
|
|
#define GHVST_MAX_SOCKET 0x7fff
|
|
|
|
#define GHVST_PROTO_VER_1 1
|
|
|
|
#define MAX_PKT_SZ SZ_64K
|
|
|
|
/* Heuristic timeout to hold ws for user to read from socket */
|
|
#define GHVST_SKB_WAKEUP_MS 500
|
|
|
|
/* list of ghvst devices */
|
|
static LIST_HEAD(ghvst_devs);
|
|
/* lock for qrtr_all_epts */
|
|
static DECLARE_RWSEM(ghvst_devs_lock);
|
|
/* local port allocation management */
|
|
static DEFINE_IDR(ghvst_ports);
|
|
static DEFINE_SPINLOCK(ghvst_port_lock);
|
|
|
|
enum ghvst_pkt_type {
|
|
GHVST_TYPE_DATA = 1,
|
|
};
|
|
|
|
/* gh_transport_buf: gunyah transport buffer
|
|
* @buf: buffer saved
|
|
* @lock: lock for the buffer
|
|
* @len: hdrlen + packet size
|
|
* @copied: size of buffer copied
|
|
* @remaining: size needed to complete the pkt_len
|
|
* @hdr_received: true if the header is already saved, else false
|
|
*/
|
|
struct gh_transport_buf {
|
|
void *buf;
|
|
/* @lock: lock for the buffer */
|
|
struct mutex lock;
|
|
size_t len;
|
|
size_t copied;
|
|
size_t remaining;
|
|
bool hdr_received;
|
|
};
|
|
|
|
/* gh_transport_device: vm devices attached to this transport
|
|
* @dev: device from platform_device.
|
|
* @cid: local cid
|
|
* @peer_name: remote cid
|
|
* @master: primary vm indicator
|
|
* @msgq_label: msgq label
|
|
* @msgq_hdl: msgq handle
|
|
* @rm_nb: notifier block for vm status from rm
|
|
* @item: list item of all vm devices
|
|
* @tx_lock: tx lock to queue only one packet at a time
|
|
* @rx_thread: rx thread to receive incoming packets
|
|
*/
|
|
struct gh_transport_device {
|
|
struct device *dev;
|
|
unsigned int cid;
|
|
unsigned int peer_name;
|
|
bool master;
|
|
|
|
enum gh_msgq_label msgq_label;
|
|
void *msgq_hdl;
|
|
|
|
struct notifier_block rm_nb;
|
|
|
|
struct list_head item;
|
|
|
|
/* @tx_lock: tx lock to queue only one packet at a time */
|
|
struct mutex tx_lock;
|
|
|
|
struct task_struct *rx_thread;
|
|
struct gh_transport_buf gbuf;
|
|
};
|
|
|
|
/**
|
|
* struct ghvst_hdr - ghvst packet header
|
|
* @version: protocol version
|
|
* @type: packet type; one of ghvst_TYPE_*
|
|
* @flags: Reserved for future use
|
|
* @optlen: length of optional header data
|
|
* @size: length of packet, excluding this header and optlen
|
|
* @src_node_id: source cid, reserved
|
|
* @src_port_id: source port
|
|
* @dst_node_id: destination cid, reserved
|
|
* @dst_port_id: destination port
|
|
*/
|
|
struct ghvst_hdr {
|
|
u8 version;
|
|
u8 type;
|
|
u8 flags;
|
|
u8 optlen;
|
|
__le32 size;
|
|
__le32 src_rsvd;
|
|
__le32 src_port_id;
|
|
__le32 dst_rsvd;
|
|
__le32 dst_port_id;
|
|
};
|
|
|
|
struct ghvst_cb {
|
|
u32 src_node;
|
|
u32 src_port;
|
|
u32 dst_node;
|
|
u32 dst_port;
|
|
|
|
u8 type;
|
|
};
|
|
|
|
/* global wakeup source to hold for client's to read from socket */
|
|
static struct wakeup_source *sock_ws;
|
|
|
|
static int ghvst_socket_init(struct vsock_sock *vsk, struct vsock_sock *psk)
|
|
{
|
|
int rc;
|
|
|
|
down_read(&ghvst_devs_lock);
|
|
rc = list_empty(&ghvst_devs);
|
|
up_read(&ghvst_devs_lock);
|
|
if (rc) {
|
|
pr_err("%s: Transport not available\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
vsk->local_addr.svm_cid = VMADDR_CID_HOST;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ghvst_destruct(struct vsock_sock *vsk)
|
|
{
|
|
}
|
|
|
|
static void ghvst_port_remove(struct vsock_sock *vsk)
|
|
{
|
|
int port = vsk->local_addr.svm_port;
|
|
unsigned long flags;
|
|
|
|
sock_put(sk_vsock(vsk));
|
|
|
|
spin_lock_irqsave(&ghvst_port_lock, flags);
|
|
idr_remove(&ghvst_ports, port);
|
|
spin_unlock_irqrestore(&ghvst_port_lock, flags);
|
|
}
|
|
|
|
static void ghvst_release(struct vsock_sock *vsk)
|
|
{
|
|
struct sock *sk = sk_vsock(vsk);
|
|
|
|
if (!sk)
|
|
return;
|
|
|
|
if (!sock_flag(sk, SOCK_DEAD))
|
|
sk->sk_state_change(sk);
|
|
|
|
if (!sock_flag(sk, SOCK_ZAPPED))
|
|
ghvst_port_remove(vsk);
|
|
}
|
|
|
|
static int ghvst_port_assign(struct vsock_sock *vsk, int *port)
|
|
{
|
|
int rc;
|
|
|
|
if (!*port || *port < 0) {
|
|
rc = idr_alloc_cyclic(&ghvst_ports, vsk, GHVST_MIN_SOCKET,
|
|
GHVST_MAX_SOCKET + 1, GFP_ATOMIC);
|
|
if (rc >= 0)
|
|
*port = rc;
|
|
} else if (*port < GHVST_MIN_SOCKET && !capable(CAP_NET_ADMIN)) {
|
|
rc = -EACCES;
|
|
} else {
|
|
rc = idr_alloc_cyclic(&ghvst_ports, vsk, *port, *port + 1,
|
|
GFP_ATOMIC);
|
|
if (rc >= 0)
|
|
*port = rc;
|
|
}
|
|
|
|
if (rc == -ENOSPC) {
|
|
pr_err("%s: Failed: EADDRINUSE\n", __func__);
|
|
return -EADDRINUSE;
|
|
} else if (rc < 0) {
|
|
pr_err("%s: Failed: rc: %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
sock_hold(sk_vsock(vsk));
|
|
return 0;
|
|
}
|
|
|
|
static int __ghvst_bind(struct vsock_sock *vsk,
|
|
struct sockaddr_vm *addr,
|
|
int zapped)
|
|
{
|
|
struct sock *sk = sk_vsock(vsk);
|
|
unsigned long flags;
|
|
int port;
|
|
int rc;
|
|
|
|
/* rebinding ok */
|
|
if (!zapped && addr->svm_port == vsk->local_addr.svm_port)
|
|
return 0;
|
|
|
|
spin_lock_irqsave(&ghvst_port_lock, flags);
|
|
port = addr->svm_port;
|
|
rc = ghvst_port_assign(vsk, &port);
|
|
spin_unlock_irqrestore(&ghvst_port_lock, flags);
|
|
pr_debug("%s: port: 0x%x\n", __func__, port);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (!zapped)
|
|
ghvst_port_remove(vsk);
|
|
|
|
vsock_addr_init(&vsk->local_addr, VMADDR_CID_HOST, port);
|
|
sock_reset_flag(sk, SOCK_ZAPPED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ghvst_dgram_bind(struct vsock_sock *vsk, struct sockaddr_vm *addr)
|
|
{
|
|
struct sock *sk = sk_vsock(vsk);
|
|
int rc;
|
|
|
|
if (addr->svm_family != AF_VSOCK) {
|
|
pr_err("%s: Failed: Invalid AF family: %d\n", __func__,
|
|
addr->svm_family);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = __ghvst_bind(vsk, addr, sock_flag(sk, SOCK_ZAPPED));
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct vsock_sock *ghvst_port_lookup(int port)
|
|
{
|
|
struct vsock_sock *vsk;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ghvst_port_lock, flags);
|
|
vsk = idr_find(&ghvst_ports, port);
|
|
if (vsk)
|
|
sock_hold(sk_vsock(vsk));
|
|
spin_unlock_irqrestore(&ghvst_port_lock, flags);
|
|
|
|
return vsk;
|
|
}
|
|
|
|
static int ghvst_dgram_post(struct gh_transport_device *gdev)
|
|
{
|
|
struct gh_transport_buf *gbuf;
|
|
struct vsock_sock *vsk;
|
|
struct ghvst_hdr *hdr;
|
|
struct sk_buff *skb;
|
|
struct ghvst_cb *cb;
|
|
unsigned int size;
|
|
unsigned int len;
|
|
u64 pl_buf = 0;
|
|
void *data;
|
|
int rc;
|
|
|
|
gbuf = &gdev->gbuf;
|
|
if (gbuf->len < sizeof(*hdr)) {
|
|
pr_err("%s: len: %d < hdr size\n", __func__, gbuf->len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
len = gbuf->len - sizeof(*hdr);
|
|
data = gbuf->buf;
|
|
if (len <= 0 || !data) {
|
|
pr_err("%s: EINVAL: len: %d\n", __func__, len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
skb = alloc_skb_with_frags(sizeof(*hdr), len, 0, &rc, GFP_ATOMIC);
|
|
if (!skb) {
|
|
pr_err("%s: Unable to get skb with len:%lu\n", __func__, len);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
skb_reserve(skb, sizeof(*hdr));
|
|
cb = (struct ghvst_cb *)skb->cb;
|
|
|
|
hdr = (struct ghvst_hdr *)data;
|
|
|
|
cb->type = le32_to_cpu(hdr->type);
|
|
cb->src_port = le32_to_cpu(hdr->src_port_id);
|
|
cb->dst_port = le32_to_cpu(hdr->dst_port_id);
|
|
size = le32_to_cpu(hdr->size);
|
|
|
|
skb->data_len = size;
|
|
skb->len = gbuf->len;
|
|
skb_store_bits(skb, 0, data + sizeof(*hdr), size);
|
|
|
|
skb_copy_bits(skb, 0, &pl_buf, sizeof(pl_buf));
|
|
pr_debug("%s: RX DATA: Len:0x%x src[0x%x] dst[0x%x] [%08x %08x]\n",
|
|
__func__, skb->len, cb->src_port, cb->dst_port,
|
|
(unsigned int)pl_buf, (unsigned int)(pl_buf >> 32));
|
|
|
|
vsk = ghvst_port_lookup(cb->dst_port);
|
|
if (!vsk) {
|
|
pr_err("%s: no vsk for port:0x%x\n", __func__, cb->dst_port);
|
|
goto err;
|
|
}
|
|
|
|
if (sock_queue_rcv_skb(sk_vsock(vsk), skb)) {
|
|
pr_err("%s: sock_queue_rcv_skb failed\n", __func__);
|
|
sock_put(sk_vsock(vsk));
|
|
goto err;
|
|
}
|
|
|
|
/* heuristic timeout for clients to read packet from socket */
|
|
pm_wakeup_ws_event(sock_ws, GHVST_SKB_WAKEUP_MS, true);
|
|
|
|
sock_put(sk_vsock(vsk));
|
|
return 0;
|
|
|
|
err:
|
|
kfree_skb(skb);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void reset_buf(struct gh_transport_buf *gbuf)
|
|
{
|
|
memset(gbuf->buf, 0, MAX_PKT_SZ);
|
|
gbuf->hdr_received = false;
|
|
gbuf->copied = 0;
|
|
gbuf->remaining = 0;
|
|
gbuf->len = 0;
|
|
}
|
|
|
|
static void update_buf(void *buf, struct gh_transport_buf *gbuf, size_t len)
|
|
{
|
|
memcpy(gbuf->buf, buf, len);
|
|
gbuf->copied += len;
|
|
gbuf->buf += len;
|
|
}
|
|
|
|
static void check_rx_complete(struct gh_transport_device *gdev)
|
|
{
|
|
if (gdev->gbuf.copied == gdev->gbuf.len) {
|
|
gdev->gbuf.buf -= gdev->gbuf.len;
|
|
ghvst_dgram_post(gdev);
|
|
reset_buf(&gdev->gbuf);
|
|
}
|
|
}
|
|
|
|
static void copy_data(struct gh_transport_device *gdev, void *buf,
|
|
struct gh_transport_buf *gbuf, size_t len)
|
|
{
|
|
size_t copy_len;
|
|
|
|
copy_len = (len > gbuf->remaining) ? gbuf->remaining : len;
|
|
update_buf(buf, gbuf, copy_len);
|
|
gbuf->remaining = gbuf->len - gbuf->copied;
|
|
check_rx_complete(gdev);
|
|
}
|
|
|
|
static void ghvst_process_msg(struct gh_transport_device *gdev,
|
|
void *buf, size_t len)
|
|
{
|
|
struct gh_transport_buf *gbuf;
|
|
struct ghvst_hdr hdr;
|
|
void *head;
|
|
|
|
gbuf = &gdev->gbuf;
|
|
if (!gbuf)
|
|
return;
|
|
|
|
mutex_lock(&gbuf->lock);
|
|
if (gbuf->hdr_received) {
|
|
copy_data(gdev, buf, gbuf, len);
|
|
mutex_unlock(&gbuf->lock);
|
|
return;
|
|
}
|
|
|
|
update_buf(buf, gbuf, len);
|
|
if (gbuf->copied >= sizeof(hdr)) {
|
|
head = gbuf->buf - gbuf->copied;
|
|
memcpy(&hdr, head, sizeof(hdr));
|
|
if (hdr.version != GHVST_PROTO_VER_1 &&
|
|
hdr.type != GHVST_TYPE_DATA) {
|
|
pr_err("%s: Incorrect info ver:%d; type:%d\n",
|
|
__func__, hdr.version, hdr.type);
|
|
goto out;
|
|
}
|
|
/* Checked the data size in pkg header */
|
|
if (hdr.size > MAX_PKT_SZ - sizeof(hdr)) {
|
|
pr_err("%s: Incorrect received header size:%d\n",
|
|
__func__, hdr.size);
|
|
goto out;
|
|
}
|
|
gbuf->len = sizeof(hdr) + hdr.size;
|
|
gbuf->hdr_received = true;
|
|
/* Check gbuf->len size, can not be smaller than gbuf->copied*/
|
|
if (gbuf->len < gbuf->copied) {
|
|
pr_err("%s: Incorrect guf size: len=%d, copied=%d\n",
|
|
__func__, gbuf->len, gbuf->copied);
|
|
goto out;
|
|
}
|
|
gbuf->remaining = gbuf->len - gbuf->copied;
|
|
check_rx_complete(gdev);
|
|
mutex_unlock(&gbuf->lock);
|
|
return;
|
|
}
|
|
out:
|
|
reset_buf(gbuf);
|
|
mutex_unlock(&gbuf->lock);
|
|
}
|
|
|
|
static int ghvst_msgq_recv(void *data)
|
|
{
|
|
struct gh_transport_device *gdev = data;
|
|
struct gh_transport_buf *gbuf;
|
|
size_t size;
|
|
void *buf;
|
|
int rc;
|
|
|
|
buf = kzalloc(GH_MSGQ_MAX_MSG_SIZE_BYTES, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
gbuf = &gdev->gbuf;
|
|
if (!gbuf)
|
|
return -EINVAL;
|
|
|
|
while (!kthread_should_stop()) {
|
|
rc = gh_msgq_recv(gdev->msgq_hdl, buf,
|
|
GH_MSGQ_MAX_MSG_SIZE_BYTES,
|
|
&size, GH_MSGQ_TX_PUSH);
|
|
if (rc)
|
|
continue;
|
|
|
|
if (size <= 0)
|
|
continue;
|
|
|
|
/* Keep awake when there's data to be handled.
|
|
* Here, ghvst_transport can only ensure recv-thread stays awake
|
|
* when processing data from MsgQ. It can't cover the process of
|
|
* the MsgQ recv (gh_msgq_recv), which means that,
|
|
* theoretically, "suspend/sleep" still could happen
|
|
*/
|
|
pm_stay_awake(gdev->dev);
|
|
ghvst_process_msg(gdev, buf, size);
|
|
pm_relax(gdev->dev);
|
|
}
|
|
kfree(buf);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ghvst_dgram_dequeue() - post incoming data
|
|
*/
|
|
static int ghvst_dgram_dequeue(struct vsock_sock *vsk,
|
|
struct msghdr *msg, size_t len,
|
|
int flags)
|
|
{
|
|
DECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg->msg_name);
|
|
struct sock *sk = sk_vsock(vsk);
|
|
struct sk_buff *skb;
|
|
struct ghvst_cb *cb;
|
|
size_t payload_len;
|
|
int noblock;
|
|
int rc = 0;
|
|
|
|
if (sock_flag(sk, SOCK_ZAPPED)) {
|
|
pr_err("%s: Invalid addr error\n", __func__);
|
|
return -EADDRNOTAVAIL;
|
|
}
|
|
|
|
noblock = flags & MSG_DONTWAIT;
|
|
|
|
/* Retrieve the head sk_buff from the socket's receive queue. */
|
|
skb = skb_recv_datagram(&vsk->sk, flags & ~MSG_DONTWAIT, noblock, &rc);
|
|
if (!skb)
|
|
return rc;
|
|
|
|
lock_sock(sk);
|
|
cb = (struct ghvst_cb *)skb->cb;
|
|
if (!cb) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
payload_len = skb->data_len;
|
|
/* Ensure the sk_buff matches the payload size claimed in the packet. */
|
|
if (payload_len != skb->len - sizeof(struct ghvst_hdr)) {
|
|
rc = -EINVAL;
|
|
pr_err("%s: payload_len:%d; skb->len:%d\n",
|
|
__func__, payload_len, skb->len);
|
|
goto out;
|
|
}
|
|
|
|
if (payload_len > len) {
|
|
payload_len = len;
|
|
msg->msg_flags |= MSG_TRUNC;
|
|
}
|
|
|
|
/* Place the datagram payload in the user's iovec. */
|
|
rc = skb_copy_datagram_msg(skb, 0, msg, payload_len);
|
|
if (rc < 0) {
|
|
pr_err("%s: skb_copy_datagram_msg failed: %d\n", __func__, rc);
|
|
goto out;
|
|
}
|
|
rc = payload_len;
|
|
|
|
if (msg->msg_name) {
|
|
vsock_addr_init(vm_addr, VMADDR_CID_HOST, cb->src_port);
|
|
msg->msg_namelen = sizeof(*vm_addr);
|
|
}
|
|
out:
|
|
skb_free_datagram(&vsk->sk, skb);
|
|
release_sock(sk);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int ghvst_sendmsg(struct gh_transport_device *gdev,
|
|
void *buf, size_t len)
|
|
{
|
|
size_t tx_len;
|
|
int rc;
|
|
|
|
if (!gdev || !gdev->msgq_hdl) {
|
|
pr_err("%s: ENODEV err\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (len <= 0 || !buf) {
|
|
pr_err("%s: EINVAL err\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&gdev->tx_lock);
|
|
while (len > 0) {
|
|
tx_len = (len > GH_MSGQ_MAX_MSG_SIZE_BYTES ?
|
|
GH_MSGQ_MAX_MSG_SIZE_BYTES : len);
|
|
|
|
rc = gh_msgq_send(gdev->msgq_hdl, buf, tx_len, GH_MSGQ_TX_PUSH);
|
|
if (rc) {
|
|
pr_err("%s: gh_msgq_send failed: %d\n", __func__, rc);
|
|
mutex_unlock(&gdev->tx_lock);
|
|
return rc;
|
|
}
|
|
len -= tx_len;
|
|
buf = buf + tx_len;
|
|
}
|
|
mutex_unlock(&gdev->tx_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
struct gh_transport_device *get_ghvst(unsigned int cid)
|
|
{
|
|
struct gh_transport_device *gdev = NULL;
|
|
struct gh_transport_device *temp;
|
|
|
|
down_read(&ghvst_devs_lock);
|
|
list_for_each_entry(temp, &ghvst_devs, item) {
|
|
if (temp->cid == cid) {
|
|
gdev = temp;
|
|
break;
|
|
}
|
|
}
|
|
up_read(&ghvst_devs_lock);
|
|
|
|
return gdev;
|
|
}
|
|
|
|
static int ghvst_dgram_enqueue(struct vsock_sock *vsk,
|
|
struct sockaddr_vm *remote,
|
|
struct msghdr *msg, size_t len)
|
|
{
|
|
DECLARE_SOCKADDR(struct sockaddr_vm *, addr, msg->msg_name);
|
|
struct gh_transport_device *gdev;
|
|
struct sockaddr_vm *local_addr;
|
|
struct ghvst_hdr *hdr;
|
|
char *buf;
|
|
int rc;
|
|
|
|
local_addr = &vsk->local_addr;
|
|
gdev = get_ghvst(local_addr->svm_cid);
|
|
if (!gdev) {
|
|
pr_err("%s: no gunyah transport device for [0x%x:0x%x]\n",
|
|
__func__, local_addr->svm_cid, local_addr->svm_port);
|
|
return -ENXIO;
|
|
}
|
|
|
|
if (msg->msg_flags & MSG_DONTWAIT) {
|
|
pr_err("%s: No support for non-blocking flag: %lu\n",
|
|
__func__, msg->msg_flags);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (len > MAX_PKT_SZ - sizeof(*hdr)) {
|
|
pr_err("%s: Invalid pk size: len: %lu\n", __func__, len);
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
if (addr) {
|
|
if (msg->msg_namelen < sizeof(*addr)) {
|
|
pr_err("%s: Invalid addr\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (addr->svm_family != AF_VSOCK) {
|
|
pr_err("%s: Invalid sock family\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
pr_err("%s: No addr\n", __func__);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
/* Allocate a buffer for the user's message and our packet header. */
|
|
buf = kmalloc(len + sizeof(*hdr), GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
/* Populate Header */
|
|
hdr = (struct ghvst_hdr *)buf;
|
|
hdr->version = GHVST_PROTO_VER_1;
|
|
hdr->type = GHVST_TYPE_DATA;
|
|
hdr->flags = 0;
|
|
hdr->optlen = 0;
|
|
hdr->size = len;
|
|
hdr->src_rsvd = 0;
|
|
hdr->src_port_id = vsk->local_addr.svm_port;
|
|
hdr->dst_rsvd = 0;
|
|
hdr->dst_port_id = remote->svm_port;
|
|
rc = memcpy_from_msg((void *)buf + sizeof(*hdr), msg, len);
|
|
if (rc) {
|
|
pr_err("%s failed: memcpy_from_msg rc: %d\n", __func__, rc);
|
|
goto send_err;
|
|
}
|
|
|
|
pr_debug("TX DATA: Len:0x%x src[0x%x] dst[0x%x]\n",
|
|
len, hdr->src_port_id, hdr->dst_port_id);
|
|
rc = ghvst_sendmsg(gdev, buf, len + sizeof(*hdr));
|
|
if (rc < 0) {
|
|
pr_err("%s: failed to send msg rc: %d\n", __func__, rc);
|
|
goto send_err;
|
|
}
|
|
kfree(buf);
|
|
|
|
return 0;
|
|
|
|
send_err:
|
|
kfree(buf);
|
|
return rc;
|
|
}
|
|
|
|
static bool ghvst_allow_rsvd_cid(u32 cid)
|
|
{
|
|
/* Allowing for cid 0 as of now as af_vsock sends 0 if no cid is
|
|
* passed by the client.
|
|
*/
|
|
if (cid == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool ghvst_dgram_allow(u32 cid, u32 port)
|
|
{
|
|
struct gh_transport_device *gdev = get_ghvst(cid);
|
|
|
|
if (gdev)
|
|
return true;
|
|
|
|
if (ghvst_allow_rsvd_cid(cid) || cid == VMADDR_CID_ANY)
|
|
return true;
|
|
|
|
pr_err("%s: dgram not allowed for cid 0x%x\n", __func__, cid);
|
|
|
|
return false;
|
|
}
|
|
|
|
static int ghvst_shutdown(struct vsock_sock *vsk, int mode)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static u32 ghvst_get_local_cid(void)
|
|
{
|
|
return VMADDR_CID_HOST;
|
|
}
|
|
|
|
static const struct vsock_transport gunyah_transport = {
|
|
/* Initialize/tear-down socket. */
|
|
.init = ghvst_socket_init,
|
|
.destruct = ghvst_destruct,
|
|
.release = ghvst_release,
|
|
|
|
/* DGRAM. */
|
|
.dgram_bind = ghvst_dgram_bind,
|
|
.dgram_dequeue = ghvst_dgram_dequeue,
|
|
.dgram_enqueue = ghvst_dgram_enqueue,
|
|
.dgram_allow = ghvst_dgram_allow,
|
|
|
|
/* Shutdown. */
|
|
.shutdown = ghvst_shutdown,
|
|
|
|
/* Addressing. */
|
|
.get_local_cid = ghvst_get_local_cid,
|
|
};
|
|
|
|
static int ghvst_rm_cb(struct notifier_block *nb, unsigned long cmd, void *data)
|
|
{
|
|
struct gh_rm_notif_vm_status_payload *vm_status_payload = data;
|
|
u8 vm_status = vm_status_payload->vm_status;
|
|
struct gh_transport_device *gdev;
|
|
|
|
gdev = container_of(nb, struct gh_transport_device, rm_nb);
|
|
|
|
if (cmd != GH_RM_NOTIF_VM_STATUS)
|
|
return NOTIFY_DONE;
|
|
|
|
switch (vm_status) {
|
|
case GH_RM_VM_STATUS_READY:
|
|
/* Use guid to check if this is the VM that this driver
|
|
* is interested in communicating with, once changes are
|
|
* available in resource manager and msgq framework.
|
|
*/
|
|
if (gdev->msgq_hdl) {
|
|
dev_err(gdev->dev, "Already have msgq handle!\n");
|
|
return NOTIFY_DONE;
|
|
}
|
|
gdev->msgq_hdl = gh_msgq_register(gdev->msgq_label);
|
|
if (IS_ERR(gdev->msgq_hdl)) {
|
|
dev_err(gdev->dev, "msgq registration failed: err:%d\n",
|
|
PTR_ERR(gdev->msgq_hdl));
|
|
return NOTIFY_DONE;
|
|
}
|
|
break;
|
|
case GH_RM_VM_STATUS_RUNNING:
|
|
break;
|
|
default:
|
|
pr_debug("Unknown notification for vmid = %d vm_status = %d\n",
|
|
vm_status_payload->vmid, vm_status);
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int gunyah_transport_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *node = pdev->dev.of_node;
|
|
struct gh_transport_device *gdev;
|
|
struct device *dev = &pdev->dev;
|
|
struct gh_transport_buf *gbuf;
|
|
int rc;
|
|
|
|
gdev = devm_kzalloc(dev, sizeof(*gdev), GFP_KERNEL);
|
|
if (!gdev)
|
|
return -ENOMEM;
|
|
|
|
gdev->dev = dev;
|
|
mutex_init(&gdev->tx_lock);
|
|
|
|
gbuf = &gdev->gbuf;
|
|
gbuf->buf = devm_kzalloc(dev, MAX_PKT_SZ, GFP_KERNEL);
|
|
if (!gbuf->buf)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&gbuf->lock);
|
|
gbuf->len = 0;
|
|
gbuf->copied = 0;
|
|
gbuf->remaining = 0;
|
|
gbuf->hdr_received = false;
|
|
|
|
gdev->cid = VMADDR_CID_HOST;
|
|
|
|
rc = of_property_read_u32(node, "msgq-label", &gdev->msgq_label);
|
|
if (rc) {
|
|
dev_err(dev, "failed to read msgq-label info %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
dev_set_drvdata(&pdev->dev, gdev);
|
|
|
|
gdev->master = of_property_read_bool(node, "qcom,master");
|
|
if (gdev->master) {
|
|
gdev->rm_nb.notifier_call = ghvst_rm_cb;
|
|
gh_rm_register_notifier(&gdev->rm_nb);
|
|
} else {
|
|
gdev->msgq_hdl = gh_msgq_register(gdev->msgq_label);
|
|
if (IS_ERR(gdev->msgq_hdl)) {
|
|
rc = PTR_ERR(gdev->msgq_hdl);
|
|
dev_err(dev, "msgq register failed rc:%d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
gdev->rx_thread = kthread_create(ghvst_msgq_recv, gdev, "ghvst_rx");
|
|
if (IS_ERR(gdev->rx_thread)) {
|
|
rc = PTR_ERR(gdev->rx_thread);
|
|
dev_err(dev, "Failed to create receiver thread rc:%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
sock_ws = wakeup_source_register(NULL, "ghvst_sock_ws");
|
|
|
|
down_write(&ghvst_devs_lock);
|
|
list_add(&gdev->item, &ghvst_devs);
|
|
up_write(&ghvst_devs_lock);
|
|
|
|
wake_up_process(gdev->rx_thread);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int gunyah_transport_remove(struct platform_device *pdev)
|
|
{
|
|
struct gh_transport_device *gdev = dev_get_drvdata(&pdev->dev);
|
|
|
|
if (gdev->master)
|
|
gh_rm_unregister_notifier(&gdev->rm_nb);
|
|
|
|
if (gdev->rx_thread)
|
|
kthread_stop(gdev->rx_thread);
|
|
|
|
wakeup_source_unregister(sock_ws);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id gunyah_transport_match_table[] = {
|
|
{ .compatible = "qcom,gunyah-vsock" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, gunyah_transport_match_table);
|
|
|
|
static struct platform_driver gunyah_vsock_driver = {
|
|
.driver = {
|
|
.name = "gunyah_vsock",
|
|
.of_match_table = gunyah_transport_match_table,
|
|
},
|
|
.probe = gunyah_transport_probe,
|
|
.remove = gunyah_transport_remove,
|
|
};
|
|
|
|
static int __init gunyah_vsock_init(void)
|
|
{
|
|
int rc;
|
|
|
|
rc = vsock_core_register(&gunyah_transport, VSOCK_TRANSPORT_F_DGRAM);
|
|
if (rc) {
|
|
pr_err("%s: vsock_core_register failed: %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
platform_driver_register(&gunyah_vsock_driver);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit gunyah_vsock_exit(void)
|
|
{
|
|
vsock_core_unregister(&gunyah_transport);
|
|
platform_driver_unregister(&gunyah_vsock_driver);
|
|
}
|
|
module_init(gunyah_vsock_init);
|
|
module_exit(gunyah_vsock_exit);
|
|
|
|
MODULE_DESCRIPTION("Gunyah Transport driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS_NETPROTO(PF_VSOCK);
|