Zynq SoC Training – DMA and Animation

Acknowledgement (Korean)
아래의 글은 Ali Aljaani가 운영하는 블로그인 embedded centric의 “Zynq SoC Training”을 번역한 문서입니다. 한글 번역 및 공개를 허용한 Ali Aljaani에게 감사의 말을 전합니다.

Acknowledgement (English)
This post is a translated version of “Zynq SoC training” in embedded centric. Thank you Ali Aljaani for allowing me to translate nice posts. I’m Taekyung Heo, and I translated this post into Korean.

Contact information (Author)
Ali Aljaani’s blog: http://embeddedcentric.com/
Ali Aljaani’s mail: alialjaani@embeddedcentric.com
Ali Aljaani’s LinkedIn: https://www.linkedin.com/in/ali-aljaani-a7130099
Ali Aljaani’s github: https://github.com/ama142


이번 실습에서는 프로세서의 간섭 없이 메모리에 대량의 데이터를 넣고 빼내는 방법에 대해 알아볼 것이다. Zynq 칩에는 여러 개의 Direct Memory Controller가 있는데, 이번 실습에서는 DMAC을 살펴볼 것이다. DMAC은 칩의 PS-side에 속한다. DMA 전송을 시작하기 위한 단계에 대해 알아볼 것이다. 우선 DMAC를 사용해 특정 문자(외계인 Alex)를 디스플레이 컨트롤러의 메모리 영역에 보내는 어플리케이션을 개발할 것이다(OLED 스크린이 Alex가 이동하는 것을 보여준다). 다음으로, 푸쉬 버튼을 사용해 Alex를 제어하는 어플리케이션을 개발한다.

lab6-design-flow1


Lab6 Design Flow

lab6-block-diagrm1


Lab6 Block Diagrm

lab6-board-interface


Lab6 Board Interface

Direct Memory Access (DMA) 컨트롤러는 임베디드 시스템과 일반 컴퓨팅 플랫폼에서 많이 사용되고 있다. 이를 사용해 프로세서의 부담을 줄이고, 시스템의 반응성을 높일 수 있다. 일반적으로 DMA 컨트롤러는 프로세서의 간섭 없이 메모리에 데이터를 전송할 수 있도록 하는 역할을 한다. DMA를 적절히 사용하면 시스템 성능을 크게 향상시킬 수 있다. DMA는 대량의 정보를 다루는 장치들에서 대부분 사용되고 있다: 비디오, 네트워크, USB, 디스크 드라이브 컨트롤러 등. Zynq 칩에는 여러 개의 DMA 컨트롤러가 있고, 이 실습에서는 DMAC을 살펴볼 것이다. DMAC은 칩의 PS-side에 속한다. 이는 Zynq의 중심 인터커넥트에 연결되어 있으며, 데이터 전송을 위해 AXI 버스를 사용한다. DMAC는 64-bit AXI를 사용해 시스템 메모리와 Zynq의 programmable logic(PL) 사이에 데이터를 전송한다. DMAC에는 여덟 개의 채널이 있으며, 따라서 여덟 개의 독립적인 장치를 제어할 수 있다. 아래의 그림에서 빨간 동그라미로 표시된 부분이 해당 채널을 나타낸다.

zynq-hardware-architecture-dmac-circled-in-red


Zynq Hardware Architecture – DMAC circled in red

DMAC을 사용하면, 프로세서가 한 번 전송을 설정하면 다량의 데이터를 프로세서 간섭 없이 전송 가능하다. 메모리 복사 지점 및 대상은 시스템 어디라도 가능하다(PS 또는 PL). DMAC의 메모리 맵은 DDR, OCM(On Chip Memory), 선형 주소의 Quad-SPI 읽기 메모리, SMC(Static Memory Controller) 메모리 그리고 PL 주변장치 또는 M_GP_AXI 인터페이스에 붙여진 메모리 모두를 포함한다. DMAC은 내부적으로 특별한 프로세서 (DMA Instruction Execution Engine)을 갖고 있으며, 이는 DMA 전송을 위한 유연한 명령어 집합을 제공한다. DMAC의 블록 다이어그램은 아래와 같다.

dmac-block-diagram


DMAC Block Diagram

DMAC의 드라이버 (xdmaps)는 DMA 명령어 실행 엔진과 쉽게 통신할 수 있도록 해준다. DMA 전송을 설정하기 위해서는 다음의 단계가 수행되어야 한다:

1. 복사하고자 하는 데이터가 있는 메모리 주소를 명시한다.
2. 전송하고자 하는 데이터의 길이를 설정한다.
3. 데이터를 전송하고자 하는 목적지 메모리 주소를 명시한다.
4. 데이터 전송이 완료되었을 때 호출될 done 핸들러를 작성한다.
5. 데이터 전송에 실패했을 때 호출될 fault 핸들러를 작성한다 (기본 핸들러는 드라이버에서 제공한다).
6. 이 데이터들을 DMA 명령어 실행 엔진에 전송하고, 데이터 전송을 시작한다.

이번 실습에서는 Alex라는 이름의 외계인이 돌아다니는 애니메이션을 DMA 전송을 사용해 OLED에 띄우고자 한다. Alex 애니메이션은 아래 그림과 같이 약간의 다른 비트맵을 화면상에 보여줌으로써 만들어낼 수 있다:

alexs-bitmaps1


Alex’s Bitmaps

Bitmap 1Bitmap 2가 순차적으로 OLED에 짧은 시간 안에 보여진다면, 이는 Alex의 다리가 움직이는 것처럼 보이게 하는 효과를 낼 것이다. 이 두 개의 비트맵을 번갈아 다른 위치에 보여줌으로써 Alex가 걷는 것처럼 보이게 할 수 있다. 두 개의 비트맵은 ZedboardOLED 컨트롤러에 저장될 것이다. 현재 문자 라이브러리에는 32개의 여유 공간이 있고, 여기에 이 비트맵들을 저장할 수 있다. Bitmap 1은 ASCII 코드 24, Bitmap 2는 ASCII 코드 25이다. 0x18 또는 0x19를 특정 주소에 씀으로써, Alex를 OLED의 특정 위치에 나타나도록 할 수 있다. lab3에서 네 개의 페이지가 있고, 각 페이지는 16개의 위치가 있음을 확인했다. 따라서 Alex가 나타날 수 있는 위치는 총 64개이다 (16×4).

data-registers-and-screen-physical-dimensions-relation


Data Registers

예를 들어, slv_reg4의 첫 번째 바이트에 0x18을 전송함으로써 Alex가 두 번째 페이지의 첫 번째 위치에 나타나도록 할 수 있다. Zedboard OLED 드라이버를 사용해 이같은 작업을 수행할 수 있다. print_char(ASCII code, page, position) 함수를 사용하면 된다. “print_chr(0x18, 1 ,0)”

alex-at-second-pagepage1


Alex at second page(Page1)

Alex가 어느 위치에 있고, 어떤 동작을 하고 있는지 (Bitmap 1, 2 중 무엇인지)에 대한 정보는 all_frames라는 파일에 저장되어 있다. 이 파일은 SDK를 사용해 DDR3 메모리의 특정 영역(Region A)에 불러올 수 있다.

frames-12


all_frames file ( Opened using HxD Editor)- Click to enlarge

해당 파일의 이진 내용을 보려면 HEX 에디터를 사용하면 된다. HxD 에디터 프로그램을 추천한다.

frame은 ZedboardOLED 컨트롤러의 메모리 영역 (68 bytes)에 대한 완벽한 이미지이다. 왜 68 byte인가? 4개의 페이지 x 16개의 위치 + 제어 레지스터 4 bytes. all_frames 파일 안에는 12개의 프레임이 저장되어 있다. DMA 컨트롤러는 이러한 프레임들을 주기적으로 ZedboardOLED 컨트롤러의 memory-mapped 영역 (Reion B)로 복사하는 것이다. 이를 사용해 Alex를 이동하도록 만들 수 있다.

dma-invloved-memory-regions


DMA-involved memory regions


프로세서는 DMA에게 명령 구조(XDmaPS_Cmd)를 전달함으로써 DMA와 통신한다. 명령 구조는 소스(Region A)와 목적지(Region B)의 주소로 채워진다. 이 명령은 XDmaPs_Start (XDmaPs * InstPtr,unsigned int Channel,XDmaPs_Cmd * Cmd,int HoldDmaProg) 함수를 통해 실제로 DMA 명령어 실행 엔진에 전달된다.
이 실험을 위한 하드웨어 설정은 이전의 실습(Zynq SoC Training – Hardware Timers)과 동일하다. 타이머는 새로운 프레임의 주기를 맞추기 위해 사용되고, GPIO는 두 번째 어플리케이션 (Alex 제어)를 위해 사용된다. UART는 디버깅 인터페이스로 사용할 수 있다. DMAC은 PS에 있으며, 기본적으로 활성화되어 있다. 따라서 이전 실습 파일에서 하드웨어 부분을 따로 변경할 것은 없다 (이전의 실습을 하지 않았으면 내 GitHub 페이지에서 이 파일을 받을 수 있다).

목표
1. Zynq 칩의 PS에서 사용할 수 있는 DMA 컨트롤러 소개
2. 두 개의 메모리 영역 사이에 DMA 컨트롤러를 사용해 데이터 전송
3. xdmaps 드라이버를 사용해 다른 함수를 호출하는 방법 확인
4. SDK를 사용해 Zedboard DDR 메인 메모리에 데이터 파일을 다운로드
5. 애니메이션 개념 소개
6. SDK에서 여러 개의 어플리케이션 생성하는 방법 학습
7. 존재하는 코드에 원하는 기능을 추가하는 방법 연습

실습 단계
A. Lab5의 실습 디렉토리를 복사
1. “C:\embeddedcentric” 디렉토리에 새로운 폴더를 생성하고, “lab6″로 이름을 변경한다.
2. 이전의 실습 디렉토리인 “C:\embeddedcentric\lab5″를 연다. “lab4.sdk” 파일을 제외한 모든 파일을 복사한다 (Ctrl+A로 모든 파일을 선택하고, Ctrl 키를 사용해 “lab4.sdk”를 선택함으로써 이를 선택 해제한다). Note: lab5 파일이 없으면 내 GitHub 페이지에서 다운로드할 수 있다.

copy-lab5-content


Copy Lab5 content

3. 복사한 파일들을 “C:\embeddedcentric\lab6″에 붙여넣는다.

paste-content-in-lab6-folder


Paste content in lab6 folder

4. “lab4.xpr”을 더블 클릭해 Vivado를 연다. IMPORTANT NOTE: Vivado 프로젝트 파일의 이름을 수정하면 안 된다. 여섯 번째 실습이지만 “lab4.xpr”로 유지해야 한다. 이 파일의 이름을 변경하는 것은 문제를 유발할 수 있다.

B. ZedboardOLED 문자 라이브러리를 수정해, Alex의 비트맵을 추가
1. Sources 창에서 Coefficient Files 그룹을 확장하고 charLib.coe를 더블 클릭해 연다. 이 파일은 ZedboardOLED 컨트롤러의 문자 라이브러리의 정적 데이터를 갖고 있다.

charlib-coe-characters-library1


charLib.coe Characters Library

2. (27)과 (28)번째 줄을 다음과 같이 변경한다. 이 줄은 Alex의 32bits(8×8) 비트맵을 나타낸다. Save를 클릭한다.

98,5C,B6,5F,5F,B6,5C,98,
18,DC,36,5F,5F,36,DC,18,

27과 28을 선택한 것은 Alex 비트맵을 ASCII 코드 24와 25에 연관시키기 위함이다. ASCII 코드와 charLib.coe의 주소에는 3의 오프셋이 있기 때문에, 3이 더 큰 27과 28에 저장한다.

Note: 내 GitHub 페이지에서 소스 코드를 다운로드했다면, 이 단계는 이미 되어있을 것이다.

C. 변경 반영을 위해 Bitstream 재생성
1. Vivado의 하단에 있는 Design Runs에서 synth_1을 우클릭하고, Reset Runs를 클릭한다.

reset-runs1


Reset Runs- synth_1

2. 확인 메시지가 뜰 것이다. Delete the generated files in the working directory를 체크하고, Reset을 클릭한다.

reset-runs-confirmation1


Reset Runs Confirmation

3. synth_1을 다시 우클릭하고, Generate Bitstream을 클릭한다.

generate-bitstream-from-design-runs


Generate Bitstream from Design Runs


합성과 구현이 완료되기까지 다소 시간이 걸릴 것이다 (10분 정도).

D. 하드웨어 설계를 SDK에 내보내기
1. File > Export > Export Hardware를 클릭한다. 반드시 Include bitstream을 선택하도록 한다.

export-hardware-to-sdk


Export Hardware to SDK

2. File>Launch SDK를 선택한다. Launch SDK 대화창이 열릴 것이다. 기본 설정을 그대로 두고, OK를 클릭한다. 설계와 관련된 하드웨어 파일들이 SDK에 내보내질 것이고, 이제 소프트웨어 개발을 시작할 수 있다.

시스템의 하드웨어 설정은 완료되었으며, Vivado를 닫아도 된다.

E. SDK에서 작업하기 (Bitstream 다운로드 및 두 개의 어플리케이션 실행)
1. SDK에서 File > New > Application Project를 선택한다.
2. Application Project 창에서 다음과 같이 설정값을 입력한다.

new-standalone-c-project-animation_test


New Standalone C Project- animation_test


다음 창에서 Empty Application을 선택하고 Finish를 클릭한다. 이는 BSP와 관련된 드라이버를 컴파일할 것이다.

3. animation_test 디렉토리를 확장하고, src 디렉토리를 우클릭해 New->Source File을 선택한다.

new-c-source-file-lab6


New C Source File -lab6.c

4. 다음 창의 Source file에 “lab6.c”를 입력하고 Finish를 클릭한다. 이는 src 디렉토리에 빈 C 파일을 생성할 것이다.

lab5-c-empty-source-file-created-in-sdk1


lab5.c empty source file created in SDK

5. SDK의 lab6.c의 편집 화면에서 lab6.c의 내용을 붙여넣고 Save을 클릭하거나 Ctrl+S를 눌러 저장한다. 이를 통해 lab6 application과 이에 대한 BSP가 자동적으로 컴파일된다. 이렇게 생성된 실행 파일은 lab5.c의 확장된 버전이며, Zynq의 PS-side에서 실행된다. lab5.c 위에 추가된 부분에 대해 표시되어 있다. 추가된 부분은 XDma_Config(), DmaISR(), IntcInitFunction()과 TMR_Intr_Handler()를 포함한다.
XDma_Config()는 DMA 명령을 생성한다. 명령은 9개의 필드를 가지며, 두 개의 필드는 채널 제어(ChanCtrl) 필드와 DMA block descriptor(BD) 필드이다. DMA block descriptor 필드는 세 개의 파라미터를 갖는다:
(1) 소스 메모리 시작 주소 (SrcAddr)
(2) 목적지 메모리 시작 주소 (DstAddr)
(3) 블록 내의 바이트의 수 (Length)

dma-block-descriptorbd-field


DMA block descriptor(BD) field


반면에, 채널 제어 필드(ChanCtrl)은 AXI 버스 트랜잭션과 관련된 파라미터를 갖는다.

channel-control-chanctrl-field


DMA Channel Control (ChanCtrl) field

XDma_Config()는 done 핸들러 (DMA 트랜잭션이 끝나면 호출되는 함수)이고, DMA를 시작하는 역할을 한다.

DmaISR() DMA 트랜잭션 성공 시마다 호출된다. DMS ISR에서는 (DMA stage # passed)라는 메시지를 UART에 전송한다. #는 프레임 수를 의미한다.

IntcInitFunction()는 DMA 컨트롤러에 인터럽트를 활성화하는 코드를 갖고 있으며, 이는 GIC 포트에 대응된다. 또한 이 함수는 DMA에 대한 fault 핸들러를 설정한다 (DMA 트랜잭션이 실패하면 호출되는 함수)

TMR_Intr_Handler()는 지금까지의 실습에서 했던 것과 완전히 다른 것을 한다. 이 함수는 Region A에서 Region B로 복사될 다음 프레임의 주소를 관리하는 역할을 한다(frame_pointer). 그리고 12개의 프레임이 복사될 때까지 다른 DMA 트랜잭션을 시작을 맡는다.

tmr_intr_handler-in-lab6


TMR_Intr_Handler() in Lab6

6. Xilinx Tools-> Program FPGA를 선택해 Bitstream을 다운로드한다 (수 초가 걸린다).

program-fpga


Download Bitstream to the PL

7. 내 GitHub 페이지에서 all_frames 파일을 다운로드한다.

IMPORTANT NOTE: all_frames 파일이 공백을 포함되지 않은 경로에 위치하도록 해야 한다. 예를 들어, “My Document”와 같은 경로가 포함되면 안 된다. Vivado는 경로에 공백이 들어간 경우에 제대로 동작하지 않는다. 공백이 포함된 경로를 사용하게 되면 Java 런타임 에러를 발생하게 된다. 나는 파일을 “C:\” 드라이브 아래에 “data” 디렉토리에 저장하기를 추천한다.

save-all_frames-in-a-folder-with-no-spaces


Save file all_frames in a folder with NO spaces

8. animation_test 프로젝트 디렉토리를 우클릭하고, Run As > Run Configurations를 선택한다.

run-configurations


Run Configurations


Run Configurations 창에서 Xilinx C/C++ application (GDB)를 더블 클릭해 animation_test 디버그 설정 프로필을 생성한다.
run-configurations-window


Run Configurations Window


우측 창에서 Application을 선택하고 Add를 클릭한다. all_frames 파일이 있는 위치를 찾아간 뒤, 선택하고 Open을 클릭한다. 이는 all_frames 파일을 어플리케이션 실행 이전에 메모리에 불러오도록 할 것이다. 0x1f00000 (Region A의 시작 주소)를 입력해 SDK가 어디에 이 파일을 저장할지 결정한다. 그리고 Apply를 누른 다음 Run을 누른다.
downloading-a-data-file-all_frames-to-memory-before-running-application-lab6


Download a data file (all_frames) to memory before running application lab6.c


이제 Alex가 OLED 스크린을 돌아다니는 것을 볼 수 있다. 데모 비디오는 아래와 같다.

F. 두 번째 애플리케이션 실행 (Alex 제어)
이번 단계에서는 존재하는 코드에 추가로 필요한 기능을 더하는 방법에 대해 알아볼 것이다. Alex를 우리 제어대로 움직이게 해야 한다. 우리는 Alex를 푸쉬 버튼을 사용해 제어하고자 한다. BTNU(Upper push button)을 눌러 Alex를 위로 이동하게 하고, BTND를 눌러 아래쪽으로, BTNR을 눌러 오른쪽으로, BTNL을 눌러 왼쪽으로 이동하게 한다. 스크린의 영역을 넘으면 안 된다는 점에 유의해야 한다.

OLED를 2차원 그리드로 생각해 문제를 풀어보도록 하자. 페이지는 y 축으로 표현하고, 페이지 내의 위치는 x 축으로 표현한다.

alex-control-grid


Alex Control Grid


Zedboard OLED 드라이버의 print_char(char,page,position) 함수를 사용할 것이다. 이는 문자 char를 OLED 의 page의 특정 위치에 표시한다. 이제 page는 y축이고, position은 x축이다.

우리는 Alex의 비트맵을 charLib.coe의 27번과 28번 주소에 저장했다. 따라서 ASCII 값은 주소에서 오프셋 3을 뺀 값인 24와 25이다.

print_char(24,0,0)은 Alex를 첫 번째 페이지의 첫 번째 위치, 즉 (0, 0)에 출력하게 된다.

이제 Alex를 어떻게 제어할 것인지에 대해 알아보자.

1. File > New > Application Project을 선택해 Alex 제어를 위한 새로운 소프트웨어 어플리케이션을 생성한다.
2. Application Project 창에서 다음 창과 같이 인자값을 입력한다. 새로운 BSP를 생성할 필요가 없으며, 이전에 사용했던 animation_test를 그대로 사용하다. 그리고 Next를 클릭한다.

new-standalone-c-project-alex_control


New Standalone C Project- alex_control


다음 창에서 Empty Application을 선택하고 Finish를 클릭한다.

3. animation_test-> src을 선택하고, lab6.c를 우클릭한 뒤 복사를 클릭한다.

copy-lab6-c-file-from-animation_test


copy lab6. c file from animation_test

4. alex_control-> src을 우클릭하고 paster를 클릭한다.

copying-lab6-c-to-alex_control-application-project


copying lab6.c to alex_control application project

5. 복사된 파일을 선택하고, 우클릭한 뒤 Rename을 선택한다. lab6_app2을 입력한 뒤, OK를 클릭한다.

rename-to-lab6_app2


Rename to lab6_app2.c

이제 우리는 새로운 요구 사항을 만족하도록 수정할 새로운 파일을 생성했다.

6. 가장 먼저 할 것은 DMA를 활성화하고 있는 부분을 주석 처리하는 것이다. DMA를 사용한 애니메이션은 더이상 사용하지 않으므로, DMA가 이제 필요하지 않다. 우리는 단순히 푸쉬 버튼을 사용해 Alex를 제어할 수 있도록 하면 된다. DMA 사용을 정지하고, XDma_Config(DMA_DEVICE_ID);를 주석 처리한다 (230번째 줄).

comment-out-line-230


Comment out line 230 ( Function DMA_Config)


Note: Window->Preference->Editors->Text EditorsShow line numbers를 선택해 줄 번호를 확인할 수 있다.
show-line-numbers


Show line numbers

7. 타이머 ISR의 본문을 주석 처리한다 (TMR_Intr_Handler). 이는 다음 DMA 트랜잭션의 주소를 관리하는 역할을 한다. DMA를 사용하지 않으므로 이 코드도 더이상 필요하지 않다. 167-187번 줄을 선택한 뒤, Source-> Toggle Comment를 선택해 주석 처리한다.

comment-out-line-lines167-187


Comment out line lines167-187

8. 서로 다른 함수들 사이에 Alex를 제어하기 위한 두 개의 정적 전역 변수를 생성한다.

다음의 코드를 87-88번 사이에 추가한다:

static unsigned int x;
static unsigned int y;

이제 코드는 다음과 같다:

add-two-static-global-variables-for-alexs-location


Add two static global variables for Alex’s location

9. show_alex() 함수의 정의를 모든 코드의 끝 부분에 추가한다. show_alex() 함수는 Alex를 지정된 좌표에 출력하는 역할을 한다. 이 함수는 내부적으로 Zedboard OLED 드라이버의 print_char() 함수를 사용한다(Lab3에서 개발된 것).

void show_alex(int x, int y)

{
print_char(24,y,x);
}

이제 코드는 다음과 같다:

add-function-show_alex-at-the-end-of-the-code


Add function show_alex at the end of the code

10. BTN_Intr_Handler()의 푸쉬 버튼 ISR에서 switch case 구문을 수정한다. 다음 코드는 Alex를 푸쉬 버튼을 사용해 제어 가능하게끔 한다.

switch (btn_value)

{

//Checking if BTNC was pressed. Action: Show Alex in the middle
case 1:
x=8;
y=2;
clear_OLED();
show_alex(x,y);
break;

//Checking if BTND was pressed. Action: Check for boundary and move Alex down.
case 2:
if (y<3)
{
y=y+1;
clear_OLED();
show_alex(x,y);
}
break;

//Checking if BTNL was pressed. Action: Check for boundary and move Alex left.
case 4:
if (x&;gt;0)
{
x=x-1;
clear_OLED();
show_alex(x,y);
}
break;

//Checking if BTNR was pressed. Action: Check for boundary and move Alex right.
case 8:
if (x<15)
{
x=x+1;
clear_OLED();
show_alex(x,y);
}
break;

//Checking if BTNU was pressed. Action: Check for boundary and move Alex up.
case 16:
if (y&;gt;0)
{
y=y-1;
clear_OLED();
show_alex(x,y);
}
break;

default:
break;

}

이제 코드는 다음과 같다:

modified-switch-case-in-btn_intr_handler1


Modified switch case in BTN_Intr_Handler()


Save를 클릭해 어플리케이션을 컴파일한다.

11. alex_control 프로젝트를 선택하고-> Run As-> Launch on Hardware (GDB)를 선택해 alex_control 어플리케이션을 ARM 프로세서에서 실행한다. 다운로드가 완료되면 Alex를 푸쉬 버튼을 사용해 제어할 수 있다. 데모 비디오를 확인해보자.


이로써 여섯 번째 실습인 DMA와 Animation를 마친다. 이번 실습에 대한 해답은 여기에 있다.


Advertisements
Tagged with: , , , , , , , , , , , , , ,
Posted in FPGA

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

누적 방문자 수
  • 93,200 hits
%d bloggers like this: