2010年1月31日 星期日

Linux Kernel(11.1)- sysfs and hotplug


Linux提供兩種非同步的hotplug機制通知user-space裝置狀態的改變,一是usermode helper,另一個則是透過netlink。

usermode helper
每當kernel收到hotplug event便會執行"CONFIG_UEVENT_HELPER_PATH"(預設值是/sbin/hotplug,可以透過修改/proc/sys/kernel/hotplug修改預設值),在embedded system中,常用的是busybox,所以,通常會把UEVENT HELPER換成/sbin/mdev(找時間來寫寫busybox),執行UEVENT HELPER會攜帶一些環境變數,如ACTION/DEVPATH/SUBSYSTEM/HOME/PATH等等,透過這些環境變數,可以取得Device Name,MAJOR/MINOR number等等,我們先來看部份的程式碼,往後有機會在作深度研究。
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
         char *envp_ext[])
{
    ... 略...
    /* call uevent_helper, usually only enabled during early boot */
    if (uevent_helper[0]) {
        char *argv [3];

        argv [0] = uevent_helper;
        argv [1] = (char *)subsystem;
        argv [2] = NULL;
        retval = add_uevent_var(env, "HOME=/");
        if (retval)
            goto exit;
        retval = add_uevent_var(env,
                        "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
        if (retval)
            goto exit;

        retval = call_usermodehelper(argv[0], argv,
                        env->envp, UMH_WAIT_EXEC);
    }
    ... 略...
}

我利用一個shell script來取代UEVENT HELPER,主要目的是要印出有哪些環境變數,和傳遞哪些參數給UEVENT HELPER。
#!/bin/sh
env >> /helper.log
echo "CMD:" $@ >> /helper.log
echo "----------     end     ----------" >> /helper.log



netlink
另外一條路徑就是netlink了,基本上也是傳遞相同的資訊。
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
         char *envp_ext[])
{
    ... 略...
#if defined(CONFIG_NET)
    /* send netlink message */
    if (uevent_sock) {
        struct sk_buff *skb;
        size_t len;

        /* allocate message with the maximum possible size */
        len = strlen(action_string) + strlen(devpath) + 2;
        skb = alloc_skb(len + env->buflen, GFP_KERNEL);
        if (skb) {
            char *scratch;

            /* add header */
            scratch = skb_put(skb, len);
            sprintf(scratch, "%s@%s", action_string, devpath);

            /* copy keys to our continuous event payload buffer */
            for (i = 0; i < env->envp_idx; i++) {
                len = strlen(env->envp[i]) + 1;
                scratch = skb_put(skb, len);
                strcpy(scratch, env->envp[i]);
            }

            NETLINK_CB(skb).dst_group = 1;
            retval = netlink_broadcast(uevent_sock, skb, 0, 1,
                            GFP_KERNEL);
            /* ENOBUFS should be handled in userspace */
            if (retval == -ENOBUFS) {
                retval = 0;
            }
        } else {
            retval = -ENOMEM;
        }
    }
#endif
    ... 略...
}

user-space的code如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <linux/types.h>
#include <linux/netlink.h>

int main(int argc, char *argv[])
{
    struct sockaddr_nl nls;
    struct pollfd pfd;
    char buf[512];

    memset(&nls, 0, sizeof(nls));
    nls.nl_family = AF_NETLINK;
    nls.nl_pid = getpid();
    nls.nl_groups = -1;

    pfd.events = POLLIN;
    pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if (pfd.fd == -1) {
        printf("Not root\n");
        exit(1);
    }

    if (bind(pfd.fd, (void*)&nls, sizeof(nls))) {
        printf("bind failed\n");
        exit(1);
    }
    while (-1 != poll(&pfd, 1, -1)) {
        int i, len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT);
        if (len == -1) {
            printf("recv\n");
            exit(1);
        }
        i = 0;
        while (i < len) {
            printf("%s\n", buf + i);
            i += strlen(buf+i) + 1;
        }
    }
    printf("\n");
    return 0;
}




Linux Kernel(11)- sysfs and device node


在linux kernel 2.6.x提供了sysfs,經由這樣的file-system可以告訴user-space系統有哪些裝置,而user-space的程式就可以動態的在/dev底下產生相對應的device node。

device node:
在/sys底下有一些名為"dev"的檔案,就是包含該裝置的major/minor number,比如:
# cat /sys/class/mem/zero/dev
1:5
# ls -al /dev/zero
crw-rw-rw- 1 root root 1, 5 2010-02-01 21:58 /dev/zero

所有的block device都可以在/sys/block/*/dev和/sys/block/*/*/dev找到。
所有的char device都可以在/sys/bus/*/devices/*/dev和/sys/class/*/*/dev找到。

所以一個簡易的動態device node產生程式就可以用script撰寫如下:
#!/bin/sh

# Block Device
for i in /sys/block/*/dev /sys/block/*/*/dev
do
    if [ -f $i ]
    then
        MAJOR=$(sed 's/:.*//' < $i)
        MINOR=$(sed 's/.*://' < $i)
        DEVNAME=$(echo $i | sed -e 's@/dev@@' -e 's@.*/@@')
        mknod /dev/$DEVNAME b $MAJOR $MINOR
    fi
done

# Char Device
for i in /sys/bus/*/devices/*/dev和/sys/class/*/*/dev
do
    if [ -f $i ]
    then
        MAJOR=$(sed 's/:.*//' < $i)
        MINOR=$(sed 's/.*://' < $i)
        DEVNAME=$(echo $i | sed -e 's@/dev@@' -e 's@.*/@@')
        mknod /dev/$DEVNAME c $MAJOR $MINOR
    fi
done



2010年1月27日 星期三

Linux Kernel(10.3)- Command line partition table parsing


MTD Partition除了在code中寫死以外,其實還可以透過一些parsers來作規劃,這一章就要來教大家如何使用"Command line partition table parsing"。首先必須在kernel中啟用"Command line partition table parsing",請參照下圖。

這樣kernel就可以支援"Command line partition table parsing",然後我們還是拿mtdram.c的code來改(紅色的部份)。

/*
 * mtdram - a test mtd device
 * Author: Alexander Larsson <alex@cendio.se>
 *
 * Copyright (c) 1999 Alexander Larsson alex@cendio.se>
 * Copyright (c) 2005 Joern Engel <joern@wh.fh-wedel.de>
 *
 * This code is GPL
 *
 */

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/mtd/compatmac.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/mtdram.h>
#include <linux/mtd/partitions.h>

static unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE;
static unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE;
#define MTDRAM_TOTAL_SIZE (total_size * 1024)
#define MTDRAM_ERASE_SIZE (erase_size * 1024)

#ifdef MODULE
module_param(total_size, ulong, 0);
MODULE_PARM_DESC(total_size, "Total device size in KiB");
module_param(erase_size, ulong, 0);
MODULE_PARM_DESC(erase_size, "Device erase block size in KiB");
#endif

// We could store these in the mtd structure, but we only support 1 device..
static struct mtd_info *mtd_info;
static char partitioned = 0;

static int ram_erase(struct mtd_info *mtd, struct erase_info *instr)
{
    if (instr->addr + instr->len > mtd->size)
        return -EINVAL;

    memset((char *)mtd->priv + instr->addr, 0xff, instr->len);

    instr->state = MTD_ERASE_DONE;
    mtd_erase_callback(instr);

    return 0;
}

static int ram_point(struct mtd_info *mtd, loff_t from, size_t len,
        size_t *retlen, void **virt, resource_size_t *phys)
{
    if (from + len > mtd->size)
        return -EINVAL;

    /* can we return a physical address with this driver? */
    if (phys)
        return -EINVAL;

    *virt = mtd->priv + from;
    *retlen = len;
    return 0;
}

static void ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
{
}

/*
 * Allow NOMMU mmap() to directly map the device (if not NULL)
 * - return the address to which the offset maps
 * - return -ENOSYS to indicate refusal to do the mapping
 */
static unsigned long ram_get_unmapped_area(struct mtd_info *mtd,
                       unsigned long len,
                       unsigned long offset,
                       unsigned long flags)
{
    return (unsigned long) mtd->priv + offset;
}

static int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
        size_t *retlen, u_char *buf)
{
    if (from + len > mtd->size)
        return -EINVAL;

    memcpy(buf, mtd->priv + from, len);

    *retlen = len;
    return 0;
}

static int ram_write(struct mtd_info *mtd, loff_t to, size_t len,
        size_t *retlen, const u_char *buf)
{
    if (to + len > mtd->size)
        return -EINVAL;

    memcpy((char *)mtd->priv + to, buf, len);

    *retlen = len;
    return 0;
}

static void __exit cleanup_mtdram(void)
{
    if (mtd_info) {

        if (mtd_has_partitions() && partitioned) {
            del_mtd_partitions(mtd_info);
        } else {
            del_mtd_device(mtd_info);
        }

        vfree(mtd_info->priv);
        kfree(mtd_info);
    }
}

int mtdram_init_device(struct mtd_info *mtd, void *mapped_address,
        unsigned long size, char *name)
{
    memset(mtd, 0, sizeof(*mtd));

    /* Setup the MTD structure */
    mtd->name = name;
    mtd->type = MTD_RAM;
    mtd->flags = MTD_CAP_RAM;
    mtd->size = size;
    mtd->writesize = 1;
    mtd->erasesize = MTDRAM_ERASE_SIZE;
    mtd->priv = mapped_address;

    mtd->owner = THIS_MODULE;
    mtd->erase = ram_erase;
    mtd->point = ram_point;
    mtd->unpoint = ram_unpoint;
    mtd->get_unmapped_area = ram_get_unmapped_area;
    mtd->read = ram_read;
    mtd->write = ram_write;

    if (mtd_has_partitions()) {
        struct mtd_partition *mtd_parts = NULL;
        static const char *probes[] =
                    { "cmdlinepart", NULL };
        int nb_parts = 0;
        printk("has partitions\n");
        if (mtd_has_cmdlinepart()) {
            printk("has probs\n");
            nb_parts = parse_mtd_partitions(mtd, probes, &mtd_parts, 0);
        }
        if (nb_parts > 0) {
            printk("partitioned\n");
            partitioned = 1;
            return add_mtd_partitions(mtd, mtd_parts, nb_parts);
        }
    }

    if (add_mtd_device(mtd)) {
        return -EIO;
    }

    return 0;
}

static int __init init_mtdram(void)
{
    void *addr;
    int err;

    if (!total_size)
        return -EINVAL;

    /* Allocate some memory */
    mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
    if (!mtd_info)
        return -ENOMEM;

    addr = vmalloc(MTDRAM_TOTAL_SIZE);
    if (!addr) {
        kfree(mtd_info);
        mtd_info = NULL;
        return -ENOMEM;
    }
    err = mtdram_init_device(mtd_info, 
                  addr, MTDRAM_TOTAL_SIZE, "brook_flash");
    if (err) {
        vfree(addr);
        kfree(mtd_info);
        mtd_info = NULL;
        return err;
    }
    memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
    return err;
}

module_init(init_mtdram);
module_exit(cleanup_mtdram);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alexander Larsson <alexl@redhat.com>");
MODULE_DESCRIPTION("Simulated MTD driver for testing");




熱門文章