fakenmea / fakenmea.c /
48f5f91 7 years ago
1 contributor
200 lines | 7.271kb
/**
 * @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);