'2008/01/26'에 해당되는 글 1건

  1. 2008/01/26 I/O Port 다루기 on x86

앞에 GPIO에서도 잠깐 언급했듯이 하드웨어를 다루는 방법은 크게 두가지가 있다. 바로, 독립된 I/O 주소를 가지는 형태와 메모리로 매핑된 형태가 있다.

아키텍쳐마다 달라질 수 있는 부분인데, 앞에 GPIO에서 매모리 매핑된 형태를 살펴봤기 때문에 여기에서는 IO 포트를 사용하는 방법을 살펴 보도록 하겠다. 인텔 머신(x86)은 두가지 형태를 다 가지고 있는데, 비디오 메모리의 경우에는 메모리 매핑된 형태이고 시리얼, 프린터 등등은 IO 포트를 사용하는 형태이다.

IO 포트에 대한 연산을 하는 경우에는 다음 함수들을 사용해서 처리할 수 있다.

void outb(u8 v, u16 port);
u8 inb(u16 port);
void outw(u16 v, u16 port);
u16 inw(u16 port);
void outl(u32 v, u16 port);
u32 inl(u32 port);



port 인수는 접근하려는 하드웨어 장치에 대한 포트번호이다. in과 out은 각각 주고 받는 자료형의 종류에 따라서 b, w, l 등으로 이름이 붙어 있으므로 원하는 형태를 사용하면 된다.

해당 포트에 한 개 이상의 데이타를 보내야 하는 경우를 위해서 s 가 붙은 형태들도 제공한다.

void insb(u16 port, void *addr, unsigned long count);
void insw(u16 port, void *addr, unsigned long count);
void insl(u16 port, void *addr, unsigned long count);

void outsb(u16 port, void *addr, unsigned long count);
void outsw(u16 port, void *addr, unsigned long count);
void outsl(u16 port, void *addr, unsigned long count);



ins?() 함수의 경우에는 port 주소에서 값을 꺼내와 addr 위치에 저장하는데, 이것을 count 횟수만큼 반복한다. outs?() 함수는 addr위치부터의 내용을 count 횟수만큼 port 주소에 쓰기를 하는 함수이다.

리눅스에서 I/O 포트를 접근할 때에 무작정 접근하지 않고 대부분은 해당 포트를 점유한 후에 사용을 하게 된다. 물론, 이 규칙을 따르지 않아도 동작할 수 있지만, 좋은 접근 방법이 아니다. 반드시 사용하고자 하는 포트를 잡고 사용하고 다 사용한 후에는 포트를 반환하는 절차를 지켜주는 것이 좋다. 이와 관련한 함수는 다음과 같다.

#include <linux/ioport.h>

struct resource *request_region(unsigned long from, unsigned long extent,
                                              const char *name);
void release_region(unsigned long from, unsigned long extent);



이 함수를 사용해서 해당 포트를 점유했다고 해서 반드시 이것을 나 혼자 쓸수 있다고 장담해서는 안된다. 다른 드라이버가 위의 명령을 사용하지 않고 접근할 수 도 있기 때문이다. 하지만, 규칙을 따라야 다른 드라이버와 충돌을 줄일 수 있는 것은 사실이기 때문에, 꼭 이 명령들을 사용하도록 하자.

현재 등록되어 있는 IO 포트 영역은 /proc/ports 파일을 통해서 확인할 수 있다.

[root@localhost ~]# cat /proc/ioports
0000-001f : dma1
0020-0021 : pic1
0040-0043 : timer0
0050-0053 : timer1
0060-006f : keyboard
0070-0077 : rtc
0080-008f : dma page reg
00a0-00a1 : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : 0000:00:07.1
  0170-0177 : libata
01f0-01f7 : 0000:00:07.1
  01f0-01f7 : libata
02f8-02ff : serial
0376-0376 : 0000:00:07.1
....




위 컴퓨터의 경우에는 0x60부터 0x6f까지를 키보드 관련한 드라이버에서 점유하고 있음을 나타낸다. 이런식으로 점유를 하고 있는 경우 다른 드라이버에서 request_region 했을 경우 NULL을 반환하게 된다.

스피커 제어 예제

여기에서는 간단히 스피커를 제어하는 예제를 살펴 보도록 하겠다. 내장 스피커를 켜거나 끄는 것은 0x61번 포트의 0, 1번 비트를 통해서 결정된다. 0, 1번 비트가 1이면 스피커가 켜지며, 0일 경우 스피커가 꺼진다. 스피커를 켜는것과 함께 출력될 소리의 높이도 결정해야 한다. 이것은 주파수를 통해서 결정되면 PIT(Programmable Interval Timer)에서 설정하게 된다.

Intel PIT(8253/8254 칩)는 3개의 채널로 구성되어 있으며, 0번 채널은 시스템 클럭 업데이트에 사용되고 채널 1은 DMA 컨트롤러 refresh용으로 사용된다. 주파수를 설정하는 것은 2번 채널을 사용한다. 각각은 다음과 같은 IO 포트를 할당받아 사용한다.

0x40 : 0번 채널, 시스템 클럭 업데이트
0x41 : 1번 채널, DMA 컨트럴 refresh
0x42 : 2번 채널, 주파수
0x43 : 컨트럴 포트

주파수를 설정하기 위해서는 2번 채널(0x42번 포트)을 사용해야 하며 이것은 0x43번 포트를 통해서 관련 설정을 먼저 해주어야 한다.

0x43 포트의 비트는 다음과 같은 구성을 가지고 있다.
7, 6 :  채널 선택
5, 4 :  자료 전달 순서(하위 바이트부터 보낼 것인지...)
3, 2, 1 : mode (011)
0 : 포맷 (BCD나 바이너리)

여기에서는 하위 바이트부터 보내는 사운드 제어를 위해서 0xB6을 사용하겠다.
데이터를 0x42번 포트에 넣을 때는 두 바이트를 한 바이트씩 집어 넣어야 한다. 또한, 주파수 값은 다음과 같은 공식으로 계산된 값을 넣어야 한다.

저장할 값(2바이트) = 1193180 / 주파수;

소리를 출력하는 코드와 제거하는 코드는 다음과 같은 형태가 될 것이다.

#define CLK_FREQ (1193180L)
#define PIO  (0x61)
#define PIT_CMD  (0x43)
#define PIT_DATA (0x42)
#define SETUP  (0xB6)
#define TONE_ON  (0x03)
#define TONE_OFF (0xFC)

void sound(int freq) {
 unsigned int value = inb(PIO);
 freq = CLK_FREQ / freq;
 if ((value & TONE_ON) == 0) {
  outb(value | TONE_ON, PIO);
  outb(SETUP, PIT_CMD);
 }
 outb(freq & 0xff, PIT_DATA);
 outb((freq >> 8) & 0xff, PIT_DATA);
}
void nosound(void) {
 unsigned int value = inb(PIO);
 value &= TONE_OFF;
 outb(value, PIO);
}



여기에서는 이 예제를 약간 응용해서 모르스 신호를 출력하는 예제를 만들어 보려고 한다. 모르스 코드 테이블은 구글에서 검색하면 쉽게 찾을 수 있다.

사용자 삽입 이미지

다음은 모르스 신호를 이용하는 간단한 드라이버이다. 이것은 x86에서만 수행된다는 점을 명심하고 보드에 올리는 실수를 하지 않기를 바란다. :)


코드를 테스트 하기 위해서 어플리케이션을 개발할 필요는 없다. 디바이스 노드를 만들고 나서(mknod 명령을 사용해서..), 다음처럼 테스트 하면 된다.

mknod /dev/my_morse c <디바이스 주번호> 0
echo SOS > /dev/my_morse

이 드라이버는 영어밖에 처리를 못한다. 한글 처리에 관심이 있다면 다음 테이블을 보고 직접 수정해 보는 것도 재미 있을 것이다.

사용자 삽입 이미지




크리에이티브 커먼즈 라이선스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)

'Linux > API' 카테고리의 다른 글

I/O Port 다루기 on x86  (0) 2008/01/26
인터럽트  (0) 2008/01/25
커널 시간 관리  (0) 2008/01/25
CPU별 변수 사용  (0) 2007/10/08
Posted by Daniel Kwon
TAG