27. DMA PCI 디바이스
목차
1. DMA
2. PCI
1. DMA
개요
•DMA 등장 배경
•기존의 문제점
•프로세서는 하드웨어에 데이터를 써넣거나 읽음으로써 제어함
•프로세서에서 메모리에 있는 데이터를 하드웨어에 써 넣을때
1.메모리 장치에서 데이터를 프로세서의 레지스터로 읽음
2.프로세서의 레지스터 내용을 하드웨어에 써넣음
•각 과정을 처리하는 명령을 읽고 해석
•데이터 전송과 읽고 쓰기 위한 하드웨어 주소를 관리해야 함
=> 프로세스 효율을 저하시킴
•해결
•DMA
•DMA 란
•구성
•대부분 채널로 구성됨
•채널은 데이터를 전송하는 기본 단위
•전송 데이터의 소스 주소
•전송 데이터의 목적지 주소
•전송 데이터의 크기
•하나의 DMAC에서 여러 채널을 관리 함
•채널이 있는 이유는 DMAC가 관리할 수 있는 하드웨어 장치의 수를 의미 함
리눅스에서의 DMA 처리
•개요
•DMA 처리와 관련된 함수나 구조체를 리눅스에서 완벽하게 표준화 할 수 없음
•최소한의 공통된 스펙에 관련된 함수만 제공
•공통 함수군을 벗어난 처리는 디바이스 드라이버에서 직접 처리 해야 함
•
•DMA 소유권 등록과 해제
•DMA 채널에 대한 소유권 등록
•디바이스드라이버가 DMA를 사용하기 위해 가장 먼저 할일
•디바이스 파일이 열렸을때(open()) 처리 함
•int request_dma(unsigned int dmanr, const char * device_id);
•dmanr : 사용하려는 DMA 채널 지정
•device_id : DMA 채널을 소유한 것을 proc에 표시하기 위해 사용하는 문자열
•
•DMA 채널에 대한 소유권 해제
•디바이스 파일이 닫혔을때(release()) 처리 함
•void free_dma(unsigned int dmanr);
•디바이스 파일이 열렸을 때의 DMA 처리
•주의사항
•open()시 request_dma() 함수를 통해 단순히 소유권 등록만 하면 안됨.
•DMA를 위한 버퍼가 필요함
•DMA 처리 종료시 발생하는 인터럽트를 처리해야 함
•디바이스 파일 open시 절차
•DMA 버퍼 할당
•DMA 인터럽트 핸들러 등록
•DMA 채널 소유권 등록
int xxx_open(struct inode *inode, struct file *filp)
{
:
xxx_dma_buff = xxx_alloc_dma_buffer();
:
request_irq(XXX_IRQ, dma_interrupt, …);
request_dma(XXX_DMA_CHANNEL, XXX_DEV_NAME);
:
return 0;
}
•디바이스 파일이 닫혔을 때의 DMA 처리
•open() 처리의 역순
•dma 소유권 해제
•인터럽트 핸들러 해제
•버퍼 해제
int xxx_release ( struct inode *inode, struct file *filp)
{
:
free_dma(XXX_DMA_CHANNEL);
free_irq(XXX_IRQ, …);
:
xxx_free_dma_buffer(xxx_dma_buff); //할당된 dma 버퍼를 해제 함
//커널에서 제공하지 않아 개발자가 직접 작성해야 함
:
return 0;
}
DMA 버퍼 할당과 해제
•DMA 전송은 메모리와 I/O 하드웨어간의 전송
•메모리는 DMA를 위한 메모리를 의미
•디바이스 드라이버에서 vmalloc말고 kmalloc으로 할당해야 함
•DMA에 사용되는 메모리는 물리적으로 연속된 공간을 가져야 함
•kmalloc()함수에 GFP_DMA 옵션을 줌
•__get_free_pages() 함수에 GFP_DMA 옵션을 줌
•__get_dma_pages()
•많이 사용됨
•__get_free_pages()의 매크로 함수
•할당
•dma_buffer = (char *) __get_dma_pages(GFP_KERNEL, get_order(XXX_DMA_BUFFER_SIZE));
•XXX_DMA_BUFFER_SIZE는 할당 하고자 하는 DMA 버퍼 크기
•해제
•if(dma_buffer)
{
free_pages((unsigned long) dma_buffer, get_order(XXX_DMA_BUFFER_SIZE));
}
•DMA 버퍼 할당과 해제
•커널 2.6 dma용 버퍼 할당 함수
•void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);
•DMA 용 버퍼를 할당함
•dev : 이 메모리를 소유하는 디바이스 객체의 주소
•size : 할당하고자 하는 메모리의 크기
•dma_handle : 할당된 메모리의 물리주소
•flag : 부가적으로 주고자 하는 메모리의 속성 ( kmalloc 속성 참고)
•void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle);
•위와 같음
•cpu_addr에 전달되어야 하는 매개변수 : dma_alloc_coherent에서 할당한 주소값
•DMA 전송 시작
•DMA 데이터를 전송 코드의 형태는 정형화 되어 있음
•int xxx_dma_start(int channel, int mode, unsigned char *dma_buff, unsigned int count)
{
unsigned long flags;
:
flags=claim_dma_lock( );
disable_dma(channel);
clear_dma_ff(channel);
set_dma_mode(channel, mode);
set_dma_addr(channel, virt_to_bus(dma_buff));
set_dma_count(channel, count);
enable_dma(channel);
release_dma_lock(flags);
:
return 0;
}
•DMA 시작을 위한 I/O 하드웨어에 관련된 처리
•하드웨어마다 다름
•DMA 전송 시작(계속)
•내부 함수
•unsigned long claim_dma_lock(void);
•다른 드라이버와의 경쟁을 방지하기 위해 잠금
•void disable_dma(unsigned int dmanr);
•DMA 전송 시작시 혹시 모를 DMA 동작을 금지 시킴. 매개변수값은 금지시키고자 하는 채널
•void clear_dma_ff(unsigned int dmanr);
•DMA의 플립플롭(flip-flop)을 클리어
•플립플롭은 16비트 데이터 전송을 8비트 버스에서 처리할 때 사용됨
•void set_dma_mode(unsigned int dmanr, char mode);
•전송 방향을 설정하기 위해 사용
•매개변수는 mode는 보통 다음 중 하나
•DMA_MODE_READ : I/O 하드웨어에서 메모리로 전송
•DMA_MODE_WRITE : 메모리에서 I/O 하드웨어로 전송
•DMA 전송 시작(계속)
•내부 함수
•void set_dma_addr(unsigned int dmanr, unsigned int a);
•DMA 버퍼의 주소를 지정
•dmanr : 채널 지정, a : 주소 지정(가상 주소 불가능)
•void set_dma_count(unsigned int dmanr, unsigned int count);
•DMA로 전송되는 데이터의 크기를 지정
•전송 단위는 바이트이며, 채널이 16비트 채널이면 반드시 짝수여야 함
•void enable_dma(unsigned int dmanr);
•전송을 위해 DMAC을 활성화 시킴
•void release_dma_lock(unsigned long flags);
•lock 되었던것을 해제함
•int get_dma_residue(unsigned int dmanr);
•현재 DMA에 전송중인지를 확인
DMA와 MMAP
•DMA와 MMAP
•DMA를 사용한다
•하드웨어 <-> 프로세서 간의 데이터 전송이 많다는 것을 의미 함(비디오 데이터, 사운드 데이터 등)
•응용 프로그램 <-> 디바이스드라이버 사이에도 많은 데이터 전달이 발생한다는 의미도 됨
Þ따라서 응용 프로그램 <-> 디바이스드라이버간에 데이터 전달을 위해 mmap을 지원하는 것이 좋음
Þuser -> kernel, kernel -> user 간의 copy 과정의 딜레이를 제거
•
•디바이스 파일이 열릴 때 DMA용 버퍼를 할당 함
•DMA용 버퍼를 mmap() 함수를 이용해 User 영역으로 매핑하는 것을 지원해야 함
2. PCI
개요
•PCI란
•프로세서와 가까운 확장 슬롯에 부착된 디바이스간의 고속 데이터 전송 버스에 대한 규약
•장점
•PnP 지원
•PC이외에 ARM, PowerPC, MIPS, SH등에서도 사용 됨
•
•디바이스 드라이버 작성
•PCI 버스에 연결된 디바이스 사용시 필요한 내용은 커널에서 사전에 처리 함
PCI 버스에 대한 스펙을 알필요 없음
PnP와 디바이스 환경 설정 공간
•PnP 구현 절차
1.시스템 전원이 켜지면 PCI 버스에 연결된 디바이스들은 버스와 연결이 끊어져 있음
•환경 설정 영역만 프로세서에서 접근 가능
2.운영체제는 PCI 버스에 연결된 디바이스의 환경설정영역을 차례로 검사 함
•디바이스가 필요한 I/O 주소 공간과 인터럽트 번호 등을 파악하여 내부적으로 저장
3.OS가 내부 정보를 정리하고, 각 디바이스의 I/O 주소와 인터럽트 번호를 내부적으로 할당 함
4.OS가 정리된 I/O주소와 인터럽트 번호를 환경 설정영역에 써넣음
5.프로세서는 해당 I/O 주소로 접근 할 수 있고, 주변 장치는 인터럽트 번호에 해당하는 인터럽트를 발생 시킬 수 있음
PCI 주변 장치들은 환경 설정 영역의 정보를 이용해 PnP를 구현함
환경 설정 영역의 헤드
•벤더 ID
•offset : 0x00
•디바이스 제조사를 나타내는 16bit ID
•디바이스 ID
•offset : 0x02
•디바이스 구분 16bit ID
•Revision ID
•offset : 0x08
•버전을 나타냄
•클래스 코드
•0x09
•디바이스의 종류와 기능을 나타냄
•기본 클래스(0x0B)
•하부 클래스(0x0A)
•프로그래밍 인터페이스(0x09)
•환경 설정 영역의 헤드
•헤더 타입
•offset : 0x0E
•디바이스 드라이버의 구분
•PCI-PCI 브릿지
•일반 디바이스
•기준 주소 레지스터
•offset : 0x10 ~ 0x24
•크기 : 32bit
•운영체제에서 드라이버가 요구하는 주소 크기와 종류를 얻거나 시스템에
할당된 선두주소를 지정하는 레지스터
•접근하는 메모리 공간은 다음과 같이 나눔
•I/O 영역
•최대 접근 가능한 주소공간 크기 : 256
•버스의 접근 속도가 매우 느리게 설계된 주소 공간
•prefetch 32bit/64bit 메모리 영역
•32bit/64bit 주소 지정이 가능한 메모리 영역을 지정
•지정 가능한 최소 크기는 16byte 이상
•none prefetch32bit/64bit 메모리 영역
•제어용 레지스터인 경우 이 메모리 공간으로 설정 됨
•인터럽트 라인
•offset : 0x3C
•OS가 디바이스 드라이버에 할당한 인터럽트 번호
•prefetch
•하나의 주소를 지정하고 읽기를 시도할 때
해당 주소 뒤쪽의 데이터도 같이 읽어 들여 처리 속도를 향상 시킴
•lspci를 이용한 PCI 정보 검색
•proc 파일 시스템을 이용한 PCI 정보 검색
•sysfs 파일 시스템을 이용한 PCI 정보 검색(2.6)
•proc 파일 시스템보다 더 많은 정보를 검색 할 수 있음
•sysfs 파일 시스템을 이용한 PCI 정보 검색(2.6)
•응용프로그램에서 해당 PCI 디바이스 정보에 접근 가능
•디바이스 드라이버에서 환경 설정 공간의 접근
•PCI 이바이스를 사용하려면 환경 설정 영역에 접근하여 데이터를 처리하는 경우가 생김
•접근하기 위해 사용하는 함수
•int pci_bus_read_config_byte(struct pci_bus *bus, unsigned int devfn, int where, u8 *val);
•int pci_bus_read_config_word(struct pci_bus *bus, unsigned int devfn, int where, u16 *val);
•int pci_bus_read_config_dword(struct pci_bus *bus, unsigned int devfn, int where, u32 *val);
•int pci_bus_write_config_byte(struct pci_bus *bus, unsigned int devfn, int where, u8 *val);
•int pci_bus_write_config_word(struct pci_bus *bus, unsigned int devfn, int where, u16 *val);
•int pci_bus_write_config_dword(struct pci_bus *bus, unsigned int devfn, int where, u32 *val);
•PCI 디바이스 정보가 담겨져 있는 pci_dev 구조체가 있는 경우
•int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
•int pci_read_config_word(struct pci_dev *dev, int where, u8 *val);
•int pci_read_config_dword(struct pci_dev *dev, int where, u8 *val);
•int pci_write_config_byte(struct pci_dev *dev, int where, u8 *val);
•int pci_write_config_word(struct pci_dev *dev, int where, u8 *val);
•int pci_write_config_dword(struct pci_dev *dev, int where, u8 *val);
int xxx_read_info(struct pci_dev *dev)
{
u8 deviceid;
:
pci_read_config_byte(dev, PCI_DEVICE_ID, &deviceid);
return deviceid;
}
PCI 디바이스의 디바이스 드라이버
•일반 디바이스 드라이버와의 차이점
•일반적인 디바이스 드라이버
1.제어하려는 하드웨어 주소와 인터럽트가 고정되어 있음
2.DMA를 다룸
•PCI 디바이스 드라이버
1.제어하려는 하드웨어 주소와 인터럽트가 고정적이지 않음
•초기 처리에 주소와 인터럽트를 얻는 부분이 추가됨
2.DMA가 없고 버스 마스터링 방식의 DMA처리
•PCI 디바이스 검출
•int pci_register_driver(struct pci_driver *drv); 을 사용하여 등록
•제어 대상 디바이스가 있는 것을 알려면 벤더 ID와 디바이스 ID가 필요함
•이 정보를 pci_register_driver()함수를 이용해 전달하기 위해 struct pci_device_id 구조체가 필요함
•struct pci_device_id
{
__u32 vendor, device; //vendor : 검출하려는 PCI 디바이스의 벤더 ID, device : 검출하려는 PCI 디바이스의 디바이스 ID
__u32 subvendor, subdevice; //subvendor : 검출하려는 pci 디바이스의 하부 시스템 벤더 ID, subdevice : 검출하려는 PCI 디바이스의 하부 시스템 디바이스 ID
__u32 class, class_mask; //class : 디바이스의 클래스 코드, class_mask : 클래스에서 유효한 비트를 표시하는 마스크
kernel_ulong_t driver_data; //driver_data : 데이터 검출 시 드라이버에서 사용할 수 있는 정보를 지정하기 위해 준비된 것, 인덱스 번호
};
•예제
static struct pci_device_id xxx_pci_tbl[] __devinitdata =
{
{0x1233, 0x4101, PCI_ANY_ID, PCI_ANY_ID, 0, 0, XXX_INDEX1};
{0x1233, 0x4102, PCI_ANY_ID, PCI_ANY_ID, 0 ,0, XXX_INDEX2};
{0, } //테이블의 끝을 나타냄
};
MODULE_DEVICE_TABLE(pci, xxx_pci_tbl);
•PCI 디바이스 검출
•struct pci_driver
•pci_register_driver() 함수를 사용하기 위한 ID 테이블이 준비 된후
•PCI가 검출 되었을 때 처리해야 할 pci_driver 구조체가 준비 됨
•struct pci_driver
{
struct list_head node;
char *name; // PCI 디바이스를 제어하는 드라이버 이름
const struct pci_device_id *id_table; //디바이스 드라이버가 제어하려고 검출하는 PCI 디바이스의 ID 테이블
int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); // ID 테이블 목록과 일치하는 디바이스가 발견되면 호출됨 일치한 ID 정보는 id 매개변수에 전달, 디바이스와 관련된 pci정보는 dev에 전달
void (*remove) (struct pci_dev *dev); // 디바이스가 제거될때 호출됨
int (*suspend) (struct pci_dev *dev, u32 stats); //커널이 PCI 디바이스를 절전모드로 만들때 호출됨
int (*resume)(struct pci_dev *dev); //절전에서 깨어나게 할때 호출됨
int (*enable_wake) (struct pci_dev *dev, u32 state, int enable);
struct device_driver driver;
struct pci_dynids dynids;
};
•PCI 디바이스 검출
•PCI 디바이스 검색과 등록 요청
•static struct pci_driver xxx_pci_driver = {
.name = XXX_DRIVER_NAME,
.id_table = xxx_pci_tbl,
.probe = xxx_probe,
.remove = __devexit_p(xxx_remove),
#ifdef CONFIG_PM
.suspend = xxx_suspend,
.resume = xxx_resume,
#endif
};
•static int __init xxx_init_module(void)
{ return pci_register_driver(&xxx_pci_driver); }
•static void __exit xxx_cleanup_module(void)
{ pci_unregister_driver(&xxx_pci_driver); }
•PCI 디바이스 검출
•probe()와 remove() 처리
•pci_register_driver() 함수를 호출해 ID테이블 리스트의 목록과 일치하는 디바이스가 발견 되면
•pci_driver의 probe 필드에 등록된 함수 호출
•int probe(struct pci_dev *dev, const struct pci_device_id *id);
1.PCI 디바이스를 활성화 시킴
2.PCI 디바이스를 제어하기 위한 I/O와 메모리 주소를 얻음
3.I/O와 메모리 영역을 등록
4.메모리 영역을 커널공간에 매핑
5.PCI 디바이스를 제어하기 위한 IRQ 번호를 얻음
6.인터럽트 핸들러 등록
7.필요시 버스 마스터 모드 설정
8.디바이스 드라이버 등록
9.하드웨어 초기화
•PCI 디바이스 검출
•int probe(struct pci_dev *dev, const struct pci_device_id *id);
1.PCI 디바이스를 활성화
•PCI 디바이스에 접근하기 위해 디바이스 활성화
•int pci_enable_device(struct pci_dev *dev);
•void pci_disable_device(struct pci_dev *dev);
2.PCI 디바이스의 하드웨어 제어 주소 정보
•주소정보는 커널 내의 데이터 구조에 존재함
•다음 함수를 이용하여 획득
•unsigned long pci_resource_start(struct pci_dev *dev, int bar);
•BAR(Base Address Register)에 해당하는 메모리의 시작주소를 얻어옴
•unsigned long pci_resource_end(struct pci_dev *dev, int bar);
•BAR(Base Address Register)에 해당하는 메모리의 끝주소를 얻어옴
•unsigned long pci_resource_len(struct pci_dev *dev, int bar);
•BAR(Base Address Register)에 해당하는 메모리의 크기를 얻어옴
•unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
•BAR(Base Address Register)에 해당하는 메모리의 속성값을 가져옴
•IORESOURCE_IO, IORESOURCE_MEM
•PCI 디바이스 검출
•int probe(struct pci_dev *dev, const struct pci_device_id *id);
•3. PCI 메모리 영역 등록과 해제
•충돌 방지를 위해 PCI 디바이스의 메모리영역을 커널에 등록
•int pci_request_region(struct pci_dev *, char *dev_name);
•void pci_release_regions(struct pci_dev *);
•4. 메모리 영역 매핑
•버스 주소를 획득하여 커널 주소공간에 매핑
•버스 주소 획득
•pci_resource_start(), pci_resource_end()
•커널 주소공간 매핑
•ioremap()
•5. PCI 디바이스 IRQ 번호와 인터럽트 핸들러 등록
•PCI 디바이스의 인터럽트 번호는 pci_dev -> irq
•request_irq()를 이용하여 등록
•6. 버스 마스터링 모드 설정
•버스 마스터링 방식의 DMA를 사용함
•void pci_set_master(struct pci_dev *dev);
•PCI 디바이스 검출
•probe 함수의 예
•int xxx_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
:
//PCI 디바이스 활성화
pci_enable_device(dev);
// 메모리 주소 획득 및 등록과 매핑
xxx_hw_addr = pci_resource_start(dev, 0);
xxx_addr_size = pci_reousrce_len(dev, 0);
pci_request_regions(dev,XXX_DEV_NAME);
xxx_vaddr = ioremap(xxx_hw_addr, xxx_addr_size);
//인터럽트 등록
xxx_irq = dev->irq;
request_irq(xxx_irq, xxx_interrupt, SA_INTERRUPT | SA_SHIRQ, XXX_DEV_NAME, &xxx_info);
//버스 마스터링 모드 설정
pci_set_master(dev);
//디바이스 드라이버 등록
register_chrdev(XX_DEV_MAJOR, XXX_DEV_NAME, &xxx_fops);
//하드웨어 초기화
xxx_hardware_init();
:
}
•PCI 디바이스 검출(수동)
•struct pci_dev *pci_find_device(unsigned int vendor, unsigned int device, const struct pci_dev *from);
struct pci_dev *pci_find_subsys
(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device const struct pci_dev *from);
struct pci_dev *pci_find_class(unsigned int class, const struct pci_dev *from);
struct pci_dev *pci_find_slot(unsigned int bus, unsigned int devfn);
•각 함수는 다음과 같은 조건과 일치하는 대상을 찾음
•pci_find_device : 벤더ID, 디바이스 ID
•pci_find_subsys : 벤더ID, 디바이스 ID, 하부 벤더 ID, 하부 디바이스 ID
•pci_find_class : 클래스 코드
•pci_find_slot : 버스번호, 슬롯, function 번호
•예제
•struct pci_dev *dev = NULL;
while(dev = pci_find_device(VENDOR_ID, DEVICE_ID, dev))
{
//찾은 PCI 디바이스 정보가 dev에 넘어온다.
:
}
버스 마스터링 DMA
•버스 마스터링 DMA
•개요
•PCI 버스 스펙에는 DMA 방식이 규정되어 있지 않음
•대신, PCI 디바이스가 버스 점유권을 획득한후 처리
•구현
•PCI 디바이스의 구조에 따라 처리 방식이 모두 다름
•DMA와 같이 데이터를 전송하기 위해 버스 주소가 필요함
•이와 관련된 함수의 이해가 필요함
'IT > 디바이스드라이버' 카테고리의 다른 글
13. 블록킹 I/O (0) | 2015.12.20 |
---|---|
12. 인터럽트 처리 (0) | 2015.12.14 |
23. 네트워크 디바이스 드라이버 (0) | 2015.11.25 |
22. 블록 디바이스 드라이버 (0) | 2015.11.10 |
20. 다중 프로세스 환경의 디바이스 드라이버 (0) | 2015.11.10 |