Rtwo/kernel/motorola/sm8550/drivers/soc/qcom/tmecom/tmecom.c
2025-09-30 19:22:48 -05:00

326 lines
7.8 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "tmecom: [%s][%d]:" fmt, __func__, __LINE__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mailbox_client.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/platform_device.h>
#include <linux/mailbox/qmp.h>
#include <linux/uaccess.h>
#include <linux/mailbox_controller.h>
#include "tmecom.h"
struct tmecom {
struct device *dev;
struct mbox_client cl;
struct mbox_chan *chan;
struct mutex lock;
struct qmp_pkt pkt;
wait_queue_head_t waitq;
void *txbuf;
bool rx_done;
};
#if IS_ENABLED(CONFIG_DEBUG_FS)
#include <linux/tme_hwkm_master_defs.h>
#include <linux/tme_hwkm_master.h>
char dpkt[MBOX_MAX_MSG_LEN + 1];
struct dentry *debugfs_file;
#endif /* CONFIG_DEBUG_FS */
static struct tmecom *tmedev;
/**
* tmecom_msg_hdr - Request/Response message header between HLOS and TME.
*
* This header is proceeding any request specific parameters.
* The transaction id is used to match request with response.
*
* Note: glink/QMP layer provides the rx/tx data size, so user payload size
* is calculated by reducing the header size.
*/
struct tmecom_msg_hdr {
unsigned int reserved; /* for future use */
unsigned int txnid; /* transaction id */
} __packed;
#define TMECOM_TX_HDR_SIZE sizeof(struct tmecom_msg_hdr)
#define CBOR_NUM_BYTES (sizeof(unsigned int))
#define TMECOM_RX_HDR_SIZE (TMECOM_TX_HDR_SIZE + CBOR_NUM_BYTES)
/*
* CBOR encode emulation
* Prepend tmecom_msg_hdr space
* CBOR tag is prepended in request
*/
static inline size_t tmecom_encode(struct tmecom *tdev, const void *reqbuf,
size_t size)
{
unsigned int *msg = tdev->txbuf + TMECOM_TX_HDR_SIZE;
unsigned int *src = (unsigned int *)reqbuf;
memcpy(msg, src, size);
return (size + TMECOM_TX_HDR_SIZE);
}
/*
* CBOR decode emulation
* Strip tmecom_msg_hdr & CBOR tag
*/
static inline size_t tmecom_decode(struct tmecom *tdev, void *respbuf)
{
unsigned int *msg = tdev->pkt.data + TMECOM_RX_HDR_SIZE;
unsigned int *rbuf = (unsigned int *)respbuf;
memcpy(rbuf, msg, (tdev->pkt.size - TMECOM_RX_HDR_SIZE));
return (tdev->pkt.size - TMECOM_RX_HDR_SIZE);
}
static bool tmecom_check_rx_done(struct tmecom *tdev)
{
return tdev->rx_done;
}
int tmecom_process_request(const void *reqbuf, size_t reqsize, void *respbuf,
size_t *respsize)
{
struct tmecom *tdev = tmedev;
long time_left = 0;
int ret = 0;
/*
* Check to handle if probe is not successful or not completed yet
*/
if (!tdev) {
pr_err("%s: tmecom dev is NULL\n", __func__);
return -ENODEV;
}
if (!reqbuf || !reqsize || (reqsize > MBOX_MAX_MSG_LEN)) {
dev_err(tdev->dev, "invalid reqbuf or reqsize\n");
return -EINVAL;
}
if (!respbuf || !respsize || (*respsize > MBOX_MAX_MSG_LEN)) {
dev_err(tdev->dev, "invalid respbuf or respsize\n");
return -EINVAL;
}
mutex_lock(&tdev->lock);
tdev->rx_done = false;
tdev->pkt.size = tmecom_encode(tdev, reqbuf, reqsize);
/*
* Controller expects a 4 byte aligned buffer
*/
tdev->pkt.size = (tdev->pkt.size + 0x3) & ~0x3;
tdev->pkt.data = tdev->txbuf;
pr_debug("tmecom encoded request size = %u\n", tdev->pkt.size);
print_hex_dump_bytes("tmecom sending bytes : ",
DUMP_PREFIX_ADDRESS, tdev->pkt.data, tdev->pkt.size);
if (mbox_send_message(tdev->chan, &tdev->pkt) < 0) {
dev_err(tdev->dev, "failed to send qmp message\n");
ret = -EAGAIN;
goto err_exit;
}
time_left = wait_event_interruptible_timeout(tdev->waitq,
tmecom_check_rx_done(tdev), tdev->cl.tx_tout);
if (!time_left) {
dev_err(tdev->dev, "request timed out\n");
ret = -ETIMEDOUT;
goto err_exit;
}
dev_info(tdev->dev, "response received\n");
pr_debug("tmecom received size = %u\n", tdev->pkt.size);
print_hex_dump_bytes("tmecom received bytes : ",
DUMP_PREFIX_ADDRESS, tdev->pkt.data, tdev->pkt.size);
if (tdev->pkt.size <= TMECOM_RX_HDR_SIZE) {
dev_err(tdev->dev, "invalid pkt.size received\n");
ret = -EPROTO;
goto err_exit;
}
*respsize = tmecom_decode(tdev, respbuf);
tdev->rx_done = false;
ret = 0;
err_exit:
mutex_unlock(&tdev->lock);
return ret;
}
EXPORT_SYMBOL(tmecom_process_request);
#if IS_ENABLED(CONFIG_DEBUG_FS)
static ssize_t tmecom_debugfs_write(struct file *file,
const char __user *userstr, size_t len, loff_t *pos)
{
int ret = 0;
size_t rxlen = 0;
struct tme_ext_err_info *err_info = (struct tme_ext_err_info *)dpkt;
if (!len || (len > MBOX_MAX_MSG_LEN)) {
pr_err("invalid message length\n");
return -EINVAL;
}
memset(dpkt, 0, sizeof(*dpkt));
ret = copy_from_user(dpkt, userstr, len);
if (ret) {
pr_err("%s copy from user failed, ret=%d\n", __func__, ret);
return len;
}
tmecom_process_request(dpkt, len, dpkt, &rxlen);
print_hex_dump_bytes("tmecom decoded bytes : ",
DUMP_PREFIX_ADDRESS, dpkt, rxlen);
pr_debug("calling TME_HWKM_CMD_BROADCAST_TP_KEY api\n");
ret = tme_hwkm_master_broadcast_transportkey(err_info);
if (ret == 0)
pr_debug("%s successful\n", __func__);
return len;
}
static const struct file_operations tmecom_debugfs_ops = {
.open = simple_open,
.write = tmecom_debugfs_write,
};
#endif /* CONFIG_DEBUG_FS */
static void tmecom_receive_message(struct mbox_client *client, void *message)
{
struct tmecom *tdev = dev_get_drvdata(client->dev);
struct qmp_pkt *pkt = NULL;
if (!message) {
dev_err(tdev->dev, "spurious message received\n");
goto tmecom_receive_end;
}
if (tdev->rx_done) {
dev_err(tdev->dev, "tmecom response pending\n");
goto tmecom_receive_end;
}
pkt = (struct qmp_pkt *)message;
tdev->pkt.size = pkt->size;
tdev->pkt.data = pkt->data;
tdev->rx_done = true;
tmecom_receive_end:
wake_up_interruptible(&tdev->waitq);
}
static int tmecom_probe(struct platform_device *pdev)
{
struct tmecom *tdev;
const char *label;
char name[32];
tdev = devm_kzalloc(&pdev->dev, sizeof(*tdev), GFP_KERNEL);
if (!tdev)
return -ENOMEM;
tdev->cl.dev = &pdev->dev;
tdev->cl.tx_block = true;
tdev->cl.tx_tout = 500;
tdev->cl.knows_txdone = false;
tdev->cl.rx_callback = tmecom_receive_message;
label = of_get_property(pdev->dev.of_node, "mbox-names", NULL);
if (!label)
return -EINVAL;
snprintf(name, 32, "%s_send_message", label);
tdev->chan = mbox_request_channel(&tdev->cl, 0);
if (IS_ERR(tdev->chan)) {
dev_err(&pdev->dev, "failed to get mbox channel\n");
return PTR_ERR(tdev->chan);
}
mutex_init(&tdev->lock);
if (tdev->chan) {
tdev->txbuf =
devm_kzalloc(&pdev->dev, MBOX_MAX_MSG_LEN, GFP_KERNEL);
if (!tdev->txbuf) {
dev_err(&pdev->dev, "message buffer alloc faile\n");
return -ENOMEM;
}
}
init_waitqueue_head(&tdev->waitq);
#if IS_ENABLED(CONFIG_DEBUG_FS)
debugfs_file = debugfs_create_file(name, 0220, NULL, tdev,
&tmecom_debugfs_ops);
if (!debugfs_file)
goto err;
#endif /* CONFIG_DEBUG_FS */
tdev->rx_done = false;
tdev->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, tdev);
tmedev = tdev;
dev_info(&pdev->dev, "tmecom probe success\n");
return 0;
#if IS_ENABLED(CONFIG_DEBUG_FS)
err:
#endif /* CONFIG_DEBUG_FS */
mbox_free_channel(tdev->chan);
return -ENOMEM;
}
static int tmecom_remove(struct platform_device *pdev)
{
struct tmecom *tdev = platform_get_drvdata(pdev);
#if IS_ENABLED(CONFIG_DEBUG_FS)
debugfs_remove(debugfs_file);
#endif /* CONFIG_DEBUG_FS */
if (tdev->chan)
mbox_free_channel(tdev->chan);
dev_info(&pdev->dev, "tmecom remove success\n");
return 0;
}
static const struct of_device_id tmecom_match_tbl[] = {
{.compatible = "qcom,tmecom-qmp-client"},
{},
};
static struct platform_driver tmecom_driver = {
.probe = tmecom_probe,
.remove = tmecom_remove,
.driver = {
.name = "tmecom-qmp-client",
.suppress_bind_attrs = true,
.of_match_table = tmecom_match_tbl,
},
};
module_platform_driver(tmecom_driver);
MODULE_DESCRIPTION("MSM TMECom QTI mailbox protocol client");
MODULE_LICENSE("GPL v2");