Showing 4 changed files with 247 additions and 0 deletions
+2
99-fakenmea.rules
... ...
@@ -0,0 +1,2 @@
1
+#Rules file for the ebbchar device driver
2
+KERNEL=="nmea", SUBSYSTEM=="nmea", MODE="0666"
+8
Makefile
... ...
@@ -0,0 +1,8 @@
1
+obj-m+=fakenmea.o
2
+
3
+all:
4
+	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
5
+	$(CC) readnmea.c -o readnmea
6
+clean:
7
+	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
8
+	rm readnmea
+200
fakenmea.c
... ...
@@ -0,0 +1,200 @@
1
+/**
2
+ * @file   fakenmea.c
3
+ * @author Yanik Cawidrone
4
+ * @date   1st July 2017
5
+ * @version 0.1
6
+ * @brief   A simple character device driver which send a preset GPS
7
+ * coordinates when the device is read in userland.
8
+ * Heavily inspired by Derek Molloy code
9
+ * @see http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/
10
+ * @see https://github.com/derekmolloy/exploringBB.git
11
+ */
12
+
13
+#include <linux/init.h>
14
+#include <linux/module.h>
15
+#include <linux/device.h>
16
+#include <linux/kernel.h>
17
+#include <linux/fs.h>
18
+#include <asm/uaccess.h>
19
+
20
+#define  DEVICE_NAME "nmea"
21
+#define  CLASS_NAME  "nmea"
22
+MODULE_LICENSE("GPL");
23
+MODULE_AUTHOR("Yanik Cawidrone");
24
+MODULE_DESCRIPTION("A simple Linux FAKENMEA");
25
+MODULE_VERSION("0.1");
26
+#define MAX_BUFFER_SZ 256
27
+
28
+/* Default position */
29
+static char *LATDIR="N";
30
+static char *LONDIR="E";
31
+static char *LAT="0116.87058";
32
+static char *LON="10350.98809";
33
+static char *ALT="110";
34
+static char *SAT="06";
35
+static int TZ=8;
36
+
37
+/* Module parameters */
38
+module_param(ALT, charp, 0000);
39
+MODULE_PARM_DESC(ALT, "Altitude (default: 110)");
40
+module_param(SAT, charp, 0000);
41
+MODULE_PARM_DESC(SAT, "Number of Satellite(s) (default: 06)");
42
+module_param(LATDIR, charp, 0000);
43
+MODULE_PARM_DESC(LATDIR, "Latitude Direction (default: N)");
44
+module_param(LONDIR, charp, 0000);
45
+MODULE_PARM_DESC(LONDIR, "Longitude Direction (default: E)");
46
+module_param(LAT, charp, 0000);
47
+MODULE_PARM_DESC(LAT, "Latitude (default:0116.87058)");
48
+module_param(LON, charp, 0000);
49
+MODULE_PARM_DESC(LON, "Longitude (default:10350.98809)");
50
+module_param(TZ, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
51
+MODULE_PARM_DESC(TZ, "Timezone drift (default:8 but currently unused)");
52
+
53
+
54
+int checksum(const char *s) {
55
+    int c = 0;
56
+    while(*s)
57
+        c ^= *s++;
58
+    return c;
59
+}
60
+
61
+static int    majorNumber;                  ///< Stores the device number -- determined automatically
62
+static char   message[MAX_BUFFER_SZ] = {0};           ///< Memory for the string that is passed from userspace
63
+static short  size_of_message=0;              ///< Used to remember the size of the string stored
64
+static int    numberOpens = 0;              ///< Counts the number of times the device is opened
65
+static struct class*  fakenmeaClass  = NULL; ///< The device-driver class struct pointer
66
+static struct device* fakenmeaDevice = NULL; ///< The device-driver device struct pointer
67
+
68
+// The prototype functions for the character driver -- must come before the struct definition
69
+static int     dev_open(struct inode *, struct file *);
70
+static int     dev_release(struct inode *, struct file *);
71
+static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
72
+static ssize_t dev_write(struct file *, const char *, size_t, loff_t *);
73
+
74
+static struct file_operations fops =
75
+{
76
+   .open = dev_open,
77
+   .read = dev_read,
78
+   .write = dev_write,
79
+   .release = dev_release,
80
+};
81
+
82
+static int __init fakenmea_init(void){
83
+   printk(KERN_INFO "FNMEA: Initializing the FAKENMEA\n");
84
+   
85
+   printk(KERN_INFO "FNMEA: Altitude %s\n",ALT);
86
+   printk(KERN_INFO "FNMEA: Position %s%s %s%s\n",LAT,LATDIR, LON,LONDIR);
87
+
88
+   // Try to dynamically allocate a major number for the device -- more difficult but worth it
89
+   majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
90
+   if (majorNumber<0){
91
+      printk(KERN_ALERT "FNMEA: failed to register a major number\n");
92
+      return majorNumber;
93
+   }
94
+   printk(KERN_INFO "FNMEA: registered correctly with major number %d\n", majorNumber);
95
+
96
+   // Register the device class
97
+   fakenmeaClass = class_create(THIS_MODULE, CLASS_NAME);
98
+   if (IS_ERR(fakenmeaClass)){                // Check for error and clean up if there is
99
+      unregister_chrdev(majorNumber, DEVICE_NAME);
100
+      printk(KERN_ALERT "FNMEA: Failed to register device class\n");
101
+      return PTR_ERR(fakenmeaClass);          // Correct way to return an error on a pointer
102
+   }
103
+   printk(KERN_INFO "FNMEA: device class registered correctly\n");
104
+
105
+   // Register the device driver
106
+   fakenmeaDevice = device_create(fakenmeaClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
107
+   if (IS_ERR(fakenmeaDevice)){               // Clean up if there is an error
108
+      class_destroy(fakenmeaClass);           // Repeated code but the alternative is goto statements
109
+      unregister_chrdev(majorNumber, DEVICE_NAME);
110
+      printk(KERN_ALERT "FNMEA: Failed to create the device\n");
111
+      return PTR_ERR(fakenmeaDevice);
112
+   }
113
+   printk(KERN_INFO "FNMEA: device class created correctly\n"); // Made it! device was initialized
114
+   return 0;
115
+}
116
+
117
+/** @brief The LKM cleanup function
118
+ *  Similar to the initialization function, it is static. The __exit macro notifies that if this
119
+ *  code is used for a built-in driver (not a LKM) that this function is not required.
120
+ */
121
+static void __exit fakenmea_exit(void){
122
+   device_destroy(fakenmeaClass, MKDEV(majorNumber, 0));     // remove the device
123
+   class_unregister(fakenmeaClass);                          // unregister the device class
124
+   class_destroy(fakenmeaClass);                             // remove the device class
125
+   unregister_chrdev(majorNumber, DEVICE_NAME);             // unregister the major number
126
+   printk(KERN_INFO "FNMEA: Module removed\n");
127
+}
128
+
129
+static int dev_open(struct inode *inodep, struct file *filep){
130
+  numberOpens++;
131
+  printk(KERN_INFO "FNMEA: Device has been opened %d time(s) %d\n", numberOpens,size_of_message);
132
+  return 0;
133
+}
134
+
135
+static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset){
136
+  int error_count = 0;
137
+  struct timeval ts;
138
+  struct tm tm;
139
+
140
+  char mystring[MAX_BUFFER_SZ]="";
141
+  char mystring_format[]="GPRMC,%02d%02d%02d.%d,V,%s,%s,%s,%s,0,0,%02d%02d%02d,,E";
142
+  char mystring2[MAX_BUFFER_SZ]="";
143
+  char mystring_format2[]="GPGGA,%02d%02d%02d.%d,%s,%s,%s,%s,0,%s,0,%s,M,0,M,,";
144
+  
145
+  do_gettimeofday(&ts);
146
+  time_to_tm(ts.tv_sec, TZ, &tm);
147
+    
148
+  sprintf(mystring, mystring_format,
149
+    tm.tm_hour,
150
+    tm.tm_min,
151
+    tm.tm_sec,
152
+    ts.tv_usec/1000,
153
+    LAT,
154
+    LATDIR,
155
+    LON,
156
+    LONDIR,
157
+    tm.tm_mday,
158
+    tm.tm_mon+1,
159
+    tm.tm_year-100);
160
+  sprintf(message,"$%s*%02X\n", mystring, checksum(mystring));
161
+  size_of_message = strlen(message);
162
+  
163
+  sprintf(mystring2, mystring_format2,
164
+    tm.tm_hour,
165
+    tm.tm_min,
166
+    tm.tm_sec,
167
+    ts.tv_usec/1000,
168
+    LAT,
169
+    LATDIR,
170
+    LON,
171
+    LONDIR,
172
+    SAT,
173
+    ALT);
174
+  sprintf(message+size_of_message,"$%s*%02X\n", mystring2, checksum(mystring2));
175
+  
176
+  error_count = copy_to_user(buffer, message, strlen(message));
177
+  if (error_count==0){            // if true then have success
178
+    printk(KERN_INFO "FNMEA: snd %d\n", size_of_message);
179
+    printk(KERN_INFO "FNMEA: snd %s", message);
180
+    return (size_of_message=0);  // clear the position to the start and return 0
181
+  }
182
+  else {
183
+    printk(KERN_INFO "FNMEA: Failed to send %d characters to the user\n", error_count);
184
+    return -EFAULT;              // Failed -- return a bad address message (i.e. -14)
185
+  }
186
+}
187
+
188
+static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset){
189
+  printk(KERN_INFO "FNMEA: recv %zu\n", len);
190
+  printk(KERN_INFO "FNMEA: recv %.*s", (int)len, buffer);
191
+  return len;
192
+}
193
+
194
+static int dev_release(struct inode *inodep, struct file *filep){
195
+   printk(KERN_INFO "FNMEA: Device successfully closed\n");
196
+   return 0;
197
+}
198
+
199
+module_init(fakenmea_init);
200
+module_exit(fakenmea_exit);
+37
readnmea.c
... ...
@@ -0,0 +1,37 @@
1
+/**
2
+ * @file   readnmea.c
3
+ * @author Yanik Cawidrone
4
+ * @date   1st July 2017
5
+ * @version 0.1
6
+ * @brief  A Linux user space program that communicates with the fakenmea LKM. 
7
+ * @see http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/
8
+ * @see https://github.com/derekmolloy/exploringBB.git
9
+*/
10
+#include<stdio.h>
11
+#include<stdlib.h>
12
+#include<errno.h>
13
+#include<fcntl.h>
14
+#include<string.h>
15
+#include<unistd.h>
16
+#include <time.h>
17
+
18
+#define BUFFER_LENGTH 256
19
+static char receive[BUFFER_LENGTH];
20
+
21
+int main(){
22
+  int ret, fd;
23
+  char stringToSend[BUFFER_LENGTH];
24
+  fd = open("/dev/nmea", O_RDONLY);             // Open the device with read/write access
25
+  if (fd < 0){
26
+    perror("Failed to open the device...");
27
+    return errno;
28
+  }
29
+  ret = read(fd, receive, BUFFER_LENGTH);        // Read the response from the LKM
30
+  if (ret < 0){
31
+    perror("Failed to read the message from the device.");
32
+    return errno;
33
+  }
34
+  printf("%s", receive);
35
+  close(fd);
36
+  return 0;
37
+}