PCI 버스의 주소할당 - 임베디드 리눅스 시스템 포럼

Download Report

Transcript PCI 버스의 주소할당 - 임베디드 리눅스 시스템 포럼

PCI 버스와 리눅스 드라이버
2007.5.13 FA리눅스공개세미나
오재경
PCI가 어렵울 거라는 생각을 지금
부터 버리도록 합시다.
버스(BUS)란?
• 32Bit MCU의 메모리 공간의 크기 4GByte
• 버스의 여러종류
– 로컬버스, PCI, I2C, AC97, USB, ATA, IEEE1394, etc …
• 두가지 버스의 구분
(접근방법에 따른, 극히 개인적인)
– Indirect BUS (USB, SCSI, IEEE1394, I2C)
– Direct BUS (Local-BUS, PCI)
• 하드웨어적인 전기적인 연결과 이를 제어하는 소프트웨
어의 표준 인터페이스를 제공하여 디바이스의 확장을 용
이하게 해주는 것
BUS 의 예
0xFFFF FFFF
PCI-BUS
0x8000 0000
PCI to HDD
PCI to USB
32bit
MCU
AC97-BUS
0x4000 0000
USB Device
USB Device
USB Device
AC97 Codec
LOCAL-BUS
0x0000 0000
HDD
HDD
Flash
PCI 버스의 특징
• 직접적인 메모리 방식의 제어를 통해 엑세
스가 가능하다.
PCI 버스의 주소할당
• PC 보드에서 PCI 슬롯에 카드들을 임의 대로 꽂아도 시
스템에서는 잘 인식한다. 어떻게 가능한 것인가?
– 컨피큐레이션 사이클이라는 특별한 전송이 존재하며 이것은 특
정 슬롯을 지정하여 전송할 수 있다.
MCU
PCI Configuration 영역
31
16 15
0
Device ID
Vender ID
0x00
Status
Command
0x04
Class ID
BIST
Header Type
Latency
Base Address #0 (I/O
Rev ID
0x08
CacheLineSize
0x0C
전용)
0x10
Base Address #1 (MEM 전용)
0x14
Base Address #2 (MEM 전용)
0x18
Base Address #3 (MEM 전용)
0x1C
Reserved ….
0x20
Reserved ….
PCI 디바이스의 Base 주소
MEM Base Address
P
B
IO Base Address
P
B
0
0
1
: 메모리 프리패치 가능
메모리일 경우 D0 = 0
IO일 경우 D1:D0 = 01
bit
1
16
1
1
1
12
1
1
1
1
8
1
1
0
0
4
0
0
0
0
Base Register 에 0xFFFF FFFF 를 쓴 후 다시 읽어 각비트의 값을 보
고 필요로 하는 주소의 크기를 판단한다.
Read Only
주소할당의 예
IO 영역과 MEM 영역의 차이
• io 영역은 한번에 하나씩의 데이터를 엑세스하는
곳이며 prefetch가 일어나지 않는다.
• io 영역은 최대 256바이트까지 할당이 가능하다.
• mem 영역은 prefetch가 가능하며 burst 전송이
가능하다.
• mem 영역은 할당되는 크기의 제한이 거의 없으
며 DMA 전송도 지원하여 고속의 데이타전송이
가능하다.
PCI 버스의 핀(32bit PCI)
•
•
•
•
•
•
•
•
•
•
•
AD[0..31]
: 어드레스/데이터 공통
C/BE[0..3]
: Commad/BUS-Enable
FRAME#
: 사이클 시작신호
DEVSEL#
: 사이클 응답신호(주소에 대한)
IRDY#
: 버스상의 데이터가 유효함을 알림
TRDY#
: 데이터를 받을 수 있음을 알림
REQ#, GNT#
: 버스점유 요구, 허락
PAR
: 짝수패리티
SERR# , PERR# : 어드레스에러, 데이타에러
CLK, RST#, STOP#, INT[A...D]#
IDSEL
: 환경영역을 엑세스할 때 사용
PCI 버스의 기본적인 동작
PCI 하드웨어 설계 시 주의점
• 회로 설계시
–
–
–
–
버스의 드라이빙 능력
버스 아비터의 개수
클럭버퍼의 사용
OC 핀의 풀업저항
• 아트웍 시
– 클럭신호는 한 디바이스 까지의 버스들의 평균거리로 설정
– 버스내 각 신호선간의 길이는 가급적 동일하게 설정(20% 오차까지(?))
• 문제점
– 보드가 너무 작을 경우 동일한 버스 길이를 설정하기 힘들다.
– PCI 디바이스가 너무 많을 경우 외부 아비터나 버스 브릿지를 고려해본
다. (버스 브릿지는 비추천.. 하지만 달아야 한다면 ㅜ.ㅜ)
PCI 디바이스 회로도
PCI 하드웨어 디버깅
1.
2.
3.
4.
디바이스의 전원이 정상인지 확인한다.
디바이스의 클럭이 공급되는지 확인한다.
디바이스의 RESET 신호가 정상인지 확인한다.
회로도가 이상이 없는지 넷트의 이름과 칩의 핀배치가 일치하는지
확인한다.
5. 주변 저항등 칩들이 모두 정상으로 붙어있는지 확인한다.
6. 디바이스의 configuraion 레지스터를 읽는다. (lspci 유틸리티)
7. Frame# 신호화 함게 디바이스의 IDSEL 핀이 High 로 선택되는지
확인한다.
8. Devsel# 신호가 동기 되는지 확인한다.
9. PERR#, SERR# 가 발생하는지 확인한다.
10. 버스의 신호가 전혀나오지 않는다면 MCU 레지스터 설정과 커널의
PCI 관련 동작이 활성화 되어있느지 또는 나의 보드에 맞게 포팅되
어 있는지 확인한다.
PCI 버스를 지원하는 MCU 구조
PCI Device
MCU
0x4100 0000
PCI/MEM
+ PCI MEM-Base
0x4100 0000 +
PCI MEM-Base
0x4000 0000
PCI/IO
PCI IO-Base
0x4000 0000 +
PCI IO-Base
리눅스커널이 부팅시 하는 일
• Configuration 싸이클을 이용하여 주소 할당
• BUS, SLOT, FUNC 번호를 이용한 IRQ 등록
디바이스 드라이버의 최초 실행
static int __init spci_init( void )
{
return pci_module_init(&spci_driver);
}
static void __exit spci_exit( void )
{
pci_unregister_driver(&spci_driver);
}
module_init(spci_init);
module_exit(spci_exit);
PCI 디바이스 드라이버 등록
• PCI 드라이버 등록 구조체 인자
struct pci_driver spci_driver = {
.name
= DEV_NAME,
.id_table
= spci_tbl,
.probe
= spci_probe,
.remove
= spci_remove,
};
• PCI 드라이버 초기화 함수
pci_module_init(&spci_driver);
// #define pci_module_init pci_register_driver
• PCI 드라이버 해제 함수
pci_unregister_driver(&spci_driver);
struct pci_device_id
•
struct pci_device_id {
// linux/mod_devicetable.h
__u32 vendor, device;
__u32 subvendor, subdevice;
__u32 class, class_mask;
kernel_ulong_t driver_data; };
static struct pci_device_id spci_tbl[] = {
{0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SPCI_PRIVATE_NUMBER },
{0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SPCI_PRIVATE_NUMBER },
{0,}
};
struct pci_driver member 함수
•
•
int spci_probe (
struct pci_dev *pdev, const struct pci_device_id *ent )
{
// 디바이스가 정상인지 확인
// 디바이스가 점유하는 베이스주소 얻기
// 사용할 메모리 할당
// 인터럽트 함수 등록
// 클래스에 맞는 드라이버 등록 ex) register_chrdev()
// 디바이스 초기화 실행
}
void spci_remove ( struct pci_dev *pdev )
{
// 점유한 리소스 반환
}
주소 (Resource) 얻기
• struct pci_dev *pdev; // 함수의 인자
• int win_nr = 0;
// PCI configuration base-address index
// 0번은 IO 베이스 주소
// 1~5 번은 메모리 베이스 주소
•
•
•
•
•
unsigned
unsigned
unsigned
unsigned
unsigned
long
long
long
long
int
base = pci_resource_start (pdev, win_nr);
end = pci_resource_end (pdev, win_nr);
flags = pci_resource_flags (pdev, win_nr);
len = pci_resource_len (pdev, win_nr);
irq = pdev->irq;
struct pci_dev
•
struct pci_dev {
struct pci_bus
unsigned int
unsigned short
unsigned short
unsigned int
u8
struct pci_driver
struct device
int
unsigned int
struct resource
*bus;
devfn;
vendor;
device;
class;
pin;
*driver;
dev;
cfg_size;
irq;
resource[DEVICE_COUNT_RESOURCE];
주소 맵핑(mapping)
• Configuration Base-Address 에 등록(할당)된 주소는 물리주소이다.
• 리눅스 커널에서는 물리주소를 바로 사용할 수 없다. 이를 사용하기
위해서는 가상주소로 변환하여 사용하여야 한다.
• vir_addr = ioremap( phy_address, len );
• iounmap( vir_addr );
• ioremap_nocache(), ioremap_cache();
• PCI 영역중 mem 영역은 가상영역으로 변환하여야 한다.
• PCI 영역중 IO 영역은 변환하지 않고 사용한다.
버스접근 함수
• PCI-IO 영역 접근함수
inb(), outb(), inw(), outw(), inl(), outl()
• PCI-MEM 영역 접근함수
readb(), writeb(), readw(), writew(), readl(), writel()
• inclue/asm/io.h 참고(중요소스)
인터럽트 처리
• request_irq( irq, spci_callback,
SA_INTERRUPT | SA_SHIRQ, “sample”, NULL );
irqreturn_t spci_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
if ( 내가 다루는 디바이스가 인터럽트를 발생시켰는가 )
{
// 인터럽트 라인이 해제될 수 있도록 처리한다.
return IRQ_HANDLED;
}
else return IRQ_NONE;
}
PCI 버스의 DMA 전송
• PCI 버스가 빠른것은 DMA 전송을 지원하기 때문이다. 이 기능을 사
용하지 않고는 PCI 디바이스를 사용할 이유가 반감된다.
1.
2.
3.
•
DMA 전송을 위해서는 물리적으로 연속된 메모리를 획득한다.
연속된 물리메모리를 획득하였다면 PCI 디바이스의 특정 영역에
호스트측의 물리 메모리 주소와 PCI 디바이스의 물리주소 그리고
전송할 데이터의 크기를 넣은 후 DMA 전송 명령을 내린다.
DMA 전송명령을 위해서는 PCI 디바이스 레지스터를 접근하여 특
정값을 넣어야 한다. 커널에 따로 이를 위한 함수는 준비되어 있지
않다.
PCI 버스에서 DMA 전송의 버스 마스터는 PCI 디바이스에 있다.
DMA 메모리 획득(K2.6.10 이상)
• 1-page 이하의 크기 (4KByte)
– kmalloc( b-size, GFP_KERNEL ), kfree( virt )
• 1-page 이상의 크기
– kmalloc( b-size, GFP_DMA );
– void *dma_alloc_coherent( struct device *dev, size_t size, dma_addr_t
*handle, gfp_t gfp );
– dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
dma_addr_t handle );
•
예제
dma_addr_t dma;
// DMA 용 물리주소
void *virt = dma_alloc_coherent( pdev->dev, 1M, &dma, GFP_KERNEL );
PCI 디바이스 드라이버 소스
• 소스를 봅시다. ^^
예제 드라이버 실행 결과
끝
• 이페이지를인쇄한분들은인쇄하기전에내
용을살피지않고오셨거나공짜로사용할수
있는회사나학교의용지를사용하셨거나한
페이지에여러페이지인쇄를하신분들입니
다아니면종이가많은부자이거나어찌됬든
이번강의를위해이페이지까지아낌없어투
자하신분들은꼭성공하실겁니다부족한강
의를끝까지들어주셔서감사합니다나가는
문이어느쪽이든남은강의열심히들으신후
경품도타가시는행운을누리시기바랍니다