/** * @file fakenmea.c * @author Yanik Cawidrone * @date 1st July 2017 * @version 0.1 * @brief A simple character device driver which send a preset GPS * coordinates when the device is read in userland. * Heavily inspired by Derek Molloy code * @see http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/ * @see https://github.com/derekmolloy/exploringBB.git */ #include #include #include #include #include #include #define DEVICE_NAME "nmea" #define CLASS_NAME "nmea" MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yanik Cawidrone"); MODULE_DESCRIPTION("A simple Linux FAKENMEA"); MODULE_VERSION("0.1"); #define MAX_BUFFER_SZ 256 /* Default position */ static char *LATDIR="N"; static char *LONDIR="E"; static char *LAT="0116.87058"; static char *LON="10350.98809"; static char *ALT="110"; static char *SAT="06"; static int TZ=8; /* Module parameters */ module_param(ALT, charp, 0000); MODULE_PARM_DESC(ALT, "Altitude (default: 110)"); module_param(SAT, charp, 0000); MODULE_PARM_DESC(SAT, "Number of Satellite(s) (default: 06)"); module_param(LATDIR, charp, 0000); MODULE_PARM_DESC(LATDIR, "Latitude Direction (default: N)"); module_param(LONDIR, charp, 0000); MODULE_PARM_DESC(LONDIR, "Longitude Direction (default: E)"); module_param(LAT, charp, 0000); MODULE_PARM_DESC(LAT, "Latitude (default:0116.87058)"); module_param(LON, charp, 0000); MODULE_PARM_DESC(LON, "Longitude (default:10350.98809)"); module_param(TZ, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); MODULE_PARM_DESC(TZ, "Timezone drift (default:8 but currently unused)"); int checksum(const char *s) { int c = 0; while(*s) c ^= *s++; return c; } static int majorNumber; ///< Stores the device number -- determined automatically static char message[MAX_BUFFER_SZ] = {0}; ///< Memory for the string that is passed from userspace static short size_of_message=0; ///< Used to remember the size of the string stored static int numberOpens = 0; ///< Counts the number of times the device is opened static struct class* fakenmeaClass = NULL; ///< The device-driver class struct pointer static struct device* fakenmeaDevice = NULL; ///< The device-driver device struct pointer // The prototype functions for the character driver -- must come before the struct definition static int dev_open(struct inode *, struct file *); static int dev_release(struct inode *, struct file *); static ssize_t dev_read(struct file *, char *, size_t, loff_t *); static ssize_t dev_write(struct file *, const char *, size_t, loff_t *); static struct file_operations fops = { .open = dev_open, .read = dev_read, .write = dev_write, .release = dev_release, }; static int __init fakenmea_init(void){ printk(KERN_INFO "FNMEA: Initializing the FAKENMEA\n"); printk(KERN_INFO "FNMEA: Altitude %s\n",ALT); printk(KERN_INFO "FNMEA: Position %s%s %s%s\n",LAT,LATDIR, LON,LONDIR); // Try to dynamically allocate a major number for the device -- more difficult but worth it majorNumber = register_chrdev(0, DEVICE_NAME, &fops); if (majorNumber<0){ printk(KERN_ALERT "FNMEA: failed to register a major number\n"); return majorNumber; } printk(KERN_INFO "FNMEA: registered correctly with major number %d\n", majorNumber); // Register the device class fakenmeaClass = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(fakenmeaClass)){ // Check for error and clean up if there is unregister_chrdev(majorNumber, DEVICE_NAME); printk(KERN_ALERT "FNMEA: Failed to register device class\n"); return PTR_ERR(fakenmeaClass); // Correct way to return an error on a pointer } printk(KERN_INFO "FNMEA: device class registered correctly\n"); // Register the device driver fakenmeaDevice = device_create(fakenmeaClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME); if (IS_ERR(fakenmeaDevice)){ // Clean up if there is an error class_destroy(fakenmeaClass); // Repeated code but the alternative is goto statements unregister_chrdev(majorNumber, DEVICE_NAME); printk(KERN_ALERT "FNMEA: Failed to create the device\n"); return PTR_ERR(fakenmeaDevice); } printk(KERN_INFO "FNMEA: device class created correctly\n"); // Made it! device was initialized return 0; } /** @brief The LKM cleanup function * Similar to the initialization function, it is static. The __exit macro notifies that if this * code is used for a built-in driver (not a LKM) that this function is not required. */ static void __exit fakenmea_exit(void){ device_destroy(fakenmeaClass, MKDEV(majorNumber, 0)); // remove the device class_unregister(fakenmeaClass); // unregister the device class class_destroy(fakenmeaClass); // remove the device class unregister_chrdev(majorNumber, DEVICE_NAME); // unregister the major number printk(KERN_INFO "FNMEA: Module removed\n"); } static int dev_open(struct inode *inodep, struct file *filep){ numberOpens++; printk(KERN_INFO "FNMEA: Device has been opened %d time(s) %d\n", numberOpens,size_of_message); return 0; } static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset){ int error_count = 0; struct timeval ts; struct tm tm; char mystring[MAX_BUFFER_SZ]=""; char mystring_format[]="GPRMC,%02d%02d%02d.%d,V,%s,%s,%s,%s,0,0,%02d%02d%02d,,E"; char mystring2[MAX_BUFFER_SZ]=""; char mystring_format2[]="GPGGA,%02d%02d%02d.%d,%s,%s,%s,%s,0,%s,0,%s,M,0,M,,"; do_gettimeofday(&ts); time_to_tm(ts.tv_sec, TZ, &tm); sprintf(mystring, mystring_format, tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_usec/1000, LAT, LATDIR, LON, LONDIR, tm.tm_mday, tm.tm_mon+1, tm.tm_year-100); sprintf(message,"$%s*%02X\n", mystring, checksum(mystring)); size_of_message = strlen(message); sprintf(mystring2, mystring_format2, tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_usec/1000, LAT, LATDIR, LON, LONDIR, SAT, ALT); sprintf(message+size_of_message,"$%s*%02X\n", mystring2, checksum(mystring2)); error_count = copy_to_user(buffer, message, strlen(message)); if (error_count==0){ // if true then have success printk(KERN_INFO "FNMEA: snd %d\n", size_of_message); printk(KERN_INFO "FNMEA: snd %s", message); return (size_of_message=0); // clear the position to the start and return 0 } else { printk(KERN_INFO "FNMEA: Failed to send %d characters to the user\n", error_count); return -EFAULT; // Failed -- return a bad address message (i.e. -14) } } static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset){ printk(KERN_INFO "FNMEA: recv %zu\n", len); printk(KERN_INFO "FNMEA: recv %.*s", (int)len, buffer); return len; } static int dev_release(struct inode *inodep, struct file *filep){ printk(KERN_INFO "FNMEA: Device successfully closed\n"); return 0; } module_init(fakenmea_init); module_exit(fakenmea_exit);