summaryrefslogtreecommitdiff
path: root/drivers/soc
diff options
context:
space:
mode:
authorKarthikeyan Ramasubramanian <kramasub@codeaurora.org>2016-02-11 14:36:19 -0700
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:08:44 -0700
commitf6f22bd54e584905d8cfb2677b8ce93fd2f32977 (patch)
tree968cc753bb67c948a4166cdb215ca550b993e0f4 /drivers/soc
parent85b08d32e4bb7556f5e83a50841b69ed9eef9ba2 (diff)
soc: qcom: Add snapshot of GLINK_PKT Driver
This snapshot is taken as of msm-3.18 commit e70ad0cd (Promotion of kernel.lnx.3.18-151201.) Signed-off-by: Karthikeyan Ramasubramanian <kramasub@codeaurora.org>
Diffstat (limited to 'drivers/soc')
-rw-r--r--drivers/soc/qcom/Kconfig9
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/msm_glink_pkt.c1368
3 files changed, 1378 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 3b967ca2c4b3..dda229dd8e84 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -211,6 +211,15 @@ config MSM_IPC_ROUTER_GLINK_XPRT
this layer registers a transport with IPC Router and enable
message exchange.
+config MSM_GLINK_PKT
+ bool "Enable device interface for GLINK packet channels"
+ depends on MSM_GLINK
+ help
+ G-link packet driver provides the interface for the userspace
+ clients to communicate over G-Link via deivce nodes.
+ This enable the usersapce clients to read and write to
+ some glink packets channel.
+
config QCOM_SMD
tristate "Qualcomm Shared Memory Driver (SMD)"
depends on QCOM_SMEM
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 2c8290adec44..7675dd5d64a3 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MSM_IPC_ROUTER_SMD_XPRT) += ipc_router_smd_xprt.o
obj-$(CONFIG_MSM_IPC_ROUTER_HSIC_XPRT) += ipc_router_hsic_xprt.o
obj-$(CONFIG_MSM_IPC_ROUTER_MHI_XPRT) += ipc_router_mhi_xprt.o
obj-$(CONFIG_MSM_IPC_ROUTER_GLINK_XPRT) += ipc_router_glink_xprt.o
+obj-$(CONFIG_MSM_GLINK_PKT) += msm_glink_pkt.o
obj-$(CONFIG_MEM_SHARE_QMI_SERVICE) += memshare/
obj-$(CONFIG_MSM_PIL_SSR_GENERIC) += subsys-pil-tz.o
diff --git a/drivers/soc/qcom/msm_glink_pkt.c b/drivers/soc/qcom/msm_glink_pkt.c
new file mode 100644
index 000000000000..95b67fcaafd2
--- /dev/null
+++ b/drivers/soc/qcom/msm_glink_pkt.c
@@ -0,0 +1,1368 @@
+/* Copyright (c) 2014-2015, 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.
+ *
+ */
+/*
+ * G-link Packet Driver -- Provides a binary G-link non-muxed packet port
+ * interface.
+ */
+
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <asm/ioctls.h>
+#include <linux/mm.h>
+#include <linux/of.h>
+#include <linux/ipc_logging.h>
+#include <linux/termios.h>
+
+#include <soc/qcom/glink.h>
+
+#define MODULE_NAME "msm_glinkpkt"
+#define DEVICE_NAME "glinkpkt"
+#define WAKEUPSOURCE_TIMEOUT (2000) /* two seconds */
+#define CLOSE_WAIT_TIMEOUT 1000 /* one seconds */
+
+#define GLINK_PKT_IOCTL_MAGIC (0xC3)
+
+#define GLINK_PKT_IOCTL_QUEUE_RX_INTENT \
+ _IOW(GLINK_PKT_IOCTL_MAGIC, 0, unsigned int)
+
+#define SMD_DTR_SIG BIT(31)
+#define SMD_CTS_SIG BIT(30)
+#define SMD_CD_SIG BIT(29)
+#define SMD_RI_SIG BIT(28)
+
+#define map_to_smd_trans_signal(sigs) \
+ do { \
+ sigs &= 0x0fff; \
+ if (sigs & TIOCM_DTR) \
+ sigs |= SMD_DTR_SIG; \
+ if (sigs & TIOCM_RTS) \
+ sigs |= SMD_CTS_SIG; \
+ if (sigs & TIOCM_CD) \
+ sigs |= SMD_CD_SIG; \
+ if (sigs & TIOCM_RI) \
+ sigs |= SMD_RI_SIG; \
+ } while (0)
+
+#define map_from_smd_trans_signal(sigs) \
+ do { \
+ if (sigs & SMD_DTR_SIG) \
+ sigs |= TIOCM_DTR; \
+ if (sigs & SMD_CTS_SIG) \
+ sigs |= TIOCM_RTS; \
+ if (sigs & SMD_CD_SIG) \
+ sigs |= TIOCM_CD; \
+ if (sigs & SMD_RI_SIG) \
+ sigs |= TIOCM_RI; \
+ sigs &= 0x0fff; \
+ } while (0)
+
+/**
+ * glink_pkt_dev - G-Link packet device structure
+ * dev_list: G-Link packets device list.
+ * open_cfg: Transport configuration used to open Logical channel.
+ * dev_name: Device node name used by the clients.
+ * handle: Opaque Channel handle returned by G-Link.
+ * ch_lock: Per channel lock for synchronization.
+ * ch_satet: flag used to check the channel state.
+ * cdev: structure to the internal character device.
+ * devicep: Pointer to the G-Link pkt class device structure.
+ * i: Index to this character device.
+ * ref_cnt: number of references to this device.
+ * poll_mode: flag to check polling mode.
+ * ch_read_wait_queue: reader thread wait queue.
+ * ch_opened_wait_queue: open thread wait queue.
+ * ch_closed_wait_queue: close thread wait queue.
+ * pkt_list: The pending Rx packets list.
+ * pkt_list_lock: Lock to protect @pkt_list.
+ * pa_ws: Packet arrival Wakeup source.
+ * packet_arrival_work: Hold the wakeup source worker info.
+ * pa_spinlock: Packet arrival spinlock.
+ * ws_locked: flag to check wakeup source state.
+ * sigs_updated: flag to check signal update.
+ * open_time_wait: wait time for channel to fully open.
+ * in_reset: flag to check SSR state.
+ */
+struct glink_pkt_dev {
+ struct list_head dev_list;
+ struct glink_open_config open_cfg;
+ const char *dev_name;
+ void *handle;
+ struct mutex ch_lock;
+ unsigned ch_state;
+
+ struct cdev cdev;
+ struct device *devicep;
+
+ int i;
+ int ref_cnt;
+ int poll_mode;
+
+ wait_queue_head_t ch_read_wait_queue;
+ wait_queue_head_t ch_opened_wait_queue;
+ wait_queue_head_t ch_closed_wait_queue;
+ struct list_head pkt_list;
+ spinlock_t pkt_list_lock;
+
+ struct wakeup_source pa_ws; /* Packet Arrival Wakeup Source */
+ struct work_struct packet_arrival_work;
+ spinlock_t pa_spinlock;
+ int ws_locked;
+ int sigs_updated;
+ int open_time_wait;
+ int in_reset;
+};
+
+/**
+ * glink_rx_pkt - Pointer to Rx packet
+ * list: Chain the Rx packets into list.
+ * data: pointer to the Rx data.
+ * pkt_ptiv: private pointer to the Rx packet.
+ * size: The size of received data.
+ */
+struct glink_rx_pkt {
+ struct list_head list;
+ const void *data;
+ const void *pkt_priv;
+ size_t size;
+};
+
+/**
+ * queue_rx_intent_work - Work item to Queue Rx intent.
+ * size: The size of intent to be queued.
+ * devp: Pointer to the device structure.
+ * work: Hold the worker function information.
+ */
+struct queue_rx_intent_work {
+ size_t intent_size;
+ struct glink_pkt_dev *devp;
+ struct work_struct work;
+};
+
+/**
+ * notify_state_work - Work item to notify channel state.
+ * state: Channel new state.
+ * devp: Pointer to the device structure.
+ * work: Hold the worker function information.
+ */
+struct notify_state_work {
+ unsigned state;
+ struct glink_pkt_dev *devp;
+ void *handle;
+ struct work_struct work;
+};
+
+static DEFINE_MUTEX(glink_pkt_dev_lock_lha1);
+static LIST_HEAD(glink_pkt_dev_list);
+static DEFINE_MUTEX(glink_pkt_driver_lock_lha1);
+static LIST_HEAD(glink_pkt_driver_list);
+
+struct class *glink_pkt_classp;
+static dev_t glink_pkt_number;
+struct workqueue_struct *glink_pkt_wq;
+
+static int num_glink_pkt_ports;
+
+#define GLINK_PKT_IPC_LOG_PAGE_CNT 2
+static void *glink_pkt_ilctxt;
+
+enum {
+ GLINK_PKT_STATUS = 1U << 0,
+};
+
+static int msm_glink_pkt_debug_mask;
+module_param_named(debug_mask, msm_glink_pkt_debug_mask,
+ int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+static void glink_pkt_queue_rx_intent_worker(struct work_struct *work);
+static void glink_pkt_notify_state_worker(struct work_struct *work);
+static bool glink_pkt_read_avail(struct glink_pkt_dev *devp);
+
+#define DEBUG
+
+#ifdef DEBUG
+
+#define GLINK_PKT_LOG_STRING(x...) \
+do { \
+ if (glink_pkt_ilctxt) \
+ ipc_log_string(glink_pkt_ilctxt, "<GLINK_PKT>: "x); \
+} while (0)
+
+#define GLINK_PKT_INFO(x...) \
+do { \
+ if (msm_glink_pkt_debug_mask & GLINK_PKT_STATUS) \
+ pr_info("Status: "x); \
+ GLINK_PKT_LOG_STRING(x); \
+} while (0)
+
+#define GLINK_PKT_ERR(x...) \
+do { \
+ pr_err("<GLINK_PKT> err: "x); \
+ GLINK_PKT_LOG_STRING(x); \
+} while (0)
+
+#else
+#define GLINK_PKT_INFO(x...) do {} while (0)
+#define GLINK_PKT_ERR(x...) do {} while (0)
+#endif
+
+static ssize_t open_timeout_store(struct device *d,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t n)
+{
+ struct glink_pkt_dev *devp;
+ long tmp;
+
+ mutex_lock(&glink_pkt_dev_lock_lha1);
+ list_for_each_entry(devp, &glink_pkt_dev_list, dev_list) {
+ if (devp->devicep == d) {
+ if (!kstrtol(buf, 0, &tmp)) {
+ devp->open_time_wait = tmp;
+ mutex_unlock(&glink_pkt_dev_lock_lha1);
+ return n;
+ } else {
+ mutex_unlock(&glink_pkt_dev_lock_lha1);
+ pr_err("%s: unable to convert: %s to an int\n",
+ __func__, buf);
+ return -EINVAL;
+ }
+ }
+ }
+ mutex_unlock(&glink_pkt_dev_lock_lha1);
+ GLINK_PKT_ERR("%s: unable to match device to valid port\n", __func__);
+ return -EINVAL;
+}
+
+static ssize_t open_timeout_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct glink_pkt_dev *devp;
+
+ mutex_lock(&glink_pkt_dev_lock_lha1);
+ list_for_each_entry(devp, &glink_pkt_dev_list, dev_list) {
+ if (devp->devicep == d) {
+ mutex_unlock(&glink_pkt_dev_lock_lha1);
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ devp->open_time_wait);
+ }
+ }
+ mutex_unlock(&glink_pkt_dev_lock_lha1);
+ GLINK_PKT_ERR("%s: unable to match device to valid port\n", __func__);
+ return -EINVAL;
+
+}
+
+static DEVICE_ATTR(open_timeout, 0664, open_timeout_show, open_timeout_store);
+
+/**
+ * packet_arrival_worker() - wakeup source timeout worker fn
+ * work: Work struct queued
+ *
+ * This function used to keep the system awake to allow
+ * userspace client to read the received packet.
+ */
+static void packet_arrival_worker(struct work_struct *work)
+{
+ struct glink_pkt_dev *devp;
+ unsigned long flags;
+
+ devp = container_of(work, struct glink_pkt_dev,
+ packet_arrival_work);
+ mutex_lock(&devp->ch_lock);
+ spin_lock_irqsave(&devp->pa_spinlock, flags);
+ if (devp->ws_locked) {
+ GLINK_PKT_INFO("%s locking glink_pkt_dev id:%d wakeup source\n",
+ __func__, devp->i);
+ /*
+ * Keep system awake long enough to allow userspace client
+ * to process the packet.
+ */
+ __pm_wakeup_event(&devp->pa_ws, WAKEUPSOURCE_TIMEOUT);
+ }
+ spin_unlock_irqrestore(&devp->pa_spinlock, flags);
+ mutex_unlock(&devp->ch_lock);
+}
+
+/**
+ * glink_pkt_notify_rx() - Rx data Callback from G-Link core layer
+ * handle: Opaque Channel handle returned by GLink.
+ * priv: private pointer to the channel.
+ * pkt_priv: private pointer to the packet.
+ * ptr: Pointer to the Rx data.
+ * size: Size of the Rx data.
+ *
+ * This callback function is notified on receiving the data from
+ * remote channel.
+ */
+void glink_pkt_notify_rx(void *handle, const void *priv,
+ const void *pkt_priv,
+ const void *ptr, size_t size)
+{
+ struct glink_rx_pkt *pkt = NULL;
+ struct glink_pkt_dev *devp = (struct glink_pkt_dev *)priv;
+ unsigned long flags;
+
+ GLINK_PKT_INFO("%s(): priv[%p] data[%p] size[%zu]\n",
+ __func__, pkt_priv, (char *)ptr, size);
+
+ pkt = kzalloc(sizeof(*pkt), GFP_ATOMIC);
+ if (!pkt) {
+ GLINK_PKT_ERR("%s: memory allocation failed\n", __func__);
+ return;
+ }
+
+ pkt->data = ptr;
+ pkt->pkt_priv = pkt_priv;
+ pkt->size = size;
+ spin_lock_irqsave(&devp->pkt_list_lock, flags);
+ list_add_tail(&pkt->list, &devp->pkt_list);
+ spin_unlock_irqrestore(&devp->pkt_list_lock, flags);
+
+ spin_lock_irqsave(&devp->pa_spinlock, flags);
+ __pm_stay_awake(&devp->pa_ws);
+ devp->ws_locked = 1;
+ spin_unlock_irqrestore(&devp->pa_spinlock, flags);
+ wake_up(&devp->ch_read_wait_queue);
+ schedule_work(&devp->packet_arrival_work);
+ return;
+}
+
+/**
+ * glink_pkt_notify_tx_done() - Tx done callback function
+ * handle: Opaque Channel handle returned by GLink.
+ * priv: private pointer to the channel.
+ * pkt_priv: private pointer to the packet.
+ * ptr: Pointer to the Tx data.
+ *
+ * This callback function is notified when the remote core
+ * signals the Rx done to the local core.
+ */
+void glink_pkt_notify_tx_done(void *handle, const void *priv,
+ const void *pkt_priv, const void *ptr)
+{
+ GLINK_PKT_INFO("%s(): priv[%p] pkt_priv[%p] ptr[%p]\n",
+ __func__, priv, pkt_priv, ptr);
+/* Free Tx buffer allocated in glink_pkt_write */
+ kfree(ptr);
+}
+
+/**
+ * glink_pkt_notify_state() - state notification callback function
+ * handle: Opaque Channel handle returned by GLink.
+ * priv: private pointer to the channel.
+ * event: channel state
+ *
+ * This callback function is notified when the remote channel alters
+ * the channel state and send the event to local G-Link core.
+ */
+void glink_pkt_notify_state(void *handle, const void *priv, unsigned event)
+{
+ struct glink_pkt_dev *devp = (struct glink_pkt_dev *)priv;
+ struct notify_state_work *work_item;
+
+ if ((devp->handle != NULL) && (devp->handle != handle)) {
+ GLINK_PKT_ERR("%s() event[%d] on incorrect channel [%s]\n",
+ __func__, event, devp->open_cfg.name);
+ return;
+ }
+ GLINK_PKT_INFO("%s(): event[%d] on [%s]\n", __func__, event,
+ devp->open_cfg.name);
+
+ work_item = kzalloc(sizeof(*work_item), GFP_ATOMIC);
+ if (!work_item) {
+ GLINK_PKT_ERR("%s() failed allocate work_item\n", __func__);
+ return;
+ }
+
+ work_item->state = event;
+ work_item->devp = devp;
+ work_item->handle = handle;
+ INIT_WORK(&work_item->work, glink_pkt_notify_state_worker);
+ queue_work(glink_pkt_wq, &work_item->work);
+}
+
+/**
+ * glink_pkt_rmt_rx_intent_req_cb() - Remote Rx intent request callback
+ * handle: Opaque Channel handle returned by GLink.
+ * priv: private pointer to the channel.
+ * sz: the size of the requested Rx intent
+ *
+ * This callback function is notified when remote client
+ * request the intent from local client.
+ */
+bool glink_pkt_rmt_rx_intent_req_cb(void *handle, const void *priv, size_t sz)
+{
+ struct queue_rx_intent_work *work_item;
+ GLINK_PKT_INFO("%s(): QUEUE RX INTENT to receive size[%zu]\n",
+ __func__, sz);
+
+ work_item = kzalloc(sizeof(*work_item), GFP_ATOMIC);
+ if (!work_item) {
+ GLINK_PKT_ERR("%s failed allocate work_item\n", __func__);
+ return false;
+ }
+
+ work_item->intent_size = sz;
+ work_item->devp = (struct glink_pkt_dev *)priv;
+ INIT_WORK(&work_item->work, glink_pkt_queue_rx_intent_worker);
+ queue_work(glink_pkt_wq, &work_item->work);
+
+ return true;
+}
+
+/**
+ * glink_pkt_notify_rx_sigs() - signals callback
+ * handle: Opaque Channel handle returned by GLink.
+ * priv: private pointer to the channel.
+ * old_sigs: signal before modification
+ * new_sigs: signal after modification
+ *
+ * This callback function is notified when remote client
+ * updated the signal.
+ */
+void glink_pkt_notify_rx_sigs(void *handle, const void *priv,
+ uint32_t old_sigs, uint32_t new_sigs)
+{
+ struct glink_pkt_dev *devp = (struct glink_pkt_dev *)priv;
+ GLINK_PKT_INFO("%s(): sigs old[%x] new[%x]\n",
+ __func__, old_sigs, new_sigs);
+ mutex_lock(&devp->ch_lock);
+ devp->sigs_updated = true;
+ mutex_unlock(&devp->ch_lock);
+ wake_up(&devp->ch_read_wait_queue);
+}
+
+/**
+ * glink_pkt_queue_rx_intent_worker() - Queue Rx worker function
+ *
+ * work: Pointer to the work struct
+ *
+ * This function is used to queue the RX intent which
+ * can sleep during allocation of larger buffers.
+ */
+static void glink_pkt_queue_rx_intent_worker(struct work_struct *work)
+{
+ int ret;
+ struct queue_rx_intent_work *work_item =
+ container_of(work,
+ struct queue_rx_intent_work, work);
+ struct glink_pkt_dev *devp = work_item->devp;
+
+ if (!devp || !devp->handle) {
+ GLINK_PKT_ERR("%s: Invalid device Handle\n", __func__);
+ kfree(work_item);
+ return;
+ }
+
+ ret = glink_queue_rx_intent(devp->handle, devp, work_item->intent_size);
+ GLINK_PKT_INFO("%s: Triggered with size[%zu] ret[%d]\n",
+ __func__, work_item->intent_size, ret);
+ if (ret)
+ GLINK_PKT_ERR("%s queue_rx_intent failed\n", __func__);
+ kfree(work_item);
+}
+
+/**
+ * glink_pkt_notify_state_worker() - Notify state worker function
+ *
+ * work: Pointer to the work struct
+ *
+ * This function is used to notify the channel state and update the
+ * internal data structure.
+ */
+static void glink_pkt_notify_state_worker(struct work_struct *work)
+{
+ struct notify_state_work *work_item =
+ container_of(work,
+ struct notify_state_work, work);
+ struct glink_pkt_dev *devp = work_item->devp;
+ unsigned event = work_item->state;
+ void *handle = work_item->handle;
+
+ if (!devp) {
+ GLINK_PKT_ERR("%s: Invalid device Handle\n", __func__);
+ kfree(work_item);
+ return;
+ }
+
+ GLINK_PKT_INFO("%s(): event[%d] on [%s]\n", __func__,
+ event, devp->open_cfg.name);
+ mutex_lock(&devp->ch_lock);
+ devp->ch_state = event;
+ if (event == GLINK_CONNECTED) {
+ if (!devp->handle)
+ devp->handle = handle;
+ devp->in_reset = 0;
+ wake_up_interruptible(&devp->ch_opened_wait_queue);
+ } else if (event == GLINK_REMOTE_DISCONNECTED) {
+ devp->in_reset = 1;
+ wake_up(&devp->ch_read_wait_queue);
+ wake_up_interruptible(&devp->ch_opened_wait_queue);
+ } else if (event == GLINK_LOCAL_DISCONNECTED) {
+ if (devp->handle == handle)
+ devp->handle = NULL;
+ wake_up_interruptible(&devp->ch_closed_wait_queue);
+ }
+ mutex_unlock(&devp->ch_lock);
+ kfree(work_item);
+}
+
+/**
+ * glink_pkt_read_avail() - check any pending packets to read
+ * devp: pointer to G-Link packet device.
+ *
+ * This function is used to check any pending data packets are
+ * available to read or not.
+ */
+static bool glink_pkt_read_avail(struct glink_pkt_dev *devp)
+{
+ bool list_is_empty;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devp->pkt_list_lock, flags);
+ list_is_empty = list_empty(&devp->pkt_list);
+ spin_unlock_irqrestore(&devp->pkt_list_lock, flags);
+ return !list_is_empty;
+}
+
+/**
+ * glink_pkt_read() - read() syscall for the glink_pkt device
+ * file: Pointer to the file structure.
+ * buf: Pointer to the userspace buffer.
+ * count: Number bytes to read from the file.
+ * ppos: Pointer to the position into the file.
+ *
+ * This function is used to Read the data from glink pkt device when
+ * userspace client do a read() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+ssize_t glink_pkt_read(struct file *file,
+ char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ int ret = 0;
+ struct glink_pkt_dev *devp;
+ struct glink_rx_pkt *pkt = NULL;
+ unsigned long flags;
+
+ devp = file->private_data;
+
+ if (!devp) {
+ GLINK_PKT_ERR("%s on NULL glink_pkt_dev\n", __func__);
+ return -EINVAL;
+ }
+ if (!devp->handle) {
+ GLINK_PKT_ERR("%s on a closed glink_pkt_dev id:%d\n",
+ __func__, devp->i);
+ return -EINVAL;
+ }
+ if (devp->in_reset) {
+ GLINK_PKT_ERR("%s: notifying reset for glink_pkt_dev id:%d\n",
+ __func__, devp->i);
+ return -ENETRESET;
+ }
+
+ if (!glink_rx_intent_exists(devp->handle, count)) {
+ ret = glink_queue_rx_intent(devp->handle, devp, count);
+ if (ret) {
+ GLINK_PKT_ERR("%s: failed to queue_rx_intent ret[%d]\n",
+ __func__, ret);
+ return ret;
+ }
+ }
+
+ GLINK_PKT_INFO("Begin %s on glink_pkt_dev id:%d buffer_size %zu\n",
+ __func__, devp->i, count);
+
+ ret = wait_event_interruptible(devp->ch_read_wait_queue,
+ !devp->handle || devp->in_reset ||
+ glink_pkt_read_avail(devp));
+ if (devp->in_reset) {
+ GLINK_PKT_ERR("%s: notifying reset for glink_pkt_dev id:%d\n",
+ __func__, devp->i);
+ return -ENETRESET;
+ }
+ if (!devp->handle) {
+ GLINK_PKT_ERR("%s on a closed glink_pkt_dev id:%d\n",
+ __func__, devp->i);
+ return -EINVAL;
+ }
+ if (ret < 0) {
+ /* qualify error message */
+ if (ret != -ERESTARTSYS) {
+ /* we get this anytime a signal comes in */
+ GLINK_PKT_ERR("%s: wait on dev id:%d ret %i\n",
+ __func__, devp->i, ret);
+ }
+ return ret;
+ }
+
+ spin_lock_irqsave(&devp->pkt_list_lock, flags);
+ pkt = list_first_entry(&devp->pkt_list, struct glink_rx_pkt, list);
+ if (pkt->size > count) {
+ GLINK_PKT_ERR("%s: Small Buff on dev Id:%d-[%zu > %zu]\n",
+ __func__, devp->i, pkt->size, count);
+ spin_unlock_irqrestore(&devp->pkt_list_lock, flags);
+ return -ETOOSMALL;
+ }
+ list_del(&pkt->list);
+ spin_unlock_irqrestore(&devp->pkt_list_lock, flags);
+
+ ret = copy_to_user(buf, pkt->data, pkt->size);
+ BUG_ON(ret != 0);
+
+ ret = pkt->size;
+ glink_rx_done(devp->handle, pkt->data, false);
+ kfree(pkt);
+
+ mutex_lock(&devp->ch_lock);
+ spin_lock_irqsave(&devp->pa_spinlock, flags);
+ if (devp->poll_mode && !glink_pkt_read_avail(devp)) {
+ __pm_relax(&devp->pa_ws);
+ devp->ws_locked = 0;
+ devp->poll_mode = 0;
+ GLINK_PKT_INFO("%s unlocked pkt_dev id:%d wakeup_source\n",
+ __func__, devp->i);
+ }
+ spin_unlock_irqrestore(&devp->pa_spinlock, flags);
+ mutex_unlock(&devp->ch_lock);
+
+ GLINK_PKT_INFO("End %s on glink_pkt_dev id:%d ret[%d]\n",
+ __func__, devp->i, ret);
+ return ret;
+}
+
+/**
+ * glink_pkt_write() - write() syscall for the glink_pkt device
+ * file: Pointer to the file structure.
+ * buf: Pointer to the userspace buffer.
+ * count: Number bytes to read from the file.
+ * ppos: Pointer to the position into the file.
+ *
+ * This function is used to write the data to glink pkt device when
+ * userspace client do a write() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+ssize_t glink_pkt_write(struct file *file,
+ const char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ int ret = 0;
+ struct glink_pkt_dev *devp;
+ void *data;
+
+ devp = file->private_data;
+
+ if (!count) {
+ GLINK_PKT_ERR("%s: data count is zero\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!devp) {
+ GLINK_PKT_ERR("%s on NULL glink_pkt_dev\n", __func__);
+ return -EINVAL;
+ }
+ if (!devp->handle) {
+ GLINK_PKT_ERR("%s on a closed glink_pkt_dev id:%d\n",
+ __func__, devp->i);
+ return -EINVAL;
+ }
+ if (devp->in_reset) {
+ GLINK_PKT_ERR("%s: notifying reset for glink_pkt_dev id:%d\n",
+ __func__, devp->i);
+ return -ENETRESET;
+ };
+
+ GLINK_PKT_INFO("Begin %s on glink_pkt_dev id:%d buffer_size %zu\n",
+ __func__, devp->i, count);
+ data = kzalloc(count, GFP_KERNEL);
+ if (!data) {
+ GLINK_PKT_ERR("%s buffer allocation failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ ret = copy_from_user(data, buf, count);
+ BUG_ON(ret != 0);
+
+ ret = glink_tx(devp->handle, data, data, count, GLINK_TX_REQ_INTENT);
+ if (ret) {
+ GLINK_PKT_ERR("%s glink_tx failed ret[%d]\n", __func__, ret);
+ kfree(data);
+ return ret;
+ }
+
+ GLINK_PKT_INFO("Finished %s on glink_pkt_dev id:%d buffer_size %zu\n",
+ __func__, devp->i, count);
+
+ return count;
+}
+
+/**
+ * glink_pkt_poll() - poll() syscall for the glink_pkt device
+ * file: Pointer to the file structure.
+ * wait: pointer to Poll table.
+ *
+ * This function is used to poll on the glink pkt device when
+ * userspace client do a poll() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+static unsigned int glink_pkt_poll(struct file *file, poll_table *wait)
+{
+ struct glink_pkt_dev *devp;
+ unsigned int mask = 0;
+
+ devp = file->private_data;
+ if (!devp || !devp->handle) {
+ GLINK_PKT_ERR("%s: Invalid device handle\n", __func__);
+ return POLLERR;
+ }
+ if (devp->in_reset)
+ return POLLHUP;
+
+ devp->poll_mode = 1;
+ poll_wait(file, &devp->ch_read_wait_queue, wait);
+ mutex_lock(&devp->ch_lock);
+ if (!devp->handle) {
+ mutex_unlock(&devp->ch_lock);
+ return POLLERR;
+ }
+ if (devp->in_reset) {
+ mutex_unlock(&devp->ch_lock);
+ return POLLHUP;
+ }
+
+ if (glink_pkt_read_avail(devp)) {
+ mask |= POLLIN | POLLRDNORM;
+ GLINK_PKT_INFO("%s sets POLLIN for glink_pkt_dev id: %d\n",
+ __func__, devp->i);
+ }
+
+ if (devp->sigs_updated) {
+ mask |= POLLPRI;
+ GLINK_PKT_INFO("%s sets POLLPRI for glink_pkt_dev id: %d\n",
+ __func__, devp->i);
+ }
+ mutex_unlock(&devp->ch_lock);
+
+ return mask;
+}
+
+/**
+ * glink_pkt_tiocmset() - set the signals for glink_pkt device
+ * devp: Pointer to the glink_pkt device structure.
+ * cmd: IOCTL command.
+ * arg: Arguments to the ioctl call.
+ *
+ * This function is used to set the signals on the glink pkt device
+ * when userspace client do a ioctl() system call with TIOCMBIS,
+ * TIOCMBIC and TICOMSET.
+ */
+static int glink_pkt_tiocmset(struct glink_pkt_dev *devp, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret;
+ uint32_t sigs;
+ uint32_t val;
+
+ ret = get_user(val, (uint32_t *)arg);
+ if (ret)
+ return ret;
+ map_to_smd_trans_signal(val);
+ ret = glink_sigs_local_get(devp->handle, &sigs);
+ if (ret < 0) {
+ GLINK_PKT_ERR("%s: Get signals failed[%d]\n", __func__, ret);
+ return ret;
+ }
+ switch (cmd) {
+ case TIOCMBIS:
+ sigs |= val;
+ break;
+ case TIOCMBIC:
+ sigs &= ~val;
+ break;
+ case TIOCMSET:
+ sigs = val;
+ break;
+ }
+ ret = glink_sigs_set(devp->handle, sigs);
+ GLINK_PKT_INFO("%s: sigs[0x%x] ret[%d]\n", __func__, sigs, ret);
+ return ret;
+}
+
+/**
+ * glink_pkt_ioctl() - ioctl() syscall for the glink_pkt device
+ * file: Pointer to the file structure.
+ * cmd: IOCTL command.
+ * arg: Arguments to the ioctl call.
+ *
+ * This function is used to ioctl on the glink pkt device when
+ * userspace client do a ioctl() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+static long glink_pkt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret;
+ struct glink_pkt_dev *devp;
+ uint32_t size = 0;
+ uint32_t sigs = 0;
+
+ devp = file->private_data;
+ if (!devp || !devp->handle) {
+ GLINK_PKT_ERR("%s: Invalid device handle\n", __func__);
+ return -EINVAL;
+ }
+
+ mutex_lock(&devp->ch_lock);
+ switch (cmd) {
+ case TIOCMGET:
+ devp->sigs_updated = false;
+ ret = glink_sigs_remote_get(devp->handle, &sigs);
+ GLINK_PKT_INFO("%s: TIOCMGET ret[%d] sigs[0x%x]\n",
+ __func__, ret, sigs);
+ map_from_smd_trans_signal(sigs);
+ if (!ret)
+ ret = put_user(sigs, (uint32_t *)arg);
+ break;
+ case TIOCMSET:
+ case TIOCMBIS:
+ case TIOCMBIC:
+ ret = glink_pkt_tiocmset(devp, cmd, arg);
+ break;
+
+ case GLINK_PKT_IOCTL_QUEUE_RX_INTENT:
+ ret = get_user(size, (uint32_t *)arg);
+ GLINK_PKT_INFO("%s: intent size[%d]\n", __func__, size);
+ ret = glink_queue_rx_intent(devp->handle, devp, size);
+ if (ret) {
+ GLINK_PKT_ERR("%s: failed to QUEUE_RX_INTENT ret[%d]\n",
+ __func__, ret);
+ }
+ break;
+ default:
+ GLINK_PKT_ERR("%s: Unrecognized ioctl command 0x%x\n",
+ __func__, cmd);
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+ mutex_unlock(&devp->ch_lock);
+
+ return ret;
+}
+
+/**
+ * glink_pkt_open() - open() syscall for the glink_pkt device
+ * inode: Pointer to the inode structure.
+ * file: Pointer to the file structure.
+ *
+ * This function is used to open the glink pkt device when
+ * userspace client do a open() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+int glink_pkt_open(struct inode *inode, struct file *file)
+{
+ int ret = 0;
+ struct glink_pkt_dev *devp = NULL;
+ int wait_time;
+
+ devp = container_of(inode->i_cdev, struct glink_pkt_dev, cdev);
+ if (!devp) {
+ GLINK_PKT_ERR("%s on NULL device\n", __func__);
+ return -EINVAL;
+ }
+ GLINK_PKT_INFO("Begin %s() on dev id:%d open_time_wait[%d] by [%s]\n",
+ __func__, devp->i, devp->open_time_wait, current->comm);
+ file->private_data = devp;
+ wait_time = devp->open_time_wait;
+
+ mutex_lock(&devp->ch_lock);
+ /* waiting for previous close to complete */
+ if (devp->handle && devp->ref_cnt == 0) {
+ mutex_unlock(&devp->ch_lock);
+ if (wait_time < 0) {
+ ret = wait_event_interruptible(
+ devp->ch_opened_wait_queue,
+ devp->ch_state == GLINK_LOCAL_DISCONNECTED);
+ } else {
+ ret = wait_event_interruptible_timeout(
+ devp->ch_opened_wait_queue,
+ devp->ch_state == GLINK_LOCAL_DISCONNECTED,
+ msecs_to_jiffies(wait_time * 1000));
+ if (ret == 0)
+ ret = -ETIMEDOUT;
+ wait_time = ret;
+ }
+ if (ret < 0) {
+ GLINK_PKT_ERR(
+ "%s:failed for prev close on dev id:%d rc:%d\n",
+ __func__, devp->i, ret);
+ return ret;
+ }
+ mutex_lock(&devp->ch_lock);
+ }
+
+ if (!devp->handle) {
+ devp->handle = glink_open(&devp->open_cfg);
+ if (IS_ERR_OR_NULL(devp->handle)) {
+ GLINK_PKT_ERR(
+ "%s: open failed xprt[%s] edge[%s] name[%s]\n",
+ __func__, devp->open_cfg.transport,
+ devp->open_cfg.edge, devp->open_cfg.name);
+ ret = -ENODEV;
+ devp->handle = NULL;
+ goto error;
+ }
+
+ mutex_unlock(&devp->ch_lock);
+ /*
+ * Wait for the channel to be complete open state so we know
+ * the remote is ready enough.
+ */
+ if (wait_time < 0) {
+ ret = wait_event_interruptible(
+ devp->ch_opened_wait_queue,
+ devp->ch_state == GLINK_CONNECTED);
+ } else {
+ ret = wait_event_interruptible_timeout(
+ devp->ch_opened_wait_queue,
+ devp->ch_state == GLINK_CONNECTED,
+ msecs_to_jiffies(wait_time * 1000));
+ if (ret == 0)
+ ret = -ETIMEDOUT;
+ }
+ mutex_lock(&devp->ch_lock);
+ if (ret < 0) {
+ GLINK_PKT_ERR("%s: open failed on dev id:%d rc:%d\n",
+ __func__, devp->i, ret);
+ glink_close(devp->handle);
+ devp->handle = NULL;
+ goto error;
+ }
+ }
+ ret = 0;
+ devp->ref_cnt++;
+
+error:
+ mutex_unlock(&devp->ch_lock);
+ GLINK_PKT_INFO("END %s() on dev id:%d ref_cnt[%d] ret[%d]\n",
+ __func__, devp->i, devp->ref_cnt, ret);
+ return ret;
+}
+
+/**
+ * glink_pkt_release() - release operation on glink_pkt device
+ * inode: Pointer to the inode structure.
+ * file: Pointer to the file structure.
+ *
+ * This function is used to release the glink pkt device when
+ * userspace client do a close() system call. All input arguments are
+ * validated by the virtual file system before calling this function.
+ */
+int glink_pkt_release(struct inode *inode, struct file *file)
+{
+ int ret = 0;
+ struct glink_pkt_dev *devp = file->private_data;
+ GLINK_PKT_INFO("%s() on dev id:%d by [%s] ref_cnt[%d]\n",
+ __func__, devp->i, current->comm, devp->ref_cnt);
+
+ mutex_lock(&devp->ch_lock);
+ if (devp->ref_cnt > 0)
+ devp->ref_cnt--;
+
+ if (devp->handle && devp->ref_cnt == 0) {
+ wake_up(&devp->ch_read_wait_queue);
+ wake_up_interruptible(&devp->ch_opened_wait_queue);
+ ret = glink_close(devp->handle);
+ if (ret) {
+ GLINK_PKT_ERR("%s: close failed ret[%d]\n",
+ __func__, ret);
+ } else {
+ mutex_unlock(&devp->ch_lock);
+ ret = wait_event_interruptible_timeout(
+ devp->ch_closed_wait_queue,
+ devp->ch_state == GLINK_LOCAL_DISCONNECTED,
+ msecs_to_jiffies(CLOSE_WAIT_TIMEOUT));
+ if (ret == 0)
+ GLINK_PKT_ERR(
+ "%s(): close TIMEOUT on dev_id[%d]\n",
+ __func__, devp->i);
+ mutex_lock(&devp->ch_lock);
+ }
+ devp->poll_mode = 0;
+ devp->ws_locked = 0;
+ devp->sigs_updated = false;
+ devp->in_reset = 0;
+ }
+ mutex_unlock(&devp->ch_lock);
+
+ if (flush_work(&devp->packet_arrival_work))
+ GLINK_PKT_INFO("%s: Flushed work for glink_pkt_dev id:%d\n",
+ __func__, devp->i);
+ return ret;
+}
+
+static const struct file_operations glink_pkt_fops = {
+ .owner = THIS_MODULE,
+ .open = glink_pkt_open,
+ .release = glink_pkt_release,
+ .read = glink_pkt_read,
+ .write = glink_pkt_write,
+ .poll = glink_pkt_poll,
+ .unlocked_ioctl = glink_pkt_ioctl,
+ .compat_ioctl = glink_pkt_ioctl,
+};
+
+/**
+ * glink_pkt_init_add_device() - Initialize G-Link packet device and add cdev
+ * devp: pointer to G-Link packet device.
+ * i: index of the G-Link packet device.
+ *
+ * return: 0 for success, Standard Linux errors
+ */
+static int glink_pkt_init_add_device(struct glink_pkt_dev *devp, int i)
+{
+ int ret = 0;
+
+ devp->open_cfg.notify_rx = glink_pkt_notify_rx;
+ devp->open_cfg.notify_tx_done = glink_pkt_notify_tx_done;
+ devp->open_cfg.notify_state = glink_pkt_notify_state;
+ devp->open_cfg.notify_rx_intent_req = glink_pkt_rmt_rx_intent_req_cb;
+ devp->open_cfg.notify_rx_sigs = glink_pkt_notify_rx_sigs;
+ devp->open_cfg.priv = devp;
+
+ devp->i = i;
+ devp->poll_mode = 0;
+ devp->ws_locked = 0;
+ devp->ch_state = GLINK_LOCAL_DISCONNECTED;
+ /* Default timeout for open wait is 120sec */
+ devp->open_time_wait = 120;
+ mutex_init(&devp->ch_lock);
+ init_waitqueue_head(&devp->ch_read_wait_queue);
+ init_waitqueue_head(&devp->ch_opened_wait_queue);
+ init_waitqueue_head(&devp->ch_closed_wait_queue);
+ spin_lock_init(&devp->pa_spinlock);
+ INIT_LIST_HEAD(&devp->pkt_list);
+ spin_lock_init(&devp->pkt_list_lock);
+ wakeup_source_init(&devp->pa_ws, devp->dev_name);
+ INIT_WORK(&devp->packet_arrival_work, packet_arrival_worker);
+
+ cdev_init(&devp->cdev, &glink_pkt_fops);
+ devp->cdev.owner = THIS_MODULE;
+
+ ret = cdev_add(&devp->cdev, (glink_pkt_number + i), 1);
+ if (IS_ERR_VALUE(ret)) {
+ GLINK_PKT_ERR("%s: cdev_add() failed for dev id:%d ret:%i\n",
+ __func__, i, ret);
+ wakeup_source_trash(&devp->pa_ws);
+ return ret;
+ }
+
+ devp->devicep = device_create(glink_pkt_classp,
+ NULL,
+ (glink_pkt_number + i),
+ NULL,
+ devp->dev_name);
+
+ if (IS_ERR_OR_NULL(devp->devicep)) {
+ GLINK_PKT_ERR("%s: device_create() failed for dev id:%d\n",
+ __func__, i);
+ ret = -ENOMEM;
+ cdev_del(&devp->cdev);
+ wakeup_source_trash(&devp->pa_ws);
+ return ret;
+ }
+
+ if (device_create_file(devp->devicep, &dev_attr_open_timeout))
+ GLINK_PKT_ERR("%s: device_create_file() failed for id:%d\n",
+ __func__, i);
+
+ mutex_lock(&glink_pkt_dev_lock_lha1);
+ list_add(&devp->dev_list, &glink_pkt_dev_list);
+ mutex_unlock(&glink_pkt_dev_lock_lha1);
+
+ return ret;
+}
+
+/**
+ * glink_pkt_core_deinit- De-initialization for this module
+ *
+ * This function remove all the memory and unregister
+ * the char device region.
+ */
+static void glink_pkt_core_deinit(void)
+{
+ struct glink_pkt_dev *glink_pkt_devp;
+ struct glink_pkt_dev *index;
+
+ mutex_lock(&glink_pkt_dev_lock_lha1);
+ list_for_each_entry_safe(glink_pkt_devp, index, &glink_pkt_dev_list,
+ dev_list) {
+ cdev_del(&glink_pkt_devp->cdev);
+ list_del(&glink_pkt_devp->dev_list);
+ device_destroy(glink_pkt_classp,
+ MKDEV(MAJOR(glink_pkt_number),
+ glink_pkt_devp->i));
+ kfree(glink_pkt_devp);
+ }
+ mutex_unlock(&glink_pkt_dev_lock_lha1);
+
+ if (!IS_ERR_OR_NULL(glink_pkt_classp))
+ class_destroy(glink_pkt_classp);
+
+ unregister_chrdev_region(MAJOR(glink_pkt_number), num_glink_pkt_ports);
+}
+
+/**
+ * glink_pkt_alloc_chrdev_region() - allocate the char device region
+ *
+ * This function allocate memory for G-Link packet character-device region and
+ * create the class.
+ */
+static int glink_pkt_alloc_chrdev_region(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&glink_pkt_number,
+ 0,
+ num_glink_pkt_ports,
+ DEVICE_NAME);
+ if (IS_ERR_VALUE(ret)) {
+ GLINK_PKT_ERR("%s: alloc_chrdev_region() failed ret:%i\n",
+ __func__, ret);
+ return ret;
+ }
+
+ glink_pkt_classp = class_create(THIS_MODULE, DEVICE_NAME);
+ if (IS_ERR(glink_pkt_classp)) {
+ GLINK_PKT_ERR("%s: class_create() failed ENOMEM\n", __func__);
+ ret = -ENOMEM;
+ unregister_chrdev_region(MAJOR(glink_pkt_number),
+ num_glink_pkt_ports);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * parse_glinkpkt_devicetree() - parse device tree binding
+ *
+ * node: pointer to device tree node
+ * glink_pkt_devp: pointer to GLINK PACKET device
+ *
+ * Return: 0 on success, -ENODEV on failure.
+ */
+static int parse_glinkpkt_devicetree(struct device_node *node,
+ struct glink_pkt_dev *glink_pkt_devp)
+{
+ char *key;
+
+ key = "qcom,glinkpkt-transport";
+ glink_pkt_devp->open_cfg.transport = of_get_property(node, key, NULL);
+ if (!glink_pkt_devp->open_cfg.transport)
+ goto error;
+ GLINK_PKT_INFO("%s transport = %s\n", __func__,
+ glink_pkt_devp->open_cfg.transport);
+
+ key = "qcom,glinkpkt-edge";
+ glink_pkt_devp->open_cfg.edge = of_get_property(node, key, NULL);
+ if (!glink_pkt_devp->open_cfg.edge)
+ goto error;
+ GLINK_PKT_INFO("%s edge = %s\n", __func__,
+ glink_pkt_devp->open_cfg.edge);
+
+ key = "qcom,glinkpkt-ch-name";
+ glink_pkt_devp->open_cfg.name = of_get_property(node, key, NULL);
+ if (!glink_pkt_devp->open_cfg.name)
+ goto error;
+ GLINK_PKT_INFO("%s ch_name = %s\n", __func__,
+ glink_pkt_devp->open_cfg.name);
+
+ key = "qcom,glinkpkt-dev-name";
+ glink_pkt_devp->dev_name = of_get_property(node, key, NULL);
+ if (!glink_pkt_devp->dev_name)
+ goto error;
+ GLINK_PKT_INFO("%s dev_name = %s\n", __func__,
+ glink_pkt_devp->dev_name);
+ return 0;
+
+error:
+ GLINK_PKT_ERR("%s: missing key: %s\n", __func__, key);
+ return -ENODEV;
+
+}
+
+/**
+ * glink_pkt_devicetree_init() - Initialize the add char device
+ *
+ * pdev: Pointer to device tree data.
+ *
+ * return: 0 on success, -ENODEV on failure.
+ */
+static int glink_pkt_devicetree_init(struct platform_device *pdev)
+{
+ int ret;
+ int i = 0;
+ struct device_node *node;
+ struct glink_pkt_dev *glink_pkt_devp;
+ int subnode_num = 0;
+
+ for_each_child_of_node(pdev->dev.of_node, node)
+ ++subnode_num;
+ if (!subnode_num) {
+ GLINK_PKT_ERR("%s subnode_num = %d\n", __func__, subnode_num);
+ return 0;
+ }
+
+ num_glink_pkt_ports = subnode_num;
+
+ ret = glink_pkt_alloc_chrdev_region();
+ if (ret) {
+ GLINK_PKT_ERR("%s: chrdev_region allocation failed ret:%i\n",
+ __func__, ret);
+ return ret;
+ }
+
+ for_each_child_of_node(pdev->dev.of_node, node) {
+ glink_pkt_devp = kzalloc(sizeof(*glink_pkt_devp),
+ GFP_KERNEL);
+ if (IS_ERR_OR_NULL(glink_pkt_devp)) {
+ GLINK_PKT_ERR("%s: allocation failed id:%d\n",
+ __func__, i);
+ ret = -ENOMEM;
+ goto error_destroy;
+ }
+
+ ret = parse_glinkpkt_devicetree(node, glink_pkt_devp);
+ if (ret) {
+ GLINK_PKT_ERR("%s: failed to parse devicetree %d\n",
+ __func__, i);
+ kfree(glink_pkt_devp);
+ goto error_destroy;
+ }
+
+ ret = glink_pkt_init_add_device(glink_pkt_devp, i);
+ if (ret < 0) {
+ GLINK_PKT_ERR("%s: add device failed idx:%d ret=%d\n",
+ __func__, i, ret);
+ kfree(glink_pkt_devp);
+ goto error_destroy;
+ }
+ i++;
+ }
+
+ GLINK_PKT_INFO("G-Link Packet Port Driver Initialized.\n");
+ return 0;
+
+error_destroy:
+ glink_pkt_core_deinit();
+ return ret;
+}
+
+/**
+ * msm_glink_pkt_probe() - Probe a G-Link packet device
+ *
+ * pdev: Pointer to device tree data.
+ *
+ * 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 G-Link packet device.
+ */
+static int msm_glink_pkt_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ if (pdev) {
+ if (pdev->dev.of_node) {
+ GLINK_PKT_INFO("%s device tree implementation\n",
+ __func__);
+ ret = glink_pkt_devicetree_init(pdev);
+ if (ret)
+ GLINK_PKT_ERR("%s: device tree init failed\n",
+ __func__);
+ }
+ }
+
+ return 0;
+}
+
+static struct of_device_id msm_glink_pkt_match_table[] = {
+ { .compatible = "qcom,glinkpkt" },
+ {},
+};
+
+static struct platform_driver msm_glink_pkt_driver = {
+ .probe = msm_glink_pkt_probe,
+ .driver = {
+ .name = MODULE_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = msm_glink_pkt_match_table,
+ },
+};
+
+/**
+ * glink_pkt_init() - Initialization function for this module
+ *
+ * returns: 0 on success, standard Linux error code otherwise.
+ */
+static int __init glink_pkt_init(void)
+{
+ int ret;
+
+ INIT_LIST_HEAD(&glink_pkt_dev_list);
+ INIT_LIST_HEAD(&glink_pkt_driver_list);
+ ret = platform_driver_register(&msm_glink_pkt_driver);
+ if (ret) {
+ GLINK_PKT_ERR("%s: msm_glink_driver register failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ glink_pkt_ilctxt = ipc_log_context_create(GLINK_PKT_IPC_LOG_PAGE_CNT,
+ "glink_pkt", 0);
+ glink_pkt_wq = create_singlethread_workqueue("glink_pkt_wq");
+ if (!glink_pkt_wq) {
+ GLINK_PKT_ERR("%s: Error creating glink_pkt_wq\n", __func__);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/**
+ * glink_pkt_cleanup() - Exit function for this module
+ *
+ * This function is used to cleanup the module during the exit.
+ */
+static void __exit glink_pkt_cleanup(void)
+{
+ glink_pkt_core_deinit();
+}
+
+module_init(glink_pkt_init);
+module_exit(glink_pkt_cleanup);
+
+MODULE_DESCRIPTION("MSM G-Link Packet Port");
+MODULE_LICENSE("GPL v2");