Max Bruning, OpenSolaris Community  

이 글은 리눅스 디바이스 드라이버 개발자들을 대상으로 솔라리스 디바이스 드라이버를 개발하는 방법에 대해 설명하고 있습니다. 기본적으로 이 글은 다음의 질문, "리눅스 드라이버에서 활용하던 것을 솔라리스에서는 어떻게 적용할 수 있을까?" 에 대한 답을 위해 작성되었습니다. 이 글은 독자가 디바이스 드라이버와 솔라리스10, 리눅스2.6에서의 기본적인 사항들에 대해 친숙하다고 가정하고 있습니다. 글의 대부분의 내용은 각 시스템의 이전 버전에서도 유효합니다.


솔라리스 디바이스 드라이버의 개요

이 섹션은 솔라리스의 드라이버에 대한 개요를 제공합니다. 모든 드라이버 루틴에 대한 완벽한 묘사는 아닙니다. 그러나 솔라리스 드라이버에 대한 전체적인 구조에 대한 아이디어를 제공하고 드라이버 구현에 대한 예제도 제공될 것입니다.

DDI/DKI

솔라리스에서 Device Driver Interface (DDI) 와 Driver/Kernel Interface (DKI) 는 드라이버, 커널 루틴, 드라이버가 사용하고 있는 자료 구조에 대한 진입 포인트를 정의합니다. DDI는 솔라리스에서 동작하기 위한 필요한 루틴들을 정의 합니다. DKI는 드라이버가 사용할 자료 구조, 커널 루틴등을 정의 합니다. 문서화 되있는 DDI/DKI 인터페이스를 이용하여 드라이버 작성자는 소스 코드를 받고 각 운영체제의 다른 버젼에서 동작 할 수 있도록 바이너리 호환성 보장받을 수 있습니다. 리눅스에서 드라이버 개발자는 커널 소스 코드에 대한 완벽한 접근 권한이 있기 ?문에 어떠한 커널 루틴이나 자료 구조도 사용할 수 있을 것입니다. 이제 솔라리스 모든 커널 소스도 사용이 가능하므로 커널 루틴 혹은 자료 구조를 사용하는 것이 가능합니다. 어쨌든 DDI/DKI를 고수 하는 것은 좋은 생각입니다. 만약 작성자가 필요한 루틴이나 자료 구조가 DDI/DKI에 없다면 DDI/DKI에 추가시키도록 제안해야 합니다. 즉 DDI/DKI는 문서화되지 않은 루틴을 사용하는것을 막지는 않습니다. 다시 말해 DDI/DKI가 여러분의 다바이스를 작동시키는데 가장 중요한 요소가 되지 않도록 합니다. 다른 한편으로는 문서화되지 않은 루틴이나 자료구조가 바뀔 수 있으므로 여러분의 드라이버가 미래에도 항상 작동한다고 가정하지 않도록 합니다.

DDI/DKI는 어디에 있을까요? 바로 메뉴얼에 있습니다. 메뉴얼 섹션 9장에는 DDI/DKI에 있는 모든 드라이버 진입 포인트와 커널 루틴 그리고 커널 데이타 구조를 포함하고 있습니다. 드라이버 진입 포인트는 섹션 9e에 커널 함수는 섹션 9f에 자료구조는 9s에 디바이스 특성은 9p에 있습니다. 예를 들어 앞으로 수행해야할 함수를 스케쥴 할 필요가 있다고 가정했을때 타이머를 설정하거나 디바이스의 상태를 체크해야 합니다. 또 하나의 방법은 소스 코드를 검색하는 것입니다. 좀 더 좋은 방법은 apropos time 혹은 man -k time 를 수행하는 것입니다.(이 명령을 수행하기 위해서는 catman -w 명령이 필요함). 이러한 커맨드들은 원하는 것 이상의 정보를 제공해 줄것입니다. 메뉴얼 페이지의 NAME 섹션에 있는 모든 time 관련 메뉴얼 페이지를 보여줄 것입니다. 저는 개인적으로 빠른 방법을 선호합니다:

bash-2.05b# cd /usr/man/sman9f
bash-2.05b# ls *time*
cv_timedwait.9f            qtimeout.9f
cv_timedwait_sig.9f        quntimeout.9f
ddi_get_time.9f            timeout.9f
devmap_set_ctx_timeout.9f  untimeout.9f
gethrtime.9f
bash-2.05b# 

이 페이지를 간단히 살펴 보면 timeout(9f)가 사용해야할 루틴임을 알 수 있습니다. 가장 좋은 방법은 Writing Device Drivers 가이드에서 찾아보는 것입니다..

함수를 찾지 못하거나 찾은 함수가 올바르지 않을 수도 있습니다. 예를 들어 timeout(9f) 은 작성자에게 클럭 틱 granularity 을 제공합니다. 클럭 틱은 보통 10 밀리세컨입니다. 좀 더 좋은 타이밍을 제공하는 함수를 원한다면 소스 코드에서 찾아야 합니다. 그러나 그것이 문서화 되지 않았다면 그것은 DDI/DKI안에 존재 하지 않는 것입니다. 작성자는 함수를 사용할 수 있지만 미래의 버젼에서 똑같은 방법으로 동작하거나 똑같은 파라미터를 받거나 혹은 값을 리턴할 것이라고 보장 할 수 없습니다.

솔라리스 드라이버 자료 구조

회소한 솔라리스 드라이버는 다음과 같은 자료 구조를 반드시 가지고 있습니다:

  • modlinkage(9s): 드라이버의 설치, 제거, 정보 검색을 위해 사용.
  • modldrv(9s): 커널에 드라이버를 연결하기 위해 사용.
  • dev_ops(9s): nexus와 leaf 노드 드라이버의 일반적인 함수.
  • cb_ops(9s): 캐릭터/블럭 드라이버의 진입 포인트.

주의할점은 몇몇 종류의 디바이스(STREAMS, GLD, SCSA, USB 등)는 추가적인 구조를 필요로 합니다. 다음은 /usr/include/sys/devops.h 혹은 /usr/include/sys/modctl.h 에 지정된 자료구조를 보여줍니다.

struct modlinkage {  /* from sys/modctl.h */
        int             ml_rev;         /* rev of loadable modules system */
#ifdef _LP64
        void            *ml_linkage[7]; /* more space in 64-bit OS */
#else
        void            *ml_linkage[4]; /* NULL terminated list of */
                                        /* linkage structures */
#endif
};

struct modldrv {  /* also in sys/modctl.h */
        struct mod_ops          *drv_modops;  /* must be &mod_driverops */
        char                    *drv_linkinfo;  /* typically, "name version #" */
        struct dev_ops          *drv_dev_ops;
};

struct dev_ops  {  /* in sys/devops.h */
        int             devo_rev;       /* Driver build version         */
        int             devo_refcnt;    /* device reference count       */

        int             (*devo_getinfo)(dev_info_t *dip,
                            ddi_info_cmd_t infocmd, void *arg, void **result);
        int             (*devo_identify)(dev_info_t *dip);
        int             (*devo_probe)(dev_info_t *dip);
        int             (*devo_attach)(dev_info_t *dip, ddi_attach_cmd_t cmd);
        int             (*devo_detach)(dev_info_t *dip, ddi_detach_cmd_t cmd);
        int             (*devo_reset)(dev_info_t *dip, ddi_reset_cmd_t cmd);

        struct cb_ops   *devo_cb_ops;   /* cb_ops pointer for leaf drivers   */
        struct bus_ops  *devo_bus_ops;  /* bus_ops pointer for nexus drivers */
        int             (*devo_power)(dev_info_t *dip, int component,
                            int level);
};

struct cb_ops  {
        int     (*cb_open)(dev_t *devp, int flag, int otyp, cred_t *credp);
        int     (*cb_close)(dev_t dev, int flag, int otyp, cred_t *credp);
        int     (*cb_strategy)(struct buf *bp);
        int     (*cb_print)(dev_t dev, char *str);
        int     (*cb_dump)(dev_t dev, caddr_t addr, daddr_t blkno, int nblk);
        int     (*cb_read)(dev_t dev, struct uio *uiop, cred_t *credp);
        int     (*cb_write)(dev_t dev, struct uio *uiop, cred_t *credp);
        int     (*cb_ioctl)(dev_t dev, int cmd, intptr_t arg, int mode,
                    cred_t *credp, int *rvalp);
        int     (*cb_devmap)(dev_t dev, devmap_cookie_t dhp, offset_t off,
                        size_t len, size_t *maplen, uint_t model);
        int     (*cb_mmap)(dev_t dev, off_t off, int prot);
        int     (*cb_segmap)(dev_t dev, off_t off, struct as *asp,
                    caddr_t *addrp, off_t len, unsigned int prot,
                    unsigned int maxprot, unsigned int flags, cred_t *credp);
        int     (*cb_chpoll)(dev_t dev, short events, int anyyet,
                    short *reventsp, struct pollhead **phpp);
        int     (*cb_prop_op)(dev_t dev, dev_info_t *dip,
                    ddi_prop_op_t prop_op, int mod_flags,
                    char *name, caddr_t valuep, int *length);

        struct streamtab *cb_str;       /* streams information */

        /*
         * The cb_flag fields are here to tell the system a
         * bit about the device. The bit definitions are
         * in <sys>.
         */
        int     cb_flag;                /* driver compatibility flag */
        int     cb_rev;                 /* cb_ops version number */
        int     (*cb_aread)(dev_t dev, struct aio_req *aio, cred_t *credp);
        int     (*cb_awrite)(dev_t dev, struct aio_req *aio, cred_t *credp);
};

주의: 리눅스의 많은 자료 구조는 .tag 필드를 통해 초기화를 수행합니다. 솔라리스는 현재 이러한 작업을 하지 않습니다.(컴파일러의 이슈 때문에) 예를 들어 리눅스는 다음과 같은 구조체를:

struct foo {
   int a;
   int b;
};

다음과 같은 코드로 초기화 합니다:

struct foo foobar = {
  .a = 10,
  .b = 20
};

솔라리스 초기화는 다음과 같은 방법으로 수행됩니다:

struct foo foobar = {
   10, /* a */

   20  /* b */
};

다시 말해 솔라리스는 .tag 대신 커맨트를 사용합니다.

다음은 단순 케릭터 디바이스 foo 가 자료구조를 사용하는 예제를 보여줍니다.

static struct cb_ops foo_cb_ops = {
        foo_open,
        foo_close,
        nodev,  /* strategy only for block devices */
        nodev,  /* print only for block devs */
        nodev,  /*   "     "   "    "    "   */
        foo_read,
        foo_write,
        foo_ioctl,
        nodev,  /* no devmap (no mmap support) */
        nodev,  /* no mmap */
        nodev,  /* no segmap (for mmap) */
        nochpoll,       /* no poll support */
        ddi_prop_op,    /* let nexus node above driver handle property requests */
        NULL,   /* no streamtab (not a STREAMS device) */
        D_MP,   /* cb_flag, (other flags for STREAMS driver) */
        CB_REV,         /* revision number of cb_ops */
        nodev,  /* no async read */
        nodev   /* no async write */
};

static struct dev_ops  foo_dev_ops = {
        DEVO_REV,  /* revision number of this struct (compatibility) */
        0,         /* devo_refcnt, set by driver framework */
        foo_getinfo,
        nulldev,        /* identify, obsolete since solaris 2.6 */
        nulldev,        /* probe, must return success for driver to attach */
        foo_attach,
        foo_detach,
        nodev,          /* no reset */

        &foo_cb_ops,
        NULL,           /* no bus_ops (needed for nexus devices) */
        nodev           /* no power(9e) */
};

static struct modldrv foo_modldrv = {
        &mod_driverops,     /* must be this value, identifies module as a driver */
        "foo driver version 0.1",
        &foo_dev_ops
};

static struct modlinkage foo_ml = {
        MODREV_1,    /* revision number of structure (compatibility) */
        &foo_modldrv,
        0
};

적재/적재해제

솔라리스 드라이버는 드라이버를 적재하고 적재해제 하는 3가지의 루틴을 가지고 있습니다. _init(9e), _info(9e),_fini(9e). 솔라리스의 모든 드라이버 만이 이 3가지 루틴을 가지고 있는게 아니라 거의 모든 커널 모듈들이 가지고 있습니다. 커널 런타임 링커인 krtld는 이러한 루틴을 기본적으로 가지고 있을 것을 전재로 하고 있습니다. 만약 그렇지 않다면 드라이버는 동적으로 시스템에 연결되지 않을 것입니다.

주의: 솔라리스의 모든 드라이버들은 동적으로 시스템에 연결됩니다. 보통 부트시나 디바이스를 처음 접근할때 연결됩니다. 여러분의 드라이버는 반드시 동적으로 적재가 가능해야 합니다.

주의: 대부분의 솔라리스 드라이버 함수는 성공일때 0 (DDI_SUCCESS) 을 리턴하고 그렇지 않을때 errno 에 0 초과의 값을 기록합니다. 리눅스에서는 반대로 오류 발생시에 errno 에 0 미만의 값을 지정합니다. 리턴된 값은 보통 변수 형식으로 전달 됩니다.

다음은 이러한 루틴을 사용하는 예제를 보여 줍니다.

int
_init(void)
{
        int error;
        /*
         * allocate and initialize any data needed for all
         * instances of the device, (per device instance data
         * is allocated and initialized in attach(9e)
         */

         error = mod_install(&foo_ml);
         return (error);
}

int
_fini(void)
{
         int error;

         error = mod_remove(&foo_ml);
         if (error == 0)
            /*
             * de-initialize and free data allocated in _init()
             */
            ;
         return (error);
}

int
_info(struct modinfo *modinfop)
{
         return (mod_info(&foo_ml, modinfop));
}

자동설정

디바이스의 각 인스턴스는 부팅시 혹은 드라이버 설치시 자동적으로 설정 됩니다. 이 작업은 커널이 디바이스의 각 인스턴스들을 호출 하는 attach(9e) 진입 포인트에서 수행됩니다. 인스턴스들은 하드웨어/부트 시스템에 의해 인식되어 지거나 driver.conf(4) 파일의 항목에 의해 설정 됩니다. 선택적인 probe(9e) 루틴은 설정된 인스턴스가 실제로 시스템에 존재 하는지 확인하기 위해 불려집니다. probe(9e) 집인 포인트는 "스스로 인식"되는 디바이스를 위해 존재할 필요는 없습니다. PCI 타입 버스에 연결되는 모든 디바이스는 "스스로 인식"되는 디바이스 입니다 SCI 타겟과 가장 디바이스는 다바이스가 실제로 존재하는지 확인하기 위해 반드시 probe 진입을 수행해야 합니다. 드라이버는 detach(9e) 엔트리를 통해서 모든 인스턴스 혹은 설정해제된 인스턴스의 항목을 확인 할 수 있습니다.

attach(9e)detach(9e) 루틴은 동적 재설정 지원과 핫-플러그 디바이스를 위해 불려 질 수 있습니다.

driver.conf(4) 파일은 선택사항 입니다. PCI 디바이스는 파일을 사용할 필요가 없지만 다른 설정사항을 설정하기 위해 사용 할 수 있습니다.다음의 예는 가상 디바이스를 위한 driver.conf 파일의 사용 예입니다.

# foo.conf (typically in /usr/kernel/drv/foo.conf)
name="foo" parent="pseudo" instance=0;
name="foo" parent="pseudo" instance=100;

instance 넘버는 다양한 드라이버 루틴에 전달됩니다. 인스턴스 넘버를 마이너 디바이스 넘버로 매핑 시키는 것은 드라이버의 임무 입니다. 가장 간단한 매핑은 인스턴스 넘버를 마이너 넘버로 일대일 매칭 시키는 것입니다. 예를 들어 8개의 직렬 포트를 가지고 있는 RS-232C 컨트롤러를 위한 드라이버는 하위 3비트에 포트 넘버를 저장하고 인스턴스 넘버의 상위 29비트를 마이너 넘버로 저장합니다. 운영체제 (혹은driver.conf) 는 주어진 컨트롤러의 인스턴스 넘버를 고르고 dev_info_t 구조에 인스턴스 넘버를 지정하여 드라이버에 넘깁니다. dev_info_t 는 불투명합니다. 그러나 DDI 루틴은 인스턴스 넘버를 포함한 다양한 필드의 정보를 검색할 수 있습니다.

probe(9e) 루틴은 리턴하기 전에 모든 디바이스의 상태를 초기화 해야 합니다. If you don't need a probe(9e) 루틴을 사용할 필요가 없다면 간단히 DDI_PROBE_SUCCESS 를 리턴하거나 probe 항목을 위한 dev_ops(9s) 구조체에 nulldev(9f) 를 사용해도 됩니다. 다음의 예는 probe(9e) 진입 포인트를 위한 가상 코드 예제를 보여줍니다.ㅣ

static int
foo_probe(dev_info_t *dip)
{
    int instance;
    int rval;

    instance = ddi_get_instance(dip);
    ddi_regs_map_setup(...);  /* get a handle for accessing device registers */
    rval = ddi_peek8(dip, ...);  /* try to read an 8bit register on the device */
    ddi_regs_map_free(...);  /* unmap the handle returned from ddi_regs_map_setup */

    if (rval == DDI_FAILURE)
        return DDI_PROBE_FAILURE;  /* device does not currently exist */

    else
        return DDI_PROBE_SUCCESS;
}

attach(9e) 진입 포인트는 시스템에 디바이스가 "보이도록" 만들어 주기 위해 사용 되는 probe(9e) 가 성공적이였을때 호출 됩니다. 보통 상태를 할당해주고 디바이스 레지스터의 핸들을 얻고 인터럽트 핸들러를 등록하고 디바이스-특수한 하드웨어 초기화를 하고 마이너 디바이스 노드를 생성하여 사용자가 open(2) 콜을 사용할 수 있도록 해줍니다. 한번 attach(9e) 가 주어진 다바이스 인스턴스에 대해 실행되고 나면 /devices 트리에 새로운 마이너 노드가 나타나게 됩니다.

다음은 attach(9e) 의 가상 코드 예제 입니다.

static int
foo_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
   int instance;

   instance = ddi_get_instance(dip);
   allocate and initialize any needed state for this instance;
   ddi_regs_map_setup(...);  /* get a handle for registers */
   ddi_dma_alloc_handle(...);  /* if device does DMA */
   initialize hardware;
   ddi_add_intr(...);  /* register the interrupt handler */
   ddi_create_minor_node(...);  /* create minor device node */
   return DDI_SUCCESS;
}

주의할점은 만약 여러개 셋의 레지스터가 있다면 커널은 ddi_regs_map_setup(9f), ddi_add_intr(9f), 그리고 ddi_create_minor_node(9f) 을 여러분 호출할 것이고 한개 이상의 인터럽트 핸들러가 있을 것이며 혹은 인스턴스를 위한 여러개의 마이너 넘버가 필요 할 수 있습니다. 또한 cmd 변수는 DDI_ATTACH 혹은 DDI_RESUME 입니다. 위의 예제는 DDI_ATTACH.에 대한 가장 기본적인 처리 입니다. DDI_RESUME 은 동적 재설정과 전원 관리를 다루게 됩니다.

detach(9e) 엔트리는 디바이스를 사용 중지 했을때 attach(9e) 에 의해 설정된 것을 되돌리는 작업을 합니다. 여기에 가상코드 예제가 있습니다:

static int
foo_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 
/* cmd = DDI_DETACH or DDI_SUSPEND */ { int instance; instance = ddi_get_instance(dip); ddi_remove_minor_node(...); de-initialize hardware; /* disable interrupts, etc. */ ddi_remove_intr(...); ddi_regs_map_free(...); return DDI_SUCCESS; }

Open, Close, Read, Write

리눅스와 다르게 솔라리스 드라이버는 inodes 혹은 file같은 자료 구조에 접근하지 않습니다. 일반적으로 드라이버는 오직 진입 포인트에 전달되는 자료구조, 드라이버 자체가 정의하는 자료구조 그리고 DDI(메뉴얼 섹션 9s)에 의해 지정된 자료 구조 만을 접근하도록 되 있습니다. 이것은 커널 자료구조가 드라이버에 영향을 주지 않고 계속해서 바뀔 수 있도록 보장해 줍니다. 드라이버가 필요한 커널의 데이타 구조의 필드는 함수에 변수로 전달되지 자료구조 자체가 전달 되지는 않습니다.(예를 들어 현재 디바이스의 read/write 위치) 만약 DDI/DKI 구조 밖에서 커널 자료구조에 접근하는 것이 필요로 한다면 작성자는 DDI/DKI를 제대로 이용하지 못한 것입니다. 물론 소스 코드는 프로그래머에게 모든 자료 구조와 커널 루틴을 접근하게 해 줍니다. 그러나 솔라리스의 새로운 버젼이 나왔을때 드라이버를 재작성하고 싶지 않다면 작성자는 DDI/DKI를 준수 하는 것이 좋습니다.

다음은open(9e), close(9e), read(9e), and write(9e)를 위한 드라이버의 가상 코드 입니다. Writing Device Driversioctl(2), mmap(2) -related routines, poll(2), 그리고 블럭-드라이버관련 루틴을 사용하는 예제가 담겨 있습니다.

static int
foo_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) 
/* called for open(2) or OTYP_LYR */ { instance = get instance from minor device number; /* driver specific */ if (no state for this instance) return ENXIO; initialize driver for this open; handle exclusive open if needed; wait for device to be "on-line", if needed; return DDI_SUCCESS; } static int foo_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
/* called only on last close of minor dev */ { undo any initialization done by open; return DDI_SUCCESS; } static int foo_read(dev_t dev, struct uio *uio_p, cred_t *cred_p) /* called from read(2) */ { copy/dma data from device to user address space via uio_p; /* see uio(9s) */ return DDI_SUCCESS; /* uio_p will contain # of bytes read */ } static int foo_write(dev_t dev, struct uio *uio_p, cred_t *cred_p) /* called from write(2) */ { copy/dma data from user address space via uio_p to device; return DDI_SUCCESS; }

솔라리스 드라이버 프로그래밍 Part 2 of 2 에서 계속됩니다.

"오픈솔라리스" 카테고리의 다른 글

2006/04/23 11:25 2006/04/23 11:25

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/104

댓글을 달아 주세요

  1. 박정숙  수정/삭제  댓글쓰기

    좋은 정보 감사해요~

    2007/09/19 04:33
[로그인][오픈아이디란?]

◀ Prev 1  ... 405 406 407 408 409 410 411 412 413  ... 626  Next ▶