12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088 |
- /* Copyright (c) 2012-2013, 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.
- */
- /*
- * Serial Mux Control Driver -- Provides a binary serial muxed control
- * port interface.
- */
- #define DEBUG
- #include <linux/cdev.h>
- #include <linux/types.h>
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/device.h>
- #include <linux/delay.h>
- #include <linux/sched.h>
- #include <linux/spinlock.h>
- #include <linux/mutex.h>
- #include <linux/uaccess.h>
- #include <linux/workqueue.h>
- #include <linux/atomic.h>
- #include <linux/platform_device.h>
- #include <linux/smux.h>
- #include <linux/slab.h>
- #include <linux/debugfs.h>
- #include <linux/poll.h>
- #include <asm/ioctls.h>
- #define MAX_WRITE_RETRY 5
- #define MAGIC_NO_V1 0x33FC
- #define DEVICE_NAME "smuxctl"
- #define SMUX_CTL_MAX_BUF_SIZE 2048
- #define SMUX_CTL_MODULE_NAME "smux_ctl"
- #define DEBUG
- static int msm_smux_ctl_debug_mask;
- module_param_named(debug_mask, msm_smux_ctl_debug_mask,
- int, S_IRUGO | S_IWUSR | S_IWGRP);
- static uint32_t smux_ctl_ch_id[] = {
- SMUX_DATA_CTL_0,
- SMUX_DATA_CTL_1,
- };
- #define SMUX_CTL_NUM_CHANNELS ARRAY_SIZE(smux_ctl_ch_id)
- #define DEFAULT_OPEN_TIMEOUT 5
- struct smux_ctl_dev {
- int id;
- char name[10];
- struct cdev cdev;
- struct device *devicep;
- struct mutex dev_lock;
- atomic_t ref_count;
- int state;
- int is_channel_reset;
- int is_high_wm;
- int write_pending;
- unsigned open_timeout_val;
- struct mutex rx_lock;
- uint32_t read_avail;
- struct list_head rx_list;
- int abort_wait;
- wait_queue_head_t read_wait_queue;
- wait_queue_head_t write_wait_queue;
- struct {
- uint32_t bytes_tx;
- uint32_t bytes_rx;
- uint32_t pkts_tx;
- uint32_t pkts_rx;
- uint32_t cnt_ssr;
- uint32_t cnt_read_fail;
- uint32_t cnt_write_fail;
- uint32_t cnt_high_wm_hit;
- } stats;
- } *smux_ctl_devp[SMUX_CTL_NUM_CHANNELS];
- struct smux_ctl_pkt {
- int data_size;
- void *data;
- };
- struct smux_ctl_list_elem {
- struct list_head list;
- struct smux_ctl_pkt ctl_pkt;
- };
- struct class *smux_ctl_classp;
- static dev_t smux_ctl_number;
- static uint32_t smux_ctl_inited;
- enum {
- MSM_SMUX_CTL_DEBUG = 1U << 0,
- MSM_SMUX_CTL_DUMP_BUFFER = 1U << 1,
- };
- #if defined(DEBUG)
- static const char *smux_ctl_event_str[] = {
- "SMUX_CONNECTED",
- "SMUX_DISCONNECTED",
- "SMUX_READ_DONE",
- "SMUX_READ_FAIL",
- "SMUX_WRITE_DONE",
- "SMUX_WRITE_FAIL",
- "SMUX_TIOCM_UPDATE",
- "SMUX_LOW_WM_HIT",
- "SMUX_HIGH_WM_HIT",
- };
- #define SMUXCTL_DUMP_BUFFER(prestr, cnt, buf) \
- do { \
- if (msm_smux_ctl_debug_mask & MSM_SMUX_CTL_DUMP_BUFFER) { \
- int i; \
- pr_err("%s", prestr); \
- for (i = 0; i < cnt; i++) \
- pr_err("%.2x", buf[i]); \
- pr_err("\n"); \
- } \
- } while (0)
- #define SMUXCTL_DBG(x...) \
- do { \
- if (msm_smux_ctl_debug_mask & MSM_SMUX_CTL_DEBUG) \
- pr_err(x); \
- } while (0)
- #else
- #define SMUXCTL_DUMP_BUFFER(prestr, cnt, buf) do {} while (0)
- #define SMUXCTL_DBG(x...) do {} while (0)
- #endif
- #if defined(DEBUG_LOOPBACK)
- #define SMUXCTL_SET_LOOPBACK(lcid) \
- msm_smux_set_ch_option(lcid, SMUX_CH_OPTION_LOCAL_LOOPBACK, 0)
- #else
- #define SMUXCTL_SET_LOOPBACK(lcid) do {} while (0)
- #endif
- static ssize_t open_timeout_store(struct device *d,
- struct device_attribute *attr,
- const char *buf,
- size_t n)
- {
- int i;
- unsigned long tmp;
- for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
- if (smux_ctl_devp[i]->devicep == d)
- break;
- }
- if (i >= SMUX_CTL_NUM_CHANNELS) {
- pr_err("%s: unable to match device to valid smux ctl port\n",
- __func__);
- return -EINVAL;
- }
- if (!kstrtoul(buf, 10, &tmp)) {
- smux_ctl_devp[i]->open_timeout_val = tmp;
- return n;
- } else {
- pr_err("%s: unable to convert: %s to an int\n", __func__,
- buf);
- return -EINVAL;
- }
- }
- static ssize_t open_timeout_show(struct device *d,
- struct device_attribute *attr,
- char *buf)
- {
- int i;
- for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
- if (smux_ctl_devp[i]->devicep == d)
- break;
- }
- if (i >= SMUX_CTL_NUM_CHANNELS) {
- pr_err("%s: unable to match device to valid smux ctl port\n",
- __func__);
- return -EINVAL;
- }
- return snprintf(buf, PAGE_SIZE, "%d\n",
- smux_ctl_devp[i]->open_timeout_val);
- }
- static DEVICE_ATTR(open_timeout, 0664, open_timeout_show, open_timeout_store);
- static int get_ctl_dev_index(int id)
- {
- int dev_index;
- for (dev_index = 0; dev_index < SMUX_CTL_NUM_CHANNELS; dev_index++) {
- if (smux_ctl_ch_id[dev_index] == id)
- return dev_index;
- }
- return -ENODEV;
- }
- static int smux_ctl_get_rx_buf_cb(void *priv, void **pkt_priv,
- void **buffer, int size)
- {
- void *buf = NULL;
- int id = ((struct smux_ctl_dev *)(priv))->id;
- int dev_index;
- if (id < 0 || id > smux_ctl_ch_id[SMUX_CTL_NUM_CHANNELS - 1])
- return -ENODEV;
- if (!buffer || 0 >= size)
- return -EINVAL;
- dev_index = get_ctl_dev_index(id);
- if (dev_index < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: Ch%d is not "
- "exported to user-space\n",
- __func__, id);
- return -ENODEV;
- }
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: Allocating Rx buf size %d "
- "for ch%d\n",
- __func__, size, smux_ctl_devp[dev_index]->id);
- buf = kmalloc(size, GFP_KERNEL);
- if (!buf) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: buffer allocation failed: "
- "Ch%d, size %d ", __func__, id, size);
- return -ENOMEM;
- }
- *buffer = buf;
- *pkt_priv = NULL;
- return 0;
- }
- void smux_ctl_notify_cb(void *priv, int event_type, const void *metadata)
- {
- int id = ((struct smux_ctl_dev *)(priv))->id;
- struct smux_ctl_list_elem *list_elem = NULL;
- int dev_index;
- void *data;
- int len;
- if (id < 0 || id > smux_ctl_ch_id[SMUX_CTL_NUM_CHANNELS - 1])
- return;
- dev_index = get_ctl_dev_index(id);
- if (dev_index < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: Ch%d is not exported "
- "to user-space\n", __func__, id);
- return;
- }
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: Ch%d, Event %d (%s)\n",
- __func__, smux_ctl_devp[dev_index]->id,
- event_type, smux_ctl_event_str[event_type]);
- switch (event_type) {
- case SMUX_CONNECTED:
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- smux_ctl_devp[dev_index]->state = SMUX_CONNECTED;
- smux_ctl_devp[dev_index]->is_high_wm = 0;
- smux_ctl_devp[dev_index]->is_channel_reset = 0;
- smux_ctl_devp[dev_index]->read_avail = 0;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- wake_up(&smux_ctl_devp[dev_index]->write_wait_queue);
- break;
- case SMUX_DISCONNECTED:
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- smux_ctl_devp[dev_index]->state = SMUX_DISCONNECTED;
- smux_ctl_devp[dev_index]->is_channel_reset =
- ((struct smux_meta_disconnected *)metadata)->is_ssr;
- if (smux_ctl_devp[dev_index]->is_channel_reset)
- smux_ctl_devp[dev_index]->stats.cnt_ssr++;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- wake_up(&smux_ctl_devp[dev_index]->write_wait_queue);
- wake_up(&smux_ctl_devp[dev_index]->read_wait_queue);
- break;
- case SMUX_READ_FAIL:
- data = ((struct smux_meta_read *)metadata)->buffer;
- kfree(data);
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- smux_ctl_devp[dev_index]->stats.cnt_read_fail++;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- wake_up(&smux_ctl_devp[dev_index]->read_wait_queue);
- break;
- case SMUX_READ_DONE:
- data = ((struct smux_meta_read *)metadata)->buffer;
- len = ((struct smux_meta_read *)metadata)->len;
- if (data && len > 0) {
- list_elem = kmalloc(sizeof(struct smux_ctl_list_elem),
- GFP_KERNEL);
- if (list_elem) {
- list_elem->ctl_pkt.data = data;
- list_elem->ctl_pkt.data_size = len;
- mutex_lock(&smux_ctl_devp[dev_index]->rx_lock);
- list_add_tail(&list_elem->list,
- &smux_ctl_devp[dev_index]->rx_list);
- smux_ctl_devp[dev_index]->read_avail += len;
- mutex_unlock(
- &smux_ctl_devp[dev_index]->rx_lock);
- } else {
- kfree(data);
- }
- }
- wake_up(&smux_ctl_devp[dev_index]->read_wait_queue);
- break;
- case SMUX_WRITE_DONE:
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- smux_ctl_devp[dev_index]->write_pending = 0;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- data = ((struct smux_meta_write *)metadata)->buffer;
- kfree(data);
- wake_up(&smux_ctl_devp[dev_index]->write_wait_queue);
- break;
- case SMUX_WRITE_FAIL:
- data = ((struct smux_meta_write *)metadata)->buffer;
- kfree(data);
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- smux_ctl_devp[dev_index]->stats.cnt_write_fail++;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- wake_up(&smux_ctl_devp[dev_index]->write_wait_queue);
- break;
- case SMUX_LOW_WM_HIT:
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- smux_ctl_devp[dev_index]->is_high_wm = 0;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- wake_up(&smux_ctl_devp[dev_index]->write_wait_queue);
- break;
- case SMUX_HIGH_WM_HIT:
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- smux_ctl_devp[dev_index]->is_high_wm = 1;
- smux_ctl_devp[dev_index]->stats.cnt_high_wm_hit++;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- break;
- case SMUX_TIOCM_UPDATE:
- default:
- pr_err(SMUX_CTL_MODULE_NAME ": %s: Event %d not supported\n",
- __func__, event_type);
- break;
- }
- }
- int smux_ctl_open(struct inode *inode, struct file *file)
- {
- int r = 0;
- struct smux_ctl_dev *devp;
- unsigned wait_time = DEFAULT_OPEN_TIMEOUT * HZ;
- if (!smux_ctl_inited)
- return -EIO;
- devp = container_of(inode->i_cdev, struct smux_ctl_dev, cdev);
- if (!devp)
- return -ENODEV;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s called on smuxctl%d device\n",
- __func__, devp->id);
- if (1 == atomic_add_return(1, &devp->ref_count)) {
- SMUXCTL_SET_LOOPBACK(devp->id);
- r = msm_smux_open(devp->id,
- devp,
- smux_ctl_notify_cb,
- smux_ctl_get_rx_buf_cb);
- if (r < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: smux_open failed "
- "for smuxctl%d with rc %d\n",
- __func__, devp->id, r);
- atomic_dec(&devp->ref_count);
- return r;
- }
- if (devp->open_timeout_val)
- wait_time = devp->open_timeout_val * HZ;
- r = wait_event_interruptible_timeout(
- devp->write_wait_queue,
- (devp->state == SMUX_CONNECTED ||
- devp->abort_wait),
- wait_time);
- if (r == 0)
- r = -ETIMEDOUT;
- if (r < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "SMUX open timed out: %d, LCID %d\n",
- __func__, r, devp->id);
- atomic_dec(&devp->ref_count);
- msm_smux_close(devp->id);
- return r;
- } else if (devp->abort_wait) {
- pr_err("%s: %s: Open command aborted\n",
- SMUX_CTL_MODULE_NAME, __func__);
- r = -EIO;
- atomic_dec(&devp->ref_count);
- msm_smux_close(devp->id);
- return r;
- } else if (devp->state != SMUX_CONNECTED) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "Invalid open notification\n", __func__);
- r = -ENODEV;
- atomic_dec(&devp->ref_count);
- msm_smux_close(devp->id);
- return r;
- }
- }
- file->private_data = devp;
- return 0;
- }
- int smux_ctl_release(struct inode *inode, struct file *file)
- {
- struct smux_ctl_dev *devp;
- struct smux_ctl_list_elem *list_elem = NULL;
- devp = file->private_data;
- if (!devp)
- return -EINVAL;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s called on smuxctl%d device\n",
- __func__, devp->id);
- mutex_lock(&devp->dev_lock);
- if (atomic_dec_and_test(&devp->ref_count)) {
- mutex_lock(&devp->rx_lock);
- while (!list_empty(&devp->rx_list)) {
- list_elem = list_first_entry(
- &devp->rx_list,
- struct smux_ctl_list_elem,
- list);
- list_del(&list_elem->list);
- kfree(list_elem->ctl_pkt.data);
- kfree(list_elem);
- }
- devp->read_avail = 0;
- mutex_unlock(&devp->rx_lock);
- msm_smux_close(devp->id);
- }
- mutex_unlock(&devp->dev_lock);
- file->private_data = NULL;
- return 0;
- }
- static int smux_ctl_readable(int id)
- {
- int r;
- int dev_index;
- if (id < 0 || id > smux_ctl_ch_id[SMUX_CTL_NUM_CHANNELS - 1])
- return -ENODEV;
- dev_index = get_ctl_dev_index(id);
- if (dev_index < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: Ch%d "
- "is not exported to user-space\n",
- __func__, id);
- return -ENODEV;
- }
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- if (signal_pending(current))
- r = -ERESTARTSYS;
- else if (smux_ctl_devp[dev_index]->abort_wait)
- r = -ENETRESET;
- else if (smux_ctl_devp[dev_index]->state == SMUX_DISCONNECTED &&
- smux_ctl_devp[dev_index]->is_channel_reset != 0)
- r = -ENETRESET;
- else if (smux_ctl_devp[dev_index]->state != SMUX_CONNECTED)
- r = -ENODEV;
- else
- r = smux_ctl_devp[dev_index]->read_avail;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- return r;
- }
- ssize_t smux_ctl_read(struct file *file,
- char __user *buf,
- size_t count,
- loff_t *ppos)
- {
- int r = 0, id, bytes_to_read, read_err;
- struct smux_ctl_dev *devp;
- struct smux_ctl_list_elem *list_elem = NULL;
- devp = file->private_data;
- if (!devp)
- return -ENODEV;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: read from ch%d\n",
- __func__, devp->id);
- id = devp->id;
- mutex_lock(&devp->rx_lock);
- while (devp->read_avail <= 0) {
- mutex_unlock(&devp->rx_lock);
- r = wait_event_interruptible(devp->read_wait_queue,
- 0 != (read_err = smux_ctl_readable(id)));
- if (r < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s:"
- "wait_event_interruptible "
- "ret %i\n", __func__, r);
- return r;
- }
- if (read_err < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s:"
- " Read block failed for Ch%d, err %d\n",
- __func__, devp->id, read_err);
- return read_err;
- }
- mutex_lock(&devp->rx_lock);
- }
- if (list_empty(&devp->rx_list)) {
- mutex_unlock(&devp->rx_lock);
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: "
- "Nothing in ch%d's rx_list\n", __func__,
- devp->id);
- return -EAGAIN;
- }
- list_elem = list_first_entry(&devp->rx_list,
- struct smux_ctl_list_elem, list);
- bytes_to_read = (uint32_t)(list_elem->ctl_pkt.data_size);
- if (bytes_to_read > count) {
- mutex_unlock(&devp->rx_lock);
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "Packet size %d > buf size %d\n", __func__,
- bytes_to_read, count);
- return -ENOMEM;
- }
- if (copy_to_user(buf, list_elem->ctl_pkt.data, bytes_to_read)) {
- mutex_unlock(&devp->rx_lock);
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "copy_to_user failed for ch%d\n", __func__,
- devp->id);
- return -EFAULT;
- }
- devp->read_avail -= bytes_to_read;
- list_del(&list_elem->list);
- kfree(list_elem->ctl_pkt.data);
- kfree(list_elem);
- devp->stats.pkts_rx++;
- devp->stats.bytes_rx += bytes_to_read;
- mutex_unlock(&devp->rx_lock);
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: "
- "Returning %d bytes to ch%d\n", __func__,
- bytes_to_read, devp->id);
- return bytes_to_read;
- }
- static int smux_ctl_writeable(int id)
- {
- int r;
- int dev_index;
- if (id < 0 || id > smux_ctl_ch_id[SMUX_CTL_NUM_CHANNELS - 1])
- return -ENODEV;
- dev_index = get_ctl_dev_index(id);
- if (dev_index < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "Ch%d is not exported to user-space\n",
- __func__, id);
- return -ENODEV;
- }
- mutex_lock(&smux_ctl_devp[dev_index]->dev_lock);
- if (signal_pending(current))
- r = -ERESTARTSYS;
- else if (smux_ctl_devp[dev_index]->abort_wait)
- r = -ENETRESET;
- else if (smux_ctl_devp[dev_index]->state == SMUX_DISCONNECTED &&
- smux_ctl_devp[dev_index]->is_channel_reset != 0)
- r = -ENETRESET;
- else if (smux_ctl_devp[dev_index]->state != SMUX_CONNECTED)
- r = -ENODEV;
- else if (smux_ctl_devp[dev_index]->is_high_wm ||
- smux_ctl_devp[dev_index]->write_pending)
- r = 0;
- else
- r = SMUX_CTL_MAX_BUF_SIZE;
- mutex_unlock(&smux_ctl_devp[dev_index]->dev_lock);
- return r;
- }
- ssize_t smux_ctl_write(struct file *file,
- const char __user *buf,
- size_t count,
- loff_t *ppos)
- {
- int r = 0, id, write_err;
- char *temp_buf;
- struct smux_ctl_dev *devp;
- if (count <= 0)
- return -EINVAL;
- devp = file->private_data;
- if (!devp)
- return -ENODEV;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: writing %i bytes on ch%d\n",
- __func__, count, devp->id);
- id = devp->id;
- r = wait_event_interruptible(devp->write_wait_queue,
- 0 != (write_err = smux_ctl_writeable(id)));
- if (r < 0) {
- pr_err(SMUX_CTL_MODULE_NAME
- ": %s: wait_event_interruptible "
- "ret %i\n", __func__, r);
- return r;
- }
- if (write_err < 0) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s:"
- "Write block failed for Ch%d, err %d\n",
- __func__, devp->id, write_err);
- return write_err;
- }
- temp_buf = kmalloc(count, GFP_KERNEL);
- if (!temp_buf) {
- pr_err(SMUX_CTL_MODULE_NAME
- ": %s: temp_buf alloc failed\n", __func__);
- return -ENOMEM;
- }
- if (copy_from_user(temp_buf, buf, count)) {
- pr_err(SMUX_CTL_MODULE_NAME
- ": %s: copy_from_user failed\n", __func__);
- kfree(temp_buf);
- return -EFAULT;
- }
- mutex_lock(&devp->dev_lock);
- devp->write_pending = 1;
- mutex_unlock(&devp->dev_lock);
- r = msm_smux_write(id, NULL, (void *)temp_buf, count);
- if (r < 0) {
- pr_err(SMUX_CTL_MODULE_NAME
- ": %s: smux_write on Ch%dfailed, err %d\n",
- __func__, id, r);
- mutex_lock(&devp->dev_lock);
- devp->write_pending = 0;
- mutex_unlock(&devp->dev_lock);
- return r;
- }
- r = wait_event_interruptible(devp->write_wait_queue,
- 0 != (write_err = smux_ctl_writeable(id)));
- if (-EIO == r) {
- pr_err("%s: %s: wait_event_interruptible ret %i\n",
- SMUX_CTL_MODULE_NAME, __func__, r);
- return -EIO;
- }
- if (r < 0) {
- pr_err(SMUX_CTL_MODULE_NAME " :%s: wait_event_interruptible "
- "ret %i\n", __func__, r);
- mutex_lock(&devp->dev_lock);
- devp->write_pending = 0;
- mutex_unlock(&devp->dev_lock);
- return r;
- }
- mutex_lock(&devp->dev_lock);
- devp->write_pending = 0;
- devp->stats.pkts_tx++;
- devp->stats.bytes_tx += count;
- mutex_unlock(&devp->dev_lock);
- return count;
- }
- static long smux_ctl_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
- {
- int ret;
- struct smux_ctl_dev *devp;
- devp = file->private_data;
- if (!devp)
- return -ENODEV;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s called on smuxctl%d device\n",
- __func__, devp->id);
- switch (cmd) {
- case TIOCMGET:
- ret = msm_smux_tiocm_get(devp->id);
- break;
- case TIOCMSET:
- ret = msm_smux_tiocm_set(devp->id, arg, ~arg);
- break;
- default:
- ret = -EINVAL;
- break;
- }
- return ret;
- }
- static unsigned int smux_ctl_poll(struct file *file, poll_table *wait)
- {
- struct smux_ctl_dev *devp;
- unsigned int mask = 0;
- int readable;
- devp = file->private_data;
- if (!devp)
- return -ENODEV;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s called on smuxctl%d\n",
- __func__, devp->id);
- poll_wait(file, &devp->read_wait_queue, wait);
- readable = smux_ctl_readable(devp->id);
- if (readable > 0) {
- mask = POLLIN | POLLRDNORM;
- } else if ((readable < 0) && (readable != -ERESTARTSYS)) {
- /* error case (non-signal) received */
- pr_err(SMUX_CTL_MODULE_NAME ": %s err%d during poll for smuxctl%d\n",
- __func__, readable, devp->id);
- mask = POLLERR;
- }
- return mask;
- }
- static const struct file_operations smux_ctl_fops = {
- .owner = THIS_MODULE,
- .open = smux_ctl_open,
- .release = smux_ctl_release,
- .read = smux_ctl_read,
- .write = smux_ctl_write,
- .unlocked_ioctl = smux_ctl_ioctl,
- .poll = smux_ctl_poll,
- };
- static void smux_ctl_reset_channel(struct smux_ctl_dev *devp)
- {
- devp->is_high_wm = 0;
- devp->write_pending = 0;
- devp->is_channel_reset = 0;
- devp->state = SMUX_DISCONNECTED;
- devp->read_avail = 0;
- devp->stats.bytes_tx = 0;
- devp->stats.bytes_rx = 0;
- devp->stats.pkts_tx = 0;
- devp->stats.pkts_rx = 0;
- devp->stats.cnt_ssr = 0;
- devp->stats.cnt_read_fail = 0;
- devp->stats.cnt_write_fail = 0;
- devp->stats.cnt_high_wm_hit = 0;
- devp->abort_wait = 0;
- }
- static int smux_ctl_probe(struct platform_device *pdev)
- {
- int i;
- int r;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Begins\n", __func__);
- if (smux_ctl_inited) {
- /* Already loaded once - reinitialize channels */
- for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
- struct smux_ctl_dev *devp = smux_ctl_devp[i];
- smux_ctl_reset_channel(devp);
- if (atomic_read(&devp->ref_count)) {
- r = msm_smux_open(devp->id,
- devp,
- smux_ctl_notify_cb,
- smux_ctl_get_rx_buf_cb);
- if (r)
- pr_err("%s: unable to reopen ch %d, ret %d\n",
- __func__, devp->id, r);
- }
- }
- return 0;
- }
- /* Create character devices */
- for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
- smux_ctl_devp[i] = kzalloc(sizeof(struct smux_ctl_dev),
- GFP_KERNEL);
- if (IS_ERR(smux_ctl_devp[i])) {
- pr_err(SMUX_CTL_MODULE_NAME
- ": %s kmalloc() ENOMEM\n", __func__);
- r = -ENOMEM;
- goto error0;
- }
- smux_ctl_devp[i]->id = smux_ctl_ch_id[i];
- atomic_set(&smux_ctl_devp[i]->ref_count, 0);
- mutex_init(&smux_ctl_devp[i]->dev_lock);
- init_waitqueue_head(&smux_ctl_devp[i]->read_wait_queue);
- init_waitqueue_head(&smux_ctl_devp[i]->write_wait_queue);
- mutex_init(&smux_ctl_devp[i]->rx_lock);
- INIT_LIST_HEAD(&smux_ctl_devp[i]->rx_list);
- smux_ctl_reset_channel(smux_ctl_devp[i]);
- }
- r = alloc_chrdev_region(&smux_ctl_number, 0, SMUX_CTL_NUM_CHANNELS,
- DEVICE_NAME);
- if (IS_ERR_VALUE(r)) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "alloc_chrdev_region() ret %i.\n",
- __func__, r);
- goto error0;
- }
- smux_ctl_classp = class_create(THIS_MODULE, DEVICE_NAME);
- if (IS_ERR(smux_ctl_classp)) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "class_create() ENOMEM\n", __func__);
- r = -ENOMEM;
- goto error1;
- }
- for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
- cdev_init(&smux_ctl_devp[i]->cdev, &smux_ctl_fops);
- smux_ctl_devp[i]->cdev.owner = THIS_MODULE;
- r = cdev_add(&smux_ctl_devp[i]->cdev,
- (smux_ctl_number + i), 1);
- if (IS_ERR_VALUE(r)) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "cdev_add() ret %i\n", __func__, r);
- kfree(smux_ctl_devp[i]);
- goto error2;
- }
- smux_ctl_devp[i]->devicep =
- device_create(smux_ctl_classp, NULL,
- (smux_ctl_number + i), NULL,
- DEVICE_NAME "%d", smux_ctl_ch_id[i]);
- if (IS_ERR(smux_ctl_devp[i]->devicep)) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: "
- "device_create() ENOMEM\n", __func__);
- r = -ENOMEM;
- cdev_del(&smux_ctl_devp[i]->cdev);
- kfree(smux_ctl_devp[i]);
- goto error2;
- }
- if (device_create_file(smux_ctl_devp[i]->devicep,
- &dev_attr_open_timeout))
- pr_err("%s: unable to create device attr for" \
- " smux ctl dev id:%d\n", __func__, i);
- }
- smux_ctl_inited = 1;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s: "
- "SMUX Control Port Driver Initialized.\n", __func__);
- return 0;
- error2:
- while (--i >= 0) {
- cdev_del(&smux_ctl_devp[i]->cdev);
- device_destroy(smux_ctl_classp,
- MKDEV(MAJOR(smux_ctl_number), i));
- }
- class_destroy(smux_ctl_classp);
- i = SMUX_CTL_NUM_CHANNELS;
- error1:
- unregister_chrdev_region(MAJOR(smux_ctl_number),
- SMUX_CTL_NUM_CHANNELS);
- error0:
- while (--i >= 0)
- kfree(smux_ctl_devp[i]);
- return r;
- }
- static int smux_ctl_remove(struct platform_device *pdev)
- {
- int i;
- int ret;
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Begins\n", __func__);
- for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
- struct smux_ctl_dev *devp = smux_ctl_devp[i];
- mutex_lock(&devp->dev_lock);
- devp->abort_wait = 1;
- wake_up(&devp->write_wait_queue);
- wake_up(&devp->read_wait_queue);
- if (atomic_read(&devp->ref_count)) {
- ret = msm_smux_close(devp->id);
- if (ret)
- pr_err("%s: unable to close ch %d, ret %d\n",
- __func__, devp->id, ret);
- }
- mutex_unlock(&devp->dev_lock);
- /* Empty RX queue */
- mutex_lock(&devp->rx_lock);
- while (!list_empty(&devp->rx_list)) {
- struct smux_ctl_list_elem *list_elem;
- list_elem = list_first_entry(
- &devp->rx_list,
- struct smux_ctl_list_elem,
- list);
- list_del(&list_elem->list);
- kfree(list_elem->ctl_pkt.data);
- kfree(list_elem);
- }
- devp->read_avail = 0;
- mutex_unlock(&devp->rx_lock);
- }
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Ends\n", __func__);
- return 0;
- }
- static struct platform_driver smux_ctl_driver = {
- .probe = smux_ctl_probe,
- .remove = smux_ctl_remove,
- .driver = {
- .name = "SMUX_CTL",
- .owner = THIS_MODULE,
- },
- };
- static int __init smux_ctl_init(void)
- {
- SMUXCTL_DBG(SMUX_CTL_MODULE_NAME ": %s Begins\n", __func__);
- return platform_driver_register(&smux_ctl_driver);
- }
- #if defined(CONFIG_DEBUG_FS)
- #define DEBUG_BUFMAX 4096
- static char debug_buffer[DEBUG_BUFMAX];
- static ssize_t debug_read(struct file *file, char __user *buf,
- size_t count, loff_t *ppos)
- {
- int bsize = 0;
- int i;
- if (!smux_ctl_inited) {
- pr_err(SMUX_CTL_MODULE_NAME ": %s: SMUX_CTL not yet inited\n",
- __func__);
- return -EIO;
- }
- bsize += scnprintf(debug_buffer + bsize, DEBUG_BUFMAX - bsize,
- "SMUX_CTL Channel States:\n");
- for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
- bsize += scnprintf(debug_buffer + bsize, DEBUG_BUFMAX - bsize,
- "Ch%02d %s RefCnt=%01d State=%02d "
- "SSR=%02d HighWM=%02d ReadAvail=%04d WritePending=%02d\n",
- smux_ctl_devp[i]->id,
- smux_ctl_devp[i]->name,
- atomic_read(&smux_ctl_devp[i]->ref_count),
- smux_ctl_devp[i]->state,
- smux_ctl_devp[i]->is_channel_reset,
- smux_ctl_devp[i]->is_high_wm,
- smux_ctl_devp[i]->read_avail,
- smux_ctl_devp[i]->write_pending);
- }
- bsize += scnprintf(debug_buffer + bsize, DEBUG_BUFMAX - bsize,
- "\nSMUX_CTL Channel Statistics:\n");
- for (i = 0; i < SMUX_CTL_NUM_CHANNELS; ++i) {
- bsize += scnprintf(debug_buffer + bsize, DEBUG_BUFMAX - bsize,
- "Ch%02d %s BytesTX=%08d "
- "BytesRx=%08d PktsTx=%04d PktsRx=%04d"
- "CntSSR=%02d CntHighWM=%02d "
- "CntReadFail%02d CntWriteFailed=%02d\n",
- smux_ctl_devp[i]->id,
- smux_ctl_devp[i]->name,
- smux_ctl_devp[i]->stats.bytes_tx,
- smux_ctl_devp[i]->stats.bytes_rx,
- smux_ctl_devp[i]->stats.pkts_tx,
- smux_ctl_devp[i]->stats.pkts_rx,
- smux_ctl_devp[i]->stats.cnt_ssr,
- smux_ctl_devp[i]->stats.cnt_high_wm_hit,
- smux_ctl_devp[i]->stats.cnt_read_fail,
- smux_ctl_devp[i]->stats.cnt_write_fail);
- }
- return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize);
- }
- static int debug_open(struct inode *inode, struct file *file)
- {
- file->private_data = inode->i_private;
- return 0;
- }
- static const struct file_operations debug_ops = {
- .read = debug_read,
- .open = debug_open,
- };
- static int __init smux_debugfs_init(void)
- {
- struct dentry *dent;
- dent = debugfs_create_dir("smux_ctl", 0);
- if (!IS_ERR(dent))
- debugfs_create_file("smux_ctl_state", 0444, dent,
- NULL, &debug_ops);
- return 0;
- }
- late_initcall(smux_debugfs_init);
- #endif
- module_init(smux_ctl_init);
- MODULE_DESCRIPTION("MSM SMUX Control Port");
- MODULE_LICENSE("GPL v2");
|