mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
04e554807c
Change-Id: Ibead64ce2e901dede2ddd1b86088b88f2350ce92 Signed-off-by: Duy Truong <dtruong@codeaurora.org>
308 lines
7.7 KiB
C
308 lines
7.7 KiB
C
/* drivers/tty/smux_loopback.c
|
|
*
|
|
* Copyright (c) 2012, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
#include <linux/types.h>
|
|
#include <linux/err.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/kfifo.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/smux.h>
|
|
#include "smux_private.h"
|
|
|
|
#define SMUX_LOOP_FIFO_SIZE 128
|
|
|
|
static void smux_loopback_rx_worker(struct work_struct *work);
|
|
static struct workqueue_struct *smux_loopback_wq;
|
|
static DECLARE_WORK(smux_loopback_work, smux_loopback_rx_worker);
|
|
static struct kfifo smux_loop_pkt_fifo;
|
|
static DEFINE_SPINLOCK(hw_fn_lock);
|
|
|
|
/**
|
|
* Initialize loopback framework (called by n_smux.c).
|
|
*/
|
|
int smux_loopback_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
spin_lock_init(&hw_fn_lock);
|
|
smux_loopback_wq = create_singlethread_workqueue("smux_loopback_wq");
|
|
if (IS_ERR(smux_loopback_wq)) {
|
|
pr_err("%s: failed to create workqueue\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret |= kfifo_alloc(&smux_loop_pkt_fifo,
|
|
SMUX_LOOP_FIFO_SIZE * sizeof(struct smux_pkt_t *),
|
|
GFP_KERNEL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Simulate a write to the TTY hardware by duplicating
|
|
* the TX packet and putting it into the RX queue.
|
|
*
|
|
* @pkt Packet to write
|
|
*
|
|
* @returns 0 on success
|
|
*/
|
|
int smux_tx_loopback(struct smux_pkt_t *pkt_ptr)
|
|
{
|
|
struct smux_pkt_t *send_pkt;
|
|
unsigned long flags;
|
|
int i;
|
|
int ret;
|
|
|
|
/* duplicate packet */
|
|
send_pkt = smux_alloc_pkt();
|
|
send_pkt->hdr = pkt_ptr->hdr;
|
|
if (pkt_ptr->hdr.payload_len) {
|
|
ret = smux_alloc_pkt_payload(send_pkt);
|
|
if (ret) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
memcpy(send_pkt->payload, pkt_ptr->payload,
|
|
pkt_ptr->hdr.payload_len);
|
|
}
|
|
|
|
/* queue duplicate as pseudo-RX data */
|
|
spin_lock_irqsave(&hw_fn_lock, flags);
|
|
i = kfifo_avail(&smux_loop_pkt_fifo);
|
|
if (i < sizeof(struct smux_pkt_t *)) {
|
|
pr_err("%s: no space in fifo\n", __func__);
|
|
ret = -ENOMEM;
|
|
goto unlock;
|
|
}
|
|
|
|
i = kfifo_in(&smux_loop_pkt_fifo,
|
|
&send_pkt,
|
|
sizeof(struct smux_pkt_t *));
|
|
if (i < 0) {
|
|
pr_err("%s: fifo error\n", __func__);
|
|
ret = -ENOMEM;
|
|
goto unlock;
|
|
}
|
|
queue_work(smux_loopback_wq, &smux_loopback_work);
|
|
ret = 0;
|
|
|
|
unlock:
|
|
spin_unlock_irqrestore(&hw_fn_lock, flags);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Receive loopback byte processor.
|
|
*
|
|
* @pkt Incoming packet
|
|
*/
|
|
static void smux_loopback_rx_byte(struct smux_pkt_t *pkt)
|
|
{
|
|
static int simulated_retry_cnt;
|
|
const char ack = SMUX_WAKEUP_ACK;
|
|
|
|
switch (pkt->hdr.flags) {
|
|
case SMUX_WAKEUP_REQ:
|
|
/* reply with ACK after appropriate delays */
|
|
++simulated_retry_cnt;
|
|
if (simulated_retry_cnt >= smux_simulate_wakeup_delay) {
|
|
pr_err("%s: completed %d of %d\n",
|
|
__func__, simulated_retry_cnt,
|
|
smux_simulate_wakeup_delay);
|
|
pr_err("%s: simulated wakeup\n", __func__);
|
|
simulated_retry_cnt = 0;
|
|
smux_rx_state_machine(&ack, 1, 0);
|
|
} else {
|
|
/* force retry */
|
|
pr_err("%s: dropping wakeup request %d of %d\n",
|
|
__func__, simulated_retry_cnt,
|
|
smux_simulate_wakeup_delay);
|
|
}
|
|
break;
|
|
case SMUX_WAKEUP_ACK:
|
|
/* this shouldn't happen since we don't send requests */
|
|
pr_err("%s: wakeup ACK unexpected\n", __func__);
|
|
break;
|
|
|
|
default:
|
|
/* invalid character */
|
|
pr_err("%s: invalid character 0x%x\n",
|
|
__func__, (unsigned)pkt->hdr.flags);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simulated remote hardware used for local loopback testing.
|
|
*
|
|
* @work Not used
|
|
*/
|
|
static void smux_loopback_rx_worker(struct work_struct *work)
|
|
{
|
|
struct smux_pkt_t *pkt;
|
|
struct smux_pkt_t reply_pkt;
|
|
char *data;
|
|
int len;
|
|
int lcid;
|
|
int i;
|
|
unsigned long flags;
|
|
|
|
data = kzalloc(SMUX_MAX_PKT_SIZE, GFP_ATOMIC);
|
|
|
|
spin_lock_irqsave(&hw_fn_lock, flags);
|
|
while (kfifo_len(&smux_loop_pkt_fifo) >= sizeof(struct smux_pkt_t *)) {
|
|
i = kfifo_out(&smux_loop_pkt_fifo, &pkt,
|
|
sizeof(struct smux_pkt_t *));
|
|
spin_unlock_irqrestore(&hw_fn_lock, flags);
|
|
|
|
if (pkt->hdr.magic != SMUX_MAGIC) {
|
|
pr_err("%s: invalid magic %x\n", __func__,
|
|
pkt->hdr.magic);
|
|
return;
|
|
}
|
|
|
|
lcid = pkt->hdr.lcid;
|
|
|
|
switch (pkt->hdr.cmd) {
|
|
case SMUX_CMD_OPEN_LCH:
|
|
if (smux_assert_lch_id(lcid)) {
|
|
pr_err("%s: invalid channel id %d\n",
|
|
__func__, lcid);
|
|
break;
|
|
}
|
|
|
|
if (pkt->hdr.flags & SMUX_CMD_OPEN_ACK)
|
|
break;
|
|
|
|
/* Reply with Open ACK */
|
|
smux_init_pkt(&reply_pkt);
|
|
reply_pkt.hdr.lcid = lcid;
|
|
reply_pkt.hdr.cmd = SMUX_CMD_OPEN_LCH;
|
|
reply_pkt.hdr.flags = SMUX_CMD_OPEN_ACK
|
|
| SMUX_CMD_OPEN_POWER_COLLAPSE;
|
|
reply_pkt.hdr.payload_len = 0;
|
|
reply_pkt.hdr.pad_len = 0;
|
|
smux_serialize(&reply_pkt, data, &len);
|
|
smux_rx_state_machine(data, len, 0);
|
|
|
|
/* Send Remote Open */
|
|
smux_init_pkt(&reply_pkt);
|
|
reply_pkt.hdr.lcid = lcid;
|
|
reply_pkt.hdr.cmd = SMUX_CMD_OPEN_LCH;
|
|
reply_pkt.hdr.flags = SMUX_CMD_OPEN_POWER_COLLAPSE;
|
|
reply_pkt.hdr.payload_len = 0;
|
|
reply_pkt.hdr.pad_len = 0;
|
|
smux_serialize(&reply_pkt, data, &len);
|
|
smux_rx_state_machine(data, len, 0);
|
|
break;
|
|
|
|
case SMUX_CMD_CLOSE_LCH:
|
|
if (smux_assert_lch_id(lcid)) {
|
|
pr_err("%s: invalid channel id %d\n",
|
|
__func__, lcid);
|
|
break;
|
|
}
|
|
|
|
if (pkt->hdr.flags == SMUX_CMD_CLOSE_ACK)
|
|
break;
|
|
|
|
/* Reply with Close ACK */
|
|
smux_init_pkt(&reply_pkt);
|
|
reply_pkt.hdr.lcid = lcid;
|
|
reply_pkt.hdr.cmd = SMUX_CMD_CLOSE_LCH;
|
|
reply_pkt.hdr.flags = SMUX_CMD_CLOSE_ACK;
|
|
reply_pkt.hdr.payload_len = 0;
|
|
reply_pkt.hdr.pad_len = 0;
|
|
smux_serialize(&reply_pkt, data, &len);
|
|
smux_rx_state_machine(data, len, 0);
|
|
|
|
/* Send Remote Close */
|
|
smux_init_pkt(&reply_pkt);
|
|
reply_pkt.hdr.lcid = lcid;
|
|
reply_pkt.hdr.cmd = SMUX_CMD_CLOSE_LCH;
|
|
reply_pkt.hdr.flags = 0;
|
|
reply_pkt.hdr.payload_len = 0;
|
|
reply_pkt.hdr.pad_len = 0;
|
|
smux_serialize(&reply_pkt, data, &len);
|
|
smux_rx_state_machine(data, len, 0);
|
|
break;
|
|
|
|
case SMUX_CMD_DATA:
|
|
if (smux_assert_lch_id(lcid)) {
|
|
pr_err("%s: invalid channel id %d\n",
|
|
__func__, lcid);
|
|
break;
|
|
}
|
|
|
|
/* Echo back received data */
|
|
smux_init_pkt(&reply_pkt);
|
|
reply_pkt.hdr.lcid = lcid;
|
|
reply_pkt.hdr.cmd = SMUX_CMD_DATA;
|
|
reply_pkt.hdr.flags = 0;
|
|
reply_pkt.hdr.payload_len = pkt->hdr.payload_len;
|
|
reply_pkt.payload = pkt->payload;
|
|
reply_pkt.hdr.pad_len = pkt->hdr.pad_len;
|
|
smux_serialize(&reply_pkt, data, &len);
|
|
smux_rx_state_machine(data, len, 0);
|
|
break;
|
|
|
|
case SMUX_CMD_STATUS:
|
|
if (smux_assert_lch_id(lcid)) {
|
|
pr_err("%s: invalid channel id %d\n",
|
|
__func__, lcid);
|
|
break;
|
|
}
|
|
|
|
/* Echo back received status */
|
|
smux_init_pkt(&reply_pkt);
|
|
reply_pkt.hdr.lcid = lcid;
|
|
reply_pkt.hdr.cmd = SMUX_CMD_STATUS;
|
|
reply_pkt.hdr.flags = pkt->hdr.flags;
|
|
reply_pkt.hdr.payload_len = 0;
|
|
reply_pkt.payload = NULL;
|
|
reply_pkt.hdr.pad_len = pkt->hdr.pad_len;
|
|
smux_serialize(&reply_pkt, data, &len);
|
|
smux_rx_state_machine(data, len, 0);
|
|
break;
|
|
|
|
case SMUX_CMD_PWR_CTL:
|
|
/* reply with ack */
|
|
smux_init_pkt(&reply_pkt);
|
|
reply_pkt.hdr.lcid = SMUX_BROADCAST_LCID;
|
|
reply_pkt.hdr.cmd = SMUX_CMD_PWR_CTL;
|
|
reply_pkt.hdr.flags = SMUX_CMD_PWR_CTL_ACK;
|
|
reply_pkt.hdr.payload_len = 0;
|
|
reply_pkt.payload = NULL;
|
|
reply_pkt.hdr.pad_len = pkt->hdr.pad_len;
|
|
smux_serialize(&reply_pkt, data, &len);
|
|
smux_rx_state_machine(data, len, 0);
|
|
break;
|
|
|
|
case SMUX_CMD_BYTE:
|
|
smux_loopback_rx_byte(pkt);
|
|
break;
|
|
|
|
default:
|
|
pr_err("%s: unknown command %d\n",
|
|
__func__, pkt->hdr.cmd);
|
|
break;
|
|
};
|
|
|
|
smux_free_pkt(pkt);
|
|
spin_lock_irqsave(&hw_fn_lock, flags);
|
|
}
|
|
spin_unlock_irqrestore(&hw_fn_lock, flags);
|
|
kfree(data);
|
|
}
|