메뉴 건너뛰기

NEOS White Paper

주의 : 본 기능은 NEOS V4에만 적용됩니다.

 

NEOS에서 장치 드라이버 개발

NEOS는 외부 장치 사용을 위해 I/O 서브 시스템은 일관된 표준 인터페이스를 사용자에게 제공한다.

 

NEOS에서 장치 드라이버

NEOS의 장치 드라이버는 크게 3가지 형태로 구분된다.

 

  • Character - 터미널 등의 문자 장치

  • Block - 디스크 등의 임의 접근이 가능한 블럭 장치

  • Miscellanous - 기타장치

 

io.png

< I/O서브 시스템 구성도 >

 

NEOS의 장치 드라이버는 형태는 다음과 같이 정의되어 있다.

 

#include <DeviceManager.h>

 

typedef enum DeviceTypeEnum

{

    DTYPE_NONE = 0,  /* Not assigned type */

    DTYPE_CHR  = 1,  /* Character device type */

    DTYPE_BLK  = 2,  /* Block device type */

    DTYPE_MISC = 3,  /* Miscellaneous device type */

} DeviceType;

 

NEOS의 문자입출력 장치 드라이버인 경우에는터미널(TTY) 드라이버를 제공하여 편리한 시리얼 입출력 제어를 도와준다. 터미널 드라이버는 내부에 입출력 버퍼를 가지고 있으며 이를 사용하여 시리얼 드라이버에 버퍼 기능을 제공한다. 자세한 사항은 NEOS 사용자 메뉴얼 4.2장 터미널 드라이버 참고 하면된다.

 

일반적으로 블록 장치는 직접 접근 방식이 아닌 파일 시스템을 통해 접근하게 된다. 따라서 I/O 서브시스템은 블록 장치를 위한 별도의 함수 인터페이스를 제공하지 않고 DIOCSTRATEGY라는 버퍼를 사용한 디스크 접근 제어 명령어만을 제공한다. 그러므로 추가적인 블록 장치 드라이버를 개발하고자 하는 경우, 개발자는 파일 시스템에서 블록 장치의 임의의 영역을 읽고 쓰기 위해 사용하는 DIOCSTRATEGY 명령어에 대한 처리를 장치 드라이버의 ioctl() 함수에 구현하여 블록 장치에 대한 임의 영역 접근을 가능하도록 구현해야 한다.

 

블록 장치는 장치를 접근하는데 파일 시스템에서 제공하는 파일과 같이 순차적인 접근을 지원해야 한다. 블록 장치에 대한 임의 접근이 아닌 순차적인 접근을 위해 블록 장치 드라이버는 read()/write() 함수를 제공해야 한다. 이와 같은 순차적인 블록 장치의 접근은 블록 장치에 새로운 파일 시스템을 생성하는 과정에서 주로 사용된다. 다음은 응용 프로그램이 블록 장치에 접근하는 과정을 나타낸다.

 

block.png

 

<블록장치 인터페이스 >

 

NEOS의 블록 장치 드라이버는 NEOS사용자 메뉴얼 4.3장을 참고 하면된다.

 

일반적으로 사용자가 개발하는 대부분의 장치들은 Miscellaneous장치 형태로 등록되어진다. 다음은  Miscellaneous장치 생성과 등록에 대해 살펴보자

 

 

장치 드라이버 인터페이스

장치 드라이버는 DeviceOperator 구조체에 정의된 공통된 장치 드라이버 인터페이스를 장치 관리자를 통해 제공한다.

 

  • init – 장치 초기화

  • open – 장치 열기

  • read – 장치로부터 읽기

  • write – 장치에 쓰기

  • control – 장치 제어

  • close – 장치 닫기

 

#include <DeviceManager.h>

 

/*DeviceOperator 구조체 */

typedef struct DeviceOperatorStruct

{

    Status (*open)    (struct DeviceStruct *theDevice, Value flags);

    Status (*read)    (struct DeviceStruct *theDevice, Address buffer, Size *size);

    Status (*write)   (struct DeviceStruct *theDevice, Address buffer, Size *size);

    Status (*control) (struct DeviceStruct *theDevice, Value command, Address arg);

    Status (*close)   (struct DeviceStruct *theDevice);

    Status (*init)    (struct DeviceStruct *theDevice);

} DeviceOperator;

 

 

/* 사용자 함수*/

static DeviceOperator MyDevOpr;

 

/* init function */

static Status

DeviceInit(struct DeviceStruct *myId)

{

    return (0);

}

 

/* open function */

static Status

DeviceOpen(struct DeviceStruct *myId, Value flags)

{

    return (0);

}

 

/* read function */

static Status

DeviceRead(struct DeviceStruct *myId, Address mybuf, Size *length)

{

    return (0);

}

 

/* write function */

static Status

DeviceWrite(struct DeviceStruct *myId, Address mybuf, Size *length)

{

    return (0);

}

 

/* control function */

static Status

DeviceControl(struct DeviceStruct *myId, Value command, Address args)

{

    return (0);

}

 

/* close function */

static Status

DeviceClose(struct DeviceStruct *myId)

{

    return (0);

}

 

DeviceOperator 구조체에 정의되지는 않지만 장치를 등록하기 위해 사용자가 정의 하는 함수로 일반적으로 DeviceConfig() 함수라고 명명하고 이 함수에서 DeviceAttach() 함수를 호출하여 장치 등록을 수행한다. 장치관리자는 등록시에 유일한 장치 번호를 할당해준다.

 

STATUS DevConfig()

{

    STATUS status = SUCCESS;

 

    MyDevOpr.init = DeviceInit;

    MyDevOpr.open = DeviceOpen;

    MyDevOpr.read = DeviceRead;

    MyDevOpr.write = DeviceWrite;

    MyDevOpr.control = DeviceControl;

    MyDevOpr.close = DeviceClose;

 

    status = DeviceAttach(“장치이름”, 장치 타입, MyDevOpr, “장치제어변수”);

 

  return status;

}

 

장치이름은 고유한 이름으로 32Byte 이하의 이름으로 설정하고, 장치 형태에 따라 값을 설정한다.  DevOperator 구조체와 장치제어 변수를 입력인자로 넣어준다.

 

DeviceAttach에서status가 SUCCESS인 경우 장치가 정상적으로 등록되었다.

 

Status

결과

DEVICE_INVALID_NAME

입력된 값이 잘못된 경우

DEVICE_ILLEGAL_TYPE

잘못된 장치 타입이 입력된 경우

DEVICE_ILLEGAL_OPERATOR

DevOperator가 NULL인 경우

DEVICE_EXIST

동일한 이름의 장치가 이미 등록된 경우

RESOURCE_NOT_AVAILABLE

장치 Object가 생성되지 않은 경우 – 일반적으로 메모리에 문제가 발생한 경우

SUCCESS

정상적으로 완료된 경우

 

 

DeviceInit()함수는 DeviceAttach() 함수에 의해 장치가 등록되는 시점에 호출되어지며 장치 초기화를 수행한다.

 

 

 

DeviceInit.png

< DeviceInit() 함수 호출관계 >

 

 

등록된 장치를 사용하기 위해서는 응용소프트웨어는 open 함수를 호출하여 장치 식별자를 할당 받아여 한다.

 

int fd;

 

fd = open(“장치이름”, 읽기쓰기 특성, 0);

 

표준 입출력함수 open함수를 호출하면, 장치관리자는 DeviceOpen() 함수호출하고 정상처리되었다면 장치식별자를 반환해준다. open 함수를 이용하여 장치 드라이버를 열 때, 다음 정보가 필요하다.

 

  • 장치 드라이버 이름열고자 하는 장치 드라이버 지정

  • 장치 드라이버 열기 옵션장치 드라이버의 열기 조건 지정하기 위해 사용미리 정의된 열기 옵션 기본 값은 없으며사용자가 자신만의 옵션을 정의할 수 있다

 

DeviceOpen.png

< open()함수와 DeviceOpen() 함수의 호출관계 >

 

 

open을 통해 반환된 장치 식별자로 read/write 함수로 장치에 데이터를 읽거나 쓰기를 할 수 있다.

 

표준 입출력함수 read함수는 장치관리자를 통해 DeviceRead() 함수를 호출하여 특정 장치로부터 데이터를 읽어올 수 있다. 장치 드라이버로부터 데이터를 읽을 때 다음 정보가 필요하다.

 

  • 장치 드라이버읽어 올 장치를 지정하기 위하여 사용

  • 데이터를 받기 위한 버퍼의 주소

  • 읽고자 하는 데이터의 크기

 

DeviceRead.png

< read()함수와 DeviceRead() 함수의 호출관계 >

 

반대로표준 입출력함수 write함수는 장치관리자를 통해 DeviceWrite() 함수로 특정 장치에 데이터를 쓸 수도 있다. 그리고 장치에 데이터를 쓸 때에는 다음 정보가 필요하다.

 

  • 장치 드라이버데이터를 쓸 장치 드라이버를 지정

  • 데이터를 쓰기 위한 버퍼 주소

  • 쓰여질 데이터의 크기

 

 

DeviceWrite.png

< write()함수와 DeviceWrite() 함수의 호출관계 >

 

또한표준 입출력함수ioctl 함수는 장치관리자를 통해 DeviceControl() 함수를 이용하면, 특정 장치에 다양한 제어 기능을 수행하기 위한 명령을 내릴 수 있다. 이 함수로 장치를 제어하려면 다음 정보가 필요하다.

 

DeviceIoctl.png

< ioctl()함수와 DeviceIoctl() 함수의 호출관계 >

 

사용자가 장치 드라이버 사용을 한다면, 표준 입출력함수 close함수를 호출하면 장치관리자는 장치 ID에 의해 지정된 장치를 닫기 위해 DeviceClose() 함수를 사용한다. 장치 드라이버를 닫을 때 다음 정보를 필요로 한다.

 

  • 장치 드라이버닫고자 하는 장치 드라이버를 지정

DeviceClose.png

< close()함수와 DeviceClose() 함수의 호출관계 >

 

 

NEOS의 장치 드라이버 표준 인터페이스를 사용하는 경우 중첩 호출에 대해 한개의 호출이 완료될때가지 다음 호출을 Block해주는 기능을 제공하며, POSIX 표준 입출력과 동일하게 응용 소프트웨어를 작성할 수 있다.

 

 

장치 드라이버 변수

 

응용소프웨어에서  open함수를 호출하는 경우 장치 제어에 필요한 정보를 생성한다. 장치제어 변수는 DeviceStruct 구조체에 포함되어 있으며, NEOS에서는 장치를 제어하기 위해 필요한 정보를 장치 제어 변수를 통해 전달하도록 되어 있다. 장치제어 변수는 struct DeviceStruct에 포함되어 있으며 정의는 다음과 같다.

 

typedef struct DeviceStruct

{

    DoubleListNode    deviceNode;                 /* Linked list for device list */

    Value             deviceId;                   /* Identifier of the device */

    DeviceType        deviceType;                 /* Type of the device */

    char              name[MAX_DEVICE_NAME_SIZE]; /* Device name */

    Boolean           isOpened;                   /* Opended state of the device */

    Value             flags;                      /* Flag of device */

    Address           data;                       /* Reference of private data */

    DeviceOperator   *methods;                    /* Reference of device interface */

} Device;

 

장치제어에 필요한 변수를 장치 제어 블럭(DCB : Device Control Block)이라고 하며,  Address data는 DCB를 주소로 가지게 된다. DCB블록은 사용자가 정의하며 필요한 정보를 생성하면 된다.

 

다음은 장치제어 변수를 생성하는 예제이다.

 

typedef struct drv_struct

{

 int num; /* 채널 번호 */

 char *name; /* 등록 장치명 */

 unsigned short conReg; /* 제어 레지스터 주소 */

 unsigned char regVal;  /* 제어 레지스터 기본값 */

 Semaphore semId; /* 동기화 객체 */

 int timeout; /* 장치 TimeOut값 */

 buffer *rBuf; /* 장치 버퍼 주소 */

 int bufSize; /* 할당된 버퍼 크기 */

 Status status;

} DRV_DCB;

 

장치 개발자는 위와 같은 구조체로 장치에 필요한 정보를 구성하고, 응용소프트웨어에서 장치관리자에게 파일 식별자를 입력인자로 넣어주면 장치 관리자는 DCB블록 변수를 전달해준다.

 

장치 드라이버의 인터럽트

 

대부분의 장치드라이버에서는 인터럽트를 통해 데이터 송수신 및 제어를 수행하는데, 이장에서는 장치를 위해 인터럽트를 등록/해제 하고 인터럽트를 활성화/비활성화 하는 것을 살펴보자

 

NEOS에는 신속한 인터럽트 처리를 위해 인터럽트 관리자 기능을 제공한다. 인터럽트 관리자는 외부 인터럽트를 처리하기 위한 커널 컴포넌트로 하드웨어 종류에 상관없이 동일한 인터페이스를 제공해준다. 즉, 인터럽트를 처리하기 위해 사용자가 등록한 함수인 인터럽트 서비스 루틴(ISR)을 관리하며, 하드웨어로부터 인터럽트 정보를 가져와 장치 드라이버를 포함한 다른 커널 컴포넌트에게 인터럽트 제어 서비스를 제공하는 역할을 한다. 인터럽트 관리자가 제공하는 제어 서비스 함수는 다음과 같다.

 

함수

설명

InterruptAttach()

인터럽트 벡터에 인터럽트 서비스 루틴(ISR) 등록

InterruptLock()

시스템 전체 인터럽트를 발생하지 못하게 함

InterruptUnLock()

시스템 전체 인터럽트를 발생할 수 있도록 함

InterruptDisable()

특정 인터럽트를 발생하지 못하도록 함

InterruptEnable()

특정 인터럽트를 발생할 수 있도록 함.

InterruptSetSense()

인터럽트 벡터의 인터럽트 센스 설정.

InterruptSetPriority()

인터럽트 벡터의 우선순위 설정

InterruptGetPriority()

인터럽트 벡터의 우선순위를 얻어옴.

InterruptSetPolarity()

인터럽트 벡터의 극성 설정

 

등록하는 절차를 살펴보면 다음과 같다.

 

10번 인터럽트를 등록하는 과정으로 InterruptSetSense를 통해 인터럽트 인식을 Level로 할것인지 Edge로 할것인 설정할수 있다. 인식 모드를 설정하고 나면 인터럽트 관리자에게 인터럽트를 등록하기 위해 InterruptAttach()라는 함수를 호출한다.  status가 SUCCESS이면 인터럽트가  등록이 완료된것이다.

 

Status

결과

INVALID_ADDRESS

입력된 두번째 인자값이 NULL인경우

INVALID_PARAM

인터럽트 벡터인자가 잘못된 경우

SUCCESS

정상적으로 완료된 경우

 

 

해당되는 인터럽트가 발생하면 인터럽트 관리자는 UserDevHandler함수를 호출 해준다.

 

#include <InerrruptManager.h>

 

// Register AINT

#define USER_IRQ 10

#define STACKSIZE 4096

 

static Interrupt    MyDevHandler;

 

/* 인터럽트 서비스 루틴 */

void UserDevHandler();

{

   return;

}

 

 

STATUS MyDevInterruptRegiser(void )

{

 STATUS status = SUCCESS;

 

  status = InterruptSetSense(USER_IRQSENSE_LEVEL);

  if(status == SUCCESS)

  {

                MyDevHandler.handler = (InterruptHandler)UserDevHandler;

                MyDevHandler.irq = USER_IRQ;

                MyDevHandler.stackSize = IRQ_STACKSIZE;

                status = InterruptAttach(MyDevHandler.irq, MyDevHandler.stackSize,& MyDevHandler);

  }

 return status;

}

 

NEOS인터럽트 서비스 루틴은 다음의 사항을 유의하고 작성되어야 한다. 인터럽트 서비스 루틴(ISR)은 커널 내부의 모든 함수 수행 및 시스템의 모든 스레드를 선점하여 수행하므로, 인터럽트 서비스 루틴 작성 시 아래와 같은 사항을 유의해야 한다.

  • 커널 내부의 스케줄링을 수행하는 함수는 사용할 수 없다

  • 블록킹 함수를 사용할 수 없다

  • 수행 시간은 최대한 짧게 작성한다

  • 메모리 할당해지 함수를 사용할 수 없다

  • I/O 서브 시스템이 제공하는 드라이버 인터페이스를 사용할 수 없다.

 

인터럽트 서비스 루틴내에서는 NEOS 커널 API 중 아래 함수들만 호출이 허용된다.

 

  • SemaphoreReleaseAsync() (for Binary or Counting Semaphore)

  • SemaphoreFlushAsync() (for Binary or Counting Semaphore)

  • InterruptLock()

  • InterruptUnLock()

  • InterruptEnable()

  • InterruptDisable()

  • InterruptSetSense()

  • InterruptSetPriority()

  • InterruptSetPolarity()

 

인터럽트 동기화를 위해 Semaphore가 사용되는 경우 인터럽트 루틴에서는 SmaphoreReleaseAsync를 통해 해제는 가능하지만 획득은 할 수 없다 획득해야 하는경우에는 별도의 thread를 생성하여 처리해야만 한다.

 

인터럽트의 활성화와 비활성화는 드라이버 레벨에서는 가능한 자신이 소유한 InterruptEnable과 InterruptDisable함수만을 사용하는것이 좋다. InterruptLock/InterruptUnLock 스케줄링과 다른 장치의 인터럽트도 비활성화 하기 때문에 다른장치에 문제를 야기 할수 있다.

 

인터럽트에 대해서는 NEOS사용자 메뉴얼 3.4장을 참고하면 된다.

 

위로