| ... | ... |
@@ -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 |
+} |