711 lines
17 KiB
C
Executable file
711 lines
17 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/types.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/gunyah/gh_rm_drv.h>
|
|
#include <linux/gunyah/gh_dbl.h>
|
|
#include <soc/qcom/secure_buffer.h>
|
|
#include "qrtr.h"
|
|
|
|
#define GUNYAH_MAGIC_KEY 0x24495043 /* "$IPC" */
|
|
#define FIFO_SIZE 0x4000
|
|
#define FIFO_FULL_RESERVE 8
|
|
#define FIFO_0_START 0x1000
|
|
#define FIFO_1_START (FIFO_0_START + FIFO_SIZE)
|
|
#define GUNYAH_MAGIC_IDX 0x0
|
|
#define TAIL_0_IDX 0x1
|
|
#define HEAD_0_IDX 0x2
|
|
#define TAIL_1_IDX 0x3
|
|
#define HEAD_1_IDX 0x4
|
|
#define NOTIFY_0_IDX 0x5
|
|
#define NOTIFY_1_IDX 0x6
|
|
#define QRTR_DBL_MASK 0x1
|
|
|
|
#define MAX_PKT_SZ SZ_64K
|
|
|
|
struct gunyah_ring {
|
|
void *buf;
|
|
size_t len;
|
|
u32 offset;
|
|
};
|
|
|
|
struct gunyah_pipe {
|
|
__le32 *tail;
|
|
__le32 *head;
|
|
__le32 *read_notify;
|
|
|
|
void *fifo;
|
|
size_t length;
|
|
};
|
|
|
|
/**
|
|
* qrtr_gunyah_dev - qrtr gunyah transport structure
|
|
* @ep: qrtr endpoint specific info.
|
|
* @dev: device from platform_device.
|
|
* @pkt: buf for reading from fifo.
|
|
* @res: resource of reserved mem region
|
|
* @memparcel: memparcel handle returned from sharing mem
|
|
* @base: Base of the shared fifo.
|
|
* @size: fifo size.
|
|
* @master: primary vm indicator.
|
|
* @peer_name: name of vm peer.
|
|
* @rm_nb: notifier block for vm status from rm
|
|
* @label: label for gunyah resources
|
|
* @tx_dbl: doorbell for tx notifications.
|
|
* @rx_dbl: doorbell for rx notifications.
|
|
* @tx_pipe: TX gunyah specific info.
|
|
* @rx_pipe: RX gunyah specific info.
|
|
*/
|
|
struct qrtr_gunyah_dev {
|
|
struct qrtr_endpoint ep;
|
|
struct device *dev;
|
|
struct gunyah_ring ring;
|
|
|
|
struct resource res;
|
|
u32 memparcel;
|
|
void *base;
|
|
size_t size;
|
|
bool master;
|
|
u32 peer_name;
|
|
struct notifier_block rm_nb;
|
|
|
|
u32 label;
|
|
void *tx_dbl;
|
|
void *rx_dbl;
|
|
struct work_struct work;
|
|
|
|
struct gunyah_pipe tx_pipe;
|
|
struct gunyah_pipe rx_pipe;
|
|
wait_queue_head_t tx_avail_notify;
|
|
};
|
|
|
|
static void qrtr_gunyah_read(struct qrtr_gunyah_dev *qdev);
|
|
static void qrtr_gunyah_fifo_init(struct qrtr_gunyah_dev *qdev);
|
|
|
|
static void qrtr_gunyah_kick(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
gh_dbl_flags_t dbl_mask = QRTR_DBL_MASK;
|
|
int ret;
|
|
|
|
ret = gh_dbl_send(qdev->tx_dbl, &dbl_mask, GH_DBL_NONBLOCK);
|
|
if (ret) {
|
|
dev_err(qdev->dev, "failed to raise doorbell %d\n", ret);
|
|
if (!qdev->master)
|
|
schedule_work(&qdev->work);
|
|
}
|
|
}
|
|
|
|
static void qrtr_gunyah_retry_work(struct work_struct *work)
|
|
{
|
|
struct qrtr_gunyah_dev *qdev = container_of(work, struct qrtr_gunyah_dev,
|
|
work);
|
|
gh_dbl_flags_t dbl_mask = QRTR_DBL_MASK;
|
|
|
|
gh_dbl_send(qdev->tx_dbl, &dbl_mask, 0);
|
|
}
|
|
|
|
static void qrtr_gunyah_cb(int irq, void *data)
|
|
{
|
|
qrtr_gunyah_read((struct qrtr_gunyah_dev *)data);
|
|
}
|
|
|
|
static size_t gunyah_rx_avail(struct gunyah_pipe *pipe)
|
|
{
|
|
size_t len;
|
|
u32 head;
|
|
u32 tail;
|
|
|
|
head = le32_to_cpu(*pipe->head);
|
|
tail = le32_to_cpu(*pipe->tail);
|
|
|
|
if (head < tail)
|
|
len = pipe->length - tail + head;
|
|
else
|
|
len = head - tail;
|
|
|
|
if (WARN_ON_ONCE(len > pipe->length))
|
|
len = 0;
|
|
|
|
return len;
|
|
}
|
|
|
|
static void gunyah_rx_peak(struct gunyah_pipe *pipe, void *data,
|
|
unsigned int offset, size_t count)
|
|
{
|
|
size_t len;
|
|
u32 tail;
|
|
|
|
tail = le32_to_cpu(*pipe->tail);
|
|
tail += offset;
|
|
if (tail >= pipe->length)
|
|
tail -= pipe->length;
|
|
|
|
len = min_t(size_t, count, pipe->length - tail);
|
|
if (len)
|
|
memcpy_fromio(data, pipe->fifo + tail, len);
|
|
|
|
if (len != count)
|
|
memcpy_fromio(data + len, pipe->fifo, (count - len));
|
|
}
|
|
|
|
static void gunyah_rx_advance(struct gunyah_pipe *pipe, size_t count)
|
|
{
|
|
u32 tail;
|
|
|
|
tail = le32_to_cpu(*pipe->tail);
|
|
|
|
tail += count;
|
|
if (tail >= pipe->length)
|
|
tail %= pipe->length;
|
|
|
|
*pipe->tail = cpu_to_le32(tail);
|
|
}
|
|
|
|
static size_t gunyah_tx_avail(struct gunyah_pipe *pipe)
|
|
{
|
|
u32 avail;
|
|
u32 head;
|
|
u32 tail;
|
|
|
|
head = le32_to_cpu(*pipe->head);
|
|
tail = le32_to_cpu(*pipe->tail);
|
|
|
|
if (tail <= head)
|
|
avail = pipe->length - head + tail;
|
|
else
|
|
avail = tail - head;
|
|
|
|
if (avail < FIFO_FULL_RESERVE)
|
|
avail = 0;
|
|
else
|
|
avail -= FIFO_FULL_RESERVE;
|
|
|
|
return avail;
|
|
}
|
|
|
|
static void gunyah_tx_write(struct gunyah_pipe *pipe, const void *data,
|
|
size_t count)
|
|
{
|
|
size_t len;
|
|
u32 head;
|
|
|
|
head = le32_to_cpu(*pipe->head);
|
|
|
|
len = min_t(size_t, count, pipe->length - head);
|
|
if (len)
|
|
memcpy_toio(pipe->fifo + head, data, len);
|
|
|
|
if (len != count)
|
|
memcpy_toio(pipe->fifo, data + len, count - len);
|
|
|
|
head += count;
|
|
if (head >= pipe->length)
|
|
head -= pipe->length;
|
|
|
|
/* Ensure ordering of fifo and head update */
|
|
smp_wmb();
|
|
|
|
*pipe->head = cpu_to_le32(head);
|
|
}
|
|
|
|
static void gunyah_set_tx_notify(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
*qdev->tx_pipe.read_notify = cpu_to_le32(1);
|
|
}
|
|
|
|
static void gunyah_clr_tx_notify(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
*qdev->tx_pipe.read_notify = 0;
|
|
}
|
|
|
|
static bool gunyah_get_read_notify(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
return le32_to_cpu(*qdev->rx_pipe.read_notify);
|
|
}
|
|
|
|
static void gunyah_wait_for_tx_avail(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
gunyah_set_tx_notify(qdev);
|
|
wait_event_timeout(qdev->tx_avail_notify,
|
|
gunyah_tx_avail(&qdev->tx_pipe), 10 * HZ);
|
|
}
|
|
|
|
/* from qrtr to gunyah */
|
|
static int qrtr_gunyah_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
|
|
{
|
|
struct qrtr_gunyah_dev *qdev;
|
|
size_t tx_avail;
|
|
int chunk_size;
|
|
int left_size;
|
|
int offset;
|
|
|
|
int rc;
|
|
|
|
qdev = container_of(ep, struct qrtr_gunyah_dev, ep);
|
|
|
|
rc = skb_linearize(skb);
|
|
if (rc) {
|
|
kfree_skb(skb);
|
|
return rc;
|
|
}
|
|
|
|
left_size = skb->len;
|
|
offset = 0;
|
|
while (left_size > 0) {
|
|
tx_avail = gunyah_tx_avail(&qdev->tx_pipe);
|
|
if (!tx_avail) {
|
|
gunyah_wait_for_tx_avail(qdev);
|
|
continue;
|
|
}
|
|
if (tx_avail < left_size)
|
|
chunk_size = tx_avail;
|
|
else
|
|
chunk_size = left_size;
|
|
|
|
gunyah_tx_write(&qdev->tx_pipe, skb->data + offset, chunk_size);
|
|
offset += chunk_size;
|
|
left_size -= chunk_size;
|
|
|
|
qrtr_gunyah_kick(qdev);
|
|
}
|
|
gunyah_clr_tx_notify(qdev);
|
|
kfree_skb(skb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void qrtr_gunyah_read_new(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
struct gunyah_ring *ring = &qdev->ring;
|
|
size_t rx_avail;
|
|
size_t pkt_len;
|
|
u32 hdr[8];
|
|
int rc;
|
|
size_t hdr_len = sizeof(hdr);
|
|
|
|
gunyah_rx_peak(&qdev->rx_pipe, &hdr, 0, hdr_len);
|
|
pkt_len = qrtr_peek_pkt_size((void *)&hdr);
|
|
if ((int)pkt_len < 0 || pkt_len > MAX_PKT_SZ) {
|
|
dev_err(qdev->dev, "invalid pkt_len %zu\n", pkt_len);
|
|
return;
|
|
}
|
|
|
|
rx_avail = gunyah_rx_avail(&qdev->rx_pipe);
|
|
if (rx_avail > pkt_len)
|
|
rx_avail = pkt_len;
|
|
|
|
gunyah_rx_peak(&qdev->rx_pipe, ring->buf, 0, rx_avail);
|
|
gunyah_rx_advance(&qdev->rx_pipe, rx_avail);
|
|
|
|
if (rx_avail == pkt_len) {
|
|
rc = qrtr_endpoint_post(&qdev->ep, ring->buf, pkt_len);
|
|
if (rc == -EINVAL)
|
|
dev_err(qdev->dev, "invalid ipcrouter packet\n");
|
|
} else {
|
|
ring->len = pkt_len;
|
|
ring->offset = rx_avail;
|
|
}
|
|
}
|
|
|
|
static void qrtr_gunyah_read_frag(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
struct gunyah_ring *ring = &qdev->ring;
|
|
size_t rx_avail;
|
|
int rc;
|
|
|
|
rx_avail = gunyah_rx_avail(&qdev->rx_pipe);
|
|
if (rx_avail + ring->offset > ring->len)
|
|
rx_avail = ring->len - ring->offset;
|
|
|
|
gunyah_rx_peak(&qdev->rx_pipe, ring->buf + ring->offset, 0, rx_avail);
|
|
gunyah_rx_advance(&qdev->rx_pipe, rx_avail);
|
|
|
|
if (rx_avail + ring->offset == ring->len) {
|
|
rc = qrtr_endpoint_post(&qdev->ep, ring->buf, ring->len);
|
|
if (rc == -EINVAL)
|
|
dev_err(qdev->dev, "invalid ipcrouter packet\n");
|
|
ring->offset = 0;
|
|
ring->len = 0;
|
|
} else {
|
|
ring->offset += rx_avail;
|
|
}
|
|
}
|
|
|
|
static void qrtr_gunyah_read(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
wake_up_all(&qdev->tx_avail_notify);
|
|
|
|
while (gunyah_rx_avail(&qdev->rx_pipe)) {
|
|
if (qdev->ring.offset)
|
|
qrtr_gunyah_read_frag(qdev);
|
|
else
|
|
qrtr_gunyah_read_new(qdev);
|
|
|
|
if (gunyah_get_read_notify(qdev))
|
|
qrtr_gunyah_kick(qdev);
|
|
}
|
|
}
|
|
|
|
static int qrtr_gunyah_share_mem(struct qrtr_gunyah_dev *qdev, gh_vmid_t self,
|
|
gh_vmid_t peer)
|
|
{
|
|
u32 src_vmlist[1] = {self};
|
|
int src_perms[2] = {PERM_READ | PERM_WRITE | PERM_EXEC};
|
|
int dst_vmlist[2] = {self, peer};
|
|
int dst_perms[2] = {PERM_READ | PERM_WRITE, PERM_READ | PERM_WRITE};
|
|
struct gh_acl_desc *acl;
|
|
struct gh_sgl_desc *sgl;
|
|
int ret;
|
|
|
|
ret = hyp_assign_phys(qdev->res.start, resource_size(&qdev->res),
|
|
src_vmlist, 1,
|
|
dst_vmlist, dst_perms, 2);
|
|
if (ret) {
|
|
pr_err("%s: hyp_assign_phys failed addr=%x size=%u err=%d\n",
|
|
__func__, qdev->res.start, qdev->size, ret);
|
|
return ret;
|
|
}
|
|
|
|
acl = kzalloc(offsetof(struct gh_acl_desc, acl_entries[2]), GFP_KERNEL);
|
|
if (!acl)
|
|
return -ENOMEM;
|
|
sgl = kzalloc(offsetof(struct gh_sgl_desc, sgl_entries[1]), GFP_KERNEL);
|
|
if (!sgl) {
|
|
kfree(acl);
|
|
return -ENOMEM;
|
|
}
|
|
acl->n_acl_entries = 2;
|
|
acl->acl_entries[0].vmid = (u16)self;
|
|
acl->acl_entries[0].perms = GH_RM_ACL_R | GH_RM_ACL_W;
|
|
acl->acl_entries[1].vmid = (u16)peer;
|
|
acl->acl_entries[1].perms = GH_RM_ACL_R | GH_RM_ACL_W;
|
|
|
|
sgl->n_sgl_entries = 1;
|
|
sgl->sgl_entries[0].ipa_base = qdev->res.start;
|
|
sgl->sgl_entries[0].size = resource_size(&qdev->res);
|
|
|
|
/* gh_rm_mem_qcom_lookup_sgl is no longer supported and is replaced with
|
|
* gh_rm_mem_share. To ease this transition, fall back to the later on error.
|
|
*/
|
|
ret = gh_rm_mem_qcom_lookup_sgl(GH_RM_MEM_TYPE_NORMAL,
|
|
qdev->label,
|
|
acl, sgl, NULL,
|
|
&qdev->memparcel);
|
|
if (ret) {
|
|
ret = gh_rm_mem_share(GH_RM_MEM_TYPE_NORMAL, 0, qdev->label,
|
|
acl, sgl, NULL, &qdev->memparcel);
|
|
}
|
|
if (ret) {
|
|
pr_err("%s: gh_rm_mem_share failed addr=%x size=%u err=%d\n",
|
|
__func__, qdev->res.start, qdev->size, ret);
|
|
/* Attempt to give resource back to HLOS */
|
|
hyp_assign_phys(qdev->res.start, resource_size(&qdev->res),
|
|
dst_vmlist, 2,
|
|
src_vmlist, src_perms, 1);
|
|
}
|
|
|
|
kfree(acl);
|
|
kfree(sgl);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void qrtr_gunyah_unshare_mem(struct qrtr_gunyah_dev *qdev,
|
|
gh_vmid_t self, gh_vmid_t peer)
|
|
{
|
|
int dst_perms[2] = {PERM_READ | PERM_WRITE | PERM_EXEC};
|
|
int src_vmlist[2] = {self, peer};
|
|
u32 dst_vmlist[1] = {self};
|
|
int ret;
|
|
|
|
ret = gh_rm_mem_reclaim(qdev->memparcel, 0);
|
|
if (ret)
|
|
pr_err("%s: Gunyah reclaim failed\n", __func__);
|
|
|
|
hyp_assign_phys(qdev->res.start, resource_size(&qdev->res),
|
|
src_vmlist, 2, dst_vmlist, dst_perms, 1);
|
|
}
|
|
|
|
static int qrtr_gunyah_rm_cb(struct notifier_block *nb, unsigned long cmd,
|
|
void *data)
|
|
{
|
|
struct gh_rm_notif_vm_status_payload *vm_status_payload;
|
|
struct qrtr_gunyah_dev *qdev;
|
|
gh_vmid_t peer_vmid;
|
|
gh_vmid_t self_vmid;
|
|
|
|
qdev = container_of(nb, struct qrtr_gunyah_dev, rm_nb);
|
|
|
|
if (cmd != GH_RM_NOTIF_VM_STATUS)
|
|
return NOTIFY_DONE;
|
|
|
|
vm_status_payload = data;
|
|
if (vm_status_payload->vm_status != GH_RM_VM_STATUS_READY &&
|
|
vm_status_payload->vm_status != GH_RM_VM_STATUS_RESET)
|
|
return NOTIFY_DONE;
|
|
if (gh_rm_get_vmid(qdev->peer_name, &peer_vmid))
|
|
return NOTIFY_DONE;
|
|
if (gh_rm_get_vmid(GH_PRIMARY_VM, &self_vmid))
|
|
return NOTIFY_DONE;
|
|
if (peer_vmid != vm_status_payload->vmid)
|
|
return NOTIFY_DONE;
|
|
|
|
if (vm_status_payload->vm_status == GH_RM_VM_STATUS_READY) {
|
|
qrtr_gunyah_fifo_init(qdev);
|
|
if (qrtr_endpoint_register(&qdev->ep, QRTR_EP_NET_ID_AUTO, false)) {
|
|
pr_err("%s: endpoint register failed\n", __func__);
|
|
return NOTIFY_DONE;
|
|
}
|
|
if (qrtr_gunyah_share_mem(qdev, self_vmid, peer_vmid)) {
|
|
pr_err("%s: failed to share memory\n", __func__);
|
|
return NOTIFY_DONE;
|
|
}
|
|
}
|
|
if (vm_status_payload->vm_status == GH_RM_VM_STATUS_RESET) {
|
|
qrtr_endpoint_unregister(&qdev->ep);
|
|
qrtr_gunyah_unshare_mem(qdev, self_vmid, peer_vmid);
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
/**
|
|
* qrtr_gunyah_fifo_init() - init gunyah xprt configs
|
|
*
|
|
* @return: 0 on success, standard Linux error codes on error.
|
|
*
|
|
* This function is called to initialize the gunyah XPRT pointer with
|
|
* the gunyah XPRT configurations either from device tree or static arrays.
|
|
*/
|
|
static void qrtr_gunyah_fifo_init(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
__le32 *descs;
|
|
|
|
if (qdev->master)
|
|
memset(qdev->base, 0, sizeof(*descs) * 10);
|
|
|
|
descs = qdev->base;
|
|
descs[GUNYAH_MAGIC_IDX] = GUNYAH_MAGIC_KEY;
|
|
|
|
if (qdev->master) {
|
|
qdev->tx_pipe.tail = &descs[TAIL_0_IDX];
|
|
qdev->tx_pipe.head = &descs[HEAD_0_IDX];
|
|
qdev->tx_pipe.fifo = qdev->base + FIFO_0_START;
|
|
qdev->tx_pipe.length = FIFO_SIZE;
|
|
qdev->tx_pipe.read_notify = &descs[NOTIFY_0_IDX];
|
|
|
|
qdev->rx_pipe.tail = &descs[TAIL_1_IDX];
|
|
qdev->rx_pipe.head = &descs[HEAD_1_IDX];
|
|
qdev->rx_pipe.fifo = qdev->base + FIFO_1_START;
|
|
qdev->rx_pipe.length = FIFO_SIZE;
|
|
qdev->rx_pipe.read_notify = &descs[NOTIFY_1_IDX];
|
|
} else {
|
|
qdev->tx_pipe.tail = &descs[TAIL_1_IDX];
|
|
qdev->tx_pipe.head = &descs[HEAD_1_IDX];
|
|
qdev->tx_pipe.fifo = qdev->base + FIFO_1_START;
|
|
qdev->tx_pipe.length = FIFO_SIZE;
|
|
qdev->tx_pipe.read_notify = &descs[NOTIFY_1_IDX];
|
|
|
|
qdev->rx_pipe.tail = &descs[TAIL_0_IDX];
|
|
qdev->rx_pipe.head = &descs[HEAD_0_IDX];
|
|
qdev->rx_pipe.fifo = qdev->base + FIFO_0_START;
|
|
qdev->rx_pipe.length = FIFO_SIZE;
|
|
qdev->rx_pipe.read_notify = &descs[NOTIFY_0_IDX];
|
|
}
|
|
|
|
/* Reset respective index */
|
|
*qdev->tx_pipe.head = 0;
|
|
*qdev->tx_pipe.read_notify = 0;
|
|
*qdev->rx_pipe.tail = 0;
|
|
}
|
|
|
|
static struct device_node *qrtr_gunyah_svm_of_parse(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
const char *compat = "qcom,qrtr-gunyah-gen";
|
|
struct device_node *np = NULL;
|
|
struct device_node *shm_np;
|
|
u32 label;
|
|
int ret;
|
|
|
|
while ((np = of_find_compatible_node(np, NULL, compat))) {
|
|
ret = of_property_read_u32(np, "qcom,label", &label);
|
|
if (ret) {
|
|
of_node_put(np);
|
|
continue;
|
|
}
|
|
if (label == qdev->label)
|
|
break;
|
|
|
|
of_node_put(np);
|
|
}
|
|
if (!np)
|
|
return NULL;
|
|
|
|
shm_np = of_parse_phandle(np, "memory-region", 0);
|
|
if (!shm_np)
|
|
dev_err(qdev->dev, "cant parse svm shared mem node!\n");
|
|
|
|
of_node_put(np);
|
|
return shm_np;
|
|
}
|
|
|
|
static int qrtr_gunyah_map_memory(struct qrtr_gunyah_dev *qdev)
|
|
{
|
|
struct device *dev = qdev->dev;
|
|
struct device_node *np;
|
|
resource_size_t size;
|
|
int ret;
|
|
|
|
np = of_parse_phandle(dev->of_node, "shared-buffer", 0);
|
|
if (!np) {
|
|
np = qrtr_gunyah_svm_of_parse(qdev);
|
|
if (!np) {
|
|
dev_err(dev, "cant parse shared mem node!\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
ret = of_address_to_resource(np, 0, &qdev->res);
|
|
of_node_put(np);
|
|
if (ret) {
|
|
dev_err(dev, "of_address_to_resource failed!\n");
|
|
return -EINVAL;
|
|
}
|
|
size = resource_size(&qdev->res);
|
|
|
|
qdev->base = devm_ioremap_resource(dev, &qdev->res);
|
|
if (IS_ERR(qdev->base)) {
|
|
dev_err(dev, "ioremap failed!\n");
|
|
return PTR_ERR(qdev->base);
|
|
}
|
|
qdev->size = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* qrtr_gunyah_probe() - Probe a gunyah xprt
|
|
*
|
|
* @pdev: Platform device corresponding to gunyah xprt.
|
|
*
|
|
* @return: 0 on success, standard Linux error codes on error.
|
|
*
|
|
* This function is called when the underlying device tree driver registers
|
|
* a platform device, mapped to a gunyah transport.
|
|
*/
|
|
static int qrtr_gunyah_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *node = pdev->dev.of_node;
|
|
struct qrtr_gunyah_dev *qdev;
|
|
enum gh_dbl_label dbl_label;
|
|
int ret;
|
|
|
|
qdev = devm_kzalloc(&pdev->dev, sizeof(*qdev), GFP_KERNEL);
|
|
if (!qdev)
|
|
return -ENOMEM;
|
|
qdev->dev = &pdev->dev;
|
|
dev_set_drvdata(&pdev->dev, qdev);
|
|
|
|
qdev->ring.buf = devm_kzalloc(&pdev->dev, MAX_PKT_SZ, GFP_KERNEL);
|
|
if (!qdev->ring.buf)
|
|
return -ENOMEM;
|
|
|
|
ret = of_property_read_u32(node, "gunyah-label", &qdev->label);
|
|
if (ret) {
|
|
dev_err(qdev->dev, "failed to read label info %d\n", ret);
|
|
return ret;
|
|
}
|
|
qdev->master = of_property_read_bool(node, "qcom,master");
|
|
|
|
ret = qrtr_gunyah_map_memory(qdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!qdev->master)
|
|
qrtr_gunyah_fifo_init(qdev);
|
|
init_waitqueue_head(&qdev->tx_avail_notify);
|
|
|
|
if (qdev->master) {
|
|
ret = of_property_read_u32(node, "peer-name", &qdev->peer_name);
|
|
if (ret)
|
|
qdev->peer_name = GH_SELF_VM;
|
|
|
|
qdev->rm_nb.notifier_call = qrtr_gunyah_rm_cb;
|
|
qdev->rm_nb.priority = INT_MAX;
|
|
gh_rm_register_notifier(&qdev->rm_nb);
|
|
}
|
|
|
|
dbl_label = qdev->label;
|
|
qdev->tx_dbl = gh_dbl_tx_register(dbl_label);
|
|
if (IS_ERR_OR_NULL(qdev->tx_dbl)) {
|
|
ret = PTR_ERR(qdev->tx_dbl);
|
|
dev_err(qdev->dev, "failed to get gunyah tx dbl %d\n", ret);
|
|
return ret;
|
|
}
|
|
INIT_WORK(&qdev->work, qrtr_gunyah_retry_work);
|
|
|
|
qdev->rx_dbl = gh_dbl_rx_register(dbl_label, qrtr_gunyah_cb, qdev);
|
|
if (IS_ERR_OR_NULL(qdev->rx_dbl)) {
|
|
ret = PTR_ERR(qdev->rx_dbl);
|
|
dev_err(qdev->dev, "failed to get gunyah rx dbl %d\n", ret);
|
|
goto fail_rx_dbl;
|
|
}
|
|
|
|
qdev->ep.xmit = qrtr_gunyah_send;
|
|
if (!qdev->master) {
|
|
ret = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NET_ID_AUTO, false);
|
|
if (ret)
|
|
goto register_fail;
|
|
|
|
if (gunyah_rx_avail(&qdev->rx_pipe))
|
|
qrtr_gunyah_read(qdev);
|
|
}
|
|
|
|
return 0;
|
|
|
|
register_fail:
|
|
gh_dbl_rx_unregister(qdev->rx_dbl);
|
|
fail_rx_dbl:
|
|
cancel_work_sync(&qdev->work);
|
|
gh_dbl_tx_unregister(qdev->tx_dbl);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qrtr_gunyah_remove(struct platform_device *pdev)
|
|
{
|
|
struct qrtr_gunyah_dev *qdev = dev_get_drvdata(&pdev->dev);
|
|
|
|
cancel_work_sync(&qdev->work);
|
|
gh_dbl_tx_unregister(qdev->tx_dbl);
|
|
gh_dbl_rx_unregister(qdev->rx_dbl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id qrtr_gunyah_match_table[] = {
|
|
{ .compatible = "qcom,qrtr-gunyah" },
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver qrtr_gunyah_driver = {
|
|
.driver = {
|
|
.name = "qcom_gunyah_qrtr",
|
|
.of_match_table = qrtr_gunyah_match_table,
|
|
},
|
|
.probe = qrtr_gunyah_probe,
|
|
.remove = qrtr_gunyah_remove,
|
|
};
|
|
module_platform_driver(qrtr_gunyah_driver);
|
|
|
|
MODULE_DESCRIPTION("QTI IPC-Router Gunyah interface driver");
|
|
MODULE_LICENSE("GPL v2");
|