Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
/* generic HDLC line discipline for Linux
 *
 * Written by Paul Fulghum paulkf@microgate.com
 * for Microgate Corporation
 *
 * Microgate and SyncLink are registered trademarks of Microgate Corporation
 *
 * Adapted from ppp.c, written by Michael Callahan <callahan@maths.ox.ac.uk>,
 *	Al Longyear <longyear@netcom.com>, Paul Mackerras <Paul.Mackerras@cs.anu.edu.au>
 *
 * Original release 01/11/99
 * $Id: n_hdlc.c,v 3.7 2003/05/01 15:45:29 paulkf Exp $
 *
 * This code is released under the GNU General Public License (GPL)
 *
 * This module implements the tty line discipline N_HDLC for use with
 * tty device drivers that support bit-synchronous HDLC communications.
 *
 * All HDLC data is frame oriented which means:
 *
 * 1. tty write calls represent one complete transmit frame of data
 *    The device driver should accept the complete frame or none of 
 *    the frame (busy) in the write method. Each write call should have
 *    a byte count in the range of 2-65535 bytes (2 is min HDLC frame
 *    with 1 addr byte and 1 ctrl byte). The max byte count of 65535
 *    should include any crc bytes required. For example, when using
 *    CCITT CRC32, 4 crc bytes are required, so the maximum size frame
 *    the application may transmit is limited to 65531 bytes. For CCITT
 *    CRC16, the maximum application frame size would be 65533.
 *
 *
 * 2. receive callbacks from the device driver represents
 *    one received frame. The device driver should bypass
 *    the tty flip buffer and call the line discipline receive
 *    callback directly to avoid fragmenting or concatenating
 *    multiple frames into a single receive callback.
 *
 *    The HDLC line discipline queues the receive frames in seperate
 *    buffers so complete receive frames can be returned by the
 *    tty read calls.
 *
 * 3. tty read calls returns an entire frame of data or nothing.
 *    
 * 4. all send and receive data is considered raw. No processing
 *    or translation is performed by the line discipline, regardless
 *    of the tty flags
 *
 * 5. When line discipline is queried for the amount of receive
 *    data available (FIOC), 0 is returned if no data available,
 *    otherwise the count of the next available frame is returned.
 *    (instead of the sum of all received frame counts).
 *
 * These conventions allow the standard tty programming interface
 * to be used for synchronous HDLC applications when used with
 * this line discipline (or another line discipline that is frame
 * oriented such as N_PPP).
 *
 * The SyncLink driver (synclink.c) implements both asynchronous
 * (using standard line discipline N_TTY) and synchronous HDLC
 * (using N_HDLC) communications, with the latter using the above
 * conventions.
 *
 * This implementation is very basic and does not maintain
 * any statistics. The main point is to enforce the raw data
 * and frame orientation of HDLC communications.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#define HDLC_MAGIC 0x239e
#define HDLC_VERSION "$Revision: 3.7 $"

#include <linux/version.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>

#undef VERSION
#define VERSION(major,minor,patch) (((((major)<<8)+(minor))<<8)+(patch))

#include <linux/poll.h>
#include <linux/in.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/errno.h>
#include <linux/string.h>	/* used in new tty drivers */
#include <linux/signal.h>	/* used in new tty drivers */
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/termios.h>
#include <linux/if.h>

#include <linux/ioctl.h>

#ifdef CONFIG_KERNELD
#include <linux/kerneld.h>
#endif

#include <asm/segment.h>
#define GET_USER(error,value,addr) error = get_user(value,addr)
#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0
#define PUT_USER(error,value,addr) error = put_user(value,addr)
#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0

#include <asm/uaccess.h>

typedef ssize_t		rw_ret_t;
typedef size_t		rw_count_t;

/*
 * Buffers for individual HDLC frames
 */
#define MAX_HDLC_FRAME_SIZE 65535 
#define DEFAULT_RX_BUF_COUNT 10
#define MAX_RX_BUF_COUNT 60
#define DEFAULT_TX_BUF_COUNT 1


typedef struct _n_hdlc_buf
{
	struct _n_hdlc_buf *link;
	int count;
	char buf[1];
} N_HDLC_BUF;

#define	N_HDLC_BUF_SIZE	(sizeof(N_HDLC_BUF)+maxframe)

typedef struct _n_hdlc_buf_list
{
	N_HDLC_BUF *head;
	N_HDLC_BUF *tail;
	int count;
	spinlock_t spinlock;
	
} N_HDLC_BUF_LIST;

/*
 * Per device instance data structure
 */
struct n_hdlc {
	int		magic;		/* magic value for structure	*/
	__u32		flags;		/* miscellaneous control flags	*/
	
	struct tty_struct *tty;		/* ptr to TTY structure	*/
	struct tty_struct *backup_tty;	/* TTY to use if tty gets closed */
	
	int		tbusy;		/* reentrancy flag for tx wakeup code */
	int		woke_up;
	N_HDLC_BUF	*tbuf;		/* currently transmitting tx buffer */
	N_HDLC_BUF_LIST tx_buf_list;	/* list of pending transmit frame buffers */	
	N_HDLC_BUF_LIST	rx_buf_list;	/* list of received frame buffers */
	N_HDLC_BUF_LIST tx_free_buf_list;	/* list unused transmit frame buffers */	
	N_HDLC_BUF_LIST	rx_free_buf_list;	/* list unused received frame buffers */
};

/*
 * HDLC buffer list manipulation functions
 */
static void n_hdlc_buf_list_init(N_HDLC_BUF_LIST *list);
static void n_hdlc_buf_put(N_HDLC_BUF_LIST *list,N_HDLC_BUF *buf);
static N_HDLC_BUF* n_hdlc_buf_get(N_HDLC_BUF_LIST *list);

/* Local functions */

static struct n_hdlc *n_hdlc_alloc (void);

MODULE_PARM(debuglevel, "i");
MODULE_PARM(maxframe, "i");

#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif

/* debug level can be set by insmod for debugging purposes */
#define DEBUG_LEVEL_INFO	1
static int debuglevel=0;

/* max frame size for memory allocations */
static ssize_t	maxframe=4096;

/* TTY callbacks */

static rw_ret_t n_hdlc_tty_read(struct tty_struct *,
	struct file *, __u8 *, rw_count_t);
static rw_ret_t n_hdlc_tty_write(struct tty_struct *,
	struct file *, const __u8 *, rw_count_t);
static int n_hdlc_tty_ioctl(struct tty_struct *,
	struct file *, unsigned int, unsigned long);
static unsigned int n_hdlc_tty_poll (struct tty_struct *tty, struct file *filp,
				  poll_table * wait);
static int n_hdlc_tty_open (struct tty_struct *);
static void n_hdlc_tty_close (struct tty_struct *);
static int n_hdlc_tty_room (struct tty_struct *tty);
static void n_hdlc_tty_receive (struct tty_struct *tty,
	const __u8 * cp, char *fp, int count);
static void n_hdlc_tty_wakeup (struct tty_struct *tty);

#define bset(p,b)	((p)[(b) >> 5] |= (1 << ((b) & 0x1f)))

#define tty2n_hdlc(tty)	((struct n_hdlc *) ((tty)->disc_data))
#define n_hdlc2tty(n_hdlc)	((n_hdlc)->tty)

/* Define this string only once for all macro invocations */
static char szVersion[] = HDLC_VERSION;

/* n_hdlc_release()
 *
 *	release an n_hdlc per device line discipline info structure
 *
 */
static void n_hdlc_release (struct n_hdlc *n_hdlc)
{
	struct tty_struct *tty = n_hdlc2tty (n_hdlc);
	N_HDLC_BUF *buf;
	
	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_release() called\n",__FILE__,__LINE__);
		
	/* Ensure that the n_hdlcd process is not hanging on select()/poll() */
	wake_up_interruptible (&tty->read_wait);
	wake_up_interruptible (&tty->write_wait);

	if (tty != NULL && tty->disc_data == n_hdlc)
		tty->disc_data = NULL;	/* Break the tty->n_hdlc link */

	/* Release transmit and receive buffers */
	for(;;) {
		buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list);
		if (buf) {
			kfree(buf);
		} else
			break;
	}
	for(;;) {
		buf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list);
		if (buf) {
			kfree(buf);
		} else
			break;
	}
	for(;;) {
		buf = n_hdlc_buf_get(&n_hdlc->rx_buf_list);
		if (buf) {
			kfree(buf);
		} else
			break;
	}
	for(;;) {
		buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
		if (buf) {
			kfree(buf);
		} else
			break;
	}
	if (n_hdlc->tbuf)
		kfree(n_hdlc->tbuf);
	kfree(n_hdlc);
	
}	/* end of n_hdlc_release() */

/* n_hdlc_tty_close()
 *
 *	Called when the line discipline is changed to something
 *	else, the tty is closed, or the tty detects a hangup.
 */
static void n_hdlc_tty_close(struct tty_struct *tty)
{
	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_close() called\n",__FILE__,__LINE__);
		
	if (n_hdlc != NULL) {
		if (n_hdlc->magic != HDLC_MAGIC) {
			printk (KERN_WARNING"n_hdlc: trying to close unopened tty!\n");
			return;
		}
#if defined(TTY_NO_WRITE_SPLIT)
		clear_bit(TTY_NO_WRITE_SPLIT,&tty->flags);
#endif
		tty->disc_data = NULL;
		if (tty == n_hdlc->backup_tty)
			n_hdlc->backup_tty = 0;
		if (tty != n_hdlc->tty)
			return;
		if (n_hdlc->backup_tty) {
			n_hdlc->tty = n_hdlc->backup_tty;
		} else {
			n_hdlc_release (n_hdlc);
			MOD_DEC_USE_COUNT;
		}
	}
	
	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_close() success\n",__FILE__,__LINE__);
		
}	/* end of n_hdlc_tty_close() */

/* n_hdlc_tty_open
 * 
 * 	called when line discipline changed to n_hdlc
 * 	
 * Arguments:	tty	pointer to tty info structure
 * Return Value:	0 if success, otherwise error code
 */
static int n_hdlc_tty_open (struct tty_struct *tty)
{
	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_open() called (major=%u,minor=%u)\n",
		__FILE__,__LINE__,
		MAJOR(tty->device), MINOR(tty->device));
		
	/* There should not be an existing table for this slot. */
	if (n_hdlc) {
		printk (KERN_ERR"n_hdlc_tty_open:tty already associated!\n" );
		return -EEXIST;
	}
	
	n_hdlc = n_hdlc_alloc();
	if (!n_hdlc) {
		printk (KERN_ERR "n_hdlc_alloc failed\n");
		return -ENFILE;
	}
		
	tty->disc_data = n_hdlc;
	n_hdlc->tty    = tty;
	
	MOD_INC_USE_COUNT;
	
#if defined(TTY_NO_WRITE_SPLIT)
	/* change tty_io write() to not split large writes into 8K chunks */
	set_bit(TTY_NO_WRITE_SPLIT,&tty->flags);
#endif
	
	/* Flush any pending characters in the driver and discipline. */
	
	if (tty->ldisc.flush_buffer)
		tty->ldisc.flush_buffer (tty);

	if (tty->driver.flush_buffer)
		tty->driver.flush_buffer (tty);
		
	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_open() success\n",__FILE__,__LINE__);
		
	return 0;
	
}	/* end of n_tty_hdlc_open() */

/* n_hdlc_send_frames()
 * 
 * 	send frames on pending send buffer list until the
 * 	driver does not accept a frame (busy)
 * 	this function is called after adding a frame to the
 * 	send buffer list and by the tty wakeup callback
 * 	
 * Arguments:		n_hdlc		pointer to ldisc instance data
 * 			tty		pointer to tty instance data
 * Return Value:	None
 */
static void n_hdlc_send_frames (struct n_hdlc *n_hdlc, struct tty_struct *tty)
{
	register int actual;
	unsigned long flags;
	N_HDLC_BUF *tbuf;

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_send_frames() called\n",__FILE__,__LINE__);
 check_again:
		
	save_flags(flags);
	cli ();
	if (n_hdlc->tbusy) {
		n_hdlc->woke_up = 1;
		restore_flags(flags);
		return;
	}
	n_hdlc->tbusy = 1;
	n_hdlc->woke_up = 0;
	restore_flags(flags);

	/* get current transmit buffer or get new transmit */
	/* buffer from list of pending transmit buffers */
		
	tbuf = n_hdlc->tbuf;
	if (!tbuf)
		tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
		
	while (tbuf) {
		if (debuglevel >= DEBUG_LEVEL_INFO)	
			printk("%s(%d)sending frame %p, count=%d\n",
				__FILE__,__LINE__,tbuf,tbuf->count);
			
		/* Send the next block of data to device */
		tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
		actual = tty->driver.write(tty, 0, tbuf->buf, tbuf->count);
		    
		/* if transmit error, throw frame away by */
		/* pretending it was accepted by driver */
		if (actual < 0)
			actual = tbuf->count;
		
		if (actual == tbuf->count) {
			if (debuglevel >= DEBUG_LEVEL_INFO)	
				printk("%s(%d)frame %p completed\n",
					__FILE__,__LINE__,tbuf);
					
			/* free current transmit buffer */
			n_hdlc_buf_put(&n_hdlc->tx_free_buf_list,tbuf);
			
			/* this tx buffer is done */
			n_hdlc->tbuf = NULL;
			
			/* wait up sleeping writers */
			wake_up_interruptible(&tty->write_wait);
	
			/* get next pending transmit buffer */
			tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
		} else {
			if (debuglevel >= DEBUG_LEVEL_INFO)	
				printk("%s(%d)frame %p pending\n",
					__FILE__,__LINE__,tbuf);
					
			/* buffer not accepted by driver */
			/* set this buffer as pending buffer */
			n_hdlc->tbuf = tbuf;
			break;
		}
	}
	
	if (!tbuf)
		tty->flags  &= ~(1 << TTY_DO_WRITE_WAKEUP);
	
	/* Clear the re-entry flag */
	save_flags(flags);
	cli ();
	n_hdlc->tbusy = 0;
	restore_flags(flags);
	
        if (n_hdlc->woke_up)
	  goto check_again;

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_send_frames() exit\n",__FILE__,__LINE__);
		
}	/* end of n_hdlc_send_frames() */

/* n_hdlc_tty_wakeup()
 *
 *	Callback for transmit wakeup. Called when low level
 *	device driver can accept more send data.
 *
 * Arguments:		tty	pointer to associated tty instance data
 * Return Value:	None
 */
static void n_hdlc_tty_wakeup (struct tty_struct *tty)
{
	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_wakeup() called\n",__FILE__,__LINE__);
		
	if (!n_hdlc)
		return;

	if (tty != n_hdlc->tty) {
		tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
		return;
	}

	n_hdlc_send_frames (n_hdlc, tty);
		
}	/* end of n_hdlc_tty_wakeup() */

/* n_hdlc_tty_room()
 * 
 *	Callback function from tty driver. Return the amount of 
 *	space left in the receiver's buffer to decide if remote
 *	transmitter is to be throttled.
 *
 * Arguments:		tty	pointer to associated tty instance data
 * Return Value:	number of bytes left in receive buffer
 */
static int n_hdlc_tty_room (struct tty_struct *tty)
{
	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_room() called\n",__FILE__,__LINE__);
	/* always return a larger number to prevent */
	/* throttling of remote transmitter. */
	return 65536;
}	/* end of n_hdlc_tty_root() */

/* n_hdlc_tty_receive()
 * 
 * 	Called by tty low level driver when receive data is
 * 	available. Data is interpreted as one HDLC frame.
 * 	
 * Arguments:	 	tty		pointer to tty isntance data
 * 			data		pointer to received data
 * 			flags		pointer to flags for data
 * 			count		count of received data in bytes
 * 	
 * Return Value:	None
 */
static void n_hdlc_tty_receive(struct tty_struct *tty,
	const __u8 * data, char *flags, int count)
{
	register struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
	register N_HDLC_BUF *buf;

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_receive() called count=%d\n",
			__FILE__,__LINE__, count);
		
	/* This can happen if stuff comes in on the backup tty */
	if (n_hdlc == 0 || tty != n_hdlc->tty)
		return;
		
	/* verify line is using HDLC discipline */
	if (n_hdlc->magic != HDLC_MAGIC) {
		printk("%s(%d) line not using HDLC discipline\n",
			__FILE__,__LINE__);
		return;
	}
	
	if ( count>maxframe ) {
		if (debuglevel >= DEBUG_LEVEL_INFO)	
			printk("%s(%d) rx count>maxframesize, data discarded\n",
			       __FILE__,__LINE__);
		return;
	}

	/* get a free HDLC buffer */	
	buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list);
	if (!buf) {
		/* no buffers in free list, attempt to allocate another rx buffer */
		/* unless the maximum count has been reached */
		if (n_hdlc->rx_buf_list.count < MAX_RX_BUF_COUNT)
			buf = (N_HDLC_BUF*)kmalloc(N_HDLC_BUF_SIZE,GFP_ATOMIC);
	}
	
	if (!buf) {
		if (debuglevel >= DEBUG_LEVEL_INFO)	
			printk("%s(%d) no more rx buffers, data discarded\n",
			       __FILE__,__LINE__);
		return;
	}
		
	/* copy received data to HDLC buffer */
	memcpy(buf->buf,data,count);
	buf->count=count;

	/* add HDLC buffer to list of received frames */
	n_hdlc_buf_put(&n_hdlc->rx_buf_list,buf);
	
	/* wake up any blocked reads and perform async signalling */
	wake_up_interruptible (&tty->read_wait);
	if (n_hdlc->tty->fasync != NULL)
		kill_fasync (&n_hdlc->tty->fasync, SIGIO, POLL_IN);

}	/* end of n_hdlc_tty_receive() */

/* n_hdlc_tty_read()
 * 
 * 	Called to retreive one frame of data (if available)
 * 	
 * Arguments:
 * 
 * 	tty		pointer to tty instance data
 * 	file		pointer to open file object
 * 	buf		pointer to returned data buffer
 * 	nr		size of returned data buffer
 * 	
 * Return Value:
 * 
 * 	Number of bytes returned or error code
 */
static rw_ret_t n_hdlc_tty_read (struct tty_struct *tty,
	struct file *file, __u8 * buf, rw_count_t nr)
{
	struct n_hdlc *n_hdlc = tty2n_hdlc(tty);
	int error;
	rw_ret_t ret;
	N_HDLC_BUF *rbuf;

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_read() called\n",__FILE__,__LINE__);
		
	/* Validate the pointers */
	if (!n_hdlc)
		return -EIO;

	/* verify user access to buffer */
	error = verify_area (VERIFY_WRITE, buf, nr);
	if (error != 0) {
		printk(KERN_WARNING"%s(%d) n_hdlc_tty_read() can't verify user "
		"buffer\n",__FILE__,__LINE__);
		return (error);
	}

	for (;;) {
		if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
			return -EIO;

		n_hdlc = tty2n_hdlc (tty);
		if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC ||
			 tty != n_hdlc->tty)
			return 0;

		rbuf = n_hdlc_buf_get(&n_hdlc->rx_buf_list);
		if (rbuf)
			break;
			
		/* no data */
		if (file->f_flags & O_NONBLOCK)
			return -EAGAIN;
			
		interruptible_sleep_on (&tty->read_wait);
		if (signal_pending(current))
			return -EINTR;
	}
		
	if (rbuf->count > nr) {
		/* frame too large for caller's buffer (discard frame) */
		ret = (rw_ret_t)-EOVERFLOW;
	} else {
		/* Copy the data to the caller's buffer */
		COPY_TO_USER(error,buf,rbuf->buf,rbuf->count);
		if (error)
			ret = (rw_ret_t)error;
		else
			ret = (rw_ret_t)rbuf->count;
	}
	
	/* return HDLC buffer to free list unless the free list */
	/* count has exceeded the default value, in which case the */
	/* buffer is freed back to the OS to conserve memory */
	if (n_hdlc->rx_free_buf_list.count > DEFAULT_RX_BUF_COUNT)
		kfree(rbuf);
	else	
		n_hdlc_buf_put(&n_hdlc->rx_free_buf_list,rbuf);
	
	return ret;
	
}	/* end of n_hdlc_tty_read() */

/* n_hdlc_tty_write()
 * 
 * 	write a single frame of data to device
 * 	
 * Arguments:	tty	pointer to associated tty device instance data
 * 		file	pointer to file object data
 * 		data	pointer to transmit data (one frame)
 * 		count	size of transmit frame in bytes
 * 		
 * Return Value:	number of bytes written (or error code)
 */
static rw_ret_t n_hdlc_tty_write (struct tty_struct *tty, struct file *file,
	const __u8 * data, rw_count_t count)
{
	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
	int error = 0;
	DECLARE_WAITQUEUE(wait, current);
	N_HDLC_BUF *tbuf;

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_write() called count=%d\n",
			__FILE__,__LINE__,count);
		
	/* Verify pointers */
	if (!n_hdlc)
		return -EIO;

	if (n_hdlc->magic != HDLC_MAGIC)
		return -EIO;

	/* verify frame size */
	if (count > maxframe ) {
		if (debuglevel & DEBUG_LEVEL_INFO)
			printk (KERN_WARNING
				"n_hdlc_tty_write: truncating user packet "
				"from %lu to %d\n", (unsigned long) count,
				maxframe );
		count = maxframe;
	}
	
	add_wait_queue(&tty->write_wait, &wait);
	set_current_state(TASK_INTERRUPTIBLE);
	
	/* Allocate transmit buffer */
	/* sleep until transmit buffer available */		
	while (!(tbuf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list))) {
		schedule();
			
		n_hdlc = tty2n_hdlc (tty);
		if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC || 
		    tty != n_hdlc->tty) {
			printk("n_hdlc_tty_write: %p invalid after wait!\n", n_hdlc);
			error = -EIO;
			break;
		}
			
		if (signal_pending(current)) {
			error = -EINTR;
			break;
		}
	}

	set_current_state(TASK_RUNNING);
	remove_wait_queue(&tty->write_wait, &wait);

	if (!error) {		
		/* Retrieve the user's buffer */
		COPY_FROM_USER (error, tbuf->buf, data, count);
		if (error) {
			/* return tx buffer to free list */
			n_hdlc_buf_put(&n_hdlc->tx_free_buf_list,tbuf);
		} else {
			/* Send the data */
			tbuf->count = error = count;
			n_hdlc_buf_put(&n_hdlc->tx_buf_list,tbuf);
			n_hdlc_send_frames(n_hdlc,tty);
		}
	}

	return error;
	
}	/* end of n_hdlc_tty_write() */

/* n_hdlc_tty_ioctl()
 *
 *	Process IOCTL system call for the tty device.
 *
 * Arguments:
 *
 *	tty		pointer to tty instance data
 *	file		pointer to open file object for device
 *	cmd		IOCTL command code
 *	arg		argument for IOCTL call (cmd dependent)
 *
 * Return Value:	Command dependent
 */
static int n_hdlc_tty_ioctl (struct tty_struct *tty, struct file * file,
               unsigned int cmd, unsigned long arg)
{
	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
	int error = 0;
	int count;
	unsigned long flags;
	
	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_ioctl() called %d\n",
			__FILE__,__LINE__,cmd);
		
	/* Verify the status of the device */
	if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC)
		return -EBADF;

	switch (cmd) {
	case FIONREAD:
		/* report count of read data available */
		/* in next available frame (if any) */
		spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock,flags);
		if (n_hdlc->rx_buf_list.head)
			count = n_hdlc->rx_buf_list.head->count;
		else
			count = 0;
		spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock,flags);
		PUT_USER (error, count, (int *) arg);
		break;

	case TIOCOUTQ:
		/* get the pending tx byte count in the driver */
		count = tty->driver.chars_in_buffer ?
				tty->driver.chars_in_buffer(tty) : 0;
		/* add size of next output frame in queue */
		spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock,flags);
		if (n_hdlc->tx_buf_list.head)
			count += n_hdlc->tx_buf_list.head->count;
		spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock,flags);
		PUT_USER (error, count, (int*)arg);
		break;

	default:
		error = n_tty_ioctl (tty, file, cmd, arg);
		break;
	}
	return error;
	
}	/* end of n_hdlc_tty_ioctl() */

/* n_hdlc_tty_poll()
 * 
 * 	TTY callback for poll system call. Determine which 
 * 	operations (read/write) will not block and return
 * 	info to caller.
 * 	
 * Arguments:
 * 
 * 	tty		pointer to tty instance data
 * 	filp		pointer to open file object for device
 * 	poll_table	wait queue for operations
 * 
 * Return Value:
 * 
 * 	bit mask containing info on which ops will not block
 */
static unsigned int n_hdlc_tty_poll (struct tty_struct *tty,
	 struct file *filp, poll_table * wait)
{
	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
	unsigned int mask = 0;

	if (debuglevel >= DEBUG_LEVEL_INFO)	
		printk("%s(%d)n_hdlc_tty_poll() called\n",__FILE__,__LINE__);
		
	if (n_hdlc && n_hdlc->magic == HDLC_MAGIC && tty == n_hdlc->tty) {
		/* queue current process into any wait queue that */
		/* may awaken in the future (read and write) */

		poll_wait(filp, &tty->read_wait, wait);
		poll_wait(filp, &tty->write_wait, wait);

		/* set bits for operations that wont block */
		if(n_hdlc->rx_buf_list.head)
			mask |= POLLIN | POLLRDNORM;	/* readable */
		if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
			mask |= POLLHUP;
		if(tty_hung_up_p(filp))
			mask |= POLLHUP;
		if(n_hdlc->tx_free_buf_list.head)
			mask |= POLLOUT | POLLWRNORM;	/* writable */
	}
	return mask;
}	/* end of n_hdlc_tty_poll() */

/* n_hdlc_alloc()
 * 
 * 	Allocate an n_hdlc instance data structure
 *
 * Arguments:		None
 * Return Value:	pointer to structure if success, otherwise 0	
 */
static struct n_hdlc *n_hdlc_alloc (void)
{
	struct n_hdlc	*n_hdlc;
	N_HDLC_BUF	*buf;
	int		i;
	
	n_hdlc = (struct n_hdlc *)kmalloc(sizeof(struct n_hdlc), GFP_KERNEL);
	if (!n_hdlc)
		return 0;

	memset(n_hdlc, 0, sizeof(*n_hdlc));

	n_hdlc_buf_list_init(&n_hdlc->rx_free_buf_list);
	n_hdlc_buf_list_init(&n_hdlc->tx_free_buf_list);
	n_hdlc_buf_list_init(&n_hdlc->rx_buf_list);
	n_hdlc_buf_list_init(&n_hdlc->tx_buf_list);
	
	/* allocate free rx buffer list */
	for(i=0;i<DEFAULT_RX_BUF_COUNT;i++) {
		buf = (N_HDLC_BUF*)kmalloc(N_HDLC_BUF_SIZE,GFP_KERNEL);
		if (buf)
			n_hdlc_buf_put(&n_hdlc->rx_free_buf_list,buf);
		else if (debuglevel >= DEBUG_LEVEL_INFO)	
			printk("%s(%d)n_hdlc_alloc(), kalloc() failed for rx buffer %d\n",__FILE__,__LINE__, i);
	}
	
	/* allocate free tx buffer list */
	for(i=0;i<DEFAULT_TX_BUF_COUNT;i++) {
		buf = (N_HDLC_BUF*)kmalloc(N_HDLC_BUF_SIZE,GFP_KERNEL);
		if (buf)
			n_hdlc_buf_put(&n_hdlc->tx_free_buf_list,buf);
		else if (debuglevel >= DEBUG_LEVEL_INFO)	
			printk("%s(%d)n_hdlc_alloc(), kalloc() failed for tx buffer %d\n",__FILE__,__LINE__, i);
	}
	
	/* Initialize the control block */
	n_hdlc->magic  = HDLC_MAGIC;
	n_hdlc->flags  = 0;
	
	return n_hdlc;
	
}	/* end of n_hdlc_alloc() */

/* n_hdlc_buf_list_init()
 * 
 * 	initialize specified HDLC buffer list
 * 	
 * Arguments:	 	list	pointer to buffer list
 * Return Value:	None	
 */
static void n_hdlc_buf_list_init(N_HDLC_BUF_LIST *list)
{
	memset(list,0,sizeof(N_HDLC_BUF_LIST));
	spin_lock_init(&list->spinlock);
}	/* end of n_hdlc_buf_list_init() */

/* n_hdlc_buf_put()
 * 
 * 	add specified HDLC buffer to tail of specified list
 * 	
 * Arguments:
 * 
 * 	list	pointer to buffer list
 * 	buf	pointer to buffer
 * 
 * Return Value:	None	
 */
static void n_hdlc_buf_put(N_HDLC_BUF_LIST *list,N_HDLC_BUF *buf)
{
	unsigned long flags;
	spin_lock_irqsave(&list->spinlock,flags);
	
	buf->link=NULL;
	if(list->tail)
		list->tail->link = buf;
	else
		list->head = buf;
	list->tail = buf;
	(list->count)++;
	
	spin_unlock_irqrestore(&list->spinlock,flags);
	
}	/* end of n_hdlc_buf_put() */

/* n_hdlc_buf_get()
 * 
 * 	remove and return an HDLC buffer from the
 * 	head of the specified HDLC buffer list
 * 	
 * Arguments:
 * 
 * 	list	pointer to HDLC buffer list
 * 	
 * Return Value:
 * 
 * 	pointer to HDLC buffer if available, otherwise NULL
 */
static N_HDLC_BUF* n_hdlc_buf_get(N_HDLC_BUF_LIST *list)
{
	unsigned long flags;
	N_HDLC_BUF *buf;
	spin_lock_irqsave(&list->spinlock,flags);
	
	buf = list->head;
	if (buf) {
		list->head = buf->link;
		(list->count)--;
	}
	if (!list->head)
		list->tail = NULL;
	
	spin_unlock_irqrestore(&list->spinlock,flags);
	return buf;
	
}	/* end of n_hdlc_buf_get() */

static int __init n_hdlc_init(void)
{
	static struct tty_ldisc	n_hdlc_ldisc;
	int    status;

	/* range check maxframe arg */
	if ( maxframe<4096)
		maxframe=4096;
	else if ( maxframe>65535)
		maxframe=65535;

	printk("HDLC line discipline: version %s, maxframe=%u\n", 
		szVersion, maxframe);

	/* Register the tty discipline */
	
	memset(&n_hdlc_ldisc, 0, sizeof (n_hdlc_ldisc));
	n_hdlc_ldisc.magic		= TTY_LDISC_MAGIC;
	n_hdlc_ldisc.name          	= "hdlc";
	n_hdlc_ldisc.open		= n_hdlc_tty_open;
	n_hdlc_ldisc.close		= n_hdlc_tty_close;
	n_hdlc_ldisc.read		= n_hdlc_tty_read;
	n_hdlc_ldisc.write		= n_hdlc_tty_write;
	n_hdlc_ldisc.ioctl		= n_hdlc_tty_ioctl;
	n_hdlc_ldisc.poll		= n_hdlc_tty_poll;
	n_hdlc_ldisc.receive_room	= n_hdlc_tty_room;
	n_hdlc_ldisc.receive_buf	= n_hdlc_tty_receive;
	n_hdlc_ldisc.write_wakeup	= n_hdlc_tty_wakeup;

	status = tty_register_ldisc(N_HDLC, &n_hdlc_ldisc);
	if (!status)
		printk (KERN_INFO"N_HDLC line discipline registered.\n");
	else
		printk (KERN_ERR"error registering line discipline: %d\n",status);

	if (status)
		printk(KERN_INFO"N_HDLC: init failure %d\n", status);
	return (status);
	
}	/* end of init_module() */

static void __exit n_hdlc_exit(void)
{
	int status;
	/* Release tty registration of line discipline */
	if ((status = tty_register_ldisc(N_HDLC, NULL)))
		printk("N_HDLC: can't unregister line discipline (err = %d)\n", status);
	else
		printk("N_HDLC: line discipline unregistered\n");
}

module_init(n_hdlc_init);
module_exit(n_hdlc_exit);

EXPORT_NO_SYMBOLS;