결론부터 요약

Direct Vector Method

  1. EIIC 값 확인
  2. Exception Cause(EIINTn) = EIIC – Exception Cause Code Base Address
  3. EIINT Offset = EIINTn x 0x10 + 0x100
  4. RBASE 혹은 EBASE + EIINT Offset

RINT가 0이라고 가정, RINT가 1이면 걍 0x100임

자! 이제 어떻게 인터럽트 핸들러가 실행되는 걸까? 상세 설명을 보시라

A. 장치로 인터럽트가 들어왔다고 치자. 그러면 EIIC 라는 레지스터에 Exception Cause Code가 뜬다.

 

B. Exception Cause(EIINTn) = EIIC value – Exception Cause Code Base Address

User Int라고 가정하자. 아래 표를 보면 EIINT 0 – 511까지 총 512개가 있고 Exception Cause Code는 0x1000 – 0x11FF임을 알 수 있다. 즉 Exception Cause Code Base Address이 0x1000이다. (당연한 내용이지만 0x11FF – 0x1000 = 0x200 즉 512)

만약 EIIC에 값이 0x1007로 들어왔다면 EIIC Offset = 0x1007 – 0x1000 = 7임을 알 수 있다.

 

아래는 그냥 참고용이다 Exception Cause Code의 시작 주소가 각기 다른 것을 확인하시라

C. EIINTn Offset 을 구해보자

EIINTn priority 0이 0x100부터 시작이다. 그러니까 아까 예시의 7번은 0x170이 되겠다

이걸 공식화 하면 0x100 + Exception Cause x 0x10

 

D. 마무리

PSW.EBV 값에 따라 RBASE 혹은 EBASE 값에 이전 단계에서 구한 EIINTn Offset을 더하면 끝

Table Reference Method는요?

설명하기 귀찮다

INTBP + Exception Cause x 4 하면 된다.

 

startup 코드에서 미리 INTBP 설정하는 거 잊지 말고오오~~~~

Posted by 쵸코케키

cortex 시리즈도 그렇고 pipeline은 기본 탑재 되어있다.

pipeline에 대한 개념은 대충 다 알고 있으리라 본다.

pipeline에 대한 이해는 정말 간단한 프로그램을 어셈으로 짜놓고 nop 최적화 같은거 하면 정말 쉽게 깨닫게 된다.

이 작업을 하고 있으면 뭔가 자신이 굉장히 하이테크의 정점에 다가선듯한 기분을 체험해볼 수 있으며 그 날은 이야 내가 인생을 허비한 것이 아니라 제대로 살았구나라고 머리속 모든 것이 긍정적으로 변하는 그런 날이 될 수 있다.

바로 직전에 롤에서 10데스하는 핵트롤을 하다가 도저히 오늘은 게임 하는 날이 아닌가보다 하고 공부를 시작했다고 하더라도 말이다.

 

여튼 pipeline에 의해 barrier 가 필요하다. 

이걸 Renesas에서는 어떻게 제공하고 있는가?

 

 

SYNCE는 FPI 소수점 관련 내용

SYNCI는 instruction(Synchronize memory for instruction writers)

SYNCM은 memory(memeory, register) load/store

SYNCP는 memory load만(synchronize pipeline)

 

SYNCI 항목에는 Calculation Instruction이라고 적혀있는데 Appendix A를 보면 연산과 관련된 작업 외 PSW, MPU 등 기타 시스템 관련 레지스터 작업 관련해서도 보장함을 알 수 있다.

 

Posted by 쵸코케키

르네사스에 대한 정보가 아예 없어 안타까워서 정리해본다.

나도 잘 모르는 내용이다. 간단하게 인터럽트 핸들러 설정 관련 글을 정리해본다.

틀렸는데요? -> 아주 좋은 지적이다. 좀 자세히 알려다오.

 

근데 생각해보니까 르네사스 칩의 메뉴얼을 봐야할 정도면 굳이 이런 내용 없어도 알아서 다 파악할 수 있는 능력을 가졌을텐데 내가 여기에 이런걸 적고 있는게 의미가 있는걸까 급 후회가 되기도 하지만 블로그에 헛소리가 너무 많으니 그걸 치우기 위해 뭔가 있어보이는 내용을 적어본다.

 

아래의 내용은 REN_r01us0165ej0120-rh850g3kh_MAS_20171026.pdf 라는 전혀 직관적이지 않은 더러운 파일명을 가진 메뉴얼을 요약한 것이다.

그냥 홈페이지에 회원 가입해서 다운 받을 수 있는 파일이다.

 

Basic System Register들 중에 여러가지가 있겠다만 PSW라는 놈이 있다.

ARM도 이런놈 있는거 알쟤?

 

PSW(Program Status Word)

현재 instruction execution result를 기록해두는 즉 status flag들을 모아둔 register

15번째 bitfield가 EBV

 

EBV

reset vector, exception vector 동작 관련 flag

RBASE, EBASE와 연관이 있다.

데이터 시트 뭐의 약자인지 안 나와있다. Exception Base Vector?

 

Exception

unusual event 즉 CPU 입장에서 아...나는 이제 망했구나 진짜 큰 일이 발생했구나 싶을 때 발생하는 이벤트

exception handler가 발동 된다.

 

언제 발생하는지에 대한 예시 목록이 있는데 예를 들면 뭐 이렇다.

Reset 발생하면 Reset 이라는 Exception이 발생하고 FPU exception, User Interrupt Exception, System Error, Memory Protection Exception 등등

FENMI, FEINT, EIINT 이런 중요한 3형제가 있는데 일단 넘어가자 여기서 힘 다 빼면 안된다

 

Interrupt Priority Order & Mask

일단 넘어가자

대충 어떤 개념인지는 알 것이라 생각된다.

뭔가 어떤 값을 설정하면 Priority 가 있고 Masking으로 인터럽트 허용 여부를 결정할 수 있겠지

집중력을 아껴야 한다

 

Exception Handler Address

서론 끝나고 드디어 도착  이제부터 중요하다.

높은 성능 향상 목적을 위해 이렇게 디자인 했다고는 하는데 진짜 왜 이렇게 디자인 했을까 싶은 생각이 드는 파트인데 뭔가 다 이유가 있지 않을까 아직 내가 시야가 좁아서 그런건가 싶은 부분이다. 일단 내용을 그대로 옮겨보자.

 

exception이 발생하면 handler를 실행해야 한다.

이 handler의 address는 2가지 방법이 있다.

 

1. Direct Vector Method

2. Table Reference Method

 

A. Direct Vector Method

CPU는 exception cause offset + Base Address 를 사용한다.

exception cause offset이라는건 exception list 가 표로 있는데 각 exception의 원인(cause)마다 offset이 할당 되어 있다.

 

위의 표가 전부다.

PSW.EBV 의 값에 따라 BASE address를 RBASE/EBASE register에서 참조할지 

그리고 RINT(RBASE/EBASE 내부에 있는 flag)의 값에 따라 offset이 결정된다.

위의 표가 이해 된다면 이하의 글은 읽지 않고 그냥 넘어가도 ok

 

Base Address

PSW.EBV = 0 : RBASE에 있는 address

PSW.EBV = 1 : EBASE에 있는 address

 

주의 사항: 몇몇의 exception은 항상 RBASE를 사용한다.

 

RINT는 R/EBASE에 있는 bitfield인데 이 flag가 1인 경우 EIINT (user interrupt)가 0x100 offset 주소로 handling 된다.

 

RBASE의 주소와 EBASE의 주소는 동일하게 설정할 수도 있고 다르게 설정할 수도 있다.

아래 그림의 INTPRx는 EIINTn과 같다

RINT = 1의 용도 : user interrupt 를 모두 0x100에서 처리하기 때문에 특정 상황에서 메모리를 적게 사용할 수 있다.

 

B. Table Reference Method

direct vector method는 각 interrupt priority level마다 하나의 user interrupt exception handler를 사용할 수 밖에 없음

여러 인터럽트와 연결된 채널은 동일한 priority와 같은 handler를 사용할 수 밖에 없음

이 문제를 해결해주는 방법임

 

direct vector method가 사용 되는 케이스

1. PSW.EBV = 0 && RBASE.RINT = 1

2. PSW.EBV = 1 && RBASE.RINT = 1

3. 인터럽트 채널 세팅이 Table Reference Method로 설정 되어있지 않음

 

위의 케이스 외는 table reference method로 동작한다.

Table Reference Method의 handler address = INTBP register + 채널 offset 내부의 값(int 채널 번호 x 4Bytes)

아래의 그림이 더 쉬운 이해를 도와줄 것이다.

 

INTBP부터 시작해서 레지스터들이 엄청나게 많은데(거의 뭐 메가 단위?) 거기에 각 interrupt handler 주소를 넣어서 

세팅할 수 있음

 

그래서 exception이후 interrupt handler 동작이 조금 느림

exception -> table reference를 함(INBTP + 채널 offset)의 레지스터 값을 read -> 그 값이 handler가 있는 곳이므로 그곳으로 이동

C. 그러면 왜 디자인을 이렇게 나누었는가? 용도는?

다음주에 추가해보도록 하겠다.

더이상 에너지가 없다.

 

그림으로 상상 해보자

 

Posted by 쵸코케키

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHBFEIB.html


4.7. Enabling interrupts using CPS instructions and MSR instructions

In normal applications there is no need to add any barrier instruction after using a CPS instruction to enable an interrupt:

_enable_irq(); /* Compiles to “CPSIE I” - Clear PRIMASK */

If an interrupt was already in the pending state, the processor accepts the interrupt after the “CPSIE I” is executed. However, additional instructions can be executed before the processor enters the exception handler:

  • for Cortex-M3 or Cortex-M4, the processor can execute up to TWO additional instructions before entering the interrupt service routine

  • for Cortex-M0, the processor can execute up to ONE additional instruction before entering the interrupt service routine.

Figure 14 shows the implemented interrupt enabling delay in the Cortex-M3 and Cortex-M4 processors.

Figure 14. Implemented interrupt enabling delay in the Cortex-M3 and Cortex-M4 processors

To view this graphic, your browser must support the SVG format. Either install a browser with native support, or install an appropriate plugin such as Adobe SVG Viewer.


Figure 15 shows the implemented interrupt enabling delay in the Cortex-M0 and Cortex-M0+ processors.

Figure 15. Implemented interrupt enabling delay in the Cortex-M0 and Cortex-M0+ processors

To view this graphic, your browser must support the SVG format. Either install a browser with native support, or install an appropriate plugin such as Adobe SVG Viewer.


Architectural requirements

ARM recommends that the architectural requirements are adopted.

  • If it is necessary to ensure a pended interrupt is recognized before subsequent operations, the ISB instruction should be used after CPSIE I. Figure 16 shows the use of the ISB instruction after enabling interrupts to permit immediate recognition of the pending interrupt.

  • If it is not necessary to ensure that a pended interrupt will be recognized immediately before subsequent operations, it is not necessary to insert a memory barrier instruction.

  • Between two time critical tasks, if you want to permit a pended interrupt to take place, you can use an ISB instruction as follows:

    __enable_irq(); // CPSIE I : Enable interrupt
    __ISB(); // Allow pended interrupts to be recognized
    __disable_irq(); // CPSID I : Disable interrupt
    

    Figure 16 shows the resulting behavior.

    Figure 16. Use ISB after enabling interrupts to permit immediate recognition of a pending interrupt

    To view this graphic, your browser must support the SVG format. Either install a browser with native support, or install an appropriate plugin such as Adobe SVG Viewer.


    A suitable architectural coding is:

    To view this graphic, your browser must support the SVG format. Either install a browser with native support, or install an appropriate plugin such as Adobe SVG Viewer.

    Figure 17 shows the resulting behavior.

    Figure 17. Architectural interrupt behavior between CSPIE and CPSID

    To view this graphic, your browser must support the SVG format. Either install a browser with native support, or install an appropriate plugin such as Adobe SVG Viewer.


Note

The same requirement applies when using the MSR instruction to enable interrupts.

Implementation requirements

In Cortex-M processors:

  • If it is necessary to ensure a pended interrupt is recognized before subsequent operations, the ISB instruction should be used after CPSIE I. This is the same as the architectural requirement, see Figure 16.

  • If it is not necessary to ensure that a pended interrupt is recognized immediately before subsequent operations, it is not necessary to insert a memory barrier instruction.

  • An exception to this rule is the sequence CPSIE followed by CPSID. In Cortex-M processors, there is no need to insert an ISB between CPSIE and CPSID.

    A suitable implementation coding is:

    To view this graphic, your browser must support the SVG format. Either install a browser with native support, or install an appropriate plugin such as Adobe SVG Viewer.

Figure 18 shows the implemented behavior.

Figure 18. Implemented behavior requires no ISB between CPSIE and CPSID

To view this graphic, your browser must support the SVG format. Either install a browser with native support, or install an appropriate plugin such as Adobe SVG Viewer.


Note

As the implementation requirements show, there is no need to add a memory barrier instruction between __enable_irq() and __disable_irq(). However, in the architecture, if the interrupt needs to be recognized between the CPSIE and CPSID instructions, then an ISB instruction is needed. The same applies when using the MSR instruction to enable interrupts.

Posted by 쵸코케키

Optimizing Multiplayer Game Server Performance on AWS

출처 - Amazon



https://aws.amazon.com/ko/blogs/korea/optimizing-multiplayer-game-server-performance-on-aws/



Optimizing-Multiplayer-Game-Server-Performance-on-AWS.PDF


Posted by 쵸코케키


dst_addr & ~(region - 1)


이렇게 하면 & 연산하는 쪽의 영역이 모두 111111bit가 되어서 dst_addr이 해당 영역 이내인지 체크 가능

.....인데 생각해보니까 그냥 > 로 크기 비교 하면 혼나나?

Posted by 쵸코케키

예를 들면 512B로 aligned 인지 확인


int check_if_aligned(unsigned long addr)

{

const unsigned long align_size = 512;

return (addr & (align_size - 1)) ? -1 : 0;                //0: aligned, -1: not aligned

}


512보다 작은 크기의 bit가 단 하나라도 & 연산으로 걸리는게 있으면 aligned가 아님

무조건 512의 배수만 &연산이 false뜸


잘 이해가 안 가면 계산기 놓고 해보면 아하~!

Posted by 쵸코케키

STMF4 시리즈 부트로더, iap 개발 팁

#0. 주의사항

작성자 컴맹임

MCU? 그거 유재석 말하는거 아니냐??

#1. 이게 왜 필요한데?

상용제품을 출시하면 펌웨어를 업데이트 해야할 일이 분명 있을 것이다.
jtag이나 st-link 라인을 실 제품에서는 제거해놓은 경우가 많고 제품을 죄다 뜯거나 할 수가 없으니 이런저런 기타 등등 문제로 uart를 통한 펌웨어 업데이트는 언젠가는 만나게 되는 문제다.

@ boot mode image

외부로 boot mode 핀을 두어 하드웨어적으로 컨트롤 할 수있게 만들면 모를까

근데 외부로 설정할 수 있는 pin을 두는 것도 size 측면에서 보면 쉬운 일은 아니다.

boot mode는 뭐 별거 아니다 pin설정 해서 평소 부팅하는 flash주소 영역으로 부팅하는게 아니라 그 외 system memory라고 불려지는(ram이 아니다) 영역으로 부팅하도록 MCU가 지원한다.

자. 그럼 그냥 uart인터럽트 핸들러 만들어서 특정 커맨드가 들어오면 펌웨어 업데이트 하도록 개발하면 되겠네라고 생각할지도 모른다.
목표는 괜찮은데 문제가 있다.

A. 펌웨어 크기가 크기 때문에 램의 용량을 벗어난다.

예를 들어 펌웨어가 200KB라고 하면 이걸 어떻게 메모리 할당 받을 수 있을까?

매우 힘들다고 할 수 있다. 그래도 메모리를 확보해서 다운 받았다고 치자

B. sector 0에 인터럽트 핸들러 코드가 있다 -_-;;;

뭔소리냐고? flash에 write할 코드가 flash에 있기 때문에 write하는 순간 인터럽트 핸들러 및 시스템 코드가 전체적으로 바보가 된다
linker descripton를 보면 이해가 빠르다.


/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 2048K
RAM (xrw)      : ORIGIN = 0x20005000, LENGTH = 288K
CCMRAM (rw)      : ORIGIN = 0x10000000, LENGTH = 64K
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    ....
  } >FLASH

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */

flash 메모리에 isr_vector 즉 인터럽트 핸들러 위치도 있고 우리가 작성한 프로그램코드 즉 text 영역도 flash에 있다
이렇기 때문에 메인 어플리케이션에서 flash를 write를 하는 순간 이 모든게 지워지고 꼬이기 때문에 ㅎㅎㅎㅎ 기기는 먹통이 될 것이다.

ram에 상주하는 ram function을 만들어도 이는 마찬가지

-> 결국 boot loader가 필요하구나!

2. 어떻게 동작하는가?

ok, boot loader가 필요한건 이해했어 그럼 어떻게 동작하는지도 알 수 있을까?

개략적인 디자인은 아래와 같다.

1. 전원 입력
2. 0x0800 0000로 이동 //flash address, 제품마다 주소다름

3. 부트로더 코드를 읽는다  
if( uart로 5초 이내에 key 입력이 있었는가? ) {  //yes
    4. uart로 펌웨어 다운로드
    5. flash write
    6. reboot
}
else {  //no
    4.  메인 어플리케이션으로 이동
}

3. Reference

http://www.st.com/en/embedded-software/stsw-stm32067.html

이곳에서 소스코드를 받을 수 있다.

적당히 자신의 환경에 맞게 수정해서 사용하면 된다.

4. 개발

A. 큰 그림 그리기

  • boot loader는 flash sector 0만 사용한다(16KB)

  • 메인 어플리케이션은 flash sector 1부터 시작한다(2MB - 16KB)

  • 메인 어플리케이션 ISR Vector table 설정 확인

  • uart로 펌웨어를 전송 받을 때 1KB 정도로 나누어 작업한다. 메모리 크기도 문제고 flash write를 혼자 너무 오래 하면 진행 상황을 알 수 없으니…

B. 개발

테스트를 위해 먼저 작게 개발하기로 함

boot loader및 main application으로 점프가 정상적으로 되는 것을 확인한 후 uart 및 flash write 기능 추가

1. boot loader에서 led on
2. boot loader에서 start 메세지 출력
3. main application으로 jump!
4. main application 동작

C. boot loader

c code

#define APPLICATION_ADDRESS (uint32_t) 0x08004000
typedef void (*fptr)(void);
fptr jump_to_app;
uint32_t jump_addr;

HAL_GPIO_WritePin(GPIOF, GPIO_PIN_0, GPIO_PIN_SET);      //LED ON
printf("boot loader start\n");               //메세지 출력
jump_addr = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
jump_to_app = (fptr) jump_addr;

/* init user app's sp */
printf("jump!\n");
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
jump_to_app();

cube mx에서 생성하였다.

uart랑 gpio설정 정도만 유지하고 나머지는 모두 비활성화 하여 코드 크기를 최소화 했다.

linker description

/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 16K
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 320K
CCMRAM (rw)      : ORIGIN = 0x10000000, LENGTH = 64K
}

flash를 sector 0의 크기만큼만 사용하도록 설정

build 후 image의 크기 역시 맞아야 한다

D. Main Application

c code

#define FLASH_BASE            0x08000000U

// system_stm32f4xx.c
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; 
/* Vector Table Relocation in Internal FLASH */

//#define VECT_TAB_OFFSET  0x00
#define VECT_TAB_OFFSET  0x4000         //이 부분을 수정
/*!< Vector Table base offset field. This value must be a multiple of 0x200. */

isr vector 주소 설정을 확인한다.

iap 에서 점프 후 systick이 동작하지 않거나 하면 이 문제일 가능성이 높다.

linker description

/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x8004000, LENGTH = 2032K
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 320K
CCMRAM (rw)      : ORIGIN = 0x10000000, LENGTH = 64K
}

flash의 크기가 sector 0를 뺀 만큼 줄어든다.

2048 - 16 = 2032KB

5. 테스트

LED가 켜지고 메세지가 출력되고 main application으로 성공적으로 jump한다.

이제 uart 다운로드 및 flash write만 포팅하면 된다

의외로 금방 포팅할 수 있다. 이를 무료로 풀어주신 stm사장님께 경롓!


Posted by 쵸코케키

2017. 3. 2. 20:51 devel/개념

Linux Kernel DMA

주의

작성자 컴맹임

2017. 03. 02 작성. 지속적으로 커널문서가 업데이트 되니 주의 바람

아 빨리 급해요!!!

  1. dma mask bit 설정 및 확인
  2. 목적에 맞는 타입으로 할당 요청 혹은 매핑 요청
  3. 다 썼으면 해제 혹은 언맵
    drivers/base/dma-mapping.c 에서 EXPORT_SYMBOL 된거 골라서 쓰면 된다.
    include/linux/dma-mapping.h에서 골라써도 되고

참조

kernel DMA 관련 문서들 + 뇌내망상 + 구글링 + kernel code
DMA-API-HOWTO.txt, DMA-API.txt 등등

근데 생각해보니까 이렇게 다 짬뽕으로 섞어서 써놓으면 커널 문서 업데이트 되었을 때 어떻게 하지?망했….

CPU and DMA addresses


kernel에서 kmalloc, vmalloc 들을 사용해서 메모리 할당하면 보통 void* 타입의 virtual address 가 할당 된다.

우리가 흔히 어딘가에서 들어본 적 있을법한 TLB나 page table 등등 이런 애들은 virtual memory system이라고 한다. (TLB에서 스크롤을 내렸습…… 잠깐 기다리라)

이들의 역할은 virtual addressCPU physical address로 변환한다.
이 주소는 phys_addr_t 혹은 resource_size_t 같은 데이터 타입을 사용한다

kernel은 이 physical address를 사용해서 장치를 관리한다.
이 주소들을 보고 싶으면 아래의 커맨드를 사용해보시라

cat /proc/iomem

physical address는 곧바로 사용할 수는 없고 ioremap 함수 같은 것으로 virtual address로 매핑을 해서 사용해야 한다.

그런데 I/O 장치들은 bus address라는걸 쓴다.

대부분 다르지만 몇몇 시스템들은 bus addressCPU physical address와 동일하다. IOMMU 와 host bridge가 위의 두 주소를 서로 매핑해준다.

I/O 장치 관점에서 DMA는 bus address space를 사용하지만 일부의 공간만 DMA가 된다. 예를 들어 메모리와 PCI BAR를 64bit 지원하는 시스템이더라도 IOMMU를 사용할 것이고 얘는 32bit DMA address만 사용할꺼다.

예를 들어 PCI 장치는 BAR라는 영역을 가지고 있다.

만약 kernel이 PCI 장치를 읽고 싶으면 BAR의 주소 bus address를 physical address 로 변환하고 그 다음 ioremap 같은 함수로 physical address를 virtual address로 바꾸어 접근을 하면 된다.
그리고 ioread32 같은 함수를 사용하여 bus address의 PCI device 레지스터에 접근할 수도 있다.

만약 장치가 DMA 지원을 한다면 다음과 같이 사용한다.
kmalloc 같은거로 virtual memory 할당을 받아서 kernel 내부 함수 도움을 받아(virtual memory system) physical address즉 시스템 램 주소로 변환을 한다.

일부는 physical address가 bus address와 동일하여 곧바로 DMA가 가능하지만 대부분의 경우 IOMMU가 DMA 주소로 변환을 해야 한다.
IOMMU 제어를 일반인이 다할 수 없으니 kernel이 제공하는 DMA API를 쓰도록.

Linux 는 dynamic DMA mapping 도 된다. 이게 뭔소린지 모르겠는데 실시간으로 kmalloc 할당해서 DMA하고 free로 제거하고 이런게 된다는 이야기인듯.

요약 #1

  • Kernel - virtual address space
  • Virtual Memory System(TLB, page table, 등) - virtual address space <-> physical address 변환
  • CPU - physical address space
  • IOMMU, host bridge - physical address <-> bus address 변환
  • I/O Device - bus address space
  • DMA - bus address space의 일부 영역만 가능

DMA 자격증

이미 알겠지만 아무거나 DMA가 되는게 아니다.
일단 DMA는 dma_addr_t 타입의 주소로 가능하다고 정했다.

page allocator를 통해 메모리를 할당 받은 경우
예 : __get_free_page, kmalloc, kmem_cache_alloc 된다

vmalloc을 통해 할당 받은 경우는 안된다.
핵심은 물리적으로 선형이 아니라서 그러는데 뭐라고 설명해야 할지 모르겠다 원문 참조 바람

This means specifically that you may _not_ use the memory/addresses
returned from vmalloc() for DMA.  It is possible to DMA to the
_underlying_ memory mapped into a vmalloc() area, but this requires
walking page tables to get the physical addresses, and then
translating each of those pages back to a kernel address using
something like __va().  [ EDIT: Update this when we integrate
Gerd Knorr's generic code which does this. ]

vmalloc 에서 dma가 가능하도록 scatter gathering 처럼 각각 physical address를 획득 한 다음 IOMMU로 선형주소 인 것 처럼 붙여서 bus address에 매핑한다는 건지 잘 모르겠다 작업중이라는거 보니 꽤 이런 유사한 요구가 많았나보다

물론 kernel image address 즉 data, text, bss segment들도 당연히 안된다. 이런걸 하려고 시도 한게 변태 아닌가? stack, module image address 역시 안된다.

DMA addressing limitations

기본으로 kernel은 32bit DMA를 다 다룰 수 있다고 가정한다. 만약 64bit 가능한 장비를 위해서는 증가시켜야 할 수도 있겠다만 장비 자체 스펙 한계로 일부 영역으로 줄여야 할 수도 있다.
말이 헷갈리는데 64bit kernel 시스템이라고 해서 64bit DMA가 되는게 아니다.

PCI-X 는 IO bus가 PCI-X mode로 운영되기 위해서 64bit addressing(DAC) 가 필요하다.
DMA controller에게 설정을 교류하는 방법들이 있다.

int dma_set_mask_and_coherent(struct device *dev, u64 mask);
int dma_set_mask(struct device *dev, u64 mask);
int dma_set_coherent_mask(struct device *dev, u64 mask);
//dev - device, mask - 너의 장비 몇 bit 지원하니?

이런 함수들은 보통 다음과 같이 사용된다
pdev, pci 등등 probe -> &pdev->device를 인자로 사용, mask 를 시스템 환경에 맞게 설정
걍 kernel 에서 샘플 코드 보는게 빠를듯

    if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

if success
return 0
else

/* 보통 실패하는 원인들 */
1. DMA mask 설정 잘못함
2. DMA mode 자체 지원 안 함
3. 무시하고 쓰지마! -> 진짜 이렇게 나와있음 -.-;;;

64bit는 다음과 같은 형식으로 접근해보란다.

64bit streaming DMA 예제

    int using_dac;

    if (!dma_set_mask(dev, DMA_BIT_MASK(64))) {
        using_dac = 1;
    } else if (!dma_set_mask(dev, DMA_BIT_MASK(32))) {
        using_dac = 0;
    } else {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

64bit consistent allocation DMA 예제

    int using_dac, consistent_using_dac;

    if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) {
        using_dac = 1;
        consistent_using_dac = 1;
    } else if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        using_dac = 0;
        consistent_using_dac = 0;
    } else {
        dev_warn(dev, "mydev: No suitable DMA available\n");
        goto ignore_this_device;
    }

24bit DMA 예제

    if (dma_set_mask(dev, DMA_BIT_MASK(24))) {
        dev_warn(dev, "mydev: 24-bit DMA addressing not available\n");
        goto ignore_this_device;
    }

Types of DMA mappings

A. Consistent DMA mapping

Think of “consistent” as “synchronous” or “coherent”.
일반적으로 드라이버의 초기화에 매핑되고 끝날 때 언맵 된다. 이를 위해 하드웨어는 반드시 다음과 같은 것들을 보장해야 한다. device와 CPU는 data를 병렬로 access할 수 있어야 하고 둘 다 어떠한 명시적 software flushing 없이 데이터 업데이트를 확인할 수 있어야 한다.
(예를 들면 linux의 sync 명령어 같은 처리를 안 해도 되도록)

Consistent DMA mappings which are usually mapped at driver initialization, unmapped at the end and for which the hardware should   guarantee that the device and the CPU can access the data in parallel and will see updates made by each other without any explicit software flushing.

현재 기본은 DMA space의 하위 32bit consistent memory를 return 하는 것이다.
그러나 미래 호환성을 위해서 지금 잘 동작하더라도 consistent mask를 설정 해 두도록.
머라는건지……64bit 체크루틴 넣어두라고?

 The current default is to return consistent memory in the low 32
  bits of the DMA space.  However, for future compatibility you should
  set the consistent mask even if this default is fine for your
  driver.

Good examples of what to use consistent mappings for are:

- Network card DMA ring descriptors.
- SCSI adapter mailbox command data structures.
- Device firmware microcode executed out of main memory.

cpu가 명령어를 reorder 할 수 있으니까 순서가 중요한 코드의 경우 memory barrier를 잘 써라 이런 경고 인거 같음.

IMPORTANT: Consistent DMA memory does not preclude the usage of
proper memory barriers.  The CPU may reorder stores to consistent memory just as it may normal memory.  

Example: if it is important for the device to see the first word
of a descriptor updated before the second, you must do something like:

    desc->word0 = address;
    wmb();
    desc->word1 = DESC_VALID;

in order to get correct behavior on all platforms.

Also, on some platforms your driver may need to flush CPU write
buffers in much the same way as it needs to flush write buffers
found in PCI bridges (such as by reading a register's value
after writing it).

B. Streaming DMA mapping

Think of “streaming” as “asynchronous” or “outside the coherency domain”.
1회의 DMA 전송을 위해 일반적으로 사용된다는데? 사용 끝나면 언맵하고
하드웨어는 sequential access를 위해 최적화 할 수 있다(뭘 최적화???)

Good examples of what to use streaming mappings for are:

- Networking buffers transmitted/received by a device.
- Filesystem buffers written/read by a SCSI device.

이 매핑타입의 인터페이스의 경우 하드웨어에 하고 싶은거 다 해서 성능을 높일 수 있다.
이런 내용인거 같다

  The interfaces for using this type of mapping were designed in
  such a way that an implementation can make whatever performance
  optimizations the hardware allows.  To this end, when using
  such mappings you must be explicit about what you want to happen.

위의 두 DMA 매핑 타입들은 bus로부터 오는 alignment 제한은 없다. 물론 있는 모델도 있지만 말야.
또한 buffer가 cache line을 다른 데이터와 공유하지 않을 때 시스템과 DMA-coherent 하지 않은 cache는 더 일 잘할꺼임.

Also, systems with caches that aren't DMA-coherent will work better
when the underlying buffers don't share cache lines with other data.

뭔 소린지 잘 모르겠는데 예를 들어 네트워크 카드로부터 트래픽 데이터가 시스템 메모리로 들어오는데 cache에 계속 등록된다면 되려 system 전체 퍼포먼스에 악영향을 줄 수도 있다 이런 맥락으로 보면 되려나? 왜냐면 그 데이터는 cache update하는 의미가 없으니까
그래서 저래 써놓은건가 싶다.

이 링크를 참조해본다
http://junbenchmarking.tistory.com/entry/Linux-%EC%BB%A4%EB%84%90%EC%97%90%EC%84%9C-CPU-%EC%99%80-DMA-%EC%82%AC%EC%9D%B4

DMA API

먼저 DMA API를 사용하고 싶다면 이것부터 하시라

#include <linux/dma-mapping.h>

mask 설정 체크 하는 것은 윗쪽의 DMA addressing limitations 항목을 참고

Consistent DMA mapping

할당

void *
dma_alloc_coherent(struct device *dev, size_t size,
                 dma_addr_t *dma_handle, gfp_t flag)
void *
dma_zalloc_coherent(struct device *dev, size_t size,
                 dma_addr_t *dma_handle, gfp_t flag)

사용 예시

dma_addr_t    dma_handle;
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp);

이 함수는 RAM을 Bytes size만큼 consistent memory를 할당한다. __get_free_pages() 랑 비슷하다.

arguments

  • dev - 생략
  • size - Bytes 단위. 할당 받고 싶은 크기
  • dma_handle - 아래 return value에서 확인 바람.
  • flag - kmalloc 호출할 때 처럼 사용하는 flag. 심지어 동일하다.

return value

  • virtual address - 할당된 메모리 주소, CPU와 dma_handle을 통해 access할 수 있다
  • NULL - 뭐긴 뭐야 할당에 실패한거지**
  • dma_handle - 이 인자에는 device의 bus width와 동일한 unsigned int형이 type casting 된 DMA address가 전달된다.

CPU virtual address와 DMA address는 요구된 메모리 사이즈에 맞는 최소한의 PAGE_SIZE order 에 align되도록 보장한다.

예를 들어 63KB를 요청해도 PAGE_SIZE order에 align맞춰서 64KB가 할당 된다 이 소리인듯

만약에 page_size 보다 작은 크기가 필요하다면 dma_poll 인터페이스를 사용하시라.
일반적으로 할당에 성공하면 32bit virtual address가 return 된다. dma_pool 역시 마찬가지

몇몇의 플랫폼에서 consistent memory는 굉장히 비쌀 수 있다. 그리고 가장 작은 할당 크기도 PAGE_SIZE 만큼 클 수 있다. 그래서 너 반드시 한 번에 퉁쳐서 요청해야 한다(오역?).

/* you should consolidate your requests for consistent memory as much as possible. */

먼소린지 잘 모르겠는데 1KB 요청해도 PAGE_SIZE로 나올 수 있으니 필요한 자료형 합쳐서 한 번에 크게 할당 받으라는 소리 같은데?

그리고 이 함수는 cache effect를 걱정할 필요 없다. 다시말해 dma로 데이터가 들어왔을 때 cpu의 cache에 이전 데이터가 있지 않을까? 하는 걱정을 할 필요 없다.
단 device에 dma로 write할 때 memory와 cpu cache의 동기화를 위해 flush를 해야할 수도 있음에 유의하라.(아까는 걱정하지 말라매!)

해제

void
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
               dma_addr_t dma_handle)

별건 없구요 cpu_addr은 할당 받은 virtual address를 넣어야 한다.

Streaming Mapping

할당 및 해제 #1

dma_addr_t
dma_map_single(struct device *dev, void *cpu_addr, size_t size,
              enum dma_data_direction direction)

void
dma_unmap_single(struct device *dev, dma_addr_t dma_addr, 
                 size_t size, enum dma_data_direction direction)

dma_alloc_coherent와 다르게 이 함수는 메모리를 미리 kmalloc 같은거로 알아서 할당을 시킨 상태로 호출해야 한다. 그러면 DMA 주소가 리턴 된다.

arguments

  • dev - 생략
  • cpu_addr - 매핑할 메모리 주소. kmalloc 처럼 physically contiguous 해야 한다. vmalloc이나 scatter/gather 지원 불가.
  • size - 생략

DMA direction

  • DMA_BIDIRECTIONAL - direction isn’t known
  • DMA_TO_DEVICE - from: main memory. to: device
  • DMA_FROM_DEVICE - from: device. to: main memory
  • DMA_NONE - for debugging

streaming mapping만이 direction을 특정화 한다.
consistent mapping은 DMA_BIDIRECTION을 암묵적으로 사용하도록 한다.
direction을 명시화 하여 플랫폼 로우레벨 코드에서 최적화를 할 수 있다는 것 같다.

메모리의 DMA 주소는 반드시 device의 dma_mask이내여야 한다.
무슨 말인고 하니 우리가 cpu_addr로 virtual memory address를 넘겨야 하니까 그 메모리 주소를 미리 잘 device의 특징에 맞게 준비 해놓으라는 말이다.

예를 들면 x86 시스템에서 너가 ISA device 장치를 쓰고 싶어. 그러면 kmalloc으로 메모리 할당할 때 GFP_DMA flag를 써서 처음 16MB 이내 주소의 메모리를 할당받은 다음에 매핑을 요청해! 이런식으로 dma_map_single 호출 하기 전 virtual memory를 할당 받아둘 때 반드시 flag를 잘 설정해서 physical address가 device 스펙에 맞도록 준비를 잘 하자.

또한 위에서 말한 physical contiguity 와 dma_mask 제한은 플랫폼이 IOMMU를 가지고 있다면 적용이 안될 수도 있음

cache cohereny 관련 문제

주의: Memory coherency는 cache line 폭(width) 이라고 불리는 granularity에 의해 동작한다. 이 API에 매핑된 메모리를 올바르게 동작시키기 위해서는 매핑된 영역은 반드시 하나의 cache line boundary에 맞춰 시작해야하고 정확히 끝나야 한다. (하나의 캐시라인에 두개의 독립된 영역이 매핑되는 것을 막기 위해) 근데 문제가 컴파일 타임에는 cache line크기를 알 수 없기 때문에 API는 이 요건 사항을 강제 못한다. 그러므로 cache line 사이즈고뭐고 고려 안 하는 드라이버 개발자는 걍 page boundary에서 시작하고 끝나는 virtual memory 영역만 매핑하는걸 추천한다.

정확하게 이해가 안 가는데 만약에 ioremap등을 통해 물리적으로 선형인 메모리 공간을 얻었는데 이 공간을 2KB로 나누어서 map을 요청하는 변태짓을 했다고 가정하면 cache line width가 4KB라고 가정했을 때 다른 메모리 2개 내용이 한 번에 캐시 되기 때문에 바보된다고 하지 말라는거 같은데??

주의 사항 - direction flag

  • DMA_TO_DEVICE - 메모리에 쓸꺼 다 쓰고 sync 완료 한 뒤 device에 DMA 해야 함. 그 뒤 device에게 메모리는 마치 read-only 인 것 처럼 취급 당한다. 만약에 device가 메모리에 write할 일이 있으면 반드시 DMA_BIDIRECTION으로 설정하도록!
  • DMA_FROM_DEVICE - 반대로 driver가 data에 access해서 읽기 전, 혹은 다 읽어 간 다음 sync 해야 한다. driver입장에서 메모리는 read-only 로 간주된다.
  • DMA_BIDIRECTIONAL - 특별 관리 요망. driver는 device에서 데이터가 발송 된 이후 수정될지 아닐지 쓸지 머할지 확신할 수 없을 때 사용. 그렇기에 항상 device와 메모리 사이에 각각 2회 sync를 해줘야 한다. A. 메모리에서 device로 데이터 발송 되었을 때 sync(확실히 하기 위해 모든 메모리의 변경 사항이 CPU에서도 flush 되야 한다. cache 문제 때문인거 같음.), B. device에서 메모리로 데이터 발송 되었을 때 반드시 sync요망(확실히 하기 위해 device에서 변화가 감지되면 어떤 CPU cache line들이라도 데이터가 업뎃 되야 한다).

걍 원문 보는게 이해가 빠를지도

DMA_BIDIRECTIONAL requires special handling: it means that the driver
isn't sure if the memory was modified before being handed off to the
device and also isn't sure if the device will also modify it.  Thus,
you must always sync bidirectional memory twice: once before the
memory is handed off to the device (to make sure all memory changes
are flushed from the processor) and once before the data may be
accessed after being used by the device (to make sure any processor
cache lines are updated with data that the device may have changed).

streaming mapping 예제

To map a single region, you do:

struct device *dev = &my_dev->dev;
dma_addr_t dma_handle;
void *addr = buffer->ptr;
size_t size = buffer->len;

dma_handle = dma_map_single(dev, addr, size, direction);
if (dma_mapping_error(dev, dma_handle)) {
    /*
     * reduce current DMA mapping usage,
     * delay and try again later or
     * reset driver.
     */
    goto map_error_handling;
}

and to unmap it:

dma_unmap_single(dev, dma_handle, size, direction);

dma_mapping_error 함수 안 쓰고 직접 주소값 비교하거나 errno 비교 같은짓 하지 마라

위 예제 처럼 있는 그대로 잘 쓰도록.

할당 및 해제 #2

dma_addr_t
dma_map_page(struct device *dev, struct page *page,
            unsigned long offset, size_t size,
            enum dma_data_direction direction)

void
dma_unmap_page(struct device *dev, dma_addr_t dma_address, 
               size_t size, enum dma_data_direction direction)

page를 직접 연결 시킬 수도 있다.

그런데 offset이랑 size 는 각 page 매핑에서 사용되는 파라메터인데 cache width를 잘 모르겠다 싶으면 걍 이 함수를 쓰지 마

할당 및 해제 #3

dma_addr_t
dma_map_resource(struct device *dev, phys_addr_t phys_addr, 
                 size_t size, enum dma_data_direction dir, unsigned long attrs)

void
dma_unmap_resource(struct device *dev, dma_addr_t addr, size_t size,
           enum dma_data_direction dir, unsigned long attrs)

MMIO device를 다룰 때 쓰시라. RAM은 매핑하면 안된다. 이 말이 바로 이해가 안간다면 MMIO에 대해서 검색 ㄱㄱ

page를 직접 지정할 수도 있다.

다만 HIGHMEM을 가지고는 할 수 없다는데 어차피 64bit에서 돌릴꺼면 신경쓰지 말자

struct device *dev = &my_dev->dev;
dma_addr_t dma_handle;
struct page *page = buffer->page;
unsigned long offset = buffer->offset;
size_t size = buffer->len;

dma_handle = dma_map_page(dev, page, offset, size, direction);
if (dma_mapping_error(dev, dma_handle)) {
    /*
     * reduce current DMA mapping usage,
     * delay and try again later or
     * reset driver.
     */
    goto map_error_handling;
}

...

dma_unmap_page(dev, dma_handle, size, direction);

예제

my_card_setup_receive_buffer(struct my_card *cp, char *buffer, int len)
{
    dma_addr_t mapping;

    mapping = dma_map_single(cp->dev, buffer, len, DMA_FROM_DEVICE);
    if (dma_mapping_error(cp->dev, mapping)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling;
    }

    cp->rx_buf = buffer;
    cp->rx_len = len;
    cp->rx_dma = mapping;

    give_rx_buf_to_card(cp);
}

...

my_card_interrupt_handler(int irq, void *devid, struct pt_regs *regs)
{
    struct my_card *cp = devid;

    ...
    if (read_card_status(cp) == RX_BUF_TRANSFERRED) {
        struct my_card_header *hp;

        /* Examine the header to see if we wish
         * to accept the data.  But synchronize
         * the DMA transfer with the CPU first
         * so that we see updated contents.
         */
        dma_sync_single_for_cpu(&cp->dev, cp->rx_dma,
                    cp->rx_len,
                    DMA_FROM_DEVICE);

        /* Now it is safe to examine the buffer. */
        hp = (struct my_card_header *) cp->rx_buf;
        if (header_is_ok(hp)) {
            dma_unmap_single(&cp->dev, cp->rx_dma, cp->rx_len,
                     DMA_FROM_DEVICE);
            pass_to_upper_layers(cp->rx_buf);
            make_and_setup_new_rx_buf(cp);
        } else {
            /* CPU should not write to
             * DMA_FROM_DEVICE-mapped area,
             * so dma_sync_single_for_device() is
             * not needed here. It would be required
             * for DMA_BIDIRECTIONAL mapping if
             * the memory was modified.
             */
            give_rx_buf_to_card(cp);
        }
    }
}

별건 아니고 dma buffer를 setup하고 packet이 들어와서 interrupt가 뜨면 먼저

Driver에서 더이상 virt_to_bus()나 bus_to_virt() 같은 api를 쓰면 안된다.
더이상 bus_to_virt() 같은 dynamic DMA mapping scheme은 없단다.
반드시 dma_alloc_coherent(), dma_pool_alloc(), dma_map_single() 같은 함수를 써야 한단다. (dma_map_sg()는 하드웨어로 다이나믹 매핑 해주는거고)

Here, “offset” means byte offset within the given page.

스캇…리스트……..가 아니라 scatterlists 기법도 있다.

할당 및 해제 #4 - scatter/gathering

int
dma_map_sg(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction direction)

void
dma_unmap_sg(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction direction)

arguments

  • dev - 생략

  • sg - sg_init_table을 통해 할당하고 set_set_page 혹은 set_set_buf를 통해 설정한 sg list 변수로 추정된다. 이 부분은 별도 항목으로 굉장히 길다.

  • nents - 필요한 sglist 엔트리의 수(the number of entries in the sglist.)

  • direction - 생략

return value

정상 : 매핑된 DMA address segments 수

실패 : 0

주의 : 만약 sg list의 몇몇 entry가 물리적으로 혹은 virtually하게 인접해있고 IOMMU가 그들을 하나의 entry로 매핑한다면 인자로 넣은 nents 값 보다 적은 값이 리턴될 수 있다.

sg를 중복해서 매핑하는 변태짓 하지 마라. 이전 매핑 내용 파괴된다.

예제 - scatter/gathering

With scatterlists, you map a region gathered from several regions by:

int i, count = dma_map_sg(dev, sglist, nents, direction);
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) {
    hw_address[i] = sg_dma_address(sg);
    hw_len[i] = sg_dma_len(sg);
}

혹은 sg_next 같은 API도 있다

lib/scatterlist.c 참고

매우 복잡 예제 - drivers/spi/spi.c

  1. vmalloc인지 kmap을 사용한 제품인지 메모리 종류를 확인한다.
  2. sg_alloc_table을 호출해서 적당히 sg table을 할당 받는다.
  3. 메모리를 page로 변환(vmalloc_to_page 혹은 kmap_to_page)
  4. sg_set_page, sg_set_buf로 sg table의 sg list 설정을 한다
  5. dma_map_sg
  6. 하 시밤 개 복잡

SYNC

void
dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, 
                        size_t size, 
                        enum dma_data_direction direction)
void
dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, 
                           size_t size, 
                           enum dma_data_direction direction)
void
dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, 
                    int nents, enum dma_data_direction direction)
void
dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
                       int nents, enum dma_data_direction direction)

연속적인 싱글 혹은 sg 매핑을 sync 한다. 파라메터는 single 매핑 api 호출했을 때와 동일 해야 한다. 그리고 dma_handle과 size 파라메터를 사용할 수 있는데 부분 sync를 위해 다르게 사용해도 ok

DMA ATTR

gg……

DMA-attributes.txt 를 직접 확인하기 바란다. 특수한 경우에 사용한다.

DMA_ATTR_NO_KERNEL_MAPPING, DMA_ATTR_NON_CONSISTENT 등등 희안한게 많다.

Handling Errors

Example 1:
    dma_addr_t dma_handle1;
    dma_addr_t dma_handle2;

    dma_handle1 = dma_map_single(dev, addr, size, direction);
    if (dma_mapping_error(dev, dma_handle1)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling1;
    }
    dma_handle2 = dma_map_single(dev, addr, size, direction);
    if (dma_mapping_error(dev, dma_handle2)) {
        /*
         * reduce current DMA mapping usage,
         * delay and try again later or
         * reset driver.
         */
        goto map_error_handling2;
    }

    ...

    map_error_handling2:
        dma_unmap_single(dma_handle1);
    map_error_handling1:
Example 2: (if buffers are allocated in a loop, unmap all mapped buffers when mapping error is detected in the middle)
    dma_addr_t dma_addr;
    dma_addr_t array[DMA_BUFFERS];
    int save_index = 0;

    for (i = 0; i < DMA_BUFFERS; i++) {

        ...

        dma_addr = dma_map_single(dev, addr, size, direction);
        if (dma_mapping_error(dev, dma_addr)) {
            /*
             * reduce current DMA mapping usage,
             * delay and try again later or
             * reset driver.
             */
            goto map_error_handling;
        }
        array[i].dma_addr = dma_addr;
        save_index++;
    }

    ...

    map_error_handling:

    for (i = 0; i < save_index; i++) {

        ...

        dma_unmap_single(array[i].dma_addr);
    }

별건 아니고 이런 형식으로 작성하면 좋다. 에러났을 때 이전까지 매핑했던걸 해제하거라.

Platform Issues

1. Struct scatterlist Requirements

scatterlist 즉 CONFIG_NEED_SG_DMA_LENGTH는 IOMMU가 지원해야 한다.

2. ARCH_DMA_MINALIGN

Architecture는 반드시 kmalloc으로 할당한 buffer는 DMA-safe하도록 확증해야 한다

우리는 할 수 없지만 일단 이렇게 알고 있자. kmalloc 할당된 메모리는 다 DMA 되는구나!

만약 fully DMA-coherent 하지 않는다면. 예를 들어 CPU cache와 main memory의 데이터가 같다고 Architecture가 보증하지 않는 시스템의 경우 ARCH_DMA_MINALIGN 가 반드시 설정 되어야 한다

이 설정을 하면 kmalloc으로 할당한 메모리는 cache line을 다른 것들과 공유하지 않는다.

예제는 arch/arm/include/asm/cache.h 에서 확인 바란다

ARCH_DMA_MINALIGN은 DMA 메모리 alignment constraints에 관한 것이다

일반적인 data alignment constraints에 대해 걱정 ㄴㄴ해

뭐라는건지 모르겠다

cache.h 주석 보면 kmalloc에 의해 할당된 메모리가 DMA에 사용될 수 있으니 우리는 cache aligned 되도록 모든 할당을 맞춰주겠노라. 반면에 관련 되어있지 않은 코드는 DMA 전송이 끝나기 전 cache에 있는 오래된 데이터를 읽어가는 문제를 일으킬 수 있다.

할당하는 메모리 크기를 cache align 맞추는거랑 cache update랑 먼 상관일까?


Posted by 쵸코케키



Linux Input Event Driver

주의

내공 부족으로 정확하지 않는 내용이 다수 있을 수 있음.
Subsystem이라고 부를 정도로 정말 거대하다.

0. The Input Subsystem

출처 : Essential Linux Device Drivers

hw -> Transfer Layer{spi, i2c, ….} -> input driver -> i2c core api -> input event driver -> Evdev Interface -> application

내가 코드 보며 이해한 바로는 이런데 실제로는 어떨련지 모르겠다.

굳이 input driver와 input event driver를 나누지 않고 하나로 합쳐서 작성된 것도 있다.


1. The Evdev Interface

include/linux/input.h
input subsystem의 자료형 및 정의가 있다

struct input_event {
  struct timeval    time;    //timestamp
  __u16                type;
  __u16                code;
  __s32                value;    //event value
}

이 구조체 형태로 /dev/input/ 디렉토리에 이벤트 값이 전달 된다.
주의 : 한 회에 한 종류 값만 전달 된다.

무슨 의미인고 하니 read로 읽을 때 이벤트 당 1개씩 값이 전달 된다는 이야기

예를 들어 (x, y) 좌표에 (12, 34) 클릭이 발생한 경우
data#1 - 클릭 발생
type = EV_KEY,
code = BTN_TOUCH,
value = 1

data#2 - x 좌표
type = EV_ABS,
code = ABS_X,
value = 12

data#3 - y 좌표
type = EV_ABS,
code = ABS_Y,
value = 34

data#4 - 이벤트 끝
type = EV_SYN, //event1 finished
code = SYN_REPORT,
value = 0

EV_SYN으로 하나의 이벤트 전송 종료를 알린다.

이벤트 리포팅을 위해

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
...
static inline void input_sync(struct input_dev *dev);

여러 이벤트 종류에 따라 각기 다양하게 함수들이 있다.


사용 예시
drivers/input/joystick/a3d.c

static void a3d_read(struct a3d *a3d, unsigned char *data)
{
  ...
  input_report_key(dev, BTN_RIGHT, data[2] & 1);
  input_report_key(dev, BTN_LEFT, data[3] & 2);
  ...
  input_report_abs(dev, ABS_X, ....);
  ...
  input_sync(dev);
}

data를 얻어와서 event 를 evdev로 보내는 모습

이 드라이버의 경우 data는 별도의 input driver에게 얻는다.

struct input_dev {
  ...
  struct input_id    id;
  unsigned long evbit[...];
  unsigned long keybit[...];
  ...
  int (*open)(struct input_dev *dev);
  void (*close)(struct input_dev *dev);
  ...
}

엄청나게 거대하므로 각각 역할은 커널소스의 주석을 직접 확인하기 바란다.


2. Example Source

drivers/input/keyboard/pxa27x_keypad.c

이 코드가 가장 보기 쉬웠어요

module_platform_driver
-> probe
-> request_mem_region, ioremap
-> input_allocate_device, input_dev 설정, request_irq
-> input_register_device

irq_handler - mmio read, input_event, input_report_key, input_sync
대략 이런 흐름인 것 같다.


3. Linux input event read Application code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <sys/time.h>
#include <string.h>

int main()
{
    int fd, ret;
    int x, y;
    const char* evdPath = "/dev/input/event3";
    struct input_event iev[3];

    fd = open(evdPath, O_RDONLY);

    if(fd < 0) {
        perror("error");
        return -1;
    }

    while(1) {
        ret = read(fd, iev, sizeof(struct input_event)*3);
        if(ret < 0) {
            perror("error");
            break;
        }

        if(iev[0].type == EV_REL && iev[0].code == REL_X)
            x = iev[0].value;
        if(idev[1].type == EV_REL && iev[1].code == REL_Y)
            y = iev[1].value;

        printf("x:%d, y:%d\n", x, y);
        printf("%hu, %hu, %d\n", iev[0].type, iev[0].code, iev[0].value);
        printf("%hu, %hu, %d\n", iev[1].type, iev[1].code, iev[1].value);
        printf("%hu, %hu, %d\n", iev[2].type, iev[2].code, iev[2].value);
    }

    close(fd);
    return 0;
}

RUN

4, 3
2, 0, 4
2, 1, 3
0, 0, 0
...

별 설명할 꺼리가 없다……
다만 왜 event3로 생성이 되는지 그건 잘 모르겠다.


4. virtual input event driver code

이름은 거창한데 사실 별거 없다
hw 에서 인터럽트 혹은 데이터를 안 받았는데 그냥 데이터 들어왔다고 evdev에게 이벤트를 알리는 것이다.

const char                 *pdev_name = "ietest";
struct platform_device    *pdev;
struct ev_test {
      struct input_dev    *input_dev;
      struct task_struct    *task;
};

int read_data_from_sensor(void)
{
  static int roll = 0;
  return roll++%2 ? 3 : 4;
}

void set_input_device_property(struct input_dev *dev)
{
  set_bit(EV_REL, dev->evbit);
  set_bit(REL_X, dev->relbit);
  set_bit(REL_Y, dev->relbit);
}

void send_event_msg_to_evdev(struct input_dev *dev)
{
  input_report_rel(dev, REL_X, read_data_from_sensor());
  input_report_rel(dev, REL_Y, read_data_from_sensor());
  input_sync(dev);
}

static int hw_fake_int_handler(void *arg)
{
  struct ev_test *event_ctrl = (struct ev_test*) arg;
  struct input_dev *idev = event_ctrl->input_dev;

  while(!kthread_sould_stop())        //check stop signal
  {
    send_event_msg_to_evdev(idev);
    msleep_interruptible(300);
  }

  return 0;
}

/* REMOVED ERROR CHECKING ROUTINE */
static int inputevent_test_init(void)
{
  int ret;
  struct ev_test        *event_ctrl;
  struct task_struct    *task;
  struct input_dev        *idev;

  /* platform or i2c or spi or serio or etc ... */
  pdev = platform_device_register_simple(pdev_name, -1, NULL, 0);

  event_ctrl = kzalloc((sizeof(struct ev_test)), GFP_KERNEL);

  idev = input_allocate_device();
  set_input_device_property(idev);
  input_register_device(idev);

  task = kthread_run(hw_fake_int_handler, event_ctrl, 
                          "ieint_%s_#%d", pdev_name, 1);

  event_ctrl->input_dev = idev;
  event_ctrl->task = task;
  dev_set_drvdata(&pdev->dev, event_ctrl);

  return 0;
}

static void inputevent_test_exit(void)
{
  struct ev_test    *event_ctrl = (struct ev_test*)dev_get_drvdata(&pdev->dev);
  kthread_stop(event_ctrl->task);

  input_unregister_device(pdev);  
}

module_init(inputevent_test_init);
module_exit(inputevent_test_exit);

이 코드에서는 pdev를 가상으로 만들었지만 실제로는 여러 통신 방식을 통해 probe 되고 거기에 interrupt handler를 등록해서 input event 를 보내는 식으로 처리하는 것 같다
인터럽트 핸들러 대신 가상으로 커널 쓰레드를 만들어서 계속 좌표를 보내도록 개발했다


기타 github에 올려본 예제

https://github.com/chocokeki/input_event_driver

Posted by 쵸코케키
이전버튼 1 2 3 이전버튼

블로그 이미지
chocokeki
쵸코케키

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함