Monday, November 23, 2015

IOCTL Android

IOCTL is a very useful system call: it is simple and multiplexes the different commands to the appropriate kernel space function. In this post, I want to describe how you can implement a module with IOCTL support for Android. There are a lot of good articles about it (links below), and I just describe the differences regarding to the Android platform.

So, let's create a very simple misc device and let's play with it, doing some reads and writes. Initially, let's define a very simple kernel module (most of the code was taken from here).

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>


#define MY_MACIG 'G'
#define READ_IOCTL _IOR(MY_MACIG, 0, int)
#define WRITE_IOCTL _IOW(MY_MACIG, 1, int)
 
static int used; 
static char msg[200];

static ssize_t device_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset)
{
  return simple_read_from_buffer(buffer, length, offset, msg, 200);
}

static ssize_t device_write(struct file *filp, const char __user *buff, size_t len, loff_t *off)
{
  if (len > 199)
    return -EINVAL;
  copy_from_user(msg, buff, len);
  msg[len] = '\0';
  return len;
}

static int device_open(struct inode *inode, struct file *file)
{
  used++;
  return 0;
}

static int device_release(struct inode *inode, struct file *file)
{
  used--;
  return 0;
}

char buf[200];
int device_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
  int len = 200;
  switch(cmd) {
  case READ_IOCTL: 
    copy_to_user((char *)arg, buf, 200);
    break;
 
  case WRITE_IOCTL:
    copy_from_user(buf, (char *)arg, len);
    break;

  default:
    return -ENOTTY;
  }
  return len;
}

static struct file_operations fops = {
        .owner = THIS_MODULE,
        .read = device_read,
        .write = device_write,
        .open = device_open,
        .release = device_release,
        .unlocked_ioctl = device_ioctl
};

static struct miscdevice my_miscdev = {
        .name         = "my_device",
        .mode         = S_IRWXUGO,
        .fops         = &fops,
};

static int __init cdevexample_module_init(void)
{
  int ret = misc_register(&my_miscdev);
  if (ret < 0) {
    printk ("Registering the character device failed\n");
    return ret;
  }
  printk("create node with mknod /dev/my_device\n");
  return 0;
}

static void __exit cdevexample_module_exit(void)
{
  misc_deregister(&my_miscdev);
}  

module_init(cdevexample_module_init);
module_exit(cdevexample_module_exit);
MODULE_LICENSE("GPL");

Compile it and insert into Android kernel.

shell@flounder:/ $ su
shell@flounder:/ $ insmod my_module.ko

Now, we can check the new device in /dev:

shell@flounder:/ $ su
shell@flounder:/ $ ls -l /dev
...
crw-rw---- root     mtp       10,  23 2015-11-23 18:04 mtp_usb
crw------- root     root      10,   0 2015-11-23 18:11 my_device
crw------- root     root      10,  36 2015-11-23 18:04 network_latency
crw------- root     root      10,  35 2015-11-23 18:04 network_throughput
crw-rw-rw- root     root       1,   3 2015-11-23 18:04 null

...

See that the permissions are limited. Don't forget to set it to:

shell@flounder:/ $ chmod 666 /dev/my_device
shell@flounder:/ $ ls -l /dev
...
crw-rw---- root     mtp       10,  23 2015-11-23 18:04 mtp_usb
crw-rw-rw- root     root      10,   0 2015-11-23 18:11 my_device
crw------- root     root      10,  36 2015-11-23 18:04 network_latency
crw------- root     root      10,  35 2015-11-23 18:04 network_throughput
crw-rw-rw- root     root       1,   3 2015-11-23 18:04 null

...

Now, let's try to do some operations with our device driver:

shell@flounder:/ $ echo "Hello world" > /dev/my_device
shell@flounder:/ $ cat /dev/my_device

You will see the following error on the logcat:

avc: denied { read write } for name="my_device" dev="tmpfs" scontext=u:r:system_app:s0 tcontext

This means that SELinux (yes, Android makes heavy usage of it) also controls the access to device drivers and you cannot read/write from/to your new drive. You have two options: i) disable SELinux in Android (you need to change some kernel options and rebuild it) or ii) add some new rules into SELinux. Let's do the last to learn a bit more :-)

So, we change the following files and give access (read, write, getattr, ioctl, open and create) to our new device /dev/my_device. If you need to restrict the access, you can adapt the policies according to your needs. For more information about SELinux and Android, take a look in this doc (specially the section "Implementation").

external/sepolicy/device.te
type fscklogs, dev_type;
type full_device, dev_type;
type my_device, dev_type;


external/sepolicy/file_contexts
/dev/rproc_user        u:object_r:rpmsg_device:s0
/dev/my_device         u:object_r:my_device:s0
/dev/snd(/.*)?         u:object_r:audio_device:s0


external/sepolicy/app.te
allow appdomain usb_device:chr_file { read write getattr ioctl };
allow appdomain usbaccessory_device:chr_file { read write getattr };
allow appdomain my_device:chr_file { read write getattr ioctl open create };


Now, let's build the Android framework again and flash the device. Everything should work fine.

shell@flounder:/ $ echo "Hello world" > /dev/my_device
shell@flounder:/ $ cat /dev/my_device
Hello world

That's it!! You can also check the following links

  • http://www.all-things-android.com/content/understanding-se-android-policy-files
  • https://source.android.com/security/selinux/

Wednesday, November 11, 2015

Latency/Bandwidth and Performance

I've been always interested in scrapping a bit more my application... no matter its level: frontend, framework or native. That's why I guess I have decided to study a bit more Software Systems. Some weeks ago, I was implementing some tests to evaluate my current project in terms of "time spent for certain operations". In addition, I had to force some cache/memory fault, work on some *non* cache-friendly program and, at the end, I was thinking about other aspects of my system. Usually, for software systems, we pretty much focus on latency and bandwidth. For example, when you are developing an application to have an efficient access to memory (cache-friendly!), you are thinking about latency, as you want to avoid (expensive) access to a lower-level memory. Of course, depending on the operation (network, memory access, etc.), latency and bandwidth have a bit different meaning. For example, for network, latency is measured by sending a packet that is returned to the sender. For memory access, latency can be explained as "the delay time between the moment a memory controller tells the memory module to access a particular memory column on a RAM module, and the moment the data from the given array location is available on the module's output pins". [Wiki]

I took a look on some amazing links that gave me a very precise and detailed view about such discussion and here they are: