... | ... |
@@ -0,0 +1,2 @@ |
1 |
+#Rules file for the ebbchar device driver |
|
2 |
+KERNEL=="nmea", SUBSYSTEM=="nmea", MODE="0666" |
... | ... |
@@ -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 |
... | ... |
@@ -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); |
... | ... |
@@ -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 |
+} |