123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- // SPDX-License-Identifier: GPL-2.0-only
- /* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
- *
- * RMNET_CTL client handlers
- *
- */
- #include <linux/debugfs.h>
- #include <linux/ipc_logging.h>
- #include <linux/version.h>
- #include "rmnet_ctl.h"
- #include "rmnet_ctl_client.h"
- #define RMNET_CTL_LOG_PAGE 10
- #define RMNET_CTL_LOG_NAME "rmnet_ctl"
- #define RMNET_CTL_LOG_LVL "ipc_log_lvl"
- struct rmnet_ctl_client {
- struct rmnet_ctl_client_hooks hooks;
- };
- struct rmnet_ctl_endpoint {
- struct rmnet_ctl_dev __rcu *dev;
- struct rmnet_ctl_client __rcu *client;
- struct dentry *dbgfs_dir;
- struct dentry *dbgfs_loglvl;
- void *ipc_log;
- };
- #if defined(CONFIG_IPA_DEBUG) || defined(CONFIG_MHI_DEBUG)
- #define CONFIG_RMNET_CTL_DEBUG 1
- #endif
- #ifdef CONFIG_RMNET_CTL_DEBUG
- static u8 ipc_log_lvl = RMNET_CTL_LOG_DEBUG;
- #else
- static u8 ipc_log_lvl = RMNET_CTL_LOG_ERR;
- #endif
- static DEFINE_SPINLOCK(client_lock);
- static struct rmnet_ctl_endpoint ctl_ep;
- void rmnet_ctl_set_dbgfs(bool enable)
- {
- if (enable) {
- if (IS_ERR_OR_NULL(ctl_ep.dbgfs_dir))
- ctl_ep.dbgfs_dir = debugfs_create_dir(
- RMNET_CTL_LOG_NAME, NULL);
- if (!IS_ERR_OR_NULL(ctl_ep.dbgfs_dir))
- debugfs_create_u8((const char *) RMNET_CTL_LOG_LVL,
- (umode_t) 0644,
- (struct dentry *) ctl_ep.dbgfs_dir,
- (u8 *) &ipc_log_lvl);
- if (!ctl_ep.ipc_log)
- ctl_ep.ipc_log = ipc_log_context_create(
- RMNET_CTL_LOG_PAGE, RMNET_CTL_LOG_NAME, 0);
- } else {
- debugfs_remove_recursive(ctl_ep.dbgfs_dir);
- ipc_log_context_destroy(ctl_ep.ipc_log);
- ctl_ep.dbgfs_dir = NULL;
- ctl_ep.dbgfs_loglvl = NULL;
- ctl_ep.ipc_log = NULL;
- }
- }
- void rmnet_ctl_endpoint_setdev(const struct rmnet_ctl_dev *dev)
- {
- rcu_assign_pointer(ctl_ep.dev, dev);
- }
- void rmnet_ctl_endpoint_post(const void *data, size_t len)
- {
- struct rmnet_ctl_client *client;
- struct sk_buff *skb;
- if (unlikely(!data || !len))
- return;
- if (len == 0xFFFFFFFF) {
- skb = (struct sk_buff *)data;
- rmnet_ctl_log_info("RX", skb->data, skb->len);
- rcu_read_lock();
- client = rcu_dereference(ctl_ep.client);
- if (client && client->hooks.ctl_dl_client_hook) {
- skb->protocol = htons(ETH_P_MAP);
- client->hooks.ctl_dl_client_hook(skb);
- } else {
- kfree(skb);
- }
- rcu_read_unlock();
- } else {
- rmnet_ctl_log_info("RX", data, len);
- rcu_read_lock();
- client = rcu_dereference(ctl_ep.client);
- if (client && client->hooks.ctl_dl_client_hook) {
- skb = alloc_skb(len, GFP_ATOMIC);
- if (skb) {
- skb_put_data(skb, data, len);
- skb->protocol = htons(ETH_P_MAP);
- client->hooks.ctl_dl_client_hook(skb);
- }
- }
- rcu_read_unlock();
- }
- }
- void *rmnet_ctl_register_client(struct rmnet_ctl_client_hooks *hook)
- {
- struct rmnet_ctl_client *client;
- if (!hook)
- return NULL;
- client = kzalloc(sizeof(*client), GFP_KERNEL);
- if (!client)
- return NULL;
- client->hooks = *hook;
- spin_lock(&client_lock);
- /* Only support one client for now */
- if (rcu_dereference(ctl_ep.client)) {
- spin_unlock(&client_lock);
- kfree(client);
- return NULL;
- }
- rcu_assign_pointer(ctl_ep.client, client);
- spin_unlock(&client_lock);
- rmnet_ctl_set_dbgfs(true);
- return client;
- }
- EXPORT_SYMBOL(rmnet_ctl_register_client);
- int rmnet_ctl_unregister_client(void *handle)
- {
- struct rmnet_ctl_client *client = (struct rmnet_ctl_client *)handle;
- spin_lock(&client_lock);
- if (rcu_dereference(ctl_ep.client) != client) {
- spin_unlock(&client_lock);
- return -EINVAL;
- }
- RCU_INIT_POINTER(ctl_ep.client, NULL);
- spin_unlock(&client_lock);
- synchronize_rcu();
- kfree(client);
- rmnet_ctl_set_dbgfs(false);
- return 0;
- }
- EXPORT_SYMBOL(rmnet_ctl_unregister_client);
- int rmnet_ctl_send_client(void *handle, struct sk_buff *skb)
- {
- struct rmnet_ctl_client *client = (struct rmnet_ctl_client *)handle;
- struct rmnet_ctl_dev *dev;
- int rc = -EINVAL;
- if (client != rcu_dereference(ctl_ep.client)) {
- kfree_skb(skb);
- return rc;
- }
- rmnet_ctl_log_info("TX", skb->data, skb->len);
- rcu_read_lock();
- dev = rcu_dereference(ctl_ep.dev);
- if (dev && dev->xmit)
- rc = dev->xmit(dev, skb);
- else
- kfree_skb(skb);
- rcu_read_unlock();
- if (rc)
- rmnet_ctl_log_err("TXE", rc, NULL, 0);
- return rc;
- }
- EXPORT_SYMBOL(rmnet_ctl_send_client);
- void rmnet_ctl_log(enum rmnet_ctl_log_lvl lvl, const char *msg,
- int rc, const void *data, unsigned int len)
- {
- if (lvl <= ipc_log_lvl && ctl_ep.ipc_log) {
- if (data == NULL || len == 0)
- ipc_log_string(ctl_ep.ipc_log, "%3s(%d): (null)\n",
- msg, rc);
- else
- ipc_log_string(ctl_ep.ipc_log, "%3s(%d): %*ph\n",
- msg, rc, len > 32 ? 32 : len, data);
- }
- }
- EXPORT_SYMBOL(rmnet_ctl_log);
- static struct rmnet_ctl_client_if client_if = {
- .reg = rmnet_ctl_register_client,
- .dereg = rmnet_ctl_unregister_client,
- .send = rmnet_ctl_send_client,
- .log = rmnet_ctl_log,
- };
- struct rmnet_ctl_client_if *rmnet_ctl_if(void)
- {
- return &client_if;
- }
- EXPORT_SYMBOL(rmnet_ctl_if);
- int rmnet_ctl_get_stats(u64 *s, int n)
- {
- struct rmnet_ctl_dev *dev;
- rcu_read_lock();
- dev = rcu_dereference(ctl_ep.dev);
- if (dev && n > 0) {
- n = min(n, (int)(sizeof(dev->stats) / sizeof(u64)));
- memcpy(s, &dev->stats, n * sizeof(u64));
- }
- rcu_read_unlock();
- return n;
- }
- EXPORT_SYMBOL(rmnet_ctl_get_stats);
|