-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathttyebusm.c
991 lines (863 loc) · 34.7 KB
/
ttyebusm.c
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
//===============================================================================================================
//
// ttyebus - real time linux kernel module for the ebusd using the PL011 UART on a Rasperry Pi
//
// Copyright (C) 2017 Galileo53 <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// 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.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// This is a LINUX kernel module exclusively for the ebusd using the PL011 UART running on Raspberry Pi 1 to 4.
// The latency between receiving and transmitting of a character is nearly zero. This is achieved by
// disabling the hardware FIFO at the UART completely and using a ring-buffer managed at the interrupt
// handler of the "Receiver Holding Register" interrupt.
//
// With RASPI 1 to 3 we are replacing the original interrupt of ttyAMA0. With RASPI 4 the interrupt is shared between
// all 5 UARTs and so the interrupt of ttyebus is added to the shared list. Note that interrupt numbers in Raspbian
// (Debian) are re-ordered by some Linux-internal logic, so we must always see what interrupt is assigned at a specific
// RASPI / Raspbian version. This can be done by executing "cat /proc/interrupts" while ttyAMA0 is still active.
//
// Btw., all procedures to dynamically get the interrupt number, as described in the literature, like polling all
// possible interrupts, have failed until now.
//
//===============================================================================================================
//
// Revision history:
// 2017-12-12 V1.1 Initial release
// 2017-12-18 V1.2 Added module description
// 2018-02-13 V1.3 Added more debug messages for IRQ read operations. Changed read timeout to 1 minute
// 2018-02-14 V1.4 Added poll to file operations
// 2018-03-21 V1.5 Fixed read buffer overrun issue
// 2019-06-16 V1.6 Changed IRQ for V4.19.42
// 2020-01-08 V1.7 Added support for RASPI4
// 2020-07-25 V1.8 Corrected set_fs(KERNEL_DS) for kernel 5.4
//
//===============================================================================================================
#include <linux/fs.h> // file stuff
#include <linux/kernel.h> // printk()
#include <linux/errno.h> // error codes
#include <linux/module.h> // THIS_MODULE
#include <linux/delay.h> // udelay
#include <linux/interrupt.h> // request_irq
#include <linux/miscdevice.h> // misc_register
#include <linux/io.h> // ioremap
#include <linux/spinlock.h> // spinlocks
#include <linux/wait.h> // poll
#include <linux/poll.h> // poll
#include <asm/uaccess.h> // copy_to_user
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <linux/version.h>
// #define DEBUG 1 // if uncommented, will write some debug messages to /var/log/kern.log
// #define IRQDEBUG 1 // if uncommented, writes messages from the interrupt handler too (there are a lot of messages!)
// #define LOOPBACK 1 // if uncommented, connects the Tx output to the Rx input of the UART. For testing only.
// prototypes
static int ttyebus_open(struct inode* inode, struct file* file);
static int ttyebus_close(struct inode* inode, struct file* file);
static unsigned int ttyebus_poll(struct file* file_ptr, poll_table* wait);
static ssize_t ttyebus_read(struct file* file_ptr, char __user* user_buffer, size_t count, loff_t* offset);
static ssize_t ttyebus_write(struct file* file_ptr, const char __user* user_buffer, size_t count, loff_t* offset);
static long ttyebus_ioctl(struct file* fp, unsigned int cmd, unsigned long arg);
#define DEVICE_NAME "ttyebus" // The device will appear at /dev/ttyebus
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Galileo53");
MODULE_DESCRIPTION("Kernel module for the ebusd directly connected through the PL011 UART to the eBus adapter");
MODULE_VERSION("1.8");
// file operations with this kernel module
static struct file_operations ttyebus_fops =
{
.owner = THIS_MODULE,
.open = ttyebus_open,
.release = ttyebus_close,
.poll = ttyebus_poll,
.read = ttyebus_read,
.write = ttyebus_write,
.unlocked_ioctl = ttyebus_ioctl
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &ttyebus_fops,
.mode = S_IRUSR | // User read
S_IWUSR | // User write
S_IRGRP | // Group read
S_IWGRP | // Group write
S_IROTH | // Other read
S_IWOTH // Other write
};
static unsigned int RaspiModel;
static unsigned int MajorNumber;
static void* GpioAddr;
static void* UartAddr;
static unsigned int DeviceOpen;
static wait_queue_head_t WaitQueue;
static spinlock_t SpinLock;
// ring buffer used for receiving data
enum { RX_BUFF_SIZE = 128 };
static volatile unsigned int RxTail = 0;
static volatile unsigned int RxHead = 0;
static unsigned int RxBuff[RX_BUFF_SIZE];
// linear buffer used for transmitting data
enum { TX_BUFF_SIZE = 64 };
static volatile unsigned int TxTail = TX_BUFF_SIZE;
static volatile unsigned int TxHead = TX_BUFF_SIZE;
static unsigned char TxBuff[TX_BUFF_SIZE];
#ifdef IRQDEBUG
static int IrqCounter = 0;
#endif
// RASPI I/O Base address
// ======================
#define RASPI_1_PERI_BASE 0x20000000 // RASPI 1
#define RASPI_23_PERI_BASE 0x3F000000 // RASPI 2 and 3
#define RASPI_4_PERI_BASE 0xFE000000 // RASPI 4
// BCM2835 base address
// ====================
#define SYST_BASE 0x00003000
#define DMA_BASE 0x00007000
#define IRQ_BASE 0x0000B000
#define CLK_BASE 0x00101000
#define GPIO_BASE 0x00200000
#define UART0_BASE 0x00201000
#define PCM_BASE 0x00203000
#define SPI0_BASE 0x00204000
#define I2C0_BASE 0x00205000
#define PWM_BASE 0x0020C000
#define UART1_BASE 0x00215000
#define I2C1_BASE 0x00804000
#define I2C2_BASE 0x00805000
#define DMA15_BASE 0x00E05000
// GPIO register
// =============
#define GPIO_INPUT 0
#define GPIO_OUTPUT 1
#define GPIO_ALT_0 4
#define GPIO_ALT_1 5
#define GPIO_ALT_2 6
#define GPIO_ALT_3 7
#define GPIO_ALT_4 3
#define GPIO_ALT_5 2
#define GPIO_FSEL0 (GpioAddr+0x00)
#define GPIO_FSEL1 (GpioAddr+0x04)
#define GPIO_FSEL2 (GpioAddr+0x08)
#define GPIO_FSEL3 (GpioAddr+0x0C)
#define GPIO_FSEL4 (GpioAddr+0x10)
#define GPIO_FSEL5 (GpioAddr+0x14)
#define GPIO_PULL (GpioAddr+0x94) // Pull up/pull down
#define GPIO_PULLCLK0 (GpioAddr+0x98) // Pull up/pull down clock
#define GPIO_PULLCLK1 (GpioAddr+0x9C) // Pull up/pull down clock
#define GPIO_BANK (Gpio >> 5)
#define GPIO_BIT (1 << (Gpio & 0x1F))
#define GPIO_PULL_OFF 0
#define GPIO_PULL_DOWN 1
#define GPIO_PULL_UP 2
// The UART interrupt on model B+ is allocated to 81.
// The UART interrupt on RASPI2,3 is allocated to 87, beginning with kernel 4.19.42, it is allocated to 81.
// For RASPI 4, the interrupt is 34 and is shared with all other UARTs.
#define RASPI_1_UART_IRQ 81
#define RASPI_4_UART_IRQ 34
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,42)
#define RASPI_23_UART_IRQ 87
#else
#define RASPI_23_UART_IRQ 81
#endif
// PL011 UART register (16C650 type)
// =================================
#define UART_DATA (UartAddr+0x00)
#define UART_RX_ERR (UartAddr+0x04)
#define UART_FLAG (UartAddr+0x18)
#define UART_ILPR (UartAddr+0x20)
#define UART_INT_BAUD (UartAddr+0x24)
#define UART_FRAC_BAUD (UartAddr+0x28)
#define UART_LINE_CTRL (UartAddr+0x2C)
#define UART_CTRL (UartAddr+0x30)
#define UART_FIFO_LEVEL (UartAddr+0x34)
#define UART_INT_MASK (UartAddr+0x38)
#define UART_RAW_INT (UartAddr+0x3C)
#define UART_INT_STAT (UartAddr+0x40)
#define UART_INT_CLR (UartAddr+0x44)
#define UART_DMA_CTRL (UartAddr+0x48)
#define UART_TEST_CTRL (UartAddr+0x80)
#define UART_TEST_IN (UartAddr+0x84)
#define UART_IEST_OUT (UartAddr+0x88)
#define UART_TEST_DATA (UartAddr+0x8C)
#define UART_MEM_SIZE 0xC0
// UART_FLAG register
// ==================
#define UART_RX_FIFO_EMPTY (1 << 4)
#define UART_TX_FIFO_FULL (1 << 5)
// UART Line Control Register
// ==========================
#define UART_LCR_BREAK (1 << 0)
#define UART_LCR_PARITY_EN (1 << 1)
#define UART_LCR_EVEN_PARITY (1 << 2)
#define UART_LCR_2_STOP (1 << 3)
#define UART_LCR_FIFO_EN (1 << 4)
#define UART_LCR_8_BITS (3 << 5)
#define UART_LCR_STICK_PARITY (1 << 7)
// UART Control Register
// ======================
#define UARTCR_UART_ENABLE (1 << 0)
#define UARTCR_LOOPBACK (1 << 7)
#define UARTCR_TX_ENABLE (1 << 8)
#define UARTCR_RX_ENABLE (1 << 9)
#define UARTCR_RTS (1 << 11)
// UART Interrupt masks
// ====================
#define INT_CTS (1 << 1)
#define INT_RX (1 << 4)
#define INT_TX (1 << 5)
#define INT_RX_TIMEOUT (1 << 6)
#define INT_FRAMING_ERR (1 << 7)
#define INT_PARITY_ERR (1 << 8)
#define INT_BREAK_ERR (1 << 9)
#define INT_OVER_ERR (1 << 10)
// ===============================================================================================
//
// delay
//
// ===============================================================================================
static inline void delay(int32_t count)
{
asm volatile("__delay_%=: subs %[count], %[count], #1; bne __delay_%=\n"
: "=r"(count): [count]"0"(count) : "cc");
}
// ===============================================================================================
//
// ttyebus_irq_handler
//
// ===============================================================================================
//
// Parameter:
//
// Returns:
//
// Description:
// Fired on interrupt. If data is in the receiver holding register, transfer it to the ring
// buffer. If transmitter holding register has become empty, fill it with another data from
// the linear buffer.
//
// ===============================================================================================
static irqreturn_t ttyebus_irq_handler(int irq, void* dev_id)
{
unsigned int IntStatus;
unsigned int DataWord;
unsigned int IntMask;
unsigned int RxNext;
#ifdef IRQDEBUG
printk(KERN_NOTICE "ttyebus: IRQ %d called. RxHead=%d, RxTail=%d, TxHead=%d, TxTail=%d", IrqCounter, RxHead, RxTail, TxHead, TxTail);
#endif
IntStatus = ioread32(UART_INT_STAT);
if (IntStatus & INT_RX)
{
// clear the interrupt
// ===================
iowrite32(INT_RX, UART_INT_CLR);
// data was received and is available in the receiver holding register
// ===================================================================
DataWord = ioread32(UART_DATA);
// see if the buffer will be full after this interrupt
// ===================================================
spin_lock(&SpinLock);
RxNext = RxHead + 1;
if (RxNext >= RX_BUFF_SIZE)
RxNext = 0;
if (RxNext != RxTail)
{
// data was received and is available in the receiver holding register
// ===================================================================
RxBuff[RxHead] = DataWord;
RxHead = RxNext;
#ifdef IRQDEBUG
printk(KERN_NOTICE "ttyebus: IRQ: One byte received. RxHead=%d, RxTail=%d", RxHead, RxTail);
#endif
}
else
{
// buffer overrun. do nothing. just discard the data.
// eventually todo: if someone needs to know, we can throw an error here
// =====================================================================
#ifdef IRQDEBUG
printk(KERN_NOTICE "ttyebus: IRQ: Buffer overrun. RxHead=%d, RxTail=%d", RxHead, RxTail);
#endif
}
spin_unlock(&SpinLock);
// clear any receiver error
// ========================
iowrite32(0, UART_RX_ERR);
// if the calling task is waiting, wake him up. If there is no task at all, this is a NOP
// ======================================================================================
wake_up(&WaitQueue);
}
// Transmitter
// ===========
if (IntStatus & INT_TX)
{
// clear the interrupt
// ===================
iowrite32(INT_TX, UART_INT_CLR);
// The transmitter holding register has become empty.
// see if some more data available
// ==================================================
spin_lock(&SpinLock);
if (TxTail < TxHead)
{
#ifdef IRQDEBUG
printk(KERN_NOTICE "ttyebus: IRQ: Transmitting one byte. TxHead=%d, TxTail=%d", TxHead, TxTail);
#endif
// fill the transmitter holding register with new data
// ===================================================
DataWord = TxBuff[TxTail++];
iowrite32(DataWord, UART_DATA);
// (do nothing with the interrupt line - keep the INT_TX active)
}
else
{
#ifdef IRQDEBUG
printk(KERN_NOTICE "ttyebus: IRQ: Stopping Tx Interrupt. TxHead=%d, TxTail=%d", TxHead, TxTail);
#endif
// no more data in the transmit buffer. disable the TX interrupt
// =============================================================
IntMask = ioread32(UART_INT_MASK);
iowrite32(IntMask & ~INT_TX, UART_INT_MASK);
}
spin_unlock(&SpinLock);
}
#ifdef IRQDEBUG
printk(KERN_NOTICE "ttyebus: IRQ %d exit. RxHead=%d, RxTail=%d, TxHead=%d, TxTail=%d", IrqCounter, RxHead, RxTail, TxHead, TxTail);
IrqCounter++;
#endif
return IRQ_HANDLED;
}
// ===============================================================================================
//
// ttyebus_set_gpio_mode
//
// ===============================================================================================
//
// Parameter:
// Gpio Number of the GPIO port
// Function one of GPIO_INPUT, GPIO_OUTPUT, GPIO_ALT_0, etc.
//
// Returns:
//
// Description:
// Set the mode for the GPIO port. Especially in this program GPIO_ALT_0 at port 14, 15 will
// connect the ports to the UART Rx and Tx
//
// ===============================================================================================
static void ttyebus_set_gpio_mode(unsigned int Gpio, unsigned int Function)
{
unsigned int RegOffset = (Gpio / 10) << 2;
unsigned int Bit = (Gpio % 10) * 3;
volatile unsigned int Value = ioread32(GpioAddr + RegOffset);
iowrite32((Value & ~(0x7 << Bit)) | ((Function & 0x7) << Bit), GpioAddr + RegOffset);
}
// ===============================================================================================
//
// ttyebus_gpio_pullupdown
//
// ===============================================================================================
//
// Parameter:
// Gpio Number of the GPIO port
// pud one of GPIO_PULL_OFF, GPIO_PULL_DOWN or GPIO_PULL_UP
//
// Returns:
//
// Description:
// Set the pull-up or pull-down at the specified GPIO port
//
// ===============================================================================================
void ttyebus_gpio_pullupdown(unsigned int Gpio, unsigned int pud)
{
// fill the new value for pull up or down
// ======================================
iowrite32(pud, GPIO_PULL);
delay(150); // provide hold time for the control signal
// transfer the new value to the GPIO pin
// ======================================
iowrite32(GPIO_BIT, GPIO_PULLCLK0 + GPIO_BANK);
delay(150); // provide hold time for the control signal
// remove the control signal to make it happen
// ===========================================
iowrite32(0, GPIO_PULL);
iowrite32(0, GPIO_PULLCLK0 + GPIO_BANK);
}
// ===============================================================================================
//
// ttyebus_poll
//
// ===============================================================================================
//
// Parameter:
// file_ptr Pointer to the open file
// wait Timeout structure
//
// Returns:
// POLLIN Data is available
//
// Description:
// Probe the receiver if some data available. Return after timeout anyway.
//
// ===============================================================================================
static unsigned int ttyebus_poll(struct file* file_ptr, poll_table* wait)
{
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Poll request");
#endif
poll_wait(file_ptr, &WaitQueue, wait);
if (RxTail != RxHead)
{
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Poll succeeded. RxHead=%d, RxTail=%d", RxHead, RxTail);
#endif
return POLLIN | POLLRDNORM;
}
else
{
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Poll timeout");
#endif
return 0;
}
}
// ===============================================================================================
//
// ttyebus_read
//
// ===============================================================================================
//
// Parameter:
// file_ptr Pointer to the open file
// user_buffer Buffer in user space where to receive the data
// Count Number of bytes to read
// offset Pointer to a counter that can hold an offset when reading chunks
//
// Returns:
// Number of bytes read
//
// Description:
// Called when a process, which already opened the dev file, attempts to read from it, like
// "cat /dev/ttyebus"
//
// ===============================================================================================
static ssize_t ttyebus_read(struct file* file_ptr, char __user* user_buffer, size_t Count, loff_t* offset)
{
unsigned int NumBytes;
unsigned int result;
unsigned long Flags;
enum { BUFFER_SIZE = 512 };
char buffer[BUFFER_SIZE];
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Read request with offset=%d and count=%u", (int)*offset, (unsigned int)Count);
#endif
// wait until a character is received or if timeout (1 min) occurs.
// note that wait_event_timeout is a macro that will already avoid the race condition that may
// happen where new data arrives between testing (RxTail != RxHead) and effective sleeping of this task.
// =====================================================================================================
result = wait_event_timeout(WaitQueue, RxTail != RxHead, msecs_to_jiffies(60000));
if (result == 0)
{
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Read timeout");
#endif
return -EBUSY; // timeout
}
#ifdef IRQDEBUG
printk(KERN_NOTICE "ttyebus: Read event. RxHead=%d, RxTail=%d", RxHead, RxTail);
#endif
// collect all bytes received so far from the receive buffer
// we must convert from a ring buffer to a linear buffer
// =========================================================
NumBytes = 0;
spin_lock_irqsave(&SpinLock, Flags);
while (RxTail != RxHead && NumBytes < Count)
{
buffer[NumBytes++] = RxBuff[RxTail++];
if (RxTail >= RX_BUFF_SIZE)
RxTail = 0;
}
spin_unlock_irqrestore(&SpinLock, Flags);
// copying data to user space requires a special function to be called
// ===================================================================
if (copy_to_user(user_buffer, buffer, Count) != 0)
return -EFAULT;
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Read exit with %d bytes read", NumBytes);
#endif
return NumBytes; // the number of bytes actually received
}
// ===============================================================================================
//
// ttyebus_write
//
// ===============================================================================================
//
// Parameter:
// file_ptr Pointer to the open file
// user_buffer Buffer in user space where to receive the data
// Count Number of bytes to write
// offset Pointer to a counter that can hold an offset when writing chunks
//
// Returns:
// Number of bytes written
//
// Description:
// Called when a process, which already opened the dev file, attempts to write to it, like
// "echo "hello" > /dev/ttyebus"
//
// ===============================================================================================
static ssize_t ttyebus_write(struct file* file_ptr, const char __user* user_buffer, size_t Count, loff_t* offset)
{
int result;
int Timeout;
unsigned long Flags;
unsigned int DataWord;
unsigned int IntMask;
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Write request with offset=%d and count=%u", (int)*offset, (unsigned int)Count);
#endif
#ifdef IRQDEBUG
printk(KERN_NOTICE "ttyebus: Write request. TxHead=%d, TxTail=%d", TxHead, TxTail);
#endif
// if transmission is still in progress, wait until done
// =====================================================
Timeout = 500;
while (TxTail < TxHead)
{
if (--Timeout < 0)
return -EBUSY;
udelay(500);
}
// copying data from user space requires a special function to be called
// =====================================================================
if (Count > TX_BUFF_SIZE)
Count = TX_BUFF_SIZE;
result = copy_from_user(TxBuff, user_buffer, Count);
if (result > 0) // not all requested bytes copied
Count = result; // nuber of bytes copied
else if (result != 0)
return -EFAULT;
// Fill the first character directly to the hardware, the rest will be
// fetched by the interrupt handler upon handling the TX interrupt
// ===================================================================
spin_lock_irqsave(&SpinLock, Flags);
DataWord = TxBuff[0];
TxTail = 1;
TxHead = Count;
iowrite32(DataWord, UART_DATA);
// enable the TX interrupt. will be asserted when the transmitter holding becomes empty
// ====================================================================================
IntMask = ioread32(UART_INT_MASK);
iowrite32(IntMask | INT_TX, UART_INT_MASK);
spin_unlock_irqrestore(&SpinLock, Flags);
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Write exit with %d bytes written", Count);
#endif
return Count; // the number of bytes actually transmitted
}
// ===============================================================================================
//
// ttyebus_open
//
// ===============================================================================================
//
// Parameter:
//
// Returns:
//
// Description:
// Called when a process tries to open the device file, like "cat /dev/ttyebus"
//
// ===============================================================================================
static int ttyebus_open(struct inode* inode, struct file* file)
{
unsigned int UartCtrl;
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Open at at major %d minor %d", imajor(inode), iminor(inode));
#endif
// do not allow another open if already open
// =========================================
if (DeviceOpen)
return -EBUSY;
DeviceOpen++;
// Disable UART0
// =============
iowrite32(0, UART_CTRL);
// reset the ring buffer and the linear buffer
// ===========================================
RxTail = RxHead = 0;
TxTail = TxHead = TX_BUFF_SIZE;
// Setup the GPIO pin 14 && 15 to ALTERNATE 0 (connect to UART)
// ============================================================
ttyebus_set_gpio_mode(15, GPIO_ALT_0); // GPIO15 connected to RxD
ttyebus_set_gpio_mode(14, GPIO_ALT_0); // GPIO14 connected to TxD
// Set pull-down for the GPIO pin
// ==============================
ttyebus_gpio_pullupdown(14, GPIO_PULL_UP);
ttyebus_gpio_pullupdown(15, GPIO_PULL_UP);
// Clear pending interrupts
// ========================
iowrite32(0x7FF, UART_INT_CLR);
// Set integer & fractional part of baud rate to 2400 Baud fixed. Divider = 3.000.000 / 2400
// Fractional part = 0. For Raspi3 and upwards, UART default frequency is 48.000.000 and divided
// by 16, which is again 3.000.000. It is assumed that the default values are not changed.
// ===========================================================================================
iowrite32(3000000 / 2400, UART_INT_BAUD);
iowrite32(0, UART_FRAC_BAUD);
// Disable FIFO & 8 bit (1 stop bit, no parity)
// ============================================
iowrite32(UART_LCR_8_BITS, UART_LINE_CTRL);
// Enable receiver interrupt
// =========================
iowrite32(INT_RX, UART_INT_MASK);
// Read data register to clear overflow error bit. In addition, clear any other receiver error
// =========================
ioread32(UART_DATA);
iowrite32(0, UART_RX_ERR);
// Enable UART0, receive & transfer part of UART
// =============================================
UartCtrl = UARTCR_UART_ENABLE | UARTCR_TX_ENABLE | UARTCR_RX_ENABLE | UARTCR_RTS;
#ifdef LOOPBACK
UartCtrl |= UARTCR_LOOPBACK;
#endif
iowrite32(UartCtrl, UART_CTRL);
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: Open exit");
#endif
return 0;
}
// ===============================================================================================
//
// ttyebus_close
//
// ===============================================================================================
//
// Parameter:
//
// Returns:
//
// Description:
// Called when a process closes the device file.
//
// ===============================================================================================
static int ttyebus_close(struct inode *inode, struct file *file)
{
printk(KERN_NOTICE "ttyebus: Close at at major %d minor %d", imajor(inode), iminor(inode));
DeviceOpen--;
// Disable UART0
// =============
iowrite32(0, UART_CTRL);
printk(KERN_NOTICE "ttyebus: Close exit");
return 0;
}
// ===============================================================================================
//
// ttyebus_ioctl
//
// ===============================================================================================
//
// Parameter:
//
// Returns:
// OK
//
// Description:
// I/O control. Currently this does nothing. ebusd just calls it to see if the device
// is working. So only return an OK status
//
// ===============================================================================================
static long ttyebus_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
return 0;
}
// ===============================================================================================
//
// ttyebus_raspi_model
//
// ===============================================================================================
// Description:
// Get the Rasperry Pi model number from /sys/firmware/devicetree/base/model. The string
// has usually the form "Raspberry Pi 3 Model B Rev 1.2"
// Extract the number and return it.
//
// ===============================================================================================
unsigned int ttyebus_raspi_model(void)
{
struct file* filp = NULL;
char buf[32];
unsigned int NumBytes = 0;
// get current segment descriptor, set segment descriptor
// associated to kernel space
// ======================================================
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
// read the file
// =============
filp = filp_open("/sys/firmware/devicetree/base/model", O_RDONLY, 0);
if (filp == NULL)
{
set_fs(old_fs);
return 0;
}
NumBytes = filp->f_op->read(filp, buf, sizeof(buf), &filp->f_pos);
set_fs(old_fs);
// restore the segment descriptor
// ==============================
filp_close(filp, NULL);
// interpret the data from the file
// ================================
if (NumBytes < 14)
return 0;
switch(buf[13])
{
case '2' : return 2; break;
case '3' : return 3; break;
case '4' : return 4; break;
default: return 1;
}
}
// ===============================================================================================
//
// ttyebus_register
//
// ===============================================================================================
//
// Parameter:
//
// Returns:
// Major Number of the driver
//
// Description:
// Register the device to the kernel by use of the register-chrdev(3) call. Since the first
// parameter to this call is 0, the system will assign a Major Number by itself. A
// device name is given and the file_operations structure is also passed to the kernel.
//
// ===============================================================================================
int ttyebus_register(void)
{
int result;
unsigned int PeriBase;
unsigned int UartIrq;
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: register_device() is called");
#endif
// Get the RASPI model
// ===================
RaspiModel = ttyebus_raspi_model();
if (RaspiModel < 1 || RaspiModel > 4)
{
printk(KERN_NOTICE "ttyebus: Unknown RASPI model %d\n", RaspiModel);
return -EFAULT;
}
printk(KERN_NOTICE "ttyebus: Found RASPI model %d\n", RaspiModel);
// Dynamically allocate a major number for the device
// ==================================================
MajorNumber = register_chrdev(0, DEVICE_NAME, &ttyebus_fops);
if (MajorNumber < 0)
{
printk(KERN_WARNING "ttyebus: can\'t register character device with errorcode = %i", MajorNumber);
return MajorNumber;
}
#ifdef DEBUG
printk(KERN_NOTICE "ttyebus: registered character device with major number = %i and minor numbers 0...255", MajorNumber);
#endif
// Register the device driver. We are using misc_register instead of
// device_create so we are able to set the attributes to rw for everybody
// ======================================================================
result = misc_register(&misc);
if (result)
{
unregister_chrdev(MajorNumber, DEVICE_NAME);
printk(KERN_ALERT "ttyebus: Failed to create the device");
return result;
}
// remap the I/O registers to some memory we can access later on
// =============================================================
PeriBase = (RaspiModel == 1) ? RASPI_1_PERI_BASE : (RaspiModel == 4) ? RASPI_4_PERI_BASE : RASPI_23_PERI_BASE;
GpioAddr = ioremap(PeriBase + GPIO_BASE, SZ_4K);
UartAddr = ioremap(PeriBase + UART0_BASE, SZ_4K);
// set up a queue for waiting
// ==========================
init_waitqueue_head(&WaitQueue);
// initialize the spinlock
// =======================
spin_lock_init(&SpinLock);
// Install Interrupt Handler
// =========================
UartIrq = (RaspiModel == 1) ? RASPI_1_UART_IRQ : (RaspiModel == 4) ? RASPI_4_UART_IRQ : RASPI_23_UART_IRQ;
if (RaspiModel == 4)
result = request_irq(UartIrq, ttyebus_irq_handler, IRQF_SHARED, "ttyebus_irq_handler", DEVICE_NAME);
else
result = request_irq(UartIrq, ttyebus_irq_handler, 0, "ttyebus_irq_handler", NULL);
if (result)
{
unregister_chrdev(MajorNumber, DEVICE_NAME);
printk(KERN_ALERT "ttyebus: Failed to request IRQ %d", UartIrq);
return result;
}
printk(KERN_INFO "ttyebus: Successfully requested IRQ %d", UartIrq);
DeviceOpen = 0;
#ifdef DEBUG
printk(KERN_INFO "ttyebus: device created correctly");
#endif
return result;
}
// ===============================================================================================
//
// ttyebus_unregister
//
// ===============================================================================================
// Parameter:
//
// Returns:
//
// Description:
// Unmap the I/O, free the IRQ and unregister the device
//
// ===============================================================================================
void ttyebus_unregister(void)
{
unsigned int UartIrq;
printk(KERN_NOTICE "ttyebus: unregister_device()");
// release the mapping
if (GpioAddr)
iounmap(GpioAddr);
if (UartAddr)
iounmap(UartAddr);
GpioAddr = 0;
UartAddr = 0;
UartIrq = (RaspiModel == 1) ? RASPI_1_UART_IRQ : (RaspiModel == 4) ? RASPI_4_UART_IRQ : RASPI_23_UART_IRQ;
free_irq(UartIrq, NULL);
misc_deregister(&misc);
unregister_chrdev(MajorNumber, DEVICE_NAME);
MajorNumber = 0;
}
// ===============================================================================================
//
// module_init() module_exit()
//
// ===============================================================================================
// Description:
// Before Linux 2.4, the init and cleanup functions have to be named init_module() and
// cleanup_module() exactly.
// As of Linux 2.4, there are two macros, module_init() and module_exit() defined in
// linux/init.h that allow us to use any name for the init and cleanup we want.
// Note that the functions must be defined *before* calling the macros, otherwise you'll
// get compilation errors.
//
// ===============================================================================================
module_init(ttyebus_register);
module_exit(ttyebus_unregister);