1327 lines
29 KiB
C
1327 lines
29 KiB
C
#include "ttyPos.h"
|
|
|
|
#define DRV_VERSION "303"
|
|
#define VERSION_DATE "2014.10.13_01"
|
|
#define MAX_RETRY_S 5
|
|
#define DRV_NAME "ttyPos"
|
|
|
|
static struct tty_pos *pdx_table[POS_TTY_MINORS];
|
|
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
static struct tty_port pos_port[POS_TTY_MINORS];
|
|
#endif
|
|
#endif
|
|
|
|
static unsigned char ResetPipePort(struct tty_pos *dev)
|
|
{
|
|
struct tty_pos *pdx = dev;
|
|
int retval;
|
|
|
|
retval = usb_clear_halt(pdx->udev, usb_sndbulkpipe(pdx->udev,
|
|
pdx->bulk_out_epAddr));
|
|
if (retval) {
|
|
ERR("%s - ERROR CLEAR %X HALT = %d",
|
|
__func__, pdx->bulk_out_epAddr, retval);
|
|
goto reset_port;
|
|
}
|
|
|
|
retval = usb_clear_halt(pdx->udev, usb_rcvbulkpipe(pdx->udev,
|
|
pdx->bulk_in_epAddr));
|
|
if (retval) {
|
|
ERR("%s - ERROR CLEAR %X HALT = %d",
|
|
__func__, pdx->bulk_in_epAddr, retval);
|
|
goto reset_port;
|
|
}
|
|
|
|
return 0;
|
|
|
|
reset_port:
|
|
retval = usb_reset_device(pdx->udev);
|
|
if (retval) {
|
|
ERR("%s - ERROR RESETTING DEVICE: %d", __func__, retval);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int VerifyChecksum(ST_BULK_IO *p_bio)
|
|
{
|
|
unsigned char a, b;
|
|
int i, dn;
|
|
|
|
dn = p_bio->Len + 4;
|
|
a = 0;
|
|
|
|
for (i = 2; i < dn; i++) {
|
|
a ^= ((unsigned char *)p_bio)[i];
|
|
}
|
|
|
|
a ^= p_bio->SeqNo & 0x0f;
|
|
a ^= p_bio->ReqType & 0x0f;
|
|
b = (p_bio->SeqNo & 0xf0) + (p_bio->ReqType >> 4);
|
|
if (a != b)
|
|
return 1;
|
|
|
|
/* clear checksum field */
|
|
p_bio->SeqNo &= 0x0f;
|
|
p_bio->ReqType &= 0x0f;
|
|
return 0;
|
|
}
|
|
|
|
static void SetChecksum(ST_BULK_IO *p_bio)
|
|
{
|
|
unsigned char a;
|
|
int i, dn;
|
|
|
|
dn = p_bio->Len + 4;
|
|
a = 0;
|
|
|
|
for (i = 2; i < dn; i++) {
|
|
a ^= ((unsigned char *)p_bio)[i];
|
|
}
|
|
|
|
a ^= p_bio->SeqNo & 0x0f;
|
|
a ^= p_bio->ReqType & 0x0f;
|
|
|
|
/* fill high 4 bits of checksum into high 4 bits of ID field */
|
|
p_bio->SeqNo = (p_bio->SeqNo & 0x0f) | (a & 0xf0);
|
|
|
|
/* fill low 4 bits of checksum into high 4 bits of REQ_TYPE field */
|
|
p_bio->ReqType |= (a << 4);
|
|
}
|
|
|
|
static void UrbCallBack(struct urb *urb)
|
|
{
|
|
struct tty_pos *pdx;
|
|
|
|
pdx = (struct tty_pos *)urb->context;
|
|
|
|
atomic_set(&pdx->urb_done, 1);
|
|
wake_up(&pdx->urb_wait);
|
|
}
|
|
|
|
static int SendAndWaitUrb(struct tty_pos *dev)
|
|
{
|
|
struct tty_pos *pdx = dev;
|
|
int retval;
|
|
|
|
atomic_set(&pdx->urb_done, 0);
|
|
|
|
/* send the data out the bulk port */
|
|
retval = usb_submit_urb(pdx->urb, /* GFP_KERNEL */ GFP_ATOMIC);
|
|
if (retval) {
|
|
ERR("%s - FAILED SUBMITTING WRITE URB: %d", __func__, retval);
|
|
retval = 1;
|
|
goto exit;
|
|
}
|
|
retval = wait_event_timeout(pdx->urb_wait,
|
|
(atomic_read(&pdx->urb_done) == 1), pdx->timeout_jiffies);
|
|
if (retval == 0) {
|
|
/* INFO("URB TIMEOUT\n"); */
|
|
|
|
if (atomic_read(&pdx->urb_done) == 0) { /* urb is not done */
|
|
#if 0
|
|
usb_unlink_urb(pdx->urb);
|
|
printk(KERN_ALERT "usb_done: %d; %d\n",
|
|
(atomic_read(&pdx->urb_done) == 1),
|
|
/* pdx->urb_done, */ usb_unlink_urb(pdx->urb));
|
|
#endif
|
|
usb_kill_urb(pdx->urb);
|
|
#if 0
|
|
wait_event(pdx->urb_wait,
|
|
(atomic_read(&pdx->urb_done) == 1));
|
|
#endif
|
|
}
|
|
#if 0
|
|
INFO("urb->status: %d, %d\n", pdx->urb->status,
|
|
pdx->urb->actual_length);
|
|
#endif
|
|
retval = 2;
|
|
goto exit;
|
|
}
|
|
else if (retval < 0) {
|
|
ERR("%s - WAIT FAILED: %d", __func__, retval);
|
|
retval = 3;
|
|
goto exit;
|
|
}
|
|
else {
|
|
retval = 0;
|
|
#if 0
|
|
printk(KERN_INFO "Use jiffies: %d", pdx->timeout_jiffies -
|
|
retval);
|
|
#endif
|
|
}
|
|
|
|
if (pdx->urb->status) {
|
|
/* if (pdx->urb->status != -EREMOTEIO) */
|
|
{
|
|
ERR("%s - nonzero status received: %d", __func__,
|
|
pdx->urb->status);
|
|
ERR("urb status:%d\n",pdx->urb->status);
|
|
retval = 4;
|
|
goto exit;
|
|
}
|
|
}
|
|
exit:
|
|
return retval;
|
|
}
|
|
|
|
/* error codes: 11~29 */
|
|
static int ProcessCommand(struct tty_pos *dev)
|
|
{
|
|
struct tty_pos *pdx = dev;
|
|
int retval;
|
|
|
|
/* stage 1: send command pack */
|
|
|
|
SetChecksum((ST_BULK_IO *)pdx->BioPack);
|
|
|
|
if (pdx->urb == NULL)
|
|
return 18;
|
|
|
|
if (pdx->urb->status == -EINPROGRESS) {
|
|
#if 0
|
|
ERR("URB IN PROGRESS\n");
|
|
#endif
|
|
return 19;
|
|
}
|
|
|
|
usb_fill_bulk_urb(pdx->urb, pdx->udev,
|
|
usb_sndbulkpipe(pdx->udev, pdx->bulk_out_epAddr),
|
|
pdx->BioPack, pdx->BioPack->Len + 4, UrbCallBack, pdx);
|
|
|
|
retval = SendAndWaitUrb((struct tty_pos *)pdx);
|
|
|
|
if (retval != 0)
|
|
return retval + 10;
|
|
|
|
/* stage 2: receive answer pack */
|
|
|
|
/* clear pack flags */
|
|
pdx->BioPack->SeqNo = 0;
|
|
pdx->BioPack->ReqType = 0;
|
|
pdx->BioPack->Len = 0;
|
|
if (pdx->urb == NULL)
|
|
return 28;
|
|
|
|
usb_fill_bulk_urb(pdx->urb, pdx->udev,
|
|
usb_rcvbulkpipe(pdx->udev, pdx->bulk_in_epAddr),
|
|
pdx->BioPack, sizeof(*pdx->BioPack), UrbCallBack, pdx);
|
|
|
|
retval = SendAndWaitUrb((struct tty_pos *)pdx);
|
|
if (retval != 0)
|
|
return retval + 20;
|
|
|
|
if (VerifyChecksum((ST_BULK_IO *)pdx->BioPack)) {
|
|
unsigned int i;
|
|
/* unsigned char x; */
|
|
|
|
ERR("VERIFY CHECKSUM FAILED: %d\n", retval);
|
|
ERR("%X; %X; %X\n", pdx->BioPack->SeqNo,
|
|
pdx->BioPack->ReqType, pdx->BioPack->Len);
|
|
|
|
for (i = 0; i < 508; i++) {
|
|
INFO("%X\n", pdx->BioPack->Data[i]);
|
|
}
|
|
#if 0
|
|
for (i = 0, x = pdx->BioPack.Data[0];
|
|
i < pdx->BioPack.Len; i++, x++) {
|
|
if (pdx->BioPack.Data[i] != x) {
|
|
printk(KERN_ALERT "%d: %X; %X\n", i - 1,
|
|
pdx->BioPack.Data[i - 1], x - 1);
|
|
printk(KERN_ALERT "%d: %X; %X\n", i,
|
|
pdx->BioPack.Data[i], x);
|
|
printk(KERN_ALERT "%d: %X; %X\n", i + 1,
|
|
pdx->BioPack.Data[i + 1], x + 1);
|
|
printk(KERN_ALERT "%d: %X; %X\n", i + 2,
|
|
pdx->BioPack.Data[i + 2], x + 2);
|
|
printk(KERN_ALERT "%d: %X; %X\n", i + 3,
|
|
pdx->BioPack.Data[i + 3], x + 3);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
return 29;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void SleepMs(unsigned int nMs, struct tty_pos *dev)
|
|
{
|
|
#if 0
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
schedule_timeout(nMs * HZ / 1000);
|
|
#endif
|
|
struct tty_pos *pdx = dev;
|
|
|
|
unsigned int timeout = (nMs * HZ / 1000);
|
|
if (timeout < 1) {
|
|
timeout = 1;
|
|
}
|
|
|
|
wait_event_timeout(pdx->write_wait,
|
|
(atomic_read(&pdx->write_flag) == 1), timeout);
|
|
}
|
|
|
|
static int ThreadCallBack(void *data)
|
|
{
|
|
struct tty_pos *pdx = data;
|
|
unsigned char loops;
|
|
int retval;
|
|
unsigned int i, rlen, wlen;
|
|
struct tty_struct *tty;
|
|
|
|
retval = ResetPipePort(pdx);
|
|
if (retval != 0) {
|
|
retval = 1;
|
|
goto exit;
|
|
}
|
|
|
|
tty = pdx->tty;
|
|
|
|
while (pdx->ThreadState == THREAD_CREATED) {
|
|
|
|
/* get device buffer status */
|
|
for (loops = 0; (loops < MAX_RETRY_S) &&
|
|
(pdx->ThreadState == THREAD_CREATED); loops++) {
|
|
/* building command pack */
|
|
pdx->SeqCount = (pdx->SeqCount + 1) & 0x0f;
|
|
pdx->BioPack->SeqNo = pdx->SeqCount;
|
|
pdx->BioPack->ReqType = STATUS_COMMAND;
|
|
pdx->BioPack->Len = 0;
|
|
|
|
retval = ProcessCommand((struct tty_pos *)pdx);
|
|
if (retval != 0) {
|
|
retval += 100;
|
|
goto loop_s_tail;
|
|
}
|
|
|
|
if (pdx->urb->actual_length != 20) {
|
|
retval = 130;
|
|
goto loop_s_tail;
|
|
}
|
|
|
|
if (pdx->BioPack->SeqNo != pdx->SeqCount) {
|
|
retval = 131;
|
|
goto loop_s_tail;
|
|
}
|
|
|
|
if (pdx->BioPack->ReqType != STATUS_COMMAND) {
|
|
retval = 132;
|
|
goto loop_s_tail;
|
|
}
|
|
|
|
memcpy(&pdx->BioDevState, pdx->BioPack->Data,
|
|
sizeof(pdx->BioDevState));
|
|
|
|
if ((!pdx->BioDevState.TxLeft) &&
|
|
IS_POOL_EMPTY(pdx->TxPool)) {
|
|
SleepMs(10, (struct tty_pos *)pdx);
|
|
}
|
|
|
|
loop_s_tail:
|
|
if (retval == 0)
|
|
break;
|
|
ERR("STATUS RETRY, loop: %d, err: %d, seq: %02X\n",
|
|
loops, retval, pdx->SeqCount);
|
|
ResetPipePort(pdx);
|
|
}
|
|
|
|
if (retval != 0)
|
|
goto exit;
|
|
|
|
r_process: /* read from usb device */
|
|
|
|
for (loops = 0; (loops < MAX_RETRY_S) &&
|
|
(pdx->ThreadState == THREAD_CREATED); loops++) {
|
|
if (!pdx->BioDevState.TxLeft)
|
|
goto w_process;
|
|
|
|
rlen = pdx->BioDevState.TxLeft;
|
|
|
|
if (rlen > sizeof(pdx->BioPack->Data)) {
|
|
rlen = sizeof(pdx->BioPack->Data);
|
|
}
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
rlen = tty_buffer_request_room(&pos_port[pdx->devIndex],rlen);
|
|
#else
|
|
rlen = tty_buffer_request_room(tty, rlen);
|
|
#endif
|
|
#endif
|
|
if (!loops) {
|
|
pdx->SeqCount = (pdx->SeqCount + 1) & 0x0f;
|
|
}
|
|
|
|
pdx->BioPack->SeqNo = pdx->SeqCount;
|
|
pdx->BioPack->ReqType = READ_COMMAND;
|
|
pdx->BioPack->Len = 2;
|
|
/* in dlen required */
|
|
pdx->BioPack->Data[0] = (unsigned short)rlen & 0xff;
|
|
pdx->BioPack->Data[1] = (unsigned short)rlen >> 8;
|
|
|
|
retval = ProcessCommand((struct tty_pos *)pdx);
|
|
if (retval != 0) {
|
|
retval += 200;
|
|
goto loop_r_tail;
|
|
}
|
|
|
|
if (pdx->BioPack->SeqNo != pdx->SeqCount) {
|
|
retval = 231;
|
|
goto loop_r_tail;
|
|
}
|
|
|
|
if (pdx->BioPack->ReqType != READ_COMMAND) {
|
|
if ((pdx->BioPack->ReqType == STATUS_COMMAND) &&
|
|
(pdx->BioPack->Len >=
|
|
sizeof(pdx->BioDevState))) {
|
|
memcpy(&pdx->BioDevState,
|
|
pdx->BioPack->Data,
|
|
sizeof(pdx->BioDevState));
|
|
goto w_process; /* no data to fetch */
|
|
}
|
|
|
|
retval = 232;
|
|
ERR(" %02X, ERROR req_type: %02X.\n",
|
|
pdx->SeqCount, pdx->BioPack->ReqType);
|
|
|
|
goto loop_r_tail;
|
|
}
|
|
|
|
if (pdx->urb->actual_length <
|
|
(int)pdx->BioPack->Len + 4) {
|
|
retval = 233;
|
|
goto loop_r_tail;
|
|
}
|
|
|
|
if (pdx->BioPack->Len > rlen) {
|
|
ERR("MORE DATA FETCHED THAN DECLARED, NEED: "
|
|
"%d, RN: %d\n", rlen, pdx->BioPack->Len);
|
|
retval = 234;
|
|
goto exit;
|
|
}
|
|
|
|
rlen = pdx->BioPack->Len;
|
|
#if 0
|
|
for (j = 0; j < rlen - 1; j++) {
|
|
if ((pdx->BioPack.Data[j] + 1 !=
|
|
pdx->BioPack.Data[j + 1])
|
|
&& (pdx->BioPack.Data[j + 1] != 0)) {
|
|
inerr = 235;
|
|
goto exit;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
tty_insert_flip_string(&pos_port[pdx->devIndex], pdx->BioPack->Data, rlen);
|
|
tty_flip_buffer_push(&pos_port[pdx->devIndex]);
|
|
#else
|
|
tty_insert_flip_string(tty, pdx->BioPack->Data, rlen);
|
|
tty_flip_buffer_push(tty);
|
|
#endif
|
|
#endif
|
|
pdx->BioDevState.TxLeft -= rlen;
|
|
#if 0
|
|
printk(KERN_ALERT "%02X, RN: %d\n", pdx->SeqCount,
|
|
rlen);
|
|
#endif
|
|
|
|
if (pdx->BioDevState.TxLeft)
|
|
goto r_process;
|
|
loop_r_tail:
|
|
if (retval == 0)
|
|
break;
|
|
|
|
ERR("RX RETRY, loop: %d, err: %d, SEQ: %02X\n",
|
|
loops, retval, pdx->SeqCount);
|
|
|
|
ResetPipePort(pdx);
|
|
}
|
|
|
|
if (retval)
|
|
goto exit;
|
|
|
|
w_process: /* write to usb device */
|
|
|
|
wlen = GET_USING_POOL(pdx->TxPool);
|
|
|
|
if (wlen > sizeof(pdx->BioPack->Data)) {
|
|
wlen = sizeof(pdx->BioPack->Data);
|
|
}
|
|
|
|
if (wlen > pdx->BioDevState.RxLeft) {
|
|
wlen = pdx->BioDevState.RxLeft;
|
|
}
|
|
|
|
for (loops = 0; (loops < MAX_RETRY_S) &&
|
|
(pdx->ThreadState == THREAD_CREATED); loops++) {
|
|
if (wlen == 0)
|
|
break;
|
|
|
|
if (!loops) {
|
|
pdx->SeqCount = (pdx->SeqCount + 1) & 0x0f;
|
|
}
|
|
|
|
pdx->BioPack->SeqNo = pdx->SeqCount;
|
|
pdx->BioPack->ReqType = WRITE_COMMAND;
|
|
pdx->BioPack->Len = (unsigned short)wlen;
|
|
|
|
for (i = 0; i < wlen; i++) {
|
|
pdx->BioPack->Data[i] =
|
|
pdx->TxPool.Buffer[(pdx->TxPool.ReadPos +
|
|
i) % POOL_SIZE];
|
|
}
|
|
|
|
retval = ProcessCommand((struct tty_pos *)pdx);
|
|
if (retval != 0) {
|
|
retval += 300;
|
|
goto loop_w_tail;
|
|
}
|
|
|
|
if (pdx->urb->actual_length != 20) {
|
|
retval = 330;
|
|
goto loop_w_tail;
|
|
}
|
|
|
|
if (pdx->BioPack->SeqNo != pdx->SeqCount) {
|
|
retval = 331;
|
|
|
|
ERR("***** Mismatched with SEQ: %02X, "
|
|
"wlen: %d, loop: %d\n", pdx->SeqCount,
|
|
wlen, loops);
|
|
|
|
ERR(" SEQ: %02X, type: %02X, dn: %d\n",
|
|
pdx->BioPack->SeqNo, pdx->BioPack->ReqType,
|
|
pdx->BioPack->Len);
|
|
|
|
for (i = 0; i < pdx->BioPack->Len; i++) {
|
|
INFO("%02X ", pdx->BioPack->Data[i]);
|
|
}
|
|
|
|
if (((pdx->BioPack->SeqNo + 1) % 16) ==
|
|
pdx->SeqCount)
|
|
goto loop_w_tail;
|
|
|
|
goto exit;
|
|
} /* mismatched seq_no */
|
|
|
|
if (pdx->BioPack->ReqType != STATUS_COMMAND) {
|
|
retval = 332;
|
|
|
|
ERR(" SEQ: %02X, type: %02X, dn: %d\n",
|
|
pdx->BioPack->SeqNo, pdx->BioPack->ReqType,
|
|
pdx->BioPack->Len);
|
|
|
|
for (i = 0; i < sizeof(pdx->BioPack->Data);
|
|
i++) {
|
|
INFO("%02X ", pdx->BioPack->Data[i]);
|
|
}
|
|
|
|
INFO("\n");
|
|
|
|
goto exit;
|
|
} /* mismatched req_type */
|
|
#if 0
|
|
printk(KERN_ALERT "%02X, WN: %d\n", pdx->SeqCount,
|
|
wlen);
|
|
#endif
|
|
pdx->TxPool.ReadPos = (pdx->TxPool.ReadPos + wlen) %
|
|
POOL_SIZE;
|
|
|
|
memcpy(&pdx->BioDevState, pdx->BioPack->Data,
|
|
sizeof(pdx->BioDevState));
|
|
|
|
if (!IS_POOL_EMPTY(pdx->TxPool))
|
|
goto w_process;
|
|
|
|
atomic_set(&pdx->write_flag, 0);
|
|
loop_w_tail:
|
|
if (retval == 0)
|
|
break;
|
|
|
|
ERR("TX RETRY, loop: %d, err: %d, SEQ: %02X\n",
|
|
loops, retval, pdx->SeqCount);
|
|
|
|
ResetPipePort(pdx);
|
|
}
|
|
|
|
if (retval)
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
#if 0
|
|
INFO("ThreadCallBack Exit\n");
|
|
#endif
|
|
if (retval != 0) {
|
|
ERR("%02X, ERR: %d\n", pdx->SeqCount, retval);
|
|
|
|
ResetPipePort(pdx);
|
|
}
|
|
|
|
pdx->ThreadState = THREAD_STOPPED;
|
|
complete(&pdx->ThreadExit_completion);
|
|
do_exit(0);
|
|
}
|
|
|
|
static void pos_delete(struct kref *kref)
|
|
{
|
|
struct tty_pos *pdx = to_pos_dev(kref);
|
|
|
|
if (pdx == NULL)
|
|
return;
|
|
|
|
if(pdx->devIndex >= POS_TTY_MINORS)
|
|
return;
|
|
|
|
if (pdx->tty) {
|
|
pdx->tty->driver_data = NULL;
|
|
}
|
|
|
|
pdx_table[pdx->devIndex] = NULL;
|
|
usb_free_urb(pdx->urb);
|
|
usb_put_dev(pdx->udev);
|
|
kfree(pdx->BioPack);
|
|
kfree(pdx);
|
|
}
|
|
|
|
static int pos_open(struct tty_struct *tty, struct file *filp)
|
|
{
|
|
struct tty_pos *pdx;
|
|
|
|
if(tty==NULL)return USB_ERR_MEM_SYSTEM;
|
|
if(tty->index >= POS_TTY_MINORS || tty->index<0)return USB_ERR_MEM_SYSTEM;
|
|
|
|
// INFO("%s index:%d\n",__func__,tty->index);
|
|
pdx = pdx_table[tty->index];
|
|
if (pdx == NULL)
|
|
return USB_ERR_DEV_ABSENT;
|
|
|
|
if (THREAD_IS_RUNNING(pdx->ThreadState)) {
|
|
return USB_ERR_BUSY;
|
|
}
|
|
|
|
tty->driver_data = pdx;
|
|
pdx->tty = tty;
|
|
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
tty_port_tty_set(&pos_port[pdx->devIndex], tty);
|
|
#endif
|
|
#endif
|
|
|
|
kref_get(&pdx->kref);
|
|
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
reinit_completion(&pdx->ThreadExit_completion);
|
|
#else
|
|
INIT_COMPLETION(pdx->ThreadExit_completion);
|
|
#endif
|
|
#endif
|
|
pdx->ThreadState = THREAD_CREATED;
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
{
|
|
struct task_struct *thread;
|
|
thread = kthread_run(ThreadCallBack,(struct tty_pos *)pdx,"ThreadCallBack");
|
|
if (IS_ERR(thread))
|
|
{
|
|
ERR("FAILED TO CREATE KERNEL THREAD!\n");
|
|
pdx->ThreadState = THREAD_INIT;
|
|
kref_put(&pdx->kref, pos_delete);
|
|
return USB_ERR_RC_SYSTEM;
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
pid_t pid;
|
|
pid = kernel_thread(ThreadCallBack, (struct tty_pos *)pdx,
|
|
CLONE_FS | CLONE_FILES);
|
|
if (pid < 0) {
|
|
ERR("FAILED TO CREATE KERNEL THREAD!\n");
|
|
pdx->ThreadState = THREAD_INIT;
|
|
kref_put(&pdx->kref, pos_delete);
|
|
return USB_ERR_RC_SYSTEM;
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
pdx->filp = filp;
|
|
return 0;
|
|
}
|
|
|
|
static void pos_close(struct tty_struct *tty, struct file *filp)
|
|
{
|
|
struct tty_pos *pdx = tty->driver_data;
|
|
|
|
if (pdx == NULL)
|
|
return;
|
|
if(pdx_table[pdx->devIndex]==NULL)
|
|
return;
|
|
|
|
if (pdx->ThreadState == THREAD_CREATED) {
|
|
pdx->ThreadState = THREAD_CLOSE;
|
|
wait_for_completion(&pdx->ThreadExit_completion);
|
|
}
|
|
|
|
pdx->ThreadState = THREAD_INIT;
|
|
|
|
kref_put(&pdx->kref, pos_delete);
|
|
//tty_ldisc_flush(tty);
|
|
}
|
|
|
|
static int pos_write(struct tty_struct *tty, const unsigned char *buf,
|
|
int count)
|
|
{
|
|
struct tty_pos *pdx = tty->driver_data;
|
|
unsigned int wn, i;
|
|
int retval;
|
|
|
|
if (!pdx)
|
|
return USB_ERR_NOT_OPEN;
|
|
|
|
if (!THREAD_IS_RUNNING(pdx->ThreadState)) {
|
|
retval = USB_ERR_INVALID;
|
|
goto exit;
|
|
}
|
|
|
|
if (count == 0) {
|
|
retval = 0;
|
|
goto exit;
|
|
}
|
|
|
|
wn = GET_SPACE_POOL(pdx->TxPool);
|
|
if (wn >= count) {
|
|
wn = count;
|
|
}
|
|
else
|
|
return USB_ERR_BUF;
|
|
|
|
for (i = 0; i < wn; i++) {
|
|
pdx->TxPool.Buffer[(pdx->TxPool.WritePos + i) % POOL_SIZE] =
|
|
buf[i];
|
|
}
|
|
|
|
pdx->TxPool.WritePos = (pdx->TxPool.WritePos + wn) % POOL_SIZE;
|
|
retval = wn;
|
|
|
|
atomic_set(&pdx->write_flag, 1);
|
|
wake_up(&pdx->write_wait);
|
|
exit:
|
|
return retval;
|
|
}
|
|
|
|
static int pos_write_room(struct tty_struct *tty)
|
|
{
|
|
struct tty_pos *pdx = tty->driver_data;
|
|
int room = -EINVAL;
|
|
|
|
if (!pdx)
|
|
return -ENODEV;
|
|
|
|
room = GET_SPACE_POOL(pdx->TxPool);
|
|
return room;
|
|
}
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
|
|
static int pos_ioctl(struct tty_struct *tty, struct file *filp,
|
|
unsigned int cmd, unsigned long arg)
|
|
#else
|
|
static int pos_ioctl(struct tty_struct *tty, unsigned int cmd,
|
|
unsigned long arg)
|
|
#endif
|
|
{
|
|
struct tty_pos *pdx = tty->driver_data;
|
|
|
|
#if 0
|
|
INFO("pos_ioctl\n");
|
|
#endif
|
|
if (!pdx)
|
|
return -ENODEV;
|
|
|
|
switch (cmd) {
|
|
case TIOCGSERIAL:
|
|
case TIOCMIWAIT:
|
|
case TIOCGICOUNT:
|
|
default:
|
|
break;
|
|
}
|
|
#if 0
|
|
INFO("%s-cmd: 0x%X\n", __func__, cmd);
|
|
#endif
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
|
|
#define RELEVANT_IFLAG(iflag) \
|
|
((iflag) & (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK))
|
|
|
|
static void pos_set_termios(struct tty_struct *tty,
|
|
struct ktermios *old_termios)
|
|
{
|
|
unsigned int cflag;
|
|
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
cflag = tty->termios.c_cflag;
|
|
#else
|
|
cflag = tty->termios->c_cflag;
|
|
#endif
|
|
#endif
|
|
/* check that they really want us to change something */
|
|
if (old_termios) {
|
|
if ((cflag == old_termios->c_cflag) &&
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
(RELEVANT_IFLAG(tty->termios.c_iflag) ==
|
|
#else
|
|
(RELEVANT_IFLAG(tty->termios->c_iflag) ==
|
|
#endif
|
|
#endif
|
|
RELEVANT_IFLAG(old_termios->c_iflag))) {
|
|
#if 0
|
|
INFO(" - nothing to change...\n");
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
#if 0
|
|
/* get the byte size */
|
|
switch (cflag & CSIZE) {
|
|
case CS5:
|
|
INFO(" - data bits = 5\n");
|
|
break;
|
|
case CS6:
|
|
INFO(" - data bits = 6\n");
|
|
break;
|
|
case CS7:
|
|
INFO(" - data bits = 7\n");
|
|
break;
|
|
default:
|
|
case CS8:
|
|
INFO(" - data bits = 8\n");
|
|
break;
|
|
}
|
|
|
|
/* determine the parity */
|
|
if (cflag & PARENB) {
|
|
if (cflag & PARODD) {
|
|
INFO(" - parity = odd\n");
|
|
}
|
|
else {
|
|
INFO(" - parity = even\n");
|
|
}
|
|
}
|
|
else {
|
|
INFO(" - parity = none\n");
|
|
}
|
|
|
|
/* figure out the stop bits requested */
|
|
if (cflag & CSTOPB) {
|
|
INFO(" - stop bits = 2\n");
|
|
}
|
|
else {
|
|
INFO(" - stop bits = 1\n");
|
|
}
|
|
|
|
/* figure out the hardware flow control settings */
|
|
if (cflag & CRTSCTS) {
|
|
INFO(" - RTS/CTS is enabled\n");
|
|
}
|
|
else {
|
|
INFO(" - RTS/CTS is disabled\n");
|
|
}
|
|
|
|
/* determine software flow control.
|
|
* if we are implementing XON/XOFF, set the start and
|
|
* stop character in the device */
|
|
if (I_IXOFF(tty) || I_IXON(tty)) {
|
|
unsigned char stop_char = STOP_CHAR(tty);
|
|
unsigned char start_char = START_CHAR(tty);
|
|
|
|
/* if we are implementing INBOUND XON/XOFF */
|
|
if (I_IXOFF(tty)) {
|
|
INFO(" - INBOUND XON/XOFF is enabled, "
|
|
"XON = %2x, XOFF = %2x", start_char, stop_char);
|
|
}
|
|
else {
|
|
INFO(" - INBOUND XON/XOFF is disabled");
|
|
}
|
|
|
|
/* if we are implementing OUTBOUND XON/XOFF */
|
|
if (I_IXON(tty)) {
|
|
INFO(" - OUTBOUND XON/XOFF is enabled, "
|
|
"XON = %2x, XOFF = %2x", start_char, stop_char);
|
|
}
|
|
else {
|
|
INFO(" - OUTBOUND XON/XOFF is disabled");
|
|
}
|
|
}
|
|
|
|
/* get the baud rate wanted */
|
|
INFO(" - baud rate = %d\n", tty_get_baud_rate(tty));
|
|
#endif
|
|
}
|
|
|
|
static void pos_throttle(struct tty_struct *tty)
|
|
{
|
|
/* INFO("pos_throttle\n"); */
|
|
}
|
|
|
|
static void pos_unthrottle(struct tty_struct *tty)
|
|
{
|
|
/* INFO("pos_unthrottle\n"); */
|
|
}
|
|
|
|
static void pos_flush_buffer(struct tty_struct *tty)
|
|
{
|
|
struct tty_pos *pdx = tty->driver_data;
|
|
|
|
#if 0
|
|
INFO("pos_flush_buffer\n");
|
|
#endif
|
|
|
|
if (!pdx)
|
|
return;
|
|
|
|
INIT_POOL_BUFFER(pdx->TxPool);
|
|
}
|
|
|
|
static int pos_chars_in_buffer(struct tty_struct *tty)
|
|
{
|
|
int in_buf_len;
|
|
struct tty_pos *pdx = tty->driver_data;
|
|
|
|
if (!pdx)
|
|
return -ENODEV;
|
|
|
|
in_buf_len = GET_USING_POOL(pdx->TxPool);
|
|
#if 0
|
|
printk(KERN_ALERT "pos_chars_in_buffer: %d\n", in_buf_len);
|
|
#endif
|
|
|
|
return in_buf_len;
|
|
}
|
|
|
|
/* Our fake UART values */
|
|
#define MCR_DTR 0x01
|
|
#define MCR_RTS 0x02
|
|
#define MCR_LOOP 0x04
|
|
#define MSR_CTS 0x08
|
|
#define MSR_CD 0x10
|
|
#define MSR_RI 0x20
|
|
#define MSR_DSR 0x40
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
|
|
static int pos_tiocmget(struct tty_struct *tty, struct file *filp)
|
|
#else
|
|
static int pos_tiocmget(struct tty_struct *tty)
|
|
#endif
|
|
{
|
|
struct tty_pos *pdx = tty->driver_data;
|
|
unsigned int msr, mcr, result;
|
|
|
|
#if 0
|
|
INFO("pos_tiocmget\n");
|
|
#endif
|
|
if (!pdx)
|
|
return -ENODEV;
|
|
|
|
msr = pdx->msr;
|
|
mcr = pdx->mcr;
|
|
|
|
result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0) | /* DTR is set */
|
|
((mcr & MCR_RTS) ? TIOCM_RTS : 0) | /* RTS is set */
|
|
((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) | /* LOOP is set */
|
|
((msr & MSR_CTS) ? TIOCM_CTS : 0) | /* CTS is set */
|
|
((msr & MSR_CD) ? TIOCM_CAR : 0) | /* Carrier detect is set */
|
|
((msr & MSR_RI) ? TIOCM_RI : 0) | /* Ring Indicator is set */
|
|
((msr & MSR_DSR) ? TIOCM_DSR : 0); /* DSR is set */
|
|
|
|
return result;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)
|
|
static int pos_tiocmset(struct tty_struct *tty, struct file *filp,
|
|
unsigned int set, unsigned int clear)
|
|
#else
|
|
static int pos_tiocmset(struct tty_struct *tty, unsigned int set,
|
|
unsigned int clear)
|
|
#endif
|
|
{
|
|
struct tty_pos *pdx = tty->driver_data;
|
|
unsigned int mcr;
|
|
|
|
if (!pdx)
|
|
return -ENODEV;
|
|
|
|
mcr = pdx->mcr;
|
|
|
|
if (set & TIOCM_RTS) {
|
|
mcr |= MCR_RTS;
|
|
}
|
|
if (set & TIOCM_DTR) {
|
|
mcr |= MCR_DTR; /* mcr |= MCR_RTS; */
|
|
}
|
|
|
|
if (clear & TIOCM_RTS) {
|
|
mcr &= ~MCR_RTS;
|
|
}
|
|
if (clear & TIOCM_DTR) {
|
|
mcr &= ~MCR_DTR; /* mcr &= ~MCR_RTS; */
|
|
}
|
|
|
|
/* set the new MCR value in the device */
|
|
pdx->mcr = mcr;
|
|
return 0;
|
|
}
|
|
|
|
static const struct tty_operations pos_ops = {
|
|
.open = pos_open,
|
|
.close = pos_close,
|
|
.write = pos_write,
|
|
.write_room = pos_write_room,
|
|
.ioctl = pos_ioctl,
|
|
.set_termios = pos_set_termios,
|
|
.throttle = pos_throttle,
|
|
.unthrottle = pos_unthrottle,
|
|
.flush_buffer = pos_flush_buffer,
|
|
.chars_in_buffer = pos_chars_in_buffer,
|
|
.tiocmget = pos_tiocmget,
|
|
.tiocmset = pos_tiocmset,
|
|
};
|
|
|
|
struct tty_driver *pos_tty_driver;
|
|
|
|
static int pos_usb_probe(struct usb_interface *interface,
|
|
const struct usb_device_id *id)
|
|
{
|
|
struct tty_pos *pdx;
|
|
struct usb_host_interface *iface_desc;
|
|
struct usb_endpoint_descriptor *endpoint;
|
|
|
|
int i, retval = -ENOMEM;
|
|
|
|
for(i=0;i<POS_TTY_MINORS;i++)
|
|
{
|
|
if (pdx_table[i] == NULL)
|
|
break;
|
|
}
|
|
|
|
if(i==POS_TTY_MINORS)
|
|
return -ENOMEM;
|
|
|
|
pdx = kzalloc(sizeof(*pdx), /* GFP_KERNEL */ GFP_ATOMIC);
|
|
if (!pdx) {
|
|
ERR("OUT OF MEMORY pdx\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pdx->BioPack = kzalloc(sizeof(*pdx->BioPack), GFP_ATOMIC);
|
|
if (!pdx) {
|
|
ERR("OUT OF MEMORY BioPack\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
printk(KERN_ALERT "ttyPos probe:%s %s,index:%d\n",DRV_VERSION,VERSION_DATE,i);
|
|
|
|
pdx->devIndex = i;
|
|
pdx_table[pdx->devIndex] = pdx;
|
|
|
|
INIT_POOL_BUFFER(pdx->TxPool);
|
|
|
|
pdx->timeout_jiffies = 400 * HZ / 1000; /* 400ms */
|
|
kref_init(&pdx->kref);
|
|
init_waitqueue_head(&pdx->urb_wait);
|
|
atomic_set(&pdx->urb_done, 0);
|
|
|
|
init_waitqueue_head(&pdx->write_wait);
|
|
atomic_set(&pdx->write_flag, 0);
|
|
|
|
init_completion(&pdx->ThreadExit_completion);
|
|
|
|
pdx->udev = usb_get_dev(interface_to_usbdev(interface));
|
|
pdx->interface = interface;
|
|
|
|
pdx->urb = usb_alloc_urb(0, /* GFP_KERNEL */ GFP_ATOMIC);
|
|
if (!pdx->urb) {
|
|
retval = -ENOMEM;
|
|
ERR("FAILED ALLOC URB!\n");
|
|
goto error;
|
|
}
|
|
|
|
iface_desc = interface->cur_altsetting;
|
|
|
|
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
|
|
endpoint = &iface_desc->endpoint[i].desc;
|
|
|
|
if (endpoint->bEndpointAddress & 0x80) {
|
|
pdx->bulk_in_epAddr = endpoint->bEndpointAddress;
|
|
}
|
|
else {
|
|
pdx->bulk_out_epAddr = endpoint->bEndpointAddress;
|
|
}
|
|
}
|
|
|
|
if (!(pdx->bulk_in_epAddr && pdx->bulk_out_epAddr)) {
|
|
ERR("COULD NOT FIND BOTH BULK-IN AND BULK-OUT ENDPOINT\n");
|
|
}
|
|
|
|
usb_set_intfdata(interface, pdx);
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
tty_port_register_device(&pos_port[pdx->devIndex], pos_tty_driver,
|
|
pdx->devIndex, NULL);
|
|
#else
|
|
tty_register_device(pos_tty_driver, pdx->devIndex, NULL);
|
|
#endif
|
|
#endif
|
|
|
|
dev_info(&interface->dev,
|
|
"USB POS device now attached to PosUsb-%d",
|
|
interface->minor);
|
|
return 0;
|
|
|
|
error:
|
|
if (pdx) {
|
|
kref_put(&pdx->kref, pos_delete);
|
|
}
|
|
|
|
ERR("--pos_probe error\n");
|
|
return retval;
|
|
}
|
|
|
|
static void pos_usb_disconnect(struct usb_interface *interface)
|
|
{
|
|
struct tty_pos *pdx;
|
|
|
|
#if 0
|
|
INFO("++pos_disconnect\n");
|
|
#endif
|
|
pdx = usb_get_intfdata(interface);
|
|
if (pdx == NULL)
|
|
return;
|
|
|
|
tty_unregister_device(pos_tty_driver, pdx->devIndex);
|
|
|
|
if (pdx->ThreadState == THREAD_CREATED) {
|
|
pdx->ThreadState = THREAD_CLOSE;
|
|
#if 0
|
|
INFO("Wait thread exit\n");
|
|
#endif
|
|
wait_for_completion(&pdx->ThreadExit_completion);
|
|
#if 0
|
|
INFO("Wait thread exit success!\n");
|
|
#endif
|
|
}
|
|
pdx->ThreadState = THREAD_INIT;
|
|
|
|
usb_set_intfdata(interface, NULL);
|
|
|
|
#if 0
|
|
usb_deregister_dev(interface, &pos_class);
|
|
#endif
|
|
|
|
pdx->interface = NULL;
|
|
|
|
kref_put(&pdx->kref, pos_delete);
|
|
#if 0
|
|
INFO("--pos_disconnect\n");
|
|
#endif
|
|
}
|
|
|
|
#if 1
|
|
static int pos_usb_suspend(struct usb_interface *intf, pm_message_t message)
|
|
{
|
|
// printk(KERN_ALERT "pos_suspend\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pos_usb_resume(struct usb_interface *intf)
|
|
{
|
|
// printk(KERN_ALERT "pos_resume\n");
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef OLD_USB_DRIVER
|
|
static void pos_usb_pre_reset(struct usb_interface *intf)
|
|
{
|
|
/* struct tty_pos *pdx = usb_get_intfdata(intf); */
|
|
#if 0
|
|
INFO("pos_pre_reset\n");
|
|
#endif
|
|
}
|
|
#else
|
|
static int pos_usb_pre_reset(struct usb_interface *intf)
|
|
{
|
|
/* struct tty_pos *pdx = usb_get_intfdata(intf); */
|
|
#if 0
|
|
INFO("pos_pre_reset\n");
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef OLD_USB_DRIVER
|
|
static void pos_usb_post_reset(struct usb_interface *intf)
|
|
{
|
|
#if 0
|
|
INFO("pos_post_reset\n");
|
|
#endif
|
|
}
|
|
#else
|
|
static int pos_usb_post_reset(struct usb_interface *intf)
|
|
{
|
|
#if 0
|
|
INFO("pos_post_reset\n");
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static struct usb_driver pos_usb_driver = {
|
|
.name = "PosUsb",
|
|
.probe = pos_usb_probe,
|
|
.disconnect = pos_usb_disconnect,
|
|
#if 1
|
|
.suspend = pos_usb_suspend,
|
|
.resume = pos_usb_resume,
|
|
.supports_autosuspend = 1,
|
|
#endif
|
|
.pre_reset = pos_usb_pre_reset,
|
|
.post_reset = pos_usb_post_reset,
|
|
.id_table = pos_usb_table,
|
|
};
|
|
|
|
/* Compatible with TTY_DRIVER_DYNAMIC_DEV and TTY_DRIVER_NO_DEVFS */
|
|
#define TTY_USB_DEV 0x0008
|
|
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
|
|
static int pos_port_activate(struct tty_port *port, struct tty_struct *tty)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void pos_port_shutdown(struct tty_port *port)
|
|
{
|
|
|
|
}
|
|
|
|
static int pos_carrier_raised(struct tty_port *port)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void pos_dtr_rts(struct tty_port *port, int onoff)
|
|
{
|
|
}
|
|
|
|
static const struct tty_port_operations pos_port_ops = {
|
|
.activate = pos_port_activate,
|
|
.shutdown = pos_port_shutdown,
|
|
.carrier_raised = pos_carrier_raised,
|
|
.dtr_rts = pos_dtr_rts,
|
|
};
|
|
#endif
|
|
#endif
|
|
|
|
static int __init pos_tty_init(void)
|
|
{
|
|
int result,i;
|
|
|
|
printk(KERN_ALERT "ttyPos:%s %s\n",DRV_VERSION,VERSION_DATE);
|
|
|
|
for(i=0;i<POS_TTY_MINORS;i++)
|
|
pdx_table[i] = NULL;
|
|
|
|
pos_tty_driver = alloc_tty_driver(POS_TTY_MINORS);
|
|
if (!pos_tty_driver)
|
|
return -ENOMEM;
|
|
|
|
pos_tty_driver->owner = THIS_MODULE;
|
|
pos_tty_driver->driver_name = "usbpos";
|
|
pos_tty_driver->name = "ttyPos";
|
|
pos_tty_driver->major = 0;//POS_TTY_MAJOR;//The major number will be chosen dynamically
|
|
pos_tty_driver->minor_start = 0;
|
|
pos_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
|
|
pos_tty_driver->subtype = SERIAL_TYPE_NORMAL;
|
|
pos_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_USB_DEV;
|
|
pos_tty_driver->init_termios = tty_std_termios;
|
|
pos_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD
|
|
| HUPCL | CLOCAL;
|
|
|
|
tty_set_operations(pos_tty_driver, &pos_ops);
|
|
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
for (i = 0; i < POS_TTY_MINORS; i++)
|
|
{
|
|
tty_port_init(&pos_port[i]);
|
|
pos_port[i].ops = &pos_port_ops;
|
|
pos_port[i].close_delay = HZ / 2; /* .5 seconds */
|
|
pos_port[i].closing_wait = 30 * HZ;/* 30 seconds */
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
result = tty_register_driver(pos_tty_driver);
|
|
if (result) {
|
|
ERR("%s - tty_register_driver failed\n", __func__);
|
|
goto byebye1;
|
|
}
|
|
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
for (i = 0; i < POS_TTY_MINORS; i++)
|
|
tty_port_destroy(&pos_port[i]);
|
|
#endif
|
|
#endif
|
|
|
|
result = usb_register(&pos_usb_driver);
|
|
if (result) {
|
|
ERR("%s - usb_register failed; err: %d\n",__func__, result);
|
|
goto byebye2;
|
|
}
|
|
|
|
|
|
return 0;
|
|
byebye2:
|
|
tty_unregister_driver(pos_tty_driver);
|
|
byebye1:
|
|
put_tty_driver(pos_tty_driver);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void __exit pos_tty_exit(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
usb_deregister(&pos_usb_driver);
|
|
tty_unregister_driver(pos_tty_driver);
|
|
put_tty_driver(pos_tty_driver);
|
|
|
|
#ifdef LINUX_VERSION_CODE
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,13,0))
|
|
for (i = 0; i < POS_TTY_MINORS; i++)
|
|
tty_port_destroy(&pos_port[i]);
|
|
#endif
|
|
#endif
|
|
|
|
INFO("pos_tty_exit\n");
|
|
}
|
|
|
|
module_init(pos_tty_init);
|
|
module_exit(pos_tty_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS_LDISC(N_SLIP);
|