Thursday 17 December 2015

Linux Device Model Internals


In this illustration I will be showing how bus_type, device_driver and device structures are linked. It also shows how the match and probe function of device driver is called for devices. I used Linux kernel 3.19 for making this simple program.

This program tries to 
1. Register a bus. 
2. Register a device_driver to that bus and 
3. Add a device_driver device to the driver.

At the last step the match and probe functions of the device driver are called.
In the probe function I am creating a simple character device.


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/major.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>

#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/semaphore.h>

#include <asm/uaccess.h>


dev_t my_devt;

char drv_name[] = "my_chr_drv";
struct class *my_class;
struct device *my_device;
struct device *my_chr_device;

char global_buf[20];

struct my_super_device
{
    struct cdev my_cdev;
    int count;
    struct semaphore my_sem;
};

struct my_super_device *my_super;

ssize_t my_read (struct file *myfile, char __user *my_buf, size_t len, loff_t *off)
{
    int ret;   
    //printk("len = %d\n",len);
    ret = copy_to_user(my_buf, global_buf, len);
    if(ret != 0)
        printk("not able to copy\n");
    printk("copied the data to user buffer\n");
    return len;
}

ssize_t my_write (struct file *my_file, const char __user *buf, size_t len, loff_t *off)
{
   
    int ret;   
    ret = copy_from_user(global_buf, buf, len);
    if(ret != 0)
        printk("not able to copy\n");
    printk("copied the data from user buffer\n");
    return len;
}

int my_mmap (struct file *my_file, struct vm_area_struct *my_vm)
{
    return 0;
}

int my_open (struct inode *my_inode, struct file *my_file)
{
    struct my_super_device *super_ptr;
    printk("open called\n");
    printk("cdev address = %p\n", my_inode->i_cdev);
   
    super_ptr = container_of(my_inode->i_cdev, struct my_super_device, my_cdev);   
    up(&(super_ptr->my_sem));
    printk("super address = %p\n", super_ptr);
    return 0;
}

int my_release (struct inode *my_inode, struct file *my_file)
{
    struct my_super_device *super_ptr;
    printk("release called\n");

    super_ptr = container_of(my_inode->i_cdev, struct my_super_device, my_cdev);   
    down(&(super_ptr->my_sem));
    return 0;
}

unsigned int my_poll (struct file *my_file, struct poll_table_struct *poll_table)
{
    return 0;
}

struct file_operations myfops = {
    .owner = THIS_MODULE,
    .read = my_read,
    .write = my_write,
    .poll = my_poll,
    .open = my_open,
    .release = my_release,
};

int my_bus_match(struct device *dev, struct device_driver *drv)
{
    printk("Entered %s\n",__func__);
    return 1;
}
int my_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    printk("Entered %s\n",__func__);
    return 0;
}

void my_bus_release(struct device *dev)
{
    printk("Entered %s\n",__func__);
    return ;
}

struct device my_bus = {
        .init_name = "my_bus",
    .release = my_bus_release,
};

struct bus_type my_bus_type = {
    .name = "my_bus",
    .match = my_bus_match,
    .uevent = my_bus_uevent,
};

int my_driver_probe(struct device *dev)
{
    int ret;   

    printk("Entered %s\n",__func__);

        ret = alloc_chrdev_region(&my_devt, 0, 1, drv_name);
        if(ret == 0)
                printk("dev_t major is %d minor is %d\n", MAJOR(my_devt), MINOR(my_devt));

        my_class =  class_create(THIS_MODULE, "my_class");
        if(my_class != NULL)
                printk("class created as %s\n", my_class->name);

        my_chr_device = device_create(my_class, NULL, my_devt, NULL, "my_chr_drv");
        if(my_chr_device != NULL)
                printk("device created dev_t as  %d\n", my_chr_device->devt);

        my_super = kzalloc(sizeof(struct my_super_device), GFP_KERNEL);

        sema_init(&(my_super->my_sem), 1);

        cdev_init(&(my_super->my_cdev), &myfops);

        cdev_add(&(my_super->my_cdev), my_devt, 1);
    return 0;
}

int my_driver_remove(struct device *dev)
{
    printk("Entered %s\n",__func__);

    if(my_chr_device != NULL)
        device_destroy(my_class, my_devt);
    if(my_class != NULL)
        class_destroy(my_class);
    if(my_devt != 0)
        unregister_chrdev_region(my_devt,1);

    return 0;
}

struct device_driver my_dev_driver = {
    .owner = THIS_MODULE,
    .name = "my_chr_drv",
    .bus = &my_bus_type,
    .probe = my_driver_probe,
    .remove = my_driver_remove,
};

static int __init my_init(void)
{
     int ret = 0;

    printk("in init\n");

    /*Register the bus*/
    ret = device_register(&my_bus);
    if(ret == 0)
        printk("device registerd correctly\n");
    else
        printk("ret = %d\n",ret);

    ret = bus_register(&my_bus_type);
    if(ret == 0)
        printk("bus registered correctly\n");
    else
        printk("ret = %d\n",ret);

    /*add device for the device driver*/
    my_device = kzalloc(sizeof(struct device), GFP_KERNEL);
    device_initialize(my_device);

    my_device->parent = &my_bus;
    my_device->bus = &my_bus_type;
    my_device->init_name = "my_chr_dev";

    ret = device_add(my_device);
    if(ret == 0)
        printk("device added correctly\n");
    else
        printk("ret = %d\n",ret);

    /*Now register the driver*/
    ret = driver_register(&my_dev_driver);
    if(ret == 0)
        printk("driver registered correctly\n");
    else
        printk("ret = %d\n",ret);

    return 0;
}

static void __exit my_exit(void)
{
    printk("in exit\n");
    driver_unregister(&my_dev_driver);
    bus_unregister(&my_bus_type);
    device_unregister(&my_bus);
}

module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("Kundan");
MODULE_LICENSE("GPL");



Here is the snipped ftrace for the device_add function of the driver taken from this program.




# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 1)               |  device_add() {
 1)               |    device_private_init() {
 1)               |      kmem_cache_alloc_trace() {
 1)   0.291 us    |        _cond_resched();
 1)   2.375 us    |      }
 1)   4.088 us    |    }
 1)               |    dev_set_name() {
 1)               |      __kmalloc_track_caller() {
 1)   0.240 us    |        kmalloc_slab();
 1)   0.220 us    |        _cond_resched();
 1)   3.277 us    |      }
 1)   0.231 us    |      kfree();
 1)   7.374 us    |    }
 1)   0.601 us    |    get_device_parent();
 1)   0.280 us    |    _raw_spin_lock();
 1)   0.210 us    |    _raw_spin_unlock();
 1)               |    sysfs_create_dir_ns() {
 1)               |        kernfs_add_one() {
 1)               |          mutex_lock() {
 1)   0.210 us    |            _cond_resched();
 1)   1.704 us    |          }
 1)   0.401 us    |          kernfs_name_hash();
 1)   0.511 us    |          kernfs_link_sibling();
 1)   0.291 us    |          mutex_unlock();
 1)               |          kernfs_activate() {
 1)               |            mutex_lock() {
 1)   0.210 us    |              _cond_resched();
 1)   1.733 us    |            }
 1)               |            kernfs_next_descendant_post() {
 1)   0.271 us    |              kernfs_leftmost_descendant();
 1)   1.903 us    |            }
 1)   0.220 us    |            kernfs_next_descendant_post();
 1)   0.261 us    |            mutex_unlock();
 1)   9.459 us    |          }
 1) + 19.107 us   |        }
 1) + 49.938 us   |      }
 1) + 51.360 us   |    }
 1)   0.251 us    |    kernfs_get();
 1)               |    acpi_platform_notify() {
 1)               |      acpi_get_bus_type() {
 1)               |        down_read() {
 1)   0.200 us    |          _cond_resched();
 1)   1.714 us    |        }
 1)   0.250 us    |        pci_acpi_bus_match();
 1)   0.221 us    |        usb_acpi_bus_match();
 1)   0.261 us    |        up_read();
 1) + 10.039 us   |      }
 1)   0.281 us    |      acpi_bind_one();
 1) + 13.626 us   |    }
 1)               |    device_create_file() {
 1) + 46.381 us   |    }
 1)   0.250 us    |    sysfs_create_groups();
 1)               |    bus_add_device() {
 1)               |      device_add_groups() {
 1)   0.220 us    |        sysfs_create_groups();
 1)   1.933 us    |      }
 1)               |      sysfs_create_link() {
 1)               |        sysfs_do_create_link_sd.isra.2() {
 1)   0.501 us    |    kfree();
 1)   0.581 us    |    kfree();
 1)               |    bus_probe_device() {
 1)               |      device_attach() {
 1)               |        mutex_lock() {
 1)   0.251 us    |          _cond_resched();
 1)   1.803 us    |        }
 1)               |        bus_for_each_drv() {
 1)   0.271 us    |          _raw_spin_lock();
 1)   0.221 us    |          _raw_spin_unlock();
 1)               |          __device_attach() {
 1)               |            my_bus_match [bus_final_1]() {
 1)               |              printk() {
 1)               |                vprintk_default() {
 1)               |                  vprintk_emit() {
 1)   0.291 us    |                    _raw_spin_lock();
 1)   0.496 us    |                    log_store();
 1)   0.085 us    |                    _raw_spin_unlock();
 1)   0.265 us    |                    console_trylock();
 1)   2.800 us    |                    console_unlock();
 1) + 15.946 us   |                  }
 1) + 17.038 us   |                }
 1) + 18.301 us   |              }
 1) + 19.412 us   |            }
 1)               |            driver_probe_device() {
 1)               |              pm_runtime_barrier() {
 1)   0.115 us    |                _raw_spin_lock_irq();
 1)   0.135 us    |                __pm_runtime_barrier();
 1)   1.393 us    |              }
 1)               |              pinctrl_bind_pins() {
 1)               |              my_driver_probe [bus_final_1]() {
 1)               |                printk() {
 1)               |                  vprintk_default() {
 1)   4.534 us    |                    vprintk_emit();
 1)   5.130 us    |                  }
 1)   5.711 us    |                }
 1)               |                alloc_chrdev_region() {
 1)               |                  __register_chrdev_region() {
 1)   0.155 us    |                    kmem_cache_alloc_trace();
 1)   0.516 us    |                    mutex_lock();
 1)   0.106 us    |                    mutex_unlock();
 1)   2.660 us    |                  }
 1)   3.327 us    |                }
 1)               |                printk() {
 1)               |                  vprintk_default() {
 1)   2.841 us    |                    vprintk_emit();
 1)   3.427 us    |                  }
 1)   4.053 us    |                }
 1)               |                __class_create() {
 1)               |                  kmem_cache_alloc_trace() {
 1)   0.090 us    |                    _cond_resched();
 1)   0.902 us    |                  }
 1)               |                  __class_register() {
 1)   1.348 us    |                    kmem_cache_alloc_trace();
 1)   0.091 us    |                    __mutex_init();
 1)   0.216 us    |                    __kmalloc_track_caller();
 1)   0.101 us    |                    kfree();
 1)   0.110 us    |                    _raw_spin_lock();
 1)   0.080 us    |                    _raw_spin_unlock();
 1)   3.927 us    |                    sysfs_create_dir_ns();
 1)   0.110 us    |                    kernfs_get();
 1)   0.090 us    |                    class_child_ns_type();
 1)   0.586 us    |                    kmem_cache_alloc_trace();
 1) + 30.034 us   |                  }
 1) + 32.197 us   |                }
 1)               |                printk() {
 1)               |                  vprintk_default() {
 1)   2.680 us    |                    vprintk_emit();
 1)   3.266 us    |                  }
 1)   3.858 us    |                }
 1)               |                device_create() {
 1)               |                  device_create_groups_vargs() {
 1)   0.542 us    |                    kmem_cache_alloc_trace();
 1)   0.861 us    |                    device_initialize();
 1)   0.206 us    |                    __kmalloc_track_caller();
 1)   0.100 us    |                    kfree();
 1) # 2434.891 us |                    device_add();
 1) # 2443.262 us |                  }
 1) # 2443.979 us |                }
 1)               |                printk() {
 1)               |                  vprintk_default() {
 1)   5.891 us    |                    vprintk_emit();
 1)   6.703 us    |                  }
 1)   7.410 us    |                }
 1)               |                kmem_cache_alloc_trace() {
 1)   0.090 us    |                  _cond_resched();
 1)   0.896 us    |                }
 1)   0.255 us    |                cdev_init();
 1)               |                cdev_add() {
 1)               |                  kobj_map() {
 1)   0.240 us    |                    __kmalloc();
 1)   0.400 us    |                    mutex_lock();
 1)   0.100 us    |                    mutex_unlock();
 1)   2.685 us    |                  }
 1)   3.402 us    |                }
 1) # 2511.166 us |              }
 1) # 3992.051 us |  }

1 comment:

  1. Why are there 2 device structures (one being registered using device_register, and the other being created and added using device_add ?

    ReplyDelete