1 contributor
/**
* @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 <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#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);