'Embedded System'에 해당되는 글 13건
- 2011.03.19 U-Boot Document Site
- 2011.02.12 Setting up my machine to build Android source code(for ODROID)
- 2009.11.26 NMEA Library
- 2009.11.15 Kalman Filter
- 2009.11.08 PID 제어기
- 2009.11.08 Ziegler–Nichols method
- 2009.10.16 P/I/PI/PID제어
- 2009.08.13 커널 부팅 로고 이미지 바꾸기(출처 : FALinux)
- 2009.07.24 __attribute__
- 2009.07.24 blob 처리 순서
-
Build environment
Install Ubuntu 9.10(Daum ftp)
-
Set Root user password
$ sudo passwd
-
Download and Install essential packages
$ sudo apt-get install flex bison gperf libsdl-dev libesd0-dev libwxgtk2.6-dev build-essential zip curl
$ sudo apt-get install valgrind
Android requires the following system packages:
1. flex : This lexical analyzer generator is used to read a given input file for a descrpition of a scanner to generate.
2. bison : This is a general-purpose parser generator.
3. gperf : This is a perfect has function generator.
4. libesd0-dev : This enlightened sound daemon (dev files) is used to mix digitized audio streams for playback by a single device.
5. libwxgtk2.6-dev : This package provides GUI components and other facilities for many differnet playforms.
6. build-essential : This package contains a list of packages considered fundamental to building to Debian Packages.
7. Android source code includes a hard dependency on the JAVA Developer Kit(JDK) 5.0 Update 12 or greater.
-
Get Android Source
These days, if you download android source, maybe Android source is downloaded except kernel source.
So you must check about this and modifiy manifest.
$ repo init u git://android.git.kernel.org/kernel/common.git
$ vi .repo/manifest.xml
Add line in .repo/manifest.xml
<project path="kernel" name="kernel/common" revision="refs/heads/android-2.6.27" />
$ sudo sync
-
Build Android
$ make
You can also build only kernel
$ cd kernel
$ make msm_defconfig ARCH=arm
$ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toochain/arm-eabi-4.2.1/bin/arm-eabi-
$ make foldfish_defconfig ARCH=arm
$ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toochain/arm-eabi-4.2.1/bin/arm-eabi-
reference : http://dev.odriod.com , http://google.com
'Embedded System > Software' 카테고리의 다른 글
NMEA Library (0) | 2009.11.26 |
---|---|
Kalman Filter (0) | 2009.11.15 |
Ziegler–Nichols method (0) | 2009.11.08 |
커널 부팅 로고 이미지 바꾸기(출처 : FALinux) (0) | 2009.08.13 |
__attribute__ (0) | 2009.07.24 |
Disclaimer
The National Marine Electronics Association (NMEA) has developed a specification that defines the interface between various pieces of marine electronic equipment. The standard permits marine electronics to send information to computers and to other marine equipment. Most computer programs that provide real time position information understand and expect data to be in NMEA format. This data includes the complete PVT (position, velocity, time) solution computed by the GPS receiver. The idea of NMEA is to send a line of data called a sentence that is totally self contained and independent from other sentences. All NMEA sentences is sequences of ACSII symbols begins with a '$' and ends with a carriage return/line feed sequence and can be no longer than 80 characters of visible text (plus the line terminators).Introduction
We present open source and free library in 'C' programming language for work with NMEA protocol. Small and easy to use. The library build on different compilers under different platforms (see below). The code was tested in real projects. Just download and try...Features
- Analysis NMEA sentences and granting GPS data in C structures
- Generate NMEA sentences
- Supported sentences: GPGGA, GPGSA, GPGSV, GPRMC, GPVTG
- Multilevel architecture of algorithms
- Additional functions of geographical mathematics and work with navigation data
Supported (tested) platforms
- Microsoft Windows (MS Visual Studio 8.0, GCC)
- Windows Mobile, Windows CE (MS Visual Studio 8.0)
- UNIX (GCC)
VirtualGPSJet (addition)
We also provide virtual COM port driver for generating a stream of NMEA protocol. It is developed for testing applications working with GPS. Now this driver made only for Windows CE platform.Downloads
Source : http://nmea.sourceforge.net/
'Embedded System > Software' 카테고리의 다른 글
Setting up my machine to build Android source code(for ODROID) (0) | 2011.02.12 |
---|---|
Kalman Filter (0) | 2009.11.15 |
Ziegler–Nichols method (0) | 2009.11.08 |
커널 부팅 로고 이미지 바꾸기(출처 : FALinux) (0) | 2009.08.13 |
__attribute__ (0) | 2009.07.24 |
Kalman Filter
Rudolf E. Kalman 이 발명한 칼만필터는 최소자승법 (Least Square Method) 를 사용해서 실시간으로 잡음 (noisy) 운동 방정식 (equations of motion) 을 가진 시간에 따른 방향 (time-dependent state vector) 를 추적하는 효율적인 재귀 계산법 (recursive computational solution) 이다. 칼만필터는 하나의 시스템이 시간에 따른 변화를 적절하게 예측할 수 있도록 잡음 (noise) 으로부터 신호 (signal) 를 찾아내기위해 사용된다.
실제로는 Peter Swerling 이 일찌기 유사한 알고리즘을 개발했다. Stanley Schmidt 는 최초로 칼만필터를 실제로 응용하여 사용했다고 알려져있다. Kalman 이 NASA Ames Research Center 를 방문했을 때 우주선 아폴로의 궤도를 추정하는 문제에 그의 아이디어가 사용될 수 있다는 것을 알게되었고, 우주선 아폴로의 운행컴퓨터 (navigation computer) 에 포함되게 되었다.
다양한 종류의 칼만필터가 지금까지 개발되었는데, 소위 simple Kalman filter 이라 불리우는 칼만의 원래의 형식에서부터, Schmidt 의 extended filter, information filter, Bierman 과 Thornton 등등에 의해 개발된 다양한 squrar-root filter 들이 있다. 칼만필터는 제어시스템공학에서 광범위하게 사용된다. Wiener filter 와 비교된다. ...... (Wikipedia : Kalman filter)
대부분의 사람들은 칼만 필터에 대해 들어 보지 못했다. 그것은 비행기와 우주 탐사 로켓, 그리고 순항 미사일을 유도하고, 위성과 경제의 추세와 혈액 흐름의 변화 등을 추적한다. 그것은 '최적의' 예측기이다. 그것은 비행기가 구름에 가리어 있을 때 비행기의 방향에 대해 '가장 잘' 예측할 수 있도록 한다. 여러분은 어떻게 미사일이 목표물을 찾고, 어떻게 우주 비행사들이 지구로 돌아오는지 궁금해해 본 적이 없는가? 칼만 필터는 어떻게 이러한 일이 가능한지 보여 준다. 다윈의 적자 생존 이론이 진화 생물학을 대표하는 것과 마찬가지로, 칼만 필터는 대부분의 공학을 대표한다. 수천 명의 엔지니어들은 칼만 필터에 약간 덧붙이거나 변형된 논문들을 발표하였다. ..............
term :
예측 (Prediciton) Rudolf E. Kalman 제어 (Control)
Site :
Paper :
칼만필터를 이용한 우리나라의 잠재적 GNP 추정과 경기변동의 추이에 관한 연구 (Potential GNP and Business Cycles of Korea : A Kalman Filtering Approach) : 이병완, 한국경제학회, 1994
확장 칼만필터를 이용한 LEO 위성의 궤도 결정 방법 (THE ORBIT DETERMINATION OF LEO SATELLITES USING EXTENDED KALMAN FILTER) : 최규홍, 손건호, 김광렬, 한국우주과학회, 1995
칼만필터를 이용한 레이다 추적계의 비교연구 : 위상규, 임청부, 한국항공우주학회, 1989
적응 칼만필터를 이용한 MTI 레이터의 이동표적 추적 기법 (Moving Target Tracking Technique of MTI Radar Using Adaptive Kalman Filter) : 박인환, 조겸래, 조설, 한국항공우주학회
칼만필터를 이용한 최고/최저 기온예보 (The Forecasting the Maximum / Minimum Temperature Using the Kalman Filter) : 이동일, 이우진, 한국기상학회, 1999
칼만필터를 이용한 무궁화위성 궤도 결정 성능분석 연구 (Performance Analysis of Kalman Filter Based KOREASAT Orbit Determination) : 박봉규, 안태성, 한국항공우주학회, 2000
반복적 확장 칼만필터를 이용한 얼굴의 3차원 움직임량 추정 (3-D Facial Motion Estimation Using Iterative Extended Kalman Filter) : 박강령, 김재희, 한국정보처리학회
최소 최대 추정량과 베이즈 추정량으로서의 Kalman 필터에 관하여 (On the Kalman Filter as the Bayes Estimator and the minimax Estimator) : 김지곤, 상명대 자연과학연구소, 1998
=========================================================================================
더 확실한 이해를 위해 세번째~~~
=========================================================================================
이번에는 위의 수학선생님이 학생 두명에게 각기 다른 방법으로 교실의 길이를 재보라고 했다고 하자. 한 학생은 자신의 보폭을 이용하고, 다른 학생은 줄자를 이용하여 교실의 길이를 측정했을때, 첫번째 학생은 10m, 두번째 학생은 12m 라는 값을 얻었다. 만약 둘 모두 같은 방법으로 길이를 측정하였다면, 선생님은 주저없이 두 값을 평균하여 11m 를 교실의 길이라고 말할 수 있겠다. 그러나, 누가 보더라도 보폭으로 잰 길이보다는 줄자로 잰 길이가 정확하다고 느낄 것이다. 그렇다면 이들 두 값을 어떻게 평균해야 할까? 이때 도입되는 개념이 경중율이다.



출처 : Tong - gayanim님의 image processing통
'Embedded System > Software' 카테고리의 다른 글
Setting up my machine to build Android source code(for ODROID) (0) | 2011.02.12 |
---|---|
NMEA Library (0) | 2009.11.26 |
Ziegler–Nichols method (0) | 2009.11.08 |
커널 부팅 로고 이미지 바꾸기(출처 : FALinux) (0) | 2009.08.13 |
__attribute__ (0) | 2009.07.24 |
PID 제어기
비례-적분-미분 제어기(PID 제어기)는 실제 응용분야에서 가장 많이 사용되는 대표적인 형태의 제어기법이다. PID 제어기는 기본적으로 피드백(feedback)제
어기의 형태를 가지고 있으며, 제어하고자 하는 대상의 출력값(output)을 측정하여 이를 원하고자 하는 참조값(reference value) 혹은 설정값(setpoint)과 비교하여 오차(error)를 계산하고, 이 오차값을 이용하여 제어에 필요한 제어값을 계산하는 구조로 되어 있다.
표준적인 형태의 PID 제어기는 아래의 식과 같이 세개의 항을 더하여 제어값(MV:manipulated variable)을 계산하도록 구성이 되어 있다.
이 항들은 각각 오차값, 오차값의 적분(integral), 오차값의 미분(derivative)에 비례하기 때문에 비례-적분-미분 제어기 (Proportional–Integral–Derivative controller)라는 명칭을 가진다. 이 세개의 항들의 직관적인 의미는 다음과 같다.
- 비례항 : 현재 상태에서의 오차값의 크기에 비례한 제어작용을 한다.
- 적분항 : 정상상태(steady-state) 오차를 없애는 작용을 한다.
- 미분항 : 출력값의 급격한 변화에 제동을 걸어 오버슛(overshoot)을 줄이고 안정성(stability)을 향상시킨다.
PID 제어기는 위와 같은 표준식의 형태로 사용하기도 하지만, 경우에 따라서는 약간 변형된 형태로 사용하는 경우도 많다. 예를 들어, 비례항만을 가지거나, 혹은 비례-적분, 비례-미분항만을 가진 제어기의 형태로 단순화하여 사용하기도 하는데, 이때는 각각 P, PI, PD 제어기라 불린다.
한편, 계산된 제어값이 실제 구동기(actuator)가 작용할 수 있는 값의 한계보다 커서 구동기의 포화(saturation)가 발생하게 되는 경우, 오차의 적분값이 큰 값으로 누적되게 되어서, 정작 출력값이 설정값에 가까지게 되었을 때, 제어값이 작아져야 함에도 불구하고 계속 큰 값을 출력하게 되어 시스템이 설정값에 도달하는 데 오랜 시간이 걸리게 되는 경우가 있는데, 이를 적분기의 와인드업이라고 한다. 이를 방지하기 위해서는 적절한 안티 와인드업(Anti-windup) 기법을 이용하여 PID 제어기를 보완해야 한다.
위의 식에서 제어 파라메터 Kp,Ki,Kd를 이득값 혹은 게인(gain)이라고 하고, 적절한 이득값을 수학적 혹은 실험적/경험적 방법을 통해 계산하는 과정을 튜닝(tuning)이라고 한다. PID 제어기의 튜닝에는 여러 가지 방법들이 있는데, 그중 가장 널리 알려진 것으로는 지글러-니콜스 방법이 있다.
'Embedded System > Hardware' 카테고리의 다른 글
P/I/PI/PID제어 (0) | 2009.10.16 |
---|---|
ARM920T Datasheet (0) | 2009.07.15 |
Ziegler–Nichols method
From Wikipedia, the free encyclopedia
The Ziegler–Nichols tuning method is a heuristic method of tuning a PID controller. It was developed by John G. Ziegler and Nathaniel B. Nichols. It is performed by setting the I and D gains to zero. The "P" gain is then increased (from zero) until it reaches the ultimate gain Ku, at which the output of the control loop oscillates with a constant amplitude. Ku and the oscillation period Tu are used to set the P, I, and D gains depending on the type of controller used:
Ziegler–Nichols method | ||||
Control Type | Kp | Ki | Kd | |
P | 0.5·Ku | - | - | |
PI | 0.45·Ku | 1.2Kp / Tu | - | |
PID | 0.6·Ku | 2Kp / Tu | KpTu / 8 |
This type of tuning creates a "quarter wave decay". This is an acceptable result, but not optimal.
References
- Van, Doren, Vance J. (July 1, 2003). "Loop Tuning Fundamentals". Control Engineering (Red Business Information). http://www.controleng.com/article/CA307745.html. Retrieved 2007-06-24.
- Co, Tomas; Michigan Technological University (February 13, 2004). "Ziegler-Nichols Closed Loop Tuning". http://www.chem.mtu.edu/~tbco/cm416/zn.html. Retrieved 2007-06-24.
External links
'Embedded System > Software' 카테고리의 다른 글
NMEA Library (0) | 2009.11.26 |
---|---|
Kalman Filter (0) | 2009.11.15 |
커널 부팅 로고 이미지 바꾸기(출처 : FALinux) (0) | 2009.08.13 |
__attribute__ (0) | 2009.07.24 |
blob 처리 순서 (0) | 2009.07.24 |
1. PID 제어란?
자동제어 방식 가운데서 가장 흔히 이용되는 제어방식으로 PID 제어라는 방식이 있다.
이 PID란,
P: Proportinal(비례)
I: Integral(적분)
D: Differential(미분)
의 3가지 조합으로 제어하는 것으로 유연한 제어가 가능해진다.
2. 단순 On/Off 제어
단순한 On/Off 제어의 경우에는 제어 조작량은 0%와 100% 사이를 왕래하므로 조작량의 변화가 너무 크고, 실제 목표값에 대해 지나치게 반복하기 때문에, 목표값의 부근에서 凸凹를 반복하는 제어로 되고 만다.
이 모양을 그림으로 나타내면 아랫 그림과 같이 된다.
3. 비례 제어
이에 대해 조작량을 목표값과 현재 위치와의 차에 비례한 크기가 되도록 하며, 서서히 조절하는 제어 방법이 비례 제어라고 하는 방식이다.
이렇게 하면 목표값에 접근하면 미묘한 제어를 가할 수 있기 때문에 미세하게 목표값에 가까이 할 수 있다.
이 모양은 아랫 그림과 같이 나타낼 수 있다.
4. PI 제어
비례 제어로 잘 제어할 수 있을 것으로 생각하겠지만, 실제로는 제어량이 목표값에 접근하면 문제가 발생한다.
그것은 조작량이 너무 작아지고, 그 이상 미세하게 제어할 수 없는 상태가 발생한다. 결과는 목표값에 아주 가까운 제어량의 상태에서 안정한 상태로 되고 만다.
이렇게 되면 목표값에 가까워지지만, 아무리 시간이 지나도 제어량과 완전히 일치하지 않는 상태로 되고 만다.
이 미소한 오차를 "잔류편차"라고 한다. 이 잔류편차를 없애기 위해 사용되는 것이 적분 제어이다.
즉, 미소한 잔류편차를 시간적으로 누적하여, 어떤 크기로 된 곳에서 조작량을 증가하여 편차를 없애는 식으로 동작시킨다.
이와 같이, 비례 동작에 적분 동작을 추가한 제어를 "PI 제어"라 부른다.
이것을 그림으로 나타내면 아랫 그림과 같이 된다.
5. 미분 제어와 PID 제어
PI 제어로 실제 목표값에 가깝게 하는 제어는 완벽하게 할 수 있다. 그러나 또 하나 개선의 여지가 있다.
그것은 제어 응답의 속도이다. PI 제어에서는 확실히 목표값으로 제어할 수 있지만, 일정한 시간(시정수)이 필요하다.
이때 정수가 크면 외란이 있을 때의 응답 성능이 나빠진다.
즉, 외란에 대하여 신속하게 반응할 수 없고, 즉시 원래의 목표값으로는 돌아갈 수 없다는 것이다.
그래서, 필요하게 된 것이 미분 동작이다.
이것은 급격히 일어나는 외란에 대해 편차를 보고, 전회 편차와의 차가 큰 경우에는 조작량을 많이 하여 기민하게 반응하도록 한다.
이 전회와의 편차에 대한 변화차를 보는 것이 "미분"에 상당한다.
이 미분동작을 추가한 PID 제어의 경우, 제어 특성은 아랫 그림과 같이 된다.
이것으로 알 수 있듯이 처음에는 상당히 over drive하는 듯이 제어하여, 신속히 목표값이 되도록 적극적으로 제어해 간다.
6. 컴퓨터에 의한 PID 제어 알고리즘
원래 PID 제어는 연속한 아날로그량을 제어하는 것이 기본으로 되어 있다. 그러나, 컴퓨터의 프로그램으로 PID 제어를 실현하려고 하는 경우에는 연속적인 양을 취급할 수 없다. 왜냐하면, 컴퓨터 데이터의 입출력은 일정시간 간격으로밖에 할 수 없기 때문이다.
게다가 미적분 연산을 착실히 하고 있는 것에서는 연산에 요하는 능력으로 인해 고성능의 컴퓨터가 필요하게 되고 만다.
그래서 생각된 것이 샘플링 방식(이산값)에 적합한 PID 연산 방식이다.
우선, 샘플링 방식의 PID 제어의 기본식은 다음과 같이 표현된다.
조작량=Kp×편차+Ki×편차의 누적값+Kd×전회 편차와의 차
(비례항) (적분항) (미분항)
기호로 나타내면
MVn=MVn-1+ΔMVn
ΔMVn=Kp(en-en-1)+Ki en+Kd((en-en-1)-(en-1-en-2))
MVn, MVn-1: 금회, 전회 조작량
ΔMVn: 금회 조작량 미분
en, en-1, en-2: 금회, 전회, 전전회의 편차
이것을 프로그램으로 실현하기 위해서는 이번과 전회의 편차값만 측정할 수 있으면 조작량을 구할 수 있다.
7. 파라미터를 구하는 방법
PID 제어 방식에 있어서의 과제는 각 항에 붙는 정수, Kp, Ki, Kd를 정하는 방법이다.
이것의 최적값을 구하는 방법은 몇 가지 있지만, 어느 것이나 난해하며, 소형의 마이크로컴퓨터로 실현하기 위해서는 번거로운 것이다(tuning이라 부른다).
그래서, 이 파라미터는 cut and try로 실제 제어한 결과에서 최적한 값을 구하고, 그 값을 설정하도록 한다.
참고로 튜닝의 수법을 소개하면 스텝 응답법과 한계 감도법이 유명한 수법이다.
또, 프로세스 제어 분야에서는 이 튜닝을 자동적으로 실행하는 Auto tuning 기능을 갖는 자동제어 유닛도 있다. 이것에는 제어 결과를 학습하고, 그 결과로부터 항상 최적한 파라미터값을 구하여 다음 제어 사이클에 반영하는 기능도 실장되어 있다.
여기서 스텝 응답법에 있어서 파라미터를 구하는 방법을 소개한다.
우선, 제어계의 입력에 스텝 신호를 가하고, 그 출력 결과가 아랫 그림이라고 하자(파라미터는 적당히 설정해 둔다).
윗 그림과 같이 상승의 곡선에 접선을 긋고, 그것과 축과의 교점, 정상값의 63%에 해당하는 값으로 된 곳의 2점에서,
L: 낭비시간 T: 시정수 K: 정상값의 3가지 값을 구한다.
이 값으로부터, 각 파라미터는 아래 표와 같이 구할 수 있다.
제어 동작 종별
Kp의 값
Ki의 값
Kd의 값
비례 제어
0.3~0.7T/KL
0
0
PI 제어
0.35~0.6T/KL
0.3~0.6/KL
0
PID 제어
0.6~0.95T/KL
0.6~0.7/KL
0.3~0.45T/K
이 파라미터에 범위가 있지만, 이 크기에 의한 차이는 특성의 차이로 나타나며, 아랫 그림과 같이, 파라미터가 많은 경우에는 미분, 적분 효과가 빨리 효력이 나타나므로 아랫 그림의 적색선의 특성과 같이 overshoot이 크게 눈에 띈다. 파라미터가 작은 쪽의 경우는 하측 황색선의 특성과 같이 된다.
'Embedded System > Hardware' 카테고리의 다른 글
PID 제어기 (0) | 2009.11.08 |
---|---|
ARM920T Datasheet (0) | 2009.07.15 |
이 강좌는 커널 부팅시 LCD로 나오는 펭귄 로고 이미지를 바꾸는 방법에 대한 기술 문서입니다.
내용 | 버전 |
리눅스 배포판 |
|
EZ-보드 커널 | linux-2.6.21 |
EZ-보드를 부팅할 때 출력되는 이미지는 이미지 파일을 그대로 읽어다가 출력하는 것이 아니라 출력할 이미지 파일을 C 소스 파일로 생성한 후, 컴파일 과정을 거쳐 커널 이미지에 추가하는 것입니다. 그러므로 이미지를 C 소스 파일을 만들기 위한 pnm 프로그램이 필요합니다.
이 프로그램을 미리 준비합니다.
■ 프로그램이 있는지 확인
아래의 프로그램 파일이 있는지 확인합니다. 이미지를 pnm 으로 변환하는 많은 실행 파일이 있지만 여기서는 gif, png, bmp, jpeg 를 예로 듭니다.
]# ls /usr/bin/giftopnm /usr/bin/giftopnm ]# ls /usr/bin/pngtopnm /usr/bin/pngtopnm ]# ls /usr/bin/bmptopnm /usr/bin/bmptopnm ]# ls /usr/bin/jpegtopnm /usr/bin/jpegtopnm ]# ls /usr/bin/pnmtoplainpnm /usr/bin/pnmtoplainpnm ]# ls /usr/bin/pnmquant /usr/bin/pnmquant ]# ls /usr/bin/pnmnoraw /usr/bin/pnmnoraw위의 파일 정도만 확인 하면 됩니다.
■ 프로그램 설치
위의 파일들이 없다면 netpbm 이라는 패키지를 설치 해 주어야 합니다.
- 우분투의 경우 설치
]# apt-get install netpbm
- Fedora Core 3 이상
http://rpmfind.net/linux/rpm2html/search.php?query=netpbm+&submit=Search+...
해당 배포판 버젼에 맞는 netpbm-xxx rpm을 다운받아서 설치를 합니다.
Fedora Core 의 경우 배포판 설치시 모든 파일 설치를 하면 기본적으로 포함되어 있습니다.
- CentOS 5 경우
yum install로 설치합니다.
]# yum install netpbm-progs설치 작업 후에 /usr/bin에 프로그램이 제대로 설치되었는지 확인합니다.
- 커널 이미지 준비
EZ-PXA270을 구매했을 때 동봉된 CD 안의 커널 이미지 또는 EZ-PXA270의 자료실 게시판에서 커널 이미지 파일을 내려 받아 적당한 곳에 압축 풀기를 합니다.
커널 이미지 파일 이름은 linux-2.6.21.tar.gz 입니다.
]# tar zxvf linux-2.6.21.tar.gz ]# cd linux-2.6.21 // 압축이 풀린 디렉토리로 이동 ]# make distclean // 모든 환경 설정 및 컴파일 결과를 삭제 ]# ./falinux-config.sh ez-pxa270 // 미리 준비된 기본 설정 파일로 환경값을 생성
- drivers/video/logo/Kconfig 파일 내용 수정
]# vi drivers/video/logo/Kconfig 23 config LOGO_LINUX_CLUT224 24 bool "Standard 224-color Linux logo" 25 depends on LOGO 26 default y 27 아래의 노랑색 문자열을 입력합니다. 28 config LOGO_FALINUX_CLUT224 29 bool "FALINUX 224-color Linux logo" 30 depends on LOGO 31 default y 32 33 config LOGO_DEC_CLUT224 34 bool "224-color Digital Equipment Corporation Linux logo" 35 depends on LOGO && (MACH_DECSTATION || ALPHA) 36 default y
- drivers/video/logo/Makefile 파일 내용 수정
]# vi drivers/video/logo/Makefile 5 obj-$(CONFIG_LOGO_LINUX_VGA16) += logo_linux_vga16.o 6 obj-$(CONFIG_LOGO_LINUX_CLUT224) += logo_linux_clut224.o 7 obj-$(CONFIG_LOGO_FALINUX_CLUT224) += logo_falinux_clut224.o <----- 추가 8 obj-$(CONFIG_LOGO_DEC_CLUT224) += logo_dec_clut224.o 9 obj-$(CONFIG_LOGO_MAC_CLUT224) += logo_mac_clut224.o
- drivers/video/logo/logo.c 파일 내용 수정
]# vi drivers/video/logo/logo.c 26 extern const struct linux_logo logo_linux_clut224; 27 extern const struct linux_logo logo_falinux_clut224; <---- 추가 28 extern const struct linux_logo logo_dec_clut224; : 65 if (depth >= 8) { 66 #ifdef CONFIG_LOGO_LINUX_CLUT224 67 /* Generic Linux logo */ 68 logo = &logo_linux_clut224; 69 #endif 70 #ifdef CONFIG_LOGO_FALINUX_CLUT224 <---- 추가 71 /* Generic Linux logo */ 72 logo = &logo_falinux_clut224; 73 #endif 74 #ifdef CONFIG_LOGO_DEC_CLUT224 75 /* DEC Linux logo on MIPS/MIPS64 or ALPHA */ 76 #ifndef CONFIG_ALPHA
]# make menuconfig Device Drivers ---> Graphics support ---> [ ] Backlight & LCD device support ---> <*> Support for frame buffer devices [ ] Enable firmware EDID [ ] Enable Video Mode Handling Helpers [ ] Enable Tile Blitting Support --- Frame buffer hardware drivers < > EP93xx frame buffer support < > EP93xx Mono frame buffer support < > Epson S1D13XXX framebuffer support <*> PXA LCD framebuffer support [*] LCD 640x480 Display [ ] LCD 480x272 Display [*] PXA LCD command line parameters < > 2700G LCD framebuffer support < > Virtual Frame Buffer support (ONLY FOR TESTING!) Console display driver support ---> Logo configuration ---> [*] Bootup logo [ ] Standard black and white Linux logo <---- 선택을 해지합니다. [ ] Standard 16-color Linux logo <---- 선택을 해지합니다. [ ] Standard 224-color Linux logo <---- 선택을 해지합니다. [*] FALINUX 224-color Linux logo <---- 선택합니다.
커널에서 타겟보드에 맞는 프레임버퍼에서 다음과 같이 수정해 줘야합니다.
수정할 부분은 xxx_probe 함수를 찾아서 return 하기 전에 다음의 소스를 추가해 주면 됩니다.
#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) if (fb_prepare_logo(&fbi->fb, FB_ROTATE_UR)) { printk("Start display and show logo\n"); /* Start display and show logo on boot */ fb_set_cmap(&fbi->fb.cmap, &fbi->fb); fb_show_logo(&fbi->fb, FB_ROTATE_UR); } #endif
■ 수정 예
]# vi drivers/video/xxxxfb.c int __init xxx_probe(struct platform_device *dev) { ..... ..... ..... #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) if (fb_prepare_logo(&fbi->fb, FB_ROTATE_UR)) { printk("Start display and show logo\n"); /* Start display and show logo on boot */ fb_set_cmap(&fbi->fb.cmap, &fbi->fb); fb_show_logo(&fbi->fb, FB_ROTATE_UR); } #endif return 0; }
예로 EZ-PXA270은 pxafb.c 입니다.
]# vi drivers/video/pxafb.c int __init pxafb_probe(struct platform_device *dev) { struct pxafb_info *fbi; struct pxafb_mach_info *inf; int ret; : /* * Ok, now enable the LCD controller */ set_ctrlr_state(fbi, C_ENABLE); #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) if (fb_prepare_logo(&fbi->fb, FB_ROTATE_UR)) { printk("Start display and show logo\n"); /* Start display and show logo on boot */ fb_set_cmap(&fbi->fb.cmap, &fbi->fb); fb_show_logo(&fbi->fb:set, FB_ROTATE_UR); } #endif return 0; failed: platform_set_drvdata(dev, NULL);
설정된 이미지 해상도에 맞는 이미지를 만듭니다.
테스트를 위해 타겟보드의 프레임 버퍼 해상도를 640x480으로 설정하고, 640x480이미지를 만듭니다.
이미지 파일 명은 image640x480.gif 이며, 최종 파일은 logo_falinux_clut224.ppm 입니다.
]# giftopnm image640x480.gif | pnmtoplainpnm > image640x480.ppm --> 준비한 gif로 ppm 파일을 생성 ]# pnmquant -fs 223 image640x480.ppm > image640x480_256.ppm --> ppm 파일을 가공 ]# pnmnoraw image640x480_256.ppm > logo_falinux_clut224.ppm --> ppm 파일을 가공 ]# cp -a logo_falinux_clut224.ppm drivers/video/logo/ --> 생성된 ppm 파일을 커널 소스로 복사
]# make clean ]# make zImage 정상적으로 컴파일되면 아래와 같이 생성된 이미지 이름이 출력됩니다. GZIP arch/arm/boot/compressed/piggy.gz AS arch/arm/boot/compressed/piggy.o CC arch/arm/boot/compressed/misc.o AS arch/arm/boot/compressed/head-xscale.o LD arch/arm/boot/compressed/vmlinux OBJCOPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready ]# 생성된 커널 이미지를 tftp를 이용하여 전송하겠습니다. 전송을 위해 /tftpboot 로 커널 이미지를 이동합니다. ]# mv arch/arm/boot/zImage /tftpboot/zImage.ez-pxa270
이제 커널 이미지를 EZ-보드로 전송합니다. 커널 이미지를 보드로 전송하는 방법에 대해서 통합 메뉴얼- 에 자세히 설명되어 있습니다. 내용을 참고하세요.
아래 내용은 EZ-PXA270에서의 커널 전송을 예로 들겠습니다.
WELCOME EZBOOT V2.2.49 (FALINUX Co.,Ltd) ..........PXA270(EZ-PXA270) Program by You Young-chang, Oh Jae-Kyoung, Jang Hyung-Gi Last Modify May 27 2008 Detect ES29LV400_B Flash : vid=4A pid=22BA SIZE 4-Mbits [512-Kbytes] Detect Samsung NAND 64M 3.3V 8-bit Flash : vid=EC pid=76 SIZE 64-Mbytes (page=512, block=16K) Quickly Autoboot [ENTER] / Goto BOOT-MENU press [space bar]. <--스페이스바를 누릅니다. AX88796B MAC : [ 00 FA 08 25 00 06 ]
AX88796B_Init : OK! EZBOOT>set <-- set 입력 후 엔터키를 누릅니다. ^^; 1) mac address : 00:FA:08:25:00:06 2) local ip : 192.168.10.95 3) local netmask : 255.255.255.0 4) local gateway : 192.168.10.1 5) server ip : 192.168.10.100 <-- 개발 호스트 IP와 다르다면 수정합니다. 6) zImage file name : zImage.ez-pxa270 <-- 전송될 커널 이미지 파일 이름입니다. 7) ram disk file name : ramdisk-1.10-12M.gz 8) boot loader file name : ezboot.ez-pxa270 9) logo image file name : 10) auto execute full name : 11) autoboot wait time : 3 12) copy ramdisk [Y/N] : Y 13) arm kernel arch number : 3003 14) nand part (B,C,L,K,R,A): 0,1,0,3,5,55 15) watchdog (sec, off=0) : 0 16) KCMD 1 : mem=64M 17) KCMD 2 : initrd=0xA0800000,5M root=/dev/ram ramdisk=16384 18) KCMD 3 : console=ttyPXA2,115200 19) KCMD 4 : 20) KCMD 5 : 21) KCMD 6 : L) Load default LF) load KCMD2 root=flash LR) load KCMD2 root=ramdisk S) Save to flash P) aPply & exit Q) Quit Select >> q <-- q와 엔터키로 set 모드를 빠져 나갑니다. EZBOOT> tfk <-- tfk 명령으로 서버로부터 커널 이미지를 내려 받습니다. receive zImage.ez-pxa270 tx ARP rx ARP HOST MAC : 00:0C:29:EB:0B:33 HOST IP : 192.168.10.51 LOCAL IP : 192.168.10.95 option [timeout 120 tsize 1920060] size = 1914880 size = 1920060 ...write 1920060 complete ...verify complete EZBOOT>rst <-- 보드를 재 부팅합니다. System Soft Reset....... EZBOOT> WELCOME EZBOOT V2.2.49 (FALINUX Co.,Ltd) ..........PXA270(EZ-PXA270) Program by You Young-chang, Oh Jae-Kyoung, Jang Hyung-Gi Last Modify May 27 2008 Detect ES29LV400_B Flash : vid=4A pid=22BA SIZE 4-Mbits [512-Kbytes] Detect Samsung NAND 64M 3.3V 8-bit Flash : vid=EC pid=76 SIZE 64-Mbytes (page=512, block=16K) ... Copy Kernel Image ..... Copy Ramdisk Image ..... Starting kernel [MARCH 3003]... kernel command [EZBOOT mem=64M initrd=0xA0800000,5M root=/dev/ram ramdisk=16384 console=ttyPXA2,115200 ip0=192.168.10.95 mac=00:FA:08:25:00:06 netmask=255.255.255.0 gw=192.168.10.1 host=192.168.10.51 nandparts=1,8,55 ] Uncompressing Linux........ ....중략... yaffs: dev is 32505858 name is "mtdblock2" yaffs: passed flags "" yaffs: Attempting MTD mount on 31.2, "mtdblock2" yaffs: auto selecting yaffs1 Starting system logger: [ OK ] Starting INET services: [ OK ] eth0 Link mode : 100 Mb/s Duplex mode. Welcome to FALinux (www.falinux.com) Linux Kernel 2.6.21-falinux falinux login: <-- 로그인 메시지가 출력되면 성공한 것입니다.
자, 재부팅을 하면 새로 지정한 이미지 파일이 출력됩니다.
커널 이미지 생성 중에 아래와 같이 에러가 난다면,
]# make clean ]# make zImage 컴파일 중에 아래와 같은 에러가 발생하는 경우가 있습니다. AR lib/lib.a GEN .version CHK include/linux/compile.h UPD include/linux/compile.h CC init/version.o LD init/built-in.o LD .tmp_vmlinux1 drivers/built-in.o(.text+0xb624): In function `fb_find_logo': : undefined reference to `logo_falinux_clut224' make: *** [.tmp_vmlinux1] 오류 1 // drivers/video/logo/에는 logo_falinux_clut224.ppm 뿐만 아니라 // logo_falinux_clut224.c 파일도 정상적으로 생성되었는데도 // 위의 에러가 발생한다면, 이는 Makefile 에서 C 소스 파일을 참고할 환경 값이 // 올바르게 지정되지 않았을 경우 발생합니다. 예로 환경 값 이름의 오타일 경우입니다. ]# vi drivers/video/logo/Makefile 5 obj-$(CONFIG_LOGO_LINUX_VGA16) += logo_linux_vga16.o 6 obj-$(CONFIG_LOGO_LINUX_CLUT224) += logo_linux_clut224.o 7 obj-$(CONFIG_LOGO_FALINUXCLUT224) += logo_falinux_clut224.o ---> FALINUX와 CLUT2244 사이에 밑줄이 없습니다. 8 obj-$(CONFIG_LOGO_DEC_CLUT224) += logo_dec_clut224.o 9 obj-$(CONFIG_LOGO_MAC_CLUT224) += logo_mac_clut224.o // 그러므로 drivers/video/logo/logo.c 에서 사용한 환경변수 이름과 // Makefile에서 지정한 환경 변수 이름이 다르지 않은 지를 확인하십시오.
'Embedded System > Software' 카테고리의 다른 글
Kalman Filter (0) | 2009.11.15 |
---|---|
Ziegler–Nichols method (0) | 2009.11.08 |
__attribute__ (0) | 2009.07.24 |
blob 처리 순서 (0) | 2009.07.24 |
Linux 방화벽 해제 (0) | 2009.07.13 |
님의 코드에서 보이는 __attribute__는 GCC 컴파일러에만
있는 메카니즘으로서, 컴파일러에게 추가적인 에러체킹같은것을
하도록 지시하는것입니다.
형식은 :
__attribute__(x)
이런형식으로 괄호안의 x는 가능한 몇가지들이있습니다 만, 여기서
쓰인 packed는, 채우라는 의미로서, 무엇을 채우냐하면
(* 지금 설명하는 부분은 아주 중요한 내용임)
typedef struct {
char a;
int b;
} myST;
위 구조체의 sizeof(myST)는 얼마가 나올까요? 8 이 나옵니다.
상식적으로, char =1byte, int = 4byte 이므로, 5가 나와야 하는데요.
이런 문제때문에, 구조체를 가지고 작업할경우 예상치못한 버그로
고생을 가끔합니다.
이런현상이 생기는 이유는, 현재 사용하는 컴터는 32비트입니다.
즉, CPU가 메모리 어드레스를 지정할때 4바이트(32비트/8) 단위가 내부적으로
가장 최적화되어 빠른 데이타 접근을 가능하게 합니다. 4바이트 배수단위가
아닌경우 당연히 접근은 가능하지만 속도차이가 있어, 디폴트로
저런 접근을 하도록 해둔것이죠.
그럼, 메모리에 저 구조체가 잡힐경우 어떻게 존재하느냐는,
4바이트단위로 채워지기때문에, char a; 는 1바이트이지만, 4바이트를 할당해서
거기다 char a; 를 담아둡니다. 다음, int b;는 4바이트므로 그냥 4바이트를 할당합니다.
이렇게해서 8바이트의 크기가 필요한거죠.
첫 char a; 에서 1바이트를 제외한 나머지 3바이트는 어떤걸로 채워질까요?
의미없는값을 채워집니다. 그냥 공간만 예약되는거죠.
그래서, 이런 현상을 방지할수있도록, 컴파일러는 __attribute__ (packed) 메카니즘을
두어서 우리가 예상한 1바이트+4바이트, 크기가 되도록 빌드합니다.
typedef struct {
char a;
int b;
} __attribute__(packed) myST;
결국, 위 처럼 사용하게되면, 컴파일러는 구조체멤버 실제크기만큼 할당합니다.
그래서 정확한 5바이트가 나오게됩니다. 물론, 속도는 약간 더 늦어지죠.
여기에서 __attribute__ 라는 의미는 , 컴파일러에게 특성을 주겠다는것을 의미하며
괄호안은 여러가지가 가능한데, 여기서는 그중 한가지인 구조체정렬을
사용하겠다는 의미인 packed 를 사용했습니다.
packed의 의미는 꽉채우다라는것인데, 이 기능을 사용하지 않았을경우 char a;는
실제 1바이트외에 나머지 3바이트가 자리만 차지한채 남아있다고 했죠?
이런 비어있는것들을 제거해서 구조체 멤버들이 빈자리없이 꽉 채우라는
단어의미입니다. 이런것을 구조체정렬(alignment)라고 합니다.
위는 unix/linux에서 사용하는 GCC에서 사용하는것이고
윈도우상의 비주얼씨의경우는
#pragma pack(1)
처럼 사용합니다. 괄호안의 의미는 1바이트단위까지 체크해서 빈자리없도록
차곡차고채우라는거죠. 4가 올수도 있겠지만, 그렇게되면 가장작은단위가 4가되어
char 형같은경우 역시 4로 채워지게되어 낭패입니다. 따라서 확실한 경우 아니면
위처럼 사용하면됩니다.
'Embedded System > Software' 카테고리의 다른 글
Ziegler–Nichols method (0) | 2009.11.08 |
---|---|
커널 부팅 로고 이미지 바꾸기(출처 : FALinux) (0) | 2009.08.13 |
blob 처리 순서 (0) | 2009.07.24 |
Linux 방화벽 해제 (0) | 2009.07.13 |
리눅스 한글 깨짐 변경(i18n) (0) | 2009.07.13 |
우선 blob는 src/start.S 파일에서 부터 시작된다.
처음에 처리하는 부분은 _start부분이며,
_start: b reset
위의 코드로 인하여 reset쪽으로 브렌치(점프) 하게 된다.
나머지 코드들은 예외 처리 코드라고 보면 된다.
reset:
/* First, mask **ALL** interrupts */
ldr r0, IC_BASE
mov r1, #0x00
str r1, [r0, #ICMR]
우선 위에서 reset으로 제어가 넘어오게 되면, r0레지스터에 IC_BASE(IC_BASE: .word 0x90050000 소스의 61라인 참고.)번지에 있는 워드를 읽어 들인다.(IC_BASE는 인터럽트 콘트롤러 레지스터의 기본 주소이다.(SA1110메뉴얼 A-4(맨뒤의 어펜딕스)에 보면 0x90050000은 Interrupt Controller Register)라고 적혀있는것을 알 수 있다.)
0x00을 r1에 쓴다.
======= str명령어에 대한 참고 사항. (김효춘님의 ARM7문서 참고.)================
STR r0,[r1,r2] ; 베이스는 r1, 옵셋은 r2를 사용한 형태
입니다. 즉 *(r1+r2)=r0 와 같은 의미..
======= str명령어에 대한 참고 끝. =============================================
위의 str 참고를 따르면 *(r0+#ICMR) = r1의 공식이 나오게 된다.
즉, r1의 값은 r0(=IC_BASE) + #ICMR = 0x90050004 가 됨을 알 수 있다.
r1레지스터에 값은 0x90050004 이다.(역시 SA1110 A-4에 보면 ICMR의 값이 0x90050004임을 확인 할 수 있다.)
/* switch CPU to correct speed */
ldr r0, PWR_BASE
LDR r1, cpuspeed
str r1, [r0, #PPCR]
r0레지스터에 PWR_BASE(PWR_BASE: .word 0x90020000 소스의 64라인 참고.)번지에 있는 워드를 읽어 들인다. (PWR_BASE는 파워 메니저 레지스터의 기본 주소이다.(SA1110메뉴얼 A-3(맨뒤의 어펜딕스)에 보면 0x90020000은 Power Manager control register)라고 적혀있는것을 알 수 있다.)
또한, r1에서 cpuspeed의 값을 로드하게 된다.( cpuspeed는 75-86라인을 참고하게 되며, 아사벳을 기준으로 하였으므로, 다음과 같은 값을 가지게 된다.
-----------------------------------------------
80 cpuspeed: .long 0x0b /* 221 MHz */
-----------------------------------------------
위의 str 참고를 따르면 *(r0+#PPCR) = r1의 공식이 나오게 된다.
즉, r1의 값은 r0(=PWR_BASE) + #PPCR = 0x90020014 가 됨을 알 수 있다.
r1레지스터에 값은 0x90020014 이다.(역시 SA1110 A-4에 보면 PPCR의 값이 0x90020014임을 확인 할 수 있다.)
LDR r1, cpuspeed
str r1, [r0, #PPCR]
도대체 해석이 안되누만.
위에서 인터럽트를 마스크 하고, cpu의 속도를 결정하였다.
---------------------------------
초기 부팅에서 메모리를 설정해 주는 부분이다.
BL은 CALL로 생각하시면됩니다. 즉 memsetup.S 파일로 이동하게 된다.
---------- start.S 파일의 부분 ----------------
105 /* setup memory */
106 bl memsetup
------------------------------------------------
memsetup에 관한 것은 memsetup.S파일에 다음과 같이 global로 설정되어 있다. 따라서 아래의 루틴으로 이동 할 수 있게 되는 것이다.
memsetup.S파일에서는 메모리를 초기화한다. SA1100 보드를 사용한다면 #if defined USE_SA1100아래부분이 수행이 된다.
MEM_BASE(=0xa0000000)은 DRAM configuration register의 주소이다. (SA1100 A-4참고)
96 .globl memsetup
97 memsetup:
98 #if defined USE_SA1100
99 /* Setup the flash memory */
100 ldr r0, MEM_BASE
만약 스트롱 암이면 (44 MEM_BASE: .long 0xa0000000 ) r0에 0xa0000000를 로드한다.
101
102 ldr r1, mcs0
103 str r1, [r0, #MCS0]
r1에 0xfff8fff8를 로드하고(mcs0: .long 0xfff8fff8), r1에 *(r0+#MCS0=0xa0000010)를 저장한다.(#define MCS0 0x10 )
104
105 /* Set up the DRAM */
106
107 /* MDCAS0 */
108 ldr r1, mdcas0
109 str r1, [r0, #MDCAS0]
r1에 0xc71c703f를 로드하고( mdcas0: .long 0xc71c703f), r1에 *(r0+#MDCAS0=0xa0000004)를 저장한다.(#define MDCAS0 0x04)
110
111 /* MDCAS1 */
112 ldr r1, mdcas1
113 str r1, [r0, #MDCAS1]
r1에 0xffc71c71를 로드하고(mdcas1: .long 0xffc71c71), r1에 *(r0+#MDCAS1=0xa0000008)를 저장한다.(#define MDCAS1 0x08)
114
115 /* MDCAS2 */
116 ldr r1, mdcas2
117 str r1, [r0, #MDCAS2]
r1에 0xffffffff를 로드하고(mdcas2: .long 0xffffffff), r1에 *(r0+#MDCAS2=0xa000000c)를 저장한다.(#define MDCAS2 0x0c)
118
119 /* MDCNFG */
120 ldr r1, mdcnfg
121 str r1, [r0, #MDCNFG]
r1에 0x0334b22f를 로드하고(mdcnfg: .long 0x0334b22f), r1에 *(r0+#MDCNFG=0xa0000000)를 저장한다.(#define MDCNFG 0x0)
참고로, mdcas0, mdcas1, mdcas2, mdcnfg, msn0 값은 보드마다 다르다.
SA1100 A-4에 있는 메모리관련 레지스터에 대하여 알아보자.
MDCNFG레지스터는 32bit read/write 레지스터로서 DRAM을 설정하는 control bit을 가진다. 모든 DRAM bank들은 같은 종류의 DRAM device들로 구성되어야 한다. MDCASX의 심벌을 가지는 레지스터는 32bit의 read/write가 가능하며, CAS의 waveform을 결정해 주는 것으로 DRAM을 접근하는데 필요한 CPU cycle을 나타내준다. MSCX의 심벌을 가지는 레지스터는 32bit의 read/write가 가능하며, ROM이나 flash와 같은 Static 메모리에 대한 제어를 담당한다. MECR 레지스터는 expansion 메모리의 설정을 관리하며, PCMCIA와 같은 것을 위해서 사용되어 진다. 이와 같은 레지스터에 특정한 값을 설정해서 BLOB를 로드하거나 C 함수를 수행하기 위해서 reset이후에 올바른 값으로 정해 주어야 한다는 것만 기억하기 바란다.
138 ldr r1, MEM_START
(45 MEM_START: .long 0xc0000000 ) r1에 0xc0000000를 로드한다.
139
140 .rept 8
141 ldr r0, [r1]
142 .endr
만약 cpu가 SA1110이면,
145 #elif defined USE_SA1110
146 /* This part is actually for the Assabet only. If your board
147 * uses other settings, you'll have to ifdef them here.
148 */
149 /* Set up the SDRAM */
150 mov r1, #0xA0000000 /* MDCNFG base address */
r1에 상수 0xA0000000를 넣는다. ( 참고로 0xA0000000은 DRAM configuration register이다. SA-1100 메뉴얼 A-4페이지 참고.)
151
152 ldr r2, =0xAAAAAA7F
153 str r2, [r1, #0x04] /* MDCAS00 */
154 str r2, [r1, #0x20] /* MDCAS20 */
r2에 0xAAAAAA7F를 로드한 후, r2에 (r1(=0xA0000000)+0x04)=0xA00000004를 저장한다.r2에 (r1(=0xA0000000)+0x20)=0xA00000020를 저장한다.
155
156 ldr r2, =0xAAAAAAAA
157 str r2, [r1, #0x08] /* MDCAS01 */
158 str r2, [r1, #0x24] /* MDCAS21 */
r2에 0xAAAAAAAA를 로드한 후, r2에 (r1(=0xA0000000)+0x08)=0xA00000008를 저장한다.r2에 (r1(=0xA0000000)+0x24)=0xA00000024를 저장한다.
159
160 ldr r2, =0xAAAAAAAA
161 str r2, [r1, #0x0C] /* MDCAS02 */
162 str r2, [r1, #0x28] /* MDCAS22 */
r2에 0xAAAAAAAA를 로드한 후, r2에 (r1(=0xA0000000)+0x0C)=0xA0000000C를 저장한다.r2에 (r1(=0xA0000000)+0x28)=0xA00000028를 저장한다.
163
164 ldr r2, =0x4dbc0327 /* MDREFR */
165 str r2, [r1, #0x1C]
r2에 0x4dbc0327를 로드한 후, r2에 (r1(=0xA0000000)+0x1C)=0xA0000001C를 저장한다.
166
167 ldr r2, =0x72547254 /* MDCNFG */
168 str r2, [r1, #0x00]
r2에 0x72547254를 로드한 후, r2에 (r1(=0xA0000000)+0x00)=0xA00000000를 저장한다.
169
170 /* Issue read requests to disabled bank to start refresh */
171
172 ldr r1, =0xC0000000
r1에 0xC0000000를 로드한다.
173
174 .rept 8
175 ldr r0, [r1]
176 .endr
메모리에 관련된 register들에 대한 설정을 마쳤다. r1에 MEM_START(=0cC0000000)의 값을 읽어 오고, r0에 r1이 가르키고 있는 곳에서 값을 읽어 넣는 일을 8번 반복한다. 이것은 disable된 bank가 refresh하지 못하도록 막는 역할을 한다.
177
178 mov r1, #0xA0000000 /* MDCNFG base address */
179
180 ldr r2, =0x72547255 /* Enable the banks */
181 str r2, [r1, #0x00] /* MDCNFG */
182
183 /* Static memory chip selects on Assabet: */
184
185 ldr r2, =0x4b90 /* MCS0 */
186 orr r2,r2,r2,lsl #16
187 str r2, [r1, #0x10]
188
189 ldr r2, =0x22212419 /* MCS1 */
190 str r2, [r1, #0x14]
191
192 ldr r2, =0x42196669 /* MCS2 */
193 str r2, [r1, #0x2C]
194
195 ldr r2, =0xafccafcc /* SMCNFG */
196 str r2, [r1, #0x30]
197
위의 Assabet부분은 SA1110의 설정하는 부분과 거의 같다. 아래의 PCMCIA부분이 약간 틀릴뿐이다.
198 /* Set up PCMCIA space */
199
200 ldr r2, =0x994a994a
201 str r2, [r1, #0x18]
r2에 0xx994a994a를 로드한 후, r2에 (r1(=0xA0000000)+0x18)=0xA00000018를 저장한다.
0xA00000018어드레스는 Expansion bus configuration register(확장 버스 설정 레지스터)이다.
202
203 /* All SDRAM memory settings should be ready to go... */
204 /* For best performance, should fill out remaining memory config regs: */
205
206
207 /* Testing ,Chester */
208 mov r3,#0x12000000
209 mov r2,#0x5000 /* D9_LED on and D8_LED off */
210 str r2,[r3]
211 mov r4, #0x20000
r3에 0x12000000를 대입하고, r2에 0x5000를 대입하고, r2 = *(r3) 한후에, r4에 0x20000를 대입한다.
212 gogogo2:
213 subs r4, r4, #1
214 bne gogogo2
215 #else
216 #error "Configuration error: CPU not defined!"
217 #endif
218
219 mov pc, lr
------------ memsetup.S 의 마지막 부분 ------------
이젠 메모리에 대한 설정을 마쳤으므로 pc(프로그램 카운터 레지스터)에서 앞에서 저장하였던 lr(link register)의 값을 가져오도록 한다.(mov)
다시 start.S로 넘어와서 다음 과정을 진행 해야 한다. (bl memsetup)다음에 나오는 루틴은 아래와 같다.
----------- setsrt.S 파일의 부분. -----------
108 /* init LED */
109 bl ledinit
---------------------------------------------
이부분은 단순히 LED에 대한 초기화만을 담당한다. LED의 GPIO를 초기화 하고, LED를 켜준다.
------------ ledasm.S ----------------------
51 .globl ledinit
52 /* initialise LED GPIO and turn LED on.
53 * clobbers r0 and r1
54 */
55 ledinit:
56 ldr r0, GPIO_BASE
57 ldr r1, LED
58 str r1, [r0, #GPDR] /* LED GPIO is output */
59 str r1, [r0, #GPSR] /* turn LED on */
60 mov pc, lr
r0에 GPIO_BASE(43 GPIO_BASE: .long 0x90040000)어드레스를 로드한다.
r1에 LED(LED: .long LED_GPIO(=0x00020000(Assabet기준)))어드레스를 로드한다.
r1에 *(r0+#GPDR=0x90040004)를 저장한다.(#define GPDR 0x00000004)
r1에 *(r0+#GPSR=0x90040008)를 저장한다.(#define GPSR 0x00000008)
이전으로 복귀한다.
----------- ../include/led.h 에 정의 되어 있는 내용 --------------
/* define the GPIO pin for the LED */
// LED를 위한 GPIO핀의 기본 값이다.
#if defined ASSABET
# define LED_GPIO 0x00020000 /* GPIO 17 */
#elif (defined CLART) || (defined LART) || (defined NESA)
# define LED_GPIO 0x00800000 /* GPIO 23 */
#elif defined PLEB
# define LED_GPIO 0x00010000 /* GPIO 16 */
#else
#warning "FIXME: Include code to turn on one of the LEDs on your board"
# define LED_GPIO 0x00000000 /* safe mode: no GPIO, so no LED */
#endif
----------------------------------------------------------------------
65 .globl led_on
66 /* turn LED on. clobbers r0 and r1 */
67 led_on:
68 ldr r0, GPIO_BASE
69 ldr r1, LED
70 str r1, [r0, #GPSR]
71 mov pc, lr
r0에 GPIO_BASE(43 GPIO_BASE: .long 0x90040000)어드레스를 로드한다.
r1에 LED(LED: .long LED_GPIO(=0x00020000(Assabet기준)))어드레스를 로드한다.
r1에 *(r0+#GPSR=0x90040008)를 저장한다.(#define GPSR 0x00000008)
이전으로 복귀한다.
76 .globl led_off
77 /* turn LED off. clobbers r0 and r1 */
78 led_off:
79 ldr r0, GPIO_BASE
80 ldr r1, LED
81 str r1, [r0, #GPCR]
82 mov pc, lr
r0에 GPIO_BASE(43 GPIO_BASE: .long 0x90040000)어드레스를 로드한다.
r1에 LED(LED: .long LED_GPIO(=0x00020000(Assabet기준)))어드레스를 로드한다.
r1에 *(r0+#GPCR=0x9004000c)를 저장한다.(#define GPCR 0x0000000c)
이전으로 복귀한다.
-----------------------------------------------------------------------
ASSABET인 경우에는 LED_GPIO가 0x00020000이 되며, 이 값으로 r1을 초기화 하고, GPDR에 output으로 GPIO의 pin direction을 설정하게 되며, 함수는 GPSR과 GPCR 레지스터의 해당 bit을 ON/OFF 시키는 동작만 하도록 되어 있다. 이제 메모리와 LED에 대한 설정을 끝냈으므로, start.S로 돌아가자.
---------------- start.S -------------------------------------
113 ldr r0, RST_BASE
114 ldr r1, [r0, #RCSR]
115 and r1, r1, #0x0f
116 teq r1, #0x08
117 bne normal_boot /* no, continue booting */
r0레지스터에 RST_BASE(RST_BASE: .word 0x90030000 소스의 68라인 참고.)번지에 있는 워드를 읽어 들인다. (RST_BASE는 리셋 제어 소프트웨어 리셋 레지스터의 기본 주소이다.(SA1110메뉴얼 A-3(맨뒤의 어펜딕스)에 보면 0x90030000은 Reset controller software reset register)라고 적혀있는것을 알 수 있다.)
위의 str 참고를 따르면 *(r0+#RCSR(#define RCSR 0x04)) = r1의 공식이 나오게 된다.
즉, r1의 값은 r0(=RST_BASE) + #RCSR = 0x90030004가 됨을 알 수 있다.
r1레지스터에 값은 0x90020014 이다.(역시 SA1110 A-4에 보면 RCSR의 값이 0x90030004임을 확인 할 수 있다.)
r1의 값을 0x0f와 and연산을 하여 하위 4비트를 다시 r1으로 저장을 한다.
normal_boot영역으로 이동 시킨다.
118
119 /* yes, a wake-up. clear RCSR by writing a 1 (see 9.6.2.1 from [1]) */
/* RSCR에 1이 써지게 되면( 9.6.1.2 참고) 동작하게 된다. */
120 mov r1, #0x08
121 str r1, [r0, #RCSR] ;
0x08을 r1에 쓴다.
즉, r1의 값은 r0(=RST_BASE) + #RCSR = 0x90030004가 됨을 알 수 있다.
122
123 /* get the value from the PSPR and jump to it */
/* PSPR로 부터 값을 가져오고나서 점프한다. */
124 ldr r0, PWR_BASE
125 ldr r1, [r0, #PSPR]
126 mov pc, r1
r0레지스터에 PWR_BASE(PWR_BASE: .word 0x90020000 소스의 64라인 참고.)번지에 있는 워드를 읽어 들인다. (PWR_BASE는 파워 메니저 레지스터의 기본 주소이다.(SA1110메뉴얼 A-3(맨뒤의 어펜딕스)에 보면 0x90020000은 Power Manager control register)라고 적혀있는것을 알 수 있다.)
위의 str 참고를 따르면 *(r0+#PSPR( #define PSPR 0x08)) = r1의 공식이 나오게 된다.
즉, r1의 값은 r0(=PWR_BASE) + #PSPR = 0x90020008이 됨을 알 수 있다.
r1레지스터에 값은 0x90020008 이다.(역시 SA1110 A-4에 보면 PSPR의 값이 0x90020008임을 확인 할 수 있다.)
-----------------------------------------------------------------------
SA1110의 Reset Controller는 다양한 Reset을 관리할 목적으로 사용되며, 프로그래머의 관점에서 보면, 크게 두개의 register로 구성된다. 하나는 sofware reset을 시키는 일을하면 다른 하나는 프로세서의 reset 이유가 무엇인가를 나타내는 목적으로 사용된다. 각각은 RSRR와 RCSR로 나뉜다.
먼저, RCSR의 레지스터를 읽어서 r1에 둔다. 이 값과 0x0f와 and여산을 하여 하위 4비트만 살려서 0x08과 같지 않으면, 하드웨어, 소프트웨어, 와치독 리셋으로 들어간 상태 이며, 나머지 하나는 슬립모드 리셋이다. 슬립모드리셋에서 깨어나게 되는 리셋이 걸린다면, RCSR에 있는 슬립모드리셋을 지우기 위해서 RCSR에 0x08을 넣어주며, PSPR(파워 매니저 스크래치패드레지스터)를 읽어서 슬립모드 이전의 프로세서의 설정을 읽어 r1에 넣어준 후, 이 값으로 점프를 한다. 즉 슬립모드에서 깨어나서 계속 연산을 진행해 나가는 것이 된다. 만약 슬립모드에서 리셋이 걸린 것이 아니라면, normal_boot으로 제어가 이동해서 진행 될 것이다. 현재 우리가 다룬고 있는 것은 하드웨어 리셋 이후의 상황이므로 normal_boot이 될 것이다.
--------------- start.S ---------------------
129 normal_boot:
130 /* enable I-cache */
131 mrc p15, 0, r1, c1, c0, 0 @ read control reg
132 orr r1, r1, #0x1000 @ set Icache
133 mcr p15, 0, r1, c1, c0, 0 @ write it back
136 /* check the first 1MB in increments of 4k */
137 mov r7, #0x1000
138 mov r6, r7, lsl #8 /* 4k << 2^8 = 1MB */
139 ldr r5, MEM_START
r7에 0x1000(=4K)를 넣는다.
r6 := r7 << (8 = 1000(2진수)) 을 3비트 왼쪽으로 쉬프트하라는 뜻으로 r6 = 1000000= 1M 가 된다.
r5에 MEM_START(72라인 MEM_START: .long 0xc0000000)을 로드한다.
141 mem_test_loop:
142 mov r0, r5
143 bl testram
144 teq r0, #1
145 beq badram
r5를 r0로 이동한다. 즉 r0는 0xc0000000이 된다.
testram으로 점프한다.
r1과 1이 같으면 badram으로점프하고 아니면, 아래를 계속 수행한다.
146
147 add r5, r5, r7
148 subs r6, r6, r7
149 bne mem_test_loop
r5에 r7을 더해서 r5에 넣는다.
r6dptj r7을 빼서 r6에 넣는다.
mem_test_loop로 간다.(루프임)
-------------------------------------------------
인스트럭션 캐쉬를 인에이블 시켜준다. 콘트롤 레지스터를 읽어서, Icache를 나타내는 비트를 설정한 후, 다시 이것으로 원래의 값을 대치시켜주면 된다. 첫번째 1Mbyte 영역에 대한 테스트에 들어간다. 먼저 r7에 4K(=0x1000)을 넣고, 다시 이를 좌측으로 8bit 쉬프트 해서 r6에 넣는다. 이렇게 하고나면, r6는 1M를 가질 것이다. r5에는 MEM_START(=0xC0000000)로 두어 메인 메모리의 시작 주소를 가르키도록 한다. 메모리 테스트가 일어나는 곳은 mem_test_loop이다. 이곳에서 loop를 돌면서 확인하게 된다. 확인하는 단위는 4KBytes로 1MBytes 길이에 대해서 일어난다. 먼저, r0에 메모리의 시작 주소를 가져온다.(r5). 테스트는 testram 에서 일어나게 되며(bl), 만약 테스트의 결과 값(r0)이 1이라며, badram으로 제어를 옮기게 되며, 그렇지 않다면, r5에 r7(=4K)를 더하고, r6에서는 r7을 빼서 다시 loop를 돌게 된다.
----------- testmem.S ----------------------------
34 .globl testram
35 @ r0 = address to test // r0 에 test의 시작주소를 넣는다.
36 @ returns r0 = 0 - ram present, r0 = 1 - no ram // r0가 0이면 RAM이 존재하는것이고, r0가 1이면 램이 없다.
37 @ clobbers r1 - r4 // r1 ~ r4에 그냥 넣는다.
---------------------------------------------------
------- 참고사항 : 김효춘님의 ARM7강좌 인용. -------
* Block Data Transfer 명령(LDM,STM)
해당 명령은 개인적인 생각으로 참 독특하다고 생각합니다. 지난 강좌에
서 다루었던 LDR, STR과 마찬가지로 실제 메모리에 레지스터의 내용을 전
달 하거나, 전달 받을 수 있는 명령입니다. ARM7에서는 이런 명령이 몇
안되죠...
LDR명령이 메모리 번지의 내용을 지정된 레지스터로 가져오는 명령이라면
LDM은 가져오긴 하는데, 여러개의 레지스터의 내용을 한큐에 가져오는 명
령입니다. 가장 많이 사용되는 경우는 스택 연산인것 같습니다. 전에 말
씀드렸었지만, ARM7에는 Push, Pop 명령이 없습니다. 대신 LDR이나 STR을
쓸 수도 있겠고... 또 LDM이나 STM을 쓸 수도 있죠... 후자 쪽이 더 많이
사용되는 듯 합니다.
1) <LDM|STM>{cond}mode Rn{!},{reg_list}{^}
위 명령에서 {cond}는 늘 보아오던 명령어 실행 조건입니다. Rn은 전송
에 사용될 베이스 번지를 지정하는 레지스터입니다. !를 붙이면 Wrte
Back 기능이죠... LDR과 STR에서 다루었습니다. 자세한 내용은 뒤에서
언급하도록 하겠습니다.
{reg_list} 부분은 전송하거나 전송받을 레지스터의 목록을 나타내는
부분입니다. 예를 들어 1000번지에 r1,r2,r3를 저장하고 싶다 했을 경
우에, 일단 1000번지 값을 어떤 레지스터에 넣어두고... 여기서는 그
레지스터를 r13이라고 하죠, 그러면 Rn은 r13이 되는 거구요,
{reg_list}는 {r1,r2,r3} 이 되는 것입니다. mode 라고 되어있는 부분
은 여러개의 레지스터를 메모리에 넣거나 가져올 때 어떤 방식으로 동
작할지를 지정하는 접미사입니다. 이 접미사 종류가 8가지가 있는데요.
무지하게 복잡해 보입니다. 일단은 각 요소의 의미만 간단하게 정리하
고, 다음으로 넘어가죠.
마지막으로 {^}부분은... 글세요 잘 이해가 안되는 부분입니다만...
제가 보는 책과 데이터 시트에서 예제가 나와 있지 않군요. 그냥 문서
의 내용을 그대로 적어보겠습니다.
{^} if present set S bit to load the CPSR along with the PC, or
force transfer of user bank when in privileged mode...(???)
자... 그럼 이제 본격적으로 설명에 들어가겠습니다.
먼저 1단계, LDM 과 STM의 의미 입니다.
LDM : 베이스 레지스터(Rn)로 지정된 번지에서 레지스터 목록으로 지
정된 각 레지스터의 내용을 읽어들이는 명령.
STM : LDM과 반대.
여기까지는 별 무리가 없으리라 생각합니다. 혹시 이 명령을 8086등에
있는 블럭 데이타 전송명령 등과 혼동하지 않으시길 바랍니다. 어렴풋이
기억하는데, 8086등에는 메모리에서 메모리로 블럭 전송을 할 수 있는
명령이 있죠... 또는 특정 길이만큼 메모리를 어떤 값으로 설정하는 블
럭 설정 명령도 있었던 듯 합니다.
ARM7의 LDM과 STM은 블럭 전송이 아니라 Multiple Register 전송입니다.
쉽게 생각하면
push ax
push bx
push cx
push dx
가 8086형태라고 할 때, ARM7에서 Single 데이터 전송 명령을 사용하면
STR r0,[sp],#4
STR r1,[sp],#4
STR r3,[sp],#4
STR r4,[sp],#4
쯤이 되겠고, 또 Multiple 전송명령을 사용하면,
STMEA sp!,{r0,r1,r2,r4}
제가 방금 적어본 것이라 맞는 것인지 확신은 없습니다만... 그냥 개
념이 이렇다는 것만 파악하셨으면 합니다.
그러면 이제 2단계, {Reg_List}를 자세히 다루어 보죠... 위의 예에서
나와있듯이 중괄호 사이에 전송 대상이 되는 레지스터를 넣어주면 됩
니다. 그렇다면 몇개까지 가능한 것일까요? ARM7에서 한시점에 사용할
수 있는 레지스터의 개수는 r0에서 r15까지 총 16개죠..
LDM이나 STM명령에서 지정할 수 있는 레지스터의 개수는 최대 16개 입
니다. 즉, 한 명령으로 모든 레지스터를 저장하거나, 가져올 수 있다
는 의미입니다.
참 재미있는 사실은, LDM명령의 니모닉상에 16비트의 공간이 있어서
각 비트가 레지스터 r0-r15와 1:1로 대응이 된다는 사실입니다. 따라
서 {reg_list}에는 어떠한 레지스터의 조합도 올 수 있습니다. 감동 !
그리고 어셈블러의 문제겠지만, {r0,r2} 이런 형식 뿐만 아니라,
{r0-r5} 와 같은 형식, {r0-r3,r6-r7} 이런 형식도 사용할 수 있습니
다.
마지막으로 확인할 것은, {r3,r2,r1} 이렇게 썼을 때와 {r1,r2,r3} 이
렇게 썼을 경우, 메모리에 저장되는 순서가 다를까요.. 아닐까요....
말씀드렸듯이 16비트의 비트필드가 존재해서 각각 레지스터와 1:1대응
이 된다고 하였으니... 소스코드에서 어떤 형식을 쓰든... 니모닉으로
변환될 때는 그 순서는 아무 의미가 없겠지요.. 결국 같다는 말입니다.
자... 이번에는 3단계입니다. 동작 모드!!!
위의 STM명령에서 제가 STMEA라고 명령을 적었습니다. EA가 동작모드
를 지정하는 부분입니다. 이와 같은 키워드가 8가지가 있고, 동작모드
는 4가지가 있습니다.
먼저 4가지의 동작모드를 말씀드리겠습니다.
A) Post-Increment Addressing
여기서 동작모드는 여러개의 레지스터값을 메모리로(혹은 로부터)
전송할 경우 해당 메모리 번지를 증가시키면서 저장할지, 혹은 감
소시키면서 저장할지를 지정하는 것과, 증/감을 하는데, 저장하기
전에 증/감을 할지, 아니면 저장하고 나서 증/감을 할지를 지정하
는 것을 의미합니다.
처음 설명할 동작모드는 저장 이후 증가하는 방식입니다.
예를 들어 R10에 0x1000이 들어있다고 가정하고 R10을 베이스레지
스터로 사용해서 {r1,r2}를 저장한다면, Post-Increment모드에서는,
1. 0x1000 번지에 r1이 저장된다.
2. Base 번지값이 0x1004로 증가한다.
3. 0x1004 번지에 r2가 저장된다.
4. Base 번지값은 0x1008로 증가한다.
만약 !를 사용했다면 r10의 값은 0x1008이 될 것입니다.
B) Pre-Increment Addressing
말 그대로 먼저 증가하고 다음에 저장하는 동작 모드입니다.
위와 같은 조건을 가정해 봅시다.
1. Base 번지값이 0x1004로 증가한다.
2. 0x1004번지에 r1이 저장된다.
3. Base 번지값이 0x1008로 증가한다.
4. 0x1008번지에 r2가 저장된다.
역시 !를 사용했다면 r10의 값은 0x1008이 됩니다.
C) Post-Decrement Addressing
이번에는 베이스 번지가 감소하는 경우죠. 좀 특이한 것은, 감소
모드로 저장(로드)을 할 경우 아까는 레지스터번호가 빠른 것부터
저장하거나 불러들였는데, 이번에는 거꾸로라는 것입니다. 결과적
으로, 메모리에 저장되는 레지스터의 순서는 항상 동일하다는 것
이죠... 마찬가지로 같은 조건에서 예제를 들겠습니다.
1. 0x1000번지에 r2가 저장된다.(!!! r1이 아니라 r2)
2. Base번지값이 0x0FFC로 감소된다.
3. 0x0FFC번지에 r1이 저장된다.
4. Base번지값이 0x0FF8로 감소된다.
만약 !를 사용했다면 r10의 값은 0xFF8이 된다.
D) Pre-Decrement Addressing
1. Base번지값이 0x0FFC로 감소된다.
2. 0x0FFC번지에 r2가 저장된다.
3. Base번지값이 0x0FF8로 감소된다.
4. 0x0FF8번지에 r1이 저장된다.
만약 !를 사용했다면 r10의 값은 0xFF8이 된다.
여기까지 4개의 동작모드를 설명했습니다.
동작모드를 나타내는 키워드는 8개인데요... 각각의 동작모드에 대해서
스택처럼 사용할 경우, 혹은 아닐 경우 2가지로 나누어서 나타내기 때
문입니다.
다음 표는 각 동작모드에 대한 명령어입니다.
==================================================================
동작 Stack Other
------------------------------------------------------------------
pre increment load LDMED LDMIB
post increment load LDMFD LDMIA
pre decrement load LDMEA LDMDB
post decrement load LDMFA LDMDA
pre increment store STMFA STMIB
post increment store STMEA STMIA
pre decrement store STMFD STMDB
post decrement store STMED STMDA
==================================================================
Stack인 경우와 아닌경우 키워드가 다른 것은, 단지 유저의 편이를위한
배려라고 생각됩니다. 즉, 키워드를 LDMED로 썼을 경우나 LDMIB로 썼을
경우, 동작상에 차이는 없는 듯 합니다.
키워드를 무작정 붙인것 같지는 않구요... 먼저 Other의 키워드를 살피
면, I는 increment를 의미하구요.. D는 decrement겠죠.. 그리고 B는
Before를, A는 After를 의미합니다.
그러므로 만약 LDMDA 는 post decrement 모드를 의미하는 것이죠....
Stack의 경우엔, E는 Empty를 F는 Full을 의미한답니다. 스택을 구현하
는 경우 현재 sp가 가리키는 번지의 내용이 차있는지, 비어있는지를 의
미한다고 보시면 될 듯 합니다. 무슨얘기냐면... 만약 Post 모드를 사
용한다면, 스택을 구현할 경우, 어떤 내용을 넣고 다음에 번지를 증/감
하므로 결국 어떤 시점에서 스택포인터가 가리키는 번지는 비어있게 됩
니다. 이 경우 Empty가 되겠죠...
Load의 경우엔 Empty 형태의 스택이라면 sp가 가리키는 공간에 아무 내
용도 없으므로 먼저 sp를 변화시키고 데이터를 가져와야겠죠.. 그래서
Load는 Pre가 Empty 와 대응이 됩니다. 하지만 반대로 Store의 경우엔
Empty 형태의 스택을 위해서는 먼저 데이터를 넣고 sp를 변화시켜야 합
니다. 그렇다면 post모드가 Empty와 대응이 되겠군요..!!
Full은 더이상 말 안해도 되리라 믿습니다.
다음으로 D는 Decending, A는 Ascending을 의미합니다. 이것은 스택이
거꾸로 커지는지 아니면 반대인지와 관련이 있습니다. 8086에서는 Push
를 하면 sp값이 작아지죠? 그렇다면 decending Stack이라고 볼 수 있습
니다. push와 STM이 대응되므로 STM의 Decrement 모드는 D 라는 키워드
를 사용했군요... Pop의 경우는 LDM과 대응되고 decending stack에서
Pop을 할경우 sp값은 증가되어야 겠죠... 그래서 LDM에서는 increment
모드가 D입니다.
A는 반대이겠죠... 결국 스택관련 명령에서는 LDM이나 STM에서 같은 접
미사를 사용하면 되는 것입니다. 물론 동작 방식은 LDM과 STM에서 각기
다르지만요...
------- 참고사항 : 김효춘님의 ARM7강좌 인용 끝. ----
----------- testmem.S ----------------------------
38 testram:
39 ldmia r0, {r1, r2} @ store current value in r1 and r2 // r0(메모리의 시작주소가 들어 있다.아래의 인용을 보면 알수 있을 것이다.)번지를 r1에 로드하고, r0의 번지가 증가되고, 이 증가된번지를 r2에 로드 한다.
// ---- start.S 파일 참고 ----------------
//
// 139 ldr r5, MEM_START
// 140
// 141 mem_test_loop:
// 142 mov r0, r5
// 143 bl testram
//
//-----------------------------------------
40 mov r3, #0x55 @ write 0x55 to first word // r3에 0x55를 쓴다.
41 mov r4, #0xaa @ 0xaa to second // r4에 0xaa를 쓴다.
42 stmia r0, {r3, r4} // r0(메모리의 시작주소가 들어 있다.위의 인용을 보면 알수 있을 것이다.)번지에 r3의 값을 저장하고, r0의 번지가 증가되고, 이 증가된번지에 r2의 값을 저장 한다.
43 ldmia r0, {r3, r4} @ read it back // 위에서 저장한것을 다시 읽는다.
44 teq r3, #0x55 @ do the values match // r3가 0x55와 같으면,
45 teqeq r4, #0xaa // r4가 0xaa와 같으면
46 bne bad @ oops, no
47 mov r3, #0xaa @ write 0xaa to first word // r3에 0x55를 쓴다.
48 mov r4, #0x55 @ 0x55 to second // r4에 0xaa를 쓴다.
49 stmia r0, {r3, r4}
50 ldmia r0, {r3, r4} @ read it back // 다시 읽어온다.
51 teq r3, #0xaa @ do the values match // 값이 같으면
52 teqeq r4, #0x55
53 bad: stmia r0, {r1, r2} @ in any case, restore old data // 이전 값을 재 저장 한다.
54 moveq r0, #0 @ ok - all values matched // 모든값이 정확하면 r0와 0을 넣는다.
55 movne r0, #1 @ no ram at this location // 모든 값이 같지 않으면 r0 1을 넣는다.
56 mov pc, lr // 이전위치로 돌아간다.
------------------------------------------------------------------------
r0에는 test의 시작 주소가, 그리고 나중에 test의 결과 값이 r0로 돌아가게 된다. 결과값이 0이라면, RAM이 존재한다는 것을 나타내고, 그렇지 않다면, RAM이 없음을 나타낸다. r1에서 r4까지는 이 subroutine에서 사용되는 register들이다.
r0가 가르키는 곳에 원래 두개의 32bit 값을 r1과 r2로 읽어 들인다(ldmia). r3에는 0x55를 r4에는 0xaa를 두고, 이 값을 r0가 가리키는 곳에 적는다(stmia). 다시 r0가 가르키는 곳에서 r3과 r4를 읽어서, 이 값들이 앞에서 적었던 값과 일치하는지를 검사한다.(teq,teqeq) 만약 같지 않다면, 쓴 값과 읽은 값이 다르므로 RAM이 존재하지 않는다는 말이 되므로, bad로 제어를 얾기게 될 것이다. r3에는 다시 0xaa를 넣고, r4에 0x55를 넣은후, r0이 가르키는 곳에 다시 r3과 r4를 저장한다.(stmia). 다시 같은 위치에서 r3과 r4를 가져와서(ldmia)이 값이 앞서 쓴 값과 같은지를 비교한다. r0에는 앞에서 저장해 두었던 r1과 r2를 다시 쓰게 도고, r0에는 RAM이 존재하는지 아닌지에 따라서, 0이나 1값을 갖게 된다.(moveq, movne) 이전의 서브루틴(start.S)으로 복귀한다.따라서, 결과값은 r0에 들어가게 된다.
---------------- start.S ----------------------
208 badram:
209 b blinky
210
blinky로 점프한다.
214 blinky:
215 /* This is test code to blink the LED
216 very useful if nothing else works */
// LED가 깜빡거리게 하는 test코드이다. 작업시 매우 유용할 것이다.
217 bl led_on
218 bl wait_loop
219 bl led_off
220 bl wait_loop
221 b blinky
led_on으로 점프
wait_loop로 점프
led_off로 점프
wait_loop로 점프
223 wait_loop:
224 /* busy wait loop*/
225 mov r2, #0x1000000
r2에 0x100000를 넣는다.
-----------------------------------------------
위에서 제대로 RAM을 찾을 수 없다면, badram으로 점프하게 되며, badram에서는 blinkly로 브랜치한다. blinky에서는 단순히 LED를 깜빡이는 일을 할것이다. led_on과 led_off는 ledasm.S에 정의가 되어 있으며, 단순히 led를 키고, 끄는 역할을 하며, wait_loop는 LED가 켜진후에 시간 지연을 주어 사람이 인식 할수 있는 시간 텀을 주는 것이다.
--------------- ledasm.S ----------------------
65 .globl led_on
66 /* turn LED on. clobbers r0 and r1 */
// LED를 켠다.
67 led_on:
68 ldr r0, GPIO_BASE
69 ldr r1, LED
70 str r1, [r0, #GPSR]
71 mov pc, lr
GPIO_BASE(=0x90040000)는 ledasm.S파일 43라인에
GPIO_BASE: .long 0x90040000
위와 같이 정의 되어 있다.
LED는 LED_GPIO로 ledasm.S파일 42라인에
LED: .long LED_GPIO
위와 같이 정의 되어 있으며 LED_GPIO는 led.h파일에 다음과 같이 정의 되어 있다.
각각의 보드마다 다르게 정의 되어 있는 것을 알 수 있다.
-------- include/led.h ----------
30 /* define the GPIO pin for the LED */
31 #if defined ASSABET
32 # define LED_GPIO 0x00020000 /* GPIO 17 */
33 #elif (defined CLART) || (defined LART) || (defined NESA)
34 # define LED_GPIO 0x00800000 /* GPIO 23 */
35 #elif defined PLEB
36 # define LED_GPIO 0x00010000 /* GPIO 16 */
37 #else
38 #warning "FIXME: Include code to turn on one of the LEDs on your board"
39 # define LED_GPIO 0x00000000 /* safe mode: no GPIO, so no LED */
40 #endif
---------- led.h 참조 끝.------------------1------
GPSR은 ledasm.S파일 45라인에
#define GPSR 0x00000008
위와 같이 정의 되어 있다.
r1에 *(r0+#GPSR=0x90040008)를 저장한다.(#define GPSR 0x00000008) [ 참고. SA1110메뉴얼 A-4에 GPSR 레지스터에 대해 나온다.]
76 .globl led_off
77 /* turn LED off. clobbers r0 and r1 */
// LED를 끈다.
78 led_off:
79 ldr r0, GPIO_BASE
80 ldr r1, LED
81 str r1, [r0, #GPCR]
82 mov pc, lr
---------- ledasm.S 참조 끝.-------------------------
위의 것은 동일하며, GPCR은 ledasm.S파일 46라인에
#define GPCR 0x00000008
위와 같이 정의 되어 있다.
r1에 *(r0+#GPCR=0x9004000c)를 저장한다.(#define GPCR 0x0000000c) [ 참고. SA1110메뉴얼 A-4에 GPCR 레지스터에 대해 나온다.]
---------------- start.S -------------------------
152 /* the first megabyte is OK, so let's clear it */
// 첫 1MBytes가 존재하는지 확인한다.
153 mov r0, #((1024 * 1024) / (8 * 4)) /* 1MB in steps of 32 bytes */
154 ldr r1, MEM_START
155 mov r2, #0
156 mov r3, #0
157 mov r4, #0
158 mov r5, #0
159 mov r6, #0
160 mov r7, #0
161 mov r8, #0
162 mov r9, #0
r0에 1MByte를 32로 나눈값을 넣는다.
r1에 MEM_STAR(=0xc0000000)의 시작 주소를 가르키게 한다.
r2-r9까지 0를 넣는다.
-------------------------------------------------
메모리의 첫 1MByte에 대한 확인이 끝나게 되면, 메모리의 1M영역을 0으로 클리어 시켜 준다.
---------------- start.S -------------------------
164 clear_loop:
165 stmia r1!, {r2-r9} // !는 Write-Back기능이다.(해석이 잘 안된다. -_-;;)
166 subs r0, r0, #(8 * 4) // r0에서 32를 뺀것을 r0에 넣는다.
167 bne clear_loop // 같지 않다면(r0 != 0) clear_loop로 이동한다.
stmia(Store and Increment after)를 사용하여 r1이 가르키는 주소에 r2에서 r9까지를 저장한 후, r1을 증가 시킨다.
r0는 원래값에서 32만큼 감소시킨다.
r0가 0이 될때까지 계속 loop를 돈다.
170 /* get a clue where we are running, so we know what to copy */
// 아는 값이 복사 동작할때 해결의 실마리를 얻을 수 있다.
171 and r0, pc, #0xff000000 /* we don't care about the low bits */
pc(프로그램카운터)의 값과 0xff000000와 and연산을 한 값을 r0에 넣는다.
174 /* relocate the second stage loader */
// 로더의 두번재 영역을 재 배치 한다.
175 add r2, r0, #(128 * 1024) /* blob is 128kB */ // blob는 128KBytes.
176 add r0, r0, #0x400 /* skip first 1024 bytes */ // 첫번재 1024 byte는 건너뛰고,
177 ldr r1, MEM_START // r1에 MEM_START를 넣고.
178 add r1, r1, #0x400 /* skip over here as well */ // r1에 0x400을 더해서 r1에 넣는다.
r0와 (128*1024)을 더한 값을 r2에 넣는다.
r0와 0x400(10000000000(이진수)=2^10=1024)을 더한 값을 r0에 넣는다. 즉 첫번째 1024바이트을 뛰어 넘는다.
r1에 MEM_START(=0xc0000000)를 로드한다.
r1과 0x400(10000000000(이진수)=2^10=1024)을 더한 값을 r1에 넣는다. 즉 C0000000에서 1024바이트을 뛰어 넘는다.(0xC0000400)
179
180 /* r0 = source address // r0는 소스 어드레스.
181 * r1 = target address // r1은 타겟 어드레스.
182 * r2 = source end address // r2는 소스의 마지막 어드레스.
183 */
-------------------------------------------------
현재 어디서 전행되고 있는지를 알기 위해서 프로그램 카운터의 상위 1byte만을 제외하고 나머지를 0d으로 클리어 시켜서 r0에 넣는다. 이 값에 128K를 곱해서 r2로 두고, r0에는 다시 0x400을 더해서 첫 1024를 건너 뛰도록 한다.
이것은 reset-ld-script라는 곳에서 시작 주소를 0xC0000400으로 준 것에 관련 되어 있는 것으로 blob가 첫 1024Bytes이후의 번지로 복사가 될 것이기 때문이다.
-------------------- start.S ---------------------
184 copy_loop:
185 ldmia r0!, {r3-r10}
186 stmia r1!, {r3-r10}
187 cmp r0, r2
188 ble copy_loop
189
190
191 /* turn off the LED. if it stays off it is an indication that
192 * we didn't make it into the C code
193 */
// LED를 끈다. 만약 OFF로 머물러 있으면, C 코드로 들어갈수 없기 때문에 조치를 취하라.
194 bl led_off
195
196
197 /* set up the stack pointer */
// 스택 포인터를 설정한다.
198 ldr r0, MEM_START // r0에 MEM_START를 로드한다.
199 add r1, r0, #(1024 * 1024) // r1 = r0 + (1024*1024 = 1M)
200 sub sp, r1, #0x04 // sp = r1 - 0x04
201
202
203 /* blob is copied to ram, so jump to it */
// blob를 램에 복사가 끝난후, 점프한다.
204 add r0, r0, #0x400 // r0 = r0 + 0x400
205 mov pc, r0 // sp = r0
--------------------------------------------------
r0에는 앞에서 copy할 source의 시작 주소가 들어가도록 하고, r1에는 target이 되는 주소를, r2에는 copy의 대상이 되는 것이 마지막 주소(128Kbyte의 크기)가 들어가도록 해주었다. 이젠 r0가 가르키는 곳에선 r1이 가르키는 곳으로 copy를 한다. 이것은 ldmia와 stmia의 연속적인 것으로 가능하며, r0가 r2와 같게 되었을때, copy가 끝나게 된다. 그렇지 않으면, copy_loop로 제어를 옮겨서 계속 copy를 할 것이다.
copy가 끝나면, LED를 off시키고(led_off),r0는 다시 메모리의 시작 번지로 만둘어 주게 되며(MEM_START), r1은 메모리의 시작주소에서 1MBytes 떨어진 곳을, sp는 다시 r1보다 4작은 곳을 가르키도록 만든다. 이젠 stack pointer를 설정해 주었으므로 C routine을 실행할 준비가 되었다. 앞에서 1024Bytes 정도 MEM_START에서 떨어진 곳에 cpy를 해두었으므로, r0에 다시 0x400을 더한 후 pc를 그곳으로 옮겨서 실행을 계속 진행하도록 한다.(mov pc, r0). 실제 수행이 옮겨지는 곳은 trampoline.S에 정의된 second stage boot loader이다.
-------------------------
의문점이 생긴다. 도대체 어떻게 trampoline.S파일로 옮겨가는 것일까. start.S파일에는 옮겨가는 루틴이 없는데 말이다.
-------------------------
------- trampoline.S --------------
30 .globl _trampoline
31 _trampoline:
32 bl main // main으로 점프한다. ( C code )
33 /* if main ever returns we just call it again */
// 만약 리턴값이 없으면 다시 호출된다.(무한루프)
34 b _trampoline
------- trampoline.S파일의 끝. --------------
여기서는 단지 main.c파일의 main()함수로 브랜치하는 연산만 수행할 뿐이다. 만약 main에서 리턴하게 되면 다시 trampoline을 호출하게 되므로 계속해서 루프를 돌게 된다.
main.c의 분석.
main.c파일에서 하는일은 serial을 초기화 하고, timer를 동작시키며, 시스템의 메모리맵을 구한후, blob를 초기화 한다. 이것을 끝내면 커널과 RAMDISK를 Flash로 부터 읽어서 RAM으로 복사하고, 사용자의 입력을 기다리게 된다.
---------------- main.c ---------------------------
76 int main(void)
77 {
78 u32 blockSize = 0x00800000; // 블럭의 크기.
79 int numRead = 0; // 초기에 읽으려는 값.
80 char commandline[128]; // 명령어를 저장해 두는 배열공간.
81 int i;
82 int retval = 0;
83
84 /* Turn the LED on again, so we can see that we safely made it
85 * into C code.
86 */
// LED가 다시 켜지면, 안전하게 C 코드로 안전하게 진입할 수 있다.
87 led_on();
88
89 /* We really want to be able to communicate, so initialise the
90 * serial port at 9k6 (which works good for terminals)
91 */
// 우리는 진정으로 대화하기를 원한다. 그래서, 시리얼포트를 9600의 속도로 초기화 한다. (터미널이 잘 동작 한다면.)
// baud9k6은 serial.h파일에 다음과 같이 정의 되어 있다.
// 아래와 같이 정의된 값은 다음의 공식을 사용해서 BaudRate를 정의하는데 사용되는 값이다. 정의된 값은 BRD에 해당한다.
BaudRate = (3.6864*10^6/16*(BRD+1))
의 공식에 의해서 다음의 값들이 얻어지게 된다.
이 공식에서 사용될 BRD값은 나중에 사용되는 serial port의 UART control register에 들어가서, serial의 BaudRate를 결정한다.
------------ main.c파일 인용끝.---------------------------------
------------- ./include/serial.h파일의 인용 시작.----------------
41 typedef enum { /* Some useful SA-1100 baud rates */
42 baud1k2 = 191,
43 baud9k6 = 23,
44 baud19k2 = 11,
45 baud38k4 = 5,
46 baud57k6 = 3,
47 baud115k2 = 1
48 } eBauds;
--------------- ./include/serial.h파일 인용 끝.---------------
이 헤더파일에서 BRD값을 지정을 해서 가져올 수 있게 하고 있다.
자신이 원하는 속도의 값을 써주면 된다. 115200을 사용하려면 baud115k2를 SerialInit함수의 인자로 써주면 될 것이다. 아래와 같이 말이다.
SerialInit(baud115k2); // 시리얼 초기화 하는 함수.
--------------- ./serial.c파일의 인용 시작.---------------------
48 void SerialInit(eBauds baudrate)
49 {
50 /* Theory of operations:
51 * - Flush the output buffer
52 * - switch receiver and transmitter off
53 * - clear all sticky bits in control register 3
54 * - set the port to sensible defaults (no break, no interrupts,
55 * no parity, 8 databits, 1 stopbit, transmitter and receiver
56 * enabled
57 * - set the baudrate to the requested value
58 * - turn the receiver and transmitter back on
59 */
// 동작의 이론.
// - 출력 버퍼를 가득 채운다.
// - 전송과 수신(통신수단)을 끈다.
// - 콘트롤 레지스터 3의 모든 스티키비트를 깨끗이한다.
// - 시리얼 포트를 초기화한다.(no break, no interrupts, no parity, 8 databits, 1 stopbit, transmitter and receiver enabled.)
// - 바운드레이트를 요청된 값으로 설정한다.
// - 전송과 수신(통신수단)을 켠다.
60
61 #if defined USE_SERIAL1 // 1번 시리얼 포트를 사용한다면
62 while(Ser1UTSR1 & UTSR1_TBY) {
63 }
64
65 Ser1UTCR3 = 0x00;
66 Ser1UTSR0 = 0xff;
67 Ser1UTCR0 = ( UTCR0_1StpBit | UTCR0_8BitData );
68 Ser1UTCR1 = 0;
69 Ser1UTCR2 = (u32)baudrate;
70 Ser1UTCR3 = ( UTCR3_RXE | UTCR3_TXE );
71 #elif defined USE_SERIAL3 // 3번 시리얼 포트를 사용한다면
72 while(Ser3UTSR1 & UTSR1_TBY) {
73 }
74
75 Ser3UTCR3 = 0x00;
76 Ser3UTSR0 = 0xff;
77 Ser3UTCR0 = ( UTCR0_1StpBit | UTCR0_8BitData );
78 Ser3UTCR1 = 0;
79 Ser3UTCR2 = (u32)baudrate;
80 Ser3UTCR3 = ( UTCR3_RXE | UTCR3_TXE );
81 #else
82 #error "Configuration error: No serial port used at all!" // 설정에러 시리얼 포트를 사용 할 수 없다.
83 #endif
84 }
--------------- ./serial.c파일의 인용 끝.---------------------
시리얼 포트 1을 사용할지, 아니면, 시리얼 포트 3을 사용할지에 따라서, 각각이 사용하는 레지스터 셋 달라지지만, 근본적으로 동일한 레지스터 셋을 시리얼1과 3이 같이 가지고 있으므로, 들어가는 내용은 동일하다. 먼저, 각각의 시리얼 포트의 상태가 바쁘면(비지하면), 상태가 바뀔때 까지 기다린다. 그리고, UTCR3(UART Control Register 3)에 0x00을 써서 Rx와 Tx를 OFF시킨후, UTCR0(UART Control Register 0)에 0xFF을 써서 지운다(클리어한다). 다시 UTCR0에 1 스탑비트와 8비트데이터로 설정을 변경한 후, UTCR1에는 0을 UTCR2에는 전달받은 바운드레이트 정보를 기입해서 어떤 바운드 레이트로 동작할지를 결정해준다. 이것을 다 마치면, 이젠 UTCR3에 Rx 인에이블비트와 UART3 Tx인에이블비트을 설정하여 Rx와 Tx가 동작하도록 만들어 준다. 여기서 UTCR1은 앞의 공식에서 사용한 BRD의 상의 4bit을, UTCR2는 하위 8bit을 가지도록 만들어 주는 것이다.
=(참고, 여기에서 말하는 레지스터들은 커널소스/include/asm-arm/arch-sa1100/SA-1100.h에 그 정의가 나온다. 다음부터 나오는 모든 레지스터들도 다 마찬가지이다.)
------------- main.c 파일 인용 시작.---------------------------
92 SerialInit(baud9k6); // 시리얼 초기화 하는 함수.
93 TimerInit(); // 타이머 초기화 하는 함수.
----------------- main.c파일의 인용 끝.---------------------
TimerInit() 함수 정의는 time.c에 있으며 다음과 같이 되어 있다. 이곳에서 하는 일은 시스템에 있는 timer interrupt의 발생 빈도를 설정하는 것이다.
------------- time.c 파일 인용 시작.---------------------------
50 void TimerInit(void)
51 {
52 /* clear counter */
// OSCR(OS timer Counter Register(운영체제 타이머 카운터 레지스터))을 깨끗이 한다.
// OSCR레지스터를 0으로 한다.
53 OSCR = 0;
54
55 /* we don't want to be interrupted */
// 인터럽트가 되어 있는것을 원하지 않는다.
// OSER(OS timer Interrupt Enable Register(OS 타이머 인터럽트 인에이블 레지스터))
// OIER레지스터를 0으로 한다.
56 OIER = 0;
57
58 /* wait until OSCR > 0 */
// OSCR이 0보다 클때까지 기다린다.
59 while(OSCR == 0) // OSCR 이 0과 같으면 무한루프를 돈다.
60 ;
61
62 /* clear match register 0 */
// 0과 매치된 레지스터를 깨끗이 한다.
// OSMR0 (OS timer Match Register 0(0번째 운영체제 타이머 매치 레지스터 ))
// OSMR0레지스터를 0으로 한다.
63 OSMR0 = 0;
64
65 /* clear match bit for OSMR0 */
// OSMR0과 매치된 레지스터를 깨끗이 한다.
// OSSR (OS timer State Register(운영체제 타이머 상태 레지스터 ))
// OSSR에 OSSR_M0을 넣는다.
-------------SA-1100.h파일 인용 시작.--------------------
#define OSSR_M(Nb) /* Match detected [0..3] */ \
(0x00000001 << (Nb))
#define OSSR_M0 OSSR_M (0) /* Match detected 0 */
OSSR_M0 = 0x00000001이 됨을 알 수 있다.
-------------SA-1100.h파일 인용 끝.--------------------
66 OSSR = OSSR_M0;
67
68 numOverflows = 0;
69 }
----------------- time.c파일의 인용 끝.---------------------
SA1100에는 크게 OSCR(Operating System Counter Register)라는 up-counter register와 4개의 OSMR(Operation System Match Register)가 존재한다. OSCR은 reset의 영향을 받지 않으며, OSMR은 사용자가 read와 write를 할 수 있다.
만약 OSCR이 OSMR의 특정 레지스터와 일치하고, interrupt enable bit이 설정된 경우에는 OSSR(Operating System Status Register)에 해당 bit이 설정되게 되며, 이러한 bit은 interrupt controller bit에 route되어 인터럽트를 발생시키도록 할 수 있다.
또한 OSMR3는 watchdog match register의 역할을 수행할 수 있도록 되어 있어서, 만약 OSCR이 OSMR3와 같아진다면, SA1100을 reset시킬 수 있다.
Reset이후에 known state로 갈 수 있는 관련된 레지스터로는 또한, WMER(Watchdog Match Enable Register)가 있으며, 사용자는 FIQ(Fast Interrupt)와 IRQ(Interrupt)를 CPU에 인에이블 시키기 전에, 레지스터들을 초기화 시켜야 하며, 상태레지스터(status register)는 클리어시켜 주어야 한다.
위에서 OSCR을 먼저 0으로 클리어하고 다시 OIER를 0으로 지웠다. 이것으로 counter와 interrupt를 다 클리어 시켜 주었다. 이제 OSCR에 countering이 일어날때 까지 기다련후, OSMR0를 다시 0으로 만들어 주었으며, OSSR의 OSMR0와 일치하는 bit을 clear시켜 주었다. 즉, OSSR의 특정 bit에 1을 write하는 것으로 clear시킨다. 또한 numOverflows는 Overflow가 일어난 회수를 가지는 변수를 0으로 초기화 시킨다.
------------- main.c 파일 인용 시작.---------------------------
94
95 /* Print the required GPL string */
// GPL라이센스를 프린팅 한다.
96 SerialOutputString("\nConsider yourself LARTed!\n\n");
97 SerialOutputString(PACKAGE " version " VERSION "\n"
98 "Copyright (C) 1999 2000 2001 "
99 "Jan-Derk Bakker and Erik Mouw\n"
100 "Copyright (C) 2000 "
101 "Johan Pouwelse\n");
102 SerialOutputString(PACKAGE " comes with ABSOLUTELY NO WARRANTY; "
103 "read the GNU GPL for details.\n");
104 SerialOutputString("This is free software, and you are welcome "
105 "to redistribute it\n");
106 SerialOutputString("under certain conditions; "
107 "read the GNU GPL for details.\n");
----------------- main.c파일의 인용 끝.---------------------
serial을 이용하여 문자열을 콘솔에 출력하는 부분이다. 여기서 사용된 함수는 SerialOutputString()이라는 함수이며 이 함수는 string.c파일에 정의 되어 있다.
------------- serial.c 파일 인용 시작.---------------------------
116 /*
117 * Write a null terminated string to the serial port.
118 */
// 시리얼 포트를 이용하여 문자를 쓴다.
119 void SerialOutputString(const char *s) {
120
121 while(*s != 0)
122 SerialOutputByte(*s++);
123
124 } /* SerialOutputString */ // 시리얼로 문자열을 출력한다.
197 /*
198 * Read a single byte from the serial port. Returns 1 on success, 0
199 * otherwise. When the function is succesfull, the character read is
200 * written into its argument c.
201 */
// 시리얼 포트로 한바이트를 읽는다. 성공시 1을 그외의 경우이면 0을 반환한다. 문자를 읽는것이 성공적이 었을때, 이것은 인자 c로 쓰여진다.
202 int SerialInputByte(char *c)
203 {
204 #if defined USE_SERIAL1 // 만약 1번 시리얼을 사용하면.
205 if(Ser1UTSR1 & UTSR1_RNE) { //receive not empty 수신버퍼에 데이터가 있음
206 int err = Ser1UTSR1 & (UTSR1_PRE | UTSR1_FRE | UTSR1_ROR);
207 *c = (char)Ser1UTDR;
208 #elif defined USE_SERIAL3 // 만약 3번 시리얼을 사용하면.
209 if(Ser3UTSR1 & UTSR1_RNE) {
210 int err = Ser3UTSR1 & (UTSR1_PRE | UTSR1_FRE | UTSR1_ROR);
211 *c = (char)Ser3UTDR;
212 #else
213 #error "Configuration error: No serial port at all"
214 #endif
215
216 /* If you're lucky, you should be able to use this as
217 * debug information ;-) -- Erik
218 */
// 너는 다음과 같은 디버깅 정보를 사용할 수 있을 것이다.
219 if(err & UTSR1_PRE) // err & UTSR1_PRE
220 SerialOutputByte('@'); // '@' 문자를 출력한다.
221 else if(err & UTSR1_FRE) // err & UTSR1_FRE
222 SerialOutputByte('#'); // '#' 문자를 출력한다.
223 else if(err & UTSR1_ROR) // err & UTSR1_ROR
224 SerialOutputByte('$'); // '$' 문자를 출력한다.
225
226 /* We currently only care about framing and parity errors */
// 프레임과 패리티 에러에 관한것을 항상 체크한다.
227 if((err & (UTSR1_PRE | UTSR1_FRE)) != 0) {
228 return SerialInputByte(c);
229 } else {
230 led_toggle();
231 return(1);
232 }
233 } else {
234 /* no bit ready */
235 return(0);
236 }
237 } /* SerialInputByte */
------------- serial.c파일의 인용 끝.----------------------------
이 함수는 문자열을 SerialOutputByte() 함수를 사용하여 1Byte단위로 계속하여 출력을 한다. 실제 문자열을 콘솔에 출력하는 함수는 SerialOutputByte()함수이다.
SerialOutputByte()함수는 사용하는 시리얼 포트에 따라 Ser1UTDR과 Ser3UTDR레지스터에 하나의 문자(character)를 써넣는 일을 한다.
먼저 쓰기 전에 UTSR 레지스터를 읽어서, Tx FIFO가 가득 차 있는지 아닌지를 검증하게 된다. 만약 Tx FIFO가 half-full이하이게 되면, UTDR레지스터에 데이터를 쓴다. 만약 쓰려고한 데이터가 "\n"문자일 경우 "\r"을 추가하여 라인 피드가 되게 만든다.
------------- main.c 파일 인용 시작.---------------------------
108
109
110 /* get the amount of memory */
// 시스템의 메모리맵을 가져온다.
111 get_memory_map();
112
------------- main.c파일의 인용 끝.----------------------------
get_memory_map함수는 시스템에 있는 메모리의 양을 구하는 함수이다. 즉, 이후에 커널 및 램디스크를 로드하기 위해서, 현재 시스템에 얼마나 많은 메모리가 있는지 알아야 한다. 또한 blob_status 변수에 대한 초기화도 이곳에서 일어나게 된다. 이변수는 main.h에 있는 blob_status_t 구조체로 정의되며, 다음과 같다.
------------- memory.c 파일 인용 시작.---------------------------
49 void get_memory_map(void)
50 {
51 u32 addr;
52 int i;
53
54 /* init */ // 초기화
55 for(i = 0; i < NUM_MEM_AREAS; i++)
56 memory_map[i].used = 0;
57
58 /* first write a 0 to all memory locations */
// 모든 메모리에 첫번째 위치에 0을 쓴다.
59 for(addr = MEMORY_START; addr < MEMORY_END; addr += TEST_BLOCK_SIZE)
60 * (u32 *)addr = 0;
61
62 /* scan memory in blocks */ // 메모리블럭을 스캔한다.
63 i = 0;
64 for(addr = MEMORY_START; addr < MEMORY_END; addr += TEST_BLOCK_SIZE) {
65 if(testram(addr) == 0) {
66 /* yes, memory */
67 if(* (u32 *)addr != 0) { /* alias? */
68 #ifdef BLOB_DEBUG
69 SerialOutputString("Detected alias at 0x");
70 SerialOutputHex(addr);
71 SerialOutputString(", aliased from 0x");
72 SerialOutputHex(* (u32 *)addr);
73 SerialOutputByte('\n');
74 #endif
75 if(memory_map[i].used)
76 i++;
77
78 continue;
79 }
80
81 /* not an alias, write the current address */
// Alias가 아니며, 현재의 주소를 쓴다.
82 * (u32 *)addr = addr;
83 #ifdef BLOB_DEBUG
84 SerialOutputString("Detected memory at 0x");
85 SerialOutputHex(addr);
86 SerialOutputByte('\n');
87 #endif
88 /* does this start a new block? */ // 새로운 블럭에서 시작하자.
89 if(memory_map[i].used == 0) {
90 memory_map[i].start = addr;
91 memory_map[i].len = TEST_BLOCK_SIZE;
92 memory_map[i].used = 1;
93 } else {
94 memory_map[i].len += TEST_BLOCK_SIZE;
95 }
96 } else {
97 /* no memory here */
98 if(memory_map[i].used == 1)
99 i++;
100 }
101 }
102
103 SerialOutputString("Memory map:\n");
104 for(i = 0; i < NUM_MEM_AREAS; i++) {
105 if(memory_map[i].used) {
106 SerialOutputString(" 0x");
107 SerialOutputHex(memory_map[i].len);
108 SerialOutputString(" @ 0x");
109 SerialOutputHex(memory_map[i].start);
110 SerialOutputString(" (");
111 SerialOutputDec(memory_map[i].len / (1024 * 1024));
112 SerialOutputString(" MB)\n");
113 }
114 }
115 }
------------- memory.c파일의 인용 끝.----------------------------
먼저 사용하게 될 메모리의 메모리 맵(map)을 나타내는 memory_map[]변수의 used필드를 전부 0으로 clear시킨다. NUM_MEM_AREAS는 전체 메모리 맵의 영역이 얼마나 있는가를 가르키는 상수 값으로 32를 가지며, memory.h에 정의되어 있다. 이 32라는 숫자는 alias로 연결되어 있는 부분을 찾는데 충분한 값으로 설정 되어야 한다. memory_map[] 변수는 아래와 같은 memory_area_t 구조체로 정의된다. 이젠 이렇게 적어 놓은 메모리에 대해서 다시 loop를 돌면서 실제로 메모리가 있는지를 확인하는 작업이 되며, 앞에서 본 것과 윳한 testram()함수가 사용된다. 단지 앞에서 본 함수와 차이가 나는 점은 이 함수는 C에서 호출이 되는 함수이기 때문에, 사용하는 레지스터들에 대해서 저장하는 것에 따르게 된다. 리턴값은 앞에서와 마찬가지로 0인 경우에는 존재한다는 것을 나타내며, 그렇지 않은 경우에는 1을 돌려 받게 된다. 정의된 곳은 testmem2.S 파일이다.
------------- memory.h파일의 인용 끝.----------------------------
34 typedef struct {
35 u32 start;
36 u32 len;
37 int used;
38 } memory_area_t;
------------- memory.h파일의 인용 끝.----------------------------
------------- main.h파일의 인용 시작.----------------------------
37 /* memory start and end */
38 #define MEMORY_START (0xc0000000)
39 #define MEMORY_END (0xe0000000)
------------- main.h파일의 인용 끝.----------------------------
메모리를 1MBytes 단위(=TEST_BLOCK_SIZE)씩 증가 시키면서 각 메모리 block의 시작 위치에 loop를 돌면서 0을 쓰도록 한다. 이때 메모리의 (MEMORY_START)와 메모리의 마지막 주소(MEMORY_END)는 각각 0XC0000000과 0XE0000000이다. 이 영역에 있는 메모리는 SA1110의 메뉴얼에서 찾을 수 있듯이 DRAM BANK 0,1,3,4가 된다.( 매뉴얼 10-4 참고) ( 메뉴얼에 어데에 나오는지 찾을 수가 엄다.-_-;;)
testram() 함수의 호출 결과가 0이 아니라면, RAM이 존재하지 않는다는 말이 되므로, memory_map[]에 해당 블럭의 used필드가 1인 경우에만 블록의 갯수를 나타내는 변수인 i를 증가시켜주고, 그렇지 않으면 다음 loop로 진행한다. 만약, 메모리가 alias되어서 사용되고 있다는 것을 의미할 수도 있다. 이때는 memory_map[]의 해당 block에 대한 used필드를 봐서 1로 설정된 경우에만 블록의 수를 증가 시켜 주기 위해서 i를 증가시킨다. 그렇지 않다면, 현재의 주소 값(addr)을 그대로 addr변수가 가르키는 곳에 적어 주어서, alias를 판단할 근거를 만들어 준다.
만약, 현재의 block이 사용중이지 않다면(memory_map[i].used == 0), memory_map[]의 element를 설정한다. 즉, 시작 주소에는 addr을 기입하고, 크기는 1MBytes(=TEST_BLCOK_SIZE), used 필드에는 1로 준다. 사용중이라면(memory_map[i].used == 1),단지 크기만을 증가시켜준다(TEST_BLOCK_SIZE를 더해줌).즉, 이 block은 새로운 block의 일부로 생각해 준다는 것이다.
------------- main.c 파일 인용 시작.---------------------------
113 /* initialise status */
// 상태를 초기화 한다.
114 blob_status.kernelSize = 0; // 커널크기를 0으로 초기화 한다.
115 blob_status.kernelType = fromFlash; // fromFlash로 커널타입을 초기화 한다.
116 blob_status.ramdiskSize = 0; // 램디스크 크기를 0으로 초기화 한다.
117 blob_status.ramdiskType = fromFlash;// fromFlash로 램디스크의 타입을 초기화 한다.
118 blob_status.blockSize = blockSize; // blockSize필드를 blockSize(=0x00800000)으로 한다.
119 blob_status.downloadSpeed = baud115k2;//시리얼 다운로드 속도를 115200BPS로 설정한다.
120
121
122 /* Load kernel and ramdisk from flash to RAM */
// 부트로더, 커널, 램디스크를 flash로 부터 RAM으로 로딩한다.
123 Reload("blob"); // 부트로더를 리로드.
124 Reload("kernel"); // 커널을 리로드.
125 Reload("ramdisk"); // 램디스크를 리로드.
126
------------- main.c파일의 인용 끝.----------------------------
Reload()함수에 대하여 알아보자. Reload 함수는 아래에서 보는 바와 같이 commandline이라고 하는 변수로 문자열을 넘겨 받는다.
------------- main.c 파일 인용 시작.---------------------------
470 void Reload(char *commandline)
471 {
472 u32 *src = 0;
473 u32 *dst = 0;
474 int numWords;
475
476 if(MyStrNCmp(commandline, "blob", 4) == 0) { // 입력받은 커맨드라인 명령어와 'blob'과 문자열 비교한다.
477 src = (u32 *)BLOB_RAM_BASE;
478 dst = (u32 *)BLOB_START;
479 numWords = BLOB_LEN / 4;
480 blob_status.blobSize = 0;
481 blob_status.blobType = fromFlash;
482 SerialOutputString("Loading blob from flash ");
483 } else if(MyStrNCmp(commandline, "kernel", 6) == 0) { // 입력받은 커맨드라인 명령어와 'kernel'과 문자열 비교한다.
484 src = (u32 *)KERNEL_RAM_BASE;
485 dst = (u32 *)KERNEL_START;
486 numWords = KERNEL_LEN / 4;
487 blob_status.kernelSize = 0;
488 blob_status.kernelType = fromFlash;
489 SerialOutputString("Loading kernel from flash ");
490 } else if(MyStrNCmp(commandline, "ramdisk", 7) == 0) { // 입력받은 커맨드라인 명령어와 'ramdisk'과 문자열 비교한다.
491 src = (u32 *)RAMDISK_RAM_BASE;
492 dst = (u32 *)INITRD_START;
493 numWords = INITRD_LEN / 4;
494 blob_status.ramdiskSize = 0;
495 blob_status.ramdiskType = fromFlash;
496 SerialOutputString("Loading ramdisk from flash ");
497 } else {
498 SerialOutputString("*** Don't know how to reload \"");
499 SerialOutputString(commandline);
500 SerialOutputString("\"\n");
501 return;
502 }
503
504 MyMemCpy(src, dst, numWords);
505 SerialOutputString(" done\n");
506 }
------------- main.c파일의 인용 끝.----------------------------
넘겨 받은 인자를 MyStrNCmp()에 해당하는 것이 있는지를 알 수 있도록, blob, kernel, ramdisk(기본 명령어)와 같은 문자열과 비교하는 문자열의 길이를 나타내는 것을 같이 준다. MyStrNCmp() 함수는 넘겨받은 문자열을 비교해서 같은지 다른지만을 보게된다. 아래와 같은 정의를 가진다. MyStrNCmp()의 정의는 util.c에 있다.
또한 여기서 MyMemCpy()함수도 알아보자.
각 if절에 있는 MyStrNCmp()를 보도록 하자. 가장 먼저 비교하는 것은 "blob"이다. 비교해서 같다면, src와 dst에 각각 BLOB_RAM_BASE(=0xC1000000)와 BLOB_START(=0x00000000)을 가져온다.(참고: 이값은 순전히 Assabet보드를 기준으로 한 값이라는것을 알아두자(flash.h파일 참조))
또한, numWords에는 BLOB_LEN(=0x10000 or 64K)를 4로 나눈 값을 넣고, blob_status.blobSize에 0을 blobType에는 fromFlash(=0 or download from flash)를 두도록 한다. 그리고 나서, 이제 이것을 loading한다는 것을 보여주기 위해서 serial에 "loading blob from flash"를 쓴다. 나머지는 MyMemCpy()에서 dst가 가르키는 메모리를 복사해 줄것이다.
commandline 변수에 "kernel"을 받았을 경우에는 src에 KRNEL_RAM_BASE(=0xC0008000(이 값은 나중에 커널을 compile할 때 적재할 주소를 알려주는 부분에서 0xC0008000으로 설정하기 때문))을 두고, dst에는 KERNEL_START(=0x10000 or 64K)를 준다. numWords에는 커널의 길이를 나타내는 KERNEL_LEN(=0xC0000 or 3*256K)를 4로 나눈 값을 주어 옮길 값이 4byte단위로 얼마나 되는지를 표시하며, blob_status.kernelSize에는 0을 blob_status.kenelType에는 fromFlash를 두어서 커널을 flash로 부터 loading함을 표시한다. 마지막으로 커널을 loading한다는 것을 나타내기 위해서 SerialOutputString()을 써서 serial로 output을 내보낸다.(실제적인 복사는 MyMemCpy()가 하게 된다.)
------------- util.c 파일 인용 시작.---------------------------
//입력받은 문자열을 비교하는 함수이다.
94 int MyStrNCmp(const char *s1, const char *s2, int maxlen)
95 {
96 int i;
97
98 for(i = 0; i < maxlen; i++) {
99 if(s1[i] != s2[i])
100 return ((int) s1[i]) - ((int) s2[i]);
101 if(s1[i] == 0)
102 return 0;
103 }
104
105 return 0;
106 } /* MyStrNCmp */
// 실제로 메모리에 복사하는 함수이다.
45 void MyMemCpy(u32 *dest, const u32 *src, int numWords)
46 {
47 #ifdef BLOB_DEBUG
48 SerialOutputString("\n### Now copying 0x");
49 SerialOutputHex(numWords);
50 SerialOutputString(" words from 0x");
51 SerialOutputHex((int)src);
52 SerialOutputString(" to 0x");
53 SerialOutputHex((int)dest);
54 SerialOutputByte('\n');
55 #endif
56
57 while(numWords--) {
58 if((numWords & 0xffff) == 0x0)
59 SerialOutputByte('.');
60
61 *dest++ = *src++;
62 }
63
64 #ifdef BLOB_DEBUG
65 SerialOutputString(" done\n");
66 #endif
67 } /* MyMemCpy */
------------- util.c파일의 인용 끝.----------------------------
MyStrNCmp() 함수는 단지 넘겨받은 두개의 문자열이 같은지 만을 보는 함수이다. 넘겨받은 문자열의 길이 만큼을 비교한다. 같다면 0을 돌려줄 것이며, 다르다면, 첫번째 문자열에서 두번째 문자열의 다른 부분에 대한 string index를 뺀 값을 돌려줄 것이다.
MyMemCpy()함수는 목적지(dest)와 원래의 주소(src) 및 복사할 크기를 넘겨받는 함수로 단순히 한번에 32비트 만큼씩을 loop를 돌면서 복사(copy) 하는 일을 해준다. 이때, 복사하려는 크기가 0xFFFF와 AND시켜서 0인 경우에는 "."을 시리얼로 출력해서 얼마나 복사가 진행되고 있는지를 나타낸다.
------------- main.c 파일 인용 시작.---------------------------
127 #ifdef BLOB_DEBUG // BLOB_DEBUG가 정의 되어 있다면(디버깅 모드일 경우)
128 /* print some information */
// 몇몇 정보를 프린트 한다.
129 SerialOutputString("Running from ");
130 if(RunningFromInternal())
131 SerialOutputString("internal");
132 else
133 SerialOutputString("external");
134
135 SerialOutputString(" flash, blockSize = 0x");
136 SerialOutputHex(blockSize);
137 SerialOutputByte('\n');
138 #endif
------------- main.c파일의 인용 끝.----------------------------
디버깅시 몇가지 정보를 사용자 들에게 보여준다. 각각의 memory_map[] element에 대해서 loop를 돌면서 메모리의 시작과 길이, 크기를 써준다. 이때 이용되는 함수가 SerialOutputHex()이다. (SeralOutputString()과 SerialOutputByte()는 위에서 설명 하였다.
마지막으로 남은 RAM disk에 대한 것이다. src에는 RAMDISK_RAM_BASE(=0xC0800000)을 주고, dst에는 INITRD_START(Initial RAM Disk Start = KERNEL_START + KERNEL_LEN)을 준다. numWords에는 INITRD_LAN(=0x280000 or 5*512K, 대략 2.5MBytes 정도)를 4로 나눈 값을 주고, blob_status.ramdiskSize에는 0을 blob_status.ramdiskType에는 fromFlash를 두어서, flash로 부터 로딩한다는 것을 알려준다. 그 외의 경우에는 잘못된 Reload()함수의 호출이므로 디버깅 메시지를 출력한 후 복귀하도록 한다. 따라서, 최종적으로 RAM에 로딩 한다는 것을 알려준다. 그외의 경우에는 잘못된 Reload() 함수의 호출이므로 debugging 메시지를 출력한 후 복귀하도록 한다. 따라서, 최종적으로 RAM에 loadding된 flash 이미지는 다음과 같게 된다.
0x00000000
+------------------+ -----
| Blob | *
| |0x00010000 * 0x10000 = BLOB_LEN
+------------------+ -----
| | *
| | *
| Kernel | * 0xC0000 = KERNEL_LEN
| | *
| |0x000D0000 *
+------------------+ -----
| | *
| | *
| | *
| Inital RAM Disk | * 0x280000 = INITRD_LEN
| | *
| | *
| |0x00350000 *
+------------------+ -----
Reload()함수 수행 이후의 메모리 상태.
여기 까지 해서, flash에 있는 메모리를 시스템의 DRAM영역으로 loading하는 것을 마쳤다.
------------- serial.c파일의 인용 시작.----------------------------
129 /*
130 * Write the argument of the function in hexadecimal to the serial
131 * port. If you want "0x" in front of it, you'll have to add it
132 * yourself.
133 */
// 인자값을 입력 받아서, 16진수의 값을 시리얼 포트로 출력한다. 만약 "0x"가 앞에 붙기를 원한다면 자기자신이 앞에 추가해 주어야 할 것이다.
134 void SerialOutputHex(const u32 h)
135 {
136 char c;
137 int i;
138
139 for(i = NIBBLES_PER_WORD - 1; i >= 0; i--) {
140 c = (char)((h >> (i * 4)) & 0x0f);
141
142 if(c > 9)
143 c += ('A' - 10);
144 else
145 c += '0';
146
147 SerialOutputByte(c);
148 }
149 }
------------- serial.c파일의 인용 끝.----------------------------
SerialOutputHex() 함수는 32bit 크기의 변수를 16진수 값으로 변환해 시리얼을 통하여 출력을 해주는 일을 한다. 결과적으로 함수를 보면 SerialOutputByte() 함수를 사용하여 한바이트 단위로 처리 됨을 알 수 있다. 이것은 데이터를 알아보기쉽게(16진수로 표현) 변환해 주는 함수 이다.
다시 main()함수로 돌아와서 시작 하자.
------------- main.c파일의 인용 시작.----------------------------
139 /* wait 10 seconds before starting autoboot */
// 자동 부팅으로 시작하기 전에 10초간 기다린다.
140 SerialOutputString("Autoboot in progress, press any key to stop ");
141 for(i = 0; i < 10; i++) {
142 SerialOutputByte('.');
143
144 retval = SerialInputBlock(commandline, 1, 1);
145
146 if(retval > 0)
147 break;
148 }
149
150 /* no key was pressed, so proceed booting the kernel */
// 입력된 키가 없으면, 커널로 부팅해서 들어간다.
151 if(retval == 0) {
152 commandline[0] = '\0';
153 boot_linux(commandline);
154 }
155
// 메시지시리얼을 통하여 콘솔로 출력하는 부분이다.
156 SerialOutputString("\nAutoboot aborted\n");
157 SerialOutputString("Type \"help\" to get a list of commands\n");
----------------- main.c파일의 인용 끝.---------------------
SerialOutputString()함수를 호출하여 Autoboot이 진행 할 것임을 나타낸다. 이때 for루프를 돌면서, SerialInputBlock()함수를 호출하여 입력을 받도록 한다. 만약 입력이 있다면, retval은 0이 될 것이며, commandline에는 "\0"를 첫번째 인자로 주고, boot_linux()함수를 호출할 것이다.
먼저 SerialInputBlock() 함수는 결과적으로 SerialInputByte()함수를 호출하므로 같이 보도록 하자.
----------------- serial.c파일의 인용 시작.-------------------
197 /*
198 * Read a single byte from the serial port. Returns 1 on success, 0
199 * otherwise. When the function is succesfull, the character read is
200 * written into its argument c.
201 */
// 한바이트를 시리얼 포트로 부터 읽는다. 성공시 1을 실패시 0을 돌려준다.
// 함수호출이 성공적이 었을때, 읽혀진 문자를 여기에 인자 c로 쓴다.
202 int SerialInputByte(char *c)
203 {
204 #if defined USE_SERIAL1
205 if(Ser1UTSR1 & UTSR1_RNE) {
206 int err = Ser1UTSR1 & (UTSR1_PRE | UTSR1_FRE | UTSR1_ROR);
207 *c = (char)Ser1UTDR;
208 #elif defined USE_SERIAL3
209 if(Ser3UTSR1 & UTSR1_RNE) {
210 int err = Ser3UTSR1 & (UTSR1_PRE | UTSR1_FRE | UTSR1_ROR);
211 *c = (char)Ser3UTDR;
212 #else
213 #error "Configuration error: No serial port at all"
214 #endif
215
216 /* If you're lucky, you should be able to use this as
217 * debug information ;-) -- Erik
218 */
// 만약 당신이 운이 좋다면, 아래의 디버깅 정보를 사용할 수 있을 것이다.
219 if(err & UTSR1_PRE)
220 SerialOutputByte('@');
221 else if(err & UTSR1_FRE)
222 SerialOutputByte('#');
223 else if(err & UTSR1_ROR)
224 SerialOutputByte('$');
225
226 /* We currently only care about framing and parity errors */
// 현재 frame과 parity 에러를 관찰한다.
227 if((err & (UTSR1_PRE | UTSR1_FRE)) != 0) {
228 return SerialInputByte(c);
229 } else {
230 led_toggle();
231 return(1);
232 }
233 } else {
234 /* no bit ready */ // 비트가 준비가 안되었다면.
235 return(0);
236 }
237 } /* SerialInputByte */
----------------- serial.c파일의 인용 끝.---------------------
SerialInputByte()함수는 serial port로 부터 input을 받는 함수이다. 성공했을 때는 1을 돌려줄 것이며, 실패시에는 0을 돌려준다. 성공적인 경우에는 읽은 character는 넘겨 받은 c변수에 저장된다. 먼저 사용할 serial port가 무엇인가에 따라서 Ser1UTSSR1이나, Ser3URSR1을 보도록 할 것이며, UTSR1_RNE는 Receive FIFO Not Empty를 나타낸다. 따라서, receiver FIFO가 비어있지 않다는 말은 입력을 받을 수 있다는 말이 되므로, input을 받기 위해서 Ser1UTDR이나 Ser3UTDR을 접근하게 된다. 읽은 값은 c에 저장된다. 에러가 있었다면, Ser1UTSR1이나 Ser3UTSR1을 확인해서 이 에러값을 err에 저장한다.
각각의 에러가 의미하는 바는 다음과 같다. PRE의 경우는 Parity Error를 나타내며, FRE는 Framing Error를 나타내고, ROR은 Receive Overrun을 나타낸다. Parity error는 받은 데이터의 parity가 문제가 있음을 나타내며, frame error는 stop bit이 1이 아니라, 0이 되었음을 나타내며, receive overrun은 receiver쪽의 FIFO가 이미 full이 되었음을 의미한다. 각 에러 상황에 대해서 SerialOutputByte()함수를 호출해서 적절한 문자를 display해준다. 만약 에러가 PRE나 FRE라면, 다시 한번 SerialInputByte()함수를 호출해서 읽게 되며, 그렇지 않다면 led_toggle()함수를 호출해서 led_off()와 led_on()을 이전의 LED상태(led_state)에 맞게 호출해서 깜빡여 준후, 1을 돌려줄 것이다. Receive FIFO가 비어 있다면, 당연히 0을 돌려준다.
----------------- serial.c파일의 인용 시작.-------------------
296 /*
297 * SerialInputBlock(): almost the same as SerialInputString(), but
298 * this one just reads a block of characters without looking at
299 * special characters.
300 */
301 int SerialInputBlock(char *buf, int bufsize, const int timeout)
302 {
303 u32 startTime, currentTime;
304 char c;
305 int i;
306 int numRead;
307 int maxRead = bufsize;
308
309 startTime = TimerGetTime();
310
311 for(numRead = 0, i = 0; numRead < maxRead;) {
312 /* try to get a byte from the serial port */
313 while(!SerialInputByte(&c)) {
314 currentTime = TimerGetTime();
315
316 /* check timeout value */
317 if((currentTime - startTime) >
318 (timeout * TICKS_PER_SECOND)) {
319 /* timeout! */
320 return(numRead);
321 }
322 }
323
324 buf[i++] = c;
325 numRead ++;
326 }
327
328 return(numRead);
329 }
----------------- serial.c파일의 인용 끝.---------------------
SerialInputBlock() 함수는 특정한 문자(character)가 들어왔는지를 보지 않고, 단순히 읽은 문자들의 block을 돌려주는 일을 한다. 먼저 startTime에 현재의 시간(TimeGetTimer())을 보관하고 , for loop를 돌면서 문자들을 읽어 들인다. 읽은 문자의 개수가 return값으로 사용된다.(numRead). Timeout값은 넘겨준 값에 의존하며, 초단위로 나타낸다. 만약 SerialInputByte()를 while loop를 돌면서 계속 호출해서 특정한 수의 입력 문자를 읽어들이면, for 루프를 끝낸다. 그렇지 않고, 입력이 없이 timeout을 검사하기 위해서 사용하는 TimeGetTime() 함수는 time.c에 아래와 같이 정의 된다.
----------------- time.c파일의 인용 시작.-------------------
74 /* returns the time in 1/TICKS_PER_SECOND seconds */
75 u32 TimerGetTime(void)
76 {
77 /* turn LED always on after one second so the user knows that
78 * the board is on
79 */
80 if((OSCR % TICKS_PER_SECOND) < (TICKS_PER_SECOND >>7))
81 led_on();
82
83 return((u32) OSCR);
84 }
----------------- time.c파일의 인용 끝.---------------------
OSCR 레지스터를 읽어서 바로 돌려준다. 중간에 시스템이 살아 있다는 것을 보여주기 위해서 led_on()을 호출해서 LED를 킨다. TICKS_PER_SECOND는 time.h에 다음과 같이 정의 되어 있다.
---------
45 #define TICKS_PER_SECOND 3686400
---------
TICKS_PER_SECOND는 OS 타이머의 초당 클럭 주파수를 나타내는 값이다.
----------------- main.c파일의 인용 시작.-------------------
159 /* the command loop. endless, of course */
// 명령 무한루프
160 for(;;) {
// 프롬프트를 보여주는 부분이다 tbloader의 경우는 tbloader>가 나온는것 처럼. blob는 NULL로 아무것도 없나보다.
161 DisplayPrompt(NULL);
162
163 /* wait 10 minutes for a command */
164 numRead = GetCommand(commandline, 128, 600);
165
// blob에서 사용할 수 있는 명령어는
// boot, clock, download, flash, help,
// reblob, reboot, reload, reset, speed, status. 11개 이다.
// 위에 해당하지 않는 명령어는
// *** Unknown command: 메시지가 출력된다.
166 if(numRead > 0) {
167 if(MyStrNCmp(commandline, "boot", 4) == 0) {
168 boot_linux(commandline + 4);
169 } else if(MyStrNCmp(commandline, "clock", 5) == 0) {
170 SetClock(commandline + 5);
171 } else if(MyStrNCmp(commandline, "download ", 9) == 0) {
172 Download(commandline + 9);
173 } else if(MyStrNCmp(commandline, "flash ", 6) == 0) {
174 Flash(commandline + 6);
175 } else if(MyStrNCmp(commandline, "help", 4) == 0) {
176 PrintHelp();
177 } else if(MyStrNCmp(commandline, "reblob", 6) == 0) {
178 Reblob();
179 } else if(MyStrNCmp(commandline, "reboot", 6) == 0) {
180 Reboot();
181 } else if(MyStrNCmp(commandline, "reload ", 7) == 0) {
182 Reload(commandline + 7);
183 } else if(MyStrNCmp(commandline, "reset", 5) == 0) {
184 ResetTerminal();
185 } else if(MyStrNCmp(commandline, "speed ", 6) == 0) {
186 SetDownloadSpeed(commandline + 6);
187 }
188 else if(MyStrNCmp(commandline, "status", 6) == 0) {
189 PrintStatus();
190 } else {
191 SerialOutputString("*** Unknown command: ");
192 SerialOutputString(commandline);
193 SerialOutputByte('\n');
194 }
195 }
196 }
197
198 return 0;
199 } /* main */
----------------- main.c파일의 인용 끝.---------------------
명령어가 있을경우에 해당명령어를 수행해주는 루틴이다.
----------------- linux.c파일의 인용 시작.-------------------
49 void boot_linux(char *commandline)
50 {
51 register u32 i;
52 void (*theKernel)(int zero, int arch) = (void (*)(int, int))KERNEL_RAM_BASE;
53
54 setup_start_tag();
55 setup_memory_tags();
56 setup_commandline_tag(commandline);
57 setup_initrd_tag();
58 setup_ramdisk_tag();
59 setup_end_tag();
60
61 /* we assume that the kernel is in place */
62 SerialOutputString("\nStarting kernel ...\n\n");
63
64 /* turn off I-cache */
65 asm ("mrc p15, 0, %0, c1, c0, 0": "=r" (i));
66 i &= ~0x1000;
67 asm ("mcr p15, 0, %0, c1, c0, 0": : "r" (i));
68
69 /* flush I-cache */
70 asm ("mcr p15, 0, %0, c7, c5, 0": : "r" (i));
71
72 theKernel(0, ARCH_NUMBER);
73
74 SerialOutputString("Hey, the kernel returned! This should not happen.\n");
75 }
----------------- linux.c파일의 인용 끝.---------------------
boot_linux()함수는 커널에 전달된 파라메타를 만드는 일과 I-cache(인스트럭션 캐쉬(SA-1100매뉴얼 6-1 참조))를 OFF시키고, flush 시키는 일이며, 최종적으로 압축된 Linux를 실행시키는 일을 한다. 따라서 압축된 커널의 시작 부분에는 uncompression하는 code가 들어가 있을 것이다. 리눅스 커널의 실행시에 중요한 넘겨주어야 할 변수로는 첫번째 인자에는 0을, 두번째 인자에는 아키텍쳐번호를 주어야 한다는 것이다. Kernel이 return을 하지 않을 것이므로, 마지막 line에 있는 SerialOutputString()은 실행되지 않을 것이다. 넘겨주는 architecher번호에 대한 정의는 linux.h에 아래와 같이 되어 있다. 같은 것을 커널 source의 ~/arch/arm/tools/mach_types과 가은 곳에처 찾을 수 있다. 넘겨준 architecher번호값은 커널 source의 ~/arch/arm/boot/compressed/head.S에서 사용된다.
------------------ kernel의 head.S파일 인용 시작-----------------
92 start:
93 .type start,#function
94 .rept 8
95 mov r0, r0
96 .endr
97
98 b 1f
99 .word 0x016f2818 @ Magic numbers to help the loader // 부트로더의 매직 넘버
100 .word start @ absolute load/run zImage address // zImage를 로딩과실행할 절대 주소
101 .word _edata @ zImage end address // zImage의 마지막 주소
102 1: mov r7, r1 @ save architecture ID // 아키텍쳐 ID 저장
103 mov r8, #0 @ save r0 // r0레지스터 저장
------------------ kernel의 head.S파일 인용 끝 ------------------
head.S에서 보듯 넘겨받은 아규먼트 값은 r0에는 0이, r1에는 ARCH_NUMBER가 들어가게 되며, 이 값은 다시 r7에 들어가서, architecture ID로서의 구실을 하게 된다. 그다음에 setup_() 함수들에 대해서 보자. setup_() 함수는 커널에 넘겨줄 파라메타를 생성해 주는 함수이다. 이것은 마치 x86에서 LILO를 사용하게 되는 경우와 동일한 것으로 커널에 적절한 인자를 넘겨주어서, 커널에서 사용하는 변수를 설정하는 일을 한다.
함수를 살펴보기 전에 함수에서 사용하는 tag구조체에 대한 정의를 살펴보자. 이 구조체는 커널/include/asm-arm/setup.h에 아래와 같은 정의를 가진다.
------------------ kernel의 setup.h파일 인용 시작-----------------
202 struct tag {
203 struct tag_header hdr;
204 union {
205 struct tag_core core;
206 struct tag_mem32 mem;
207 struct tag_videotext videotext;
208 struct tag_ramdisk ramdisk;
209 struct tag_initrd initrd;
210 struct tag_serialnr serialnr;
211 struct tag_revision revision;
212 struct tag_videolfb videolfb;
213 struct tag_cmdline cmdline;
214
215 /*
216 * Acorn specific
217 */
218 struct tag_acorn acorn;
219
220 /*
221 * DC21285 specific
222 */
223 struct tag_memclk memclk;
224 } u;
225 };
------------------ kernel의 setup.h파일 인용 끝 ------------------
위와 같이 정의된 tag구조체를 적절한 메모리 위치에 두고, 커널에서는 나중에 이곳에 정의된 정보를 다시 읽어서 사용하도록 하는 것이다. 이것이 정의된 위치는 main.h에 있는 BOOT_PARAMS(=0XC0000100)에 들어가게 된다.
------ main.h 인용 시작.-------
#define BOOT_PARAMS (0xc0000100) // 77라인.
------ main.h 인용 끝.-------
----------------- linux.c파일의 인용 시작.---------------------
78 static void setup_start_tag(void)
79 {
80 params = (struct tag *)BOOT_PARAMS;
81
82 params->hdr.tag = ATAG_CORE;
83 params->hdr.size = tag_size(tag_core);
84
85 params->u.core.flags = 0;
86 params->u.core.pagesize = 0;
87 params->u.core.rootdev = 0;
88
89 params = tag_next(params);
90 }
----------------- linux.c파일의 인용 끝.---------------------
이 함수는 BOOT_PARMS에 가지고 있는 tag구조체를 초기화 하는 역할을 한다. ATAG_CORE는 커널의 kernel/include/asm-arm/setup.h에 정의된 것으로 0x54410001이라는 값을 가진다.(현재로서는 단순히 매직넘버를 가지는 것으로 보인다.) 크기는 tag_core의 크기를 주도록 하고 (tag_size()), union 구조체로 선언된 부분의 core에 대한 초기화를 위해서 flags에 0, pagesize에 0, rootdev에도 0을 준다. 다음번의 tag의 위치를 주기 위해서 tag_next()를 사용하고 있다.(참고: tag_size()나 tag_next()는 새로운 버전의 커널에 들어 있는 것을 찾을 수 있을 것이다. 커널 버전 2.4.X 에서는 ~/include/asm-arm/setup.h 에서 찾을 수 있다 다른 버전의 커널도 확인해 보기 바란다. 이것을 정의 하고 있지 않다면 BLOB의 빌드가 제대로 되지 않을 것이다.
------------------ kernel의 setup.h파일 인용 시작 ------------------
240 #define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
241 #define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
------------------ kernel의 setup.h파일 인용 끝 ---------------------
tag_next()매크로는 다음 tag구조체의 위치를 나타내는데 사용하며, tag_size() 매크로는 tag header의 길이와 해당하는 type의 크기를 합한 값을 4로 나누어 4byte 단위로 나타낸 크기를 돌려준다.
----------------- linux.c파일의 인용 시작.---------------------
93 static void setup_memory_tags(void)
94 {
95 int i;
96
97 for(i = 0; i < NUM_MEM_AREAS; i++) {
98 if(memory_map[i].used) {
99 params->hdr.tag = ATAG_MEM;
100 params->hdr.size = tag_size(tag_mem32);
101
102 params->u.mem.start = memory_map[i].start;
103 params->u.mem.size = memory_map[i].len;
104
105 params = tag_next(params);
106 }
107 }
108 }
----------------- linux.c파일의 인용 끝.-----------------------
setup_memory_tags() 함수는 현재 시스템 메모리 설정에 대한 정보를 가르쳐줄 목적으로 사용한다. Tag의 header에 ATAG_MEM을 넣고 tag의 크기를 알려준 다음, 각각의 메모리맵(memory_map[])의 entry에 대해서 시작과 크기를 넣어 준다.
----------------- linux.c파일의 인용 시작.---------------------
111 static void setup_commandline_tag(char *commandline)
112 {
113 char *p;
114
115 /* eat leading white space */ // 읽은 공백을 먹는다. (공백 제거)
116 for(p = commandline; *p == ' '; p++)
117 ;
118
119 /* skip non-existent command lines so the kernel will still
120 * use its default command line.
121 */
122 if(*p == '\0')
123 return;
124
125 params->hdr.tag = ATAG_CMDLINE;
126 params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;
127
128 strcpy(params->u.cmdline.cmdline, p);
129
130 params = tag_next(params);
131 }
----------------- linux.c파일의 인용 끝.-----------------------
이제 넘겨 받은 명령라인에 해당하는 tag를 생성할 차례이다. 먼저, 주어진 명령라인에서 blank를 제거하기 위해서 문자단위로 검사한다. 만약 이러한 검사를 진행하는 도중에 "\0"를 만난다면, 문자열의 끝을 나탄내므로 그냥 리턴한다. 그렇지 않다면, header의 tag필드에는 명령라인이라는 것을 알려주는 ATAG_CMDLINE을 넣어주고, 명령라인의 크기를 계산해 주고, 넘겨 받은 실제 명령라인에서 blank를 제외한 부분을 복사하여 넣어 준다.
----------------- linux.c파일의 인용 시작.---------------------
134 static void setup_initrd_tag(void)
135 {
136 /* an ATAG_INITRD node tells the kernel where the compressed
137 * ramdisk can be found. ATAG_RDIMG is a better name, actually.
138 */
// ATAG_INITRD 노드는 압축되어 있는 램디스크를 찾는 동안 커널에게 말한다.
// ATAG_INITRD는 실제적으로 좋은 이름이다.
139 params->hdr.tag = ATAG_INITRD;
140 params->hdr.size = tag_size(tag_initrd);
141
142 params->u.initrd.start = RAMDISK_RAM_BASE;
143 params->u.initrd.size = INITRD_LEN;
144
145 params = tag_next(params);
146 }
----------------- linux.c파일의 인용 끝.-----------------------
setup_initrd_tag()는 initial램디스크에 대한 tag를 설정한다. ATAG_INITRD를 tag head에 넣어주고, RAMDISK_RAM_BASE(=0xC0800000)를 initial램디스크의 시작 주소로 주고, 크기를 INITRD_LEN(=0X280000)으로 설정했다.
----------------- linux.c파일의 인용 시작.---------------------
149 static void setup_ramdisk_tag(void)
150 {
151 /* an ATAG_RAMDISK node tells the kernel how large the
152 * decompressed ramdisk will become.
153 */
// ATAG_INITRD 노드는 압축되어 있는 램디스크가 얼마나 큰지 동안 커널에게 말한다.
154 params->hdr.tag = ATAG_RAMDISK;
155 params->hdr.size = tag_size(tag_ramdisk);
156
157 params->u.ramdisk.start = 0;
158 params->u.ramdisk.size = RAMDISK_SIZE;
159 params->u.ramdisk.flags = 1; /* automatically load ramdisk */ // 램디스크를 자동으로 로드한다.
160
161 params = tag_next(params);
162 }
----------------- linux.c파일의 인용 끝.-----------------------
setup_ramdisk_tag()함수는 램디스크에 대한 tag를 설정하는 함수이다. 시작 주소로 0을, 크기로는 RAMDISK_SIZE(=8*1024*1024 or 8MBytes)를 주었으며, 자동으로 램디스크를 로드 하라는 뜻으로 flags에 1을 설정해 주었다.
----------------- linux.c파일의 인용 시작.---------------------
165 static void setup_end_tag(void)
166 {
167 params->hdr.tag = ATAG_NONE;
168 params->hdr.size = 0;
169 }
----------------- linux.c파일의 인용 끝.-----------------------
tag의 마지막임을 나타내기 위해서 setup_end_tag()을 사용한다. ATAG_NONE을 tag header에 넣었고, tag의 크기가 0임을 알려 주었다. 이것으로 커널에 넘겨질 tag에 대한 설정이 끝났다.
실제로 커널에서 넘겨받은 tag에 대한 처리가 일어나는 곳은 kernel/arch/arm/kernel/setup.c의 parse_tag_()와 같은 함수 이다. 이 함수에서는 넘겨준 tag들을 tag가 의미하는 뜻에 따라, 앞에서 이미 보았듯이, core, 메모리, 명령라인, initrd, 램디스크 등의 순서로 처리해 나가는 것을 볼 수 있을 것이다.
--------------- kernel/arch/arm/kernel/setup.c인용 시작. ----------------
311 /*
312 * Tag parsing.
313 *
314 * This is the new way of passing data to the kernel at boot time. Rather
315 * than passing a fixed inflexible structure to the kernel, we pass a list
316 * of variable-sized tags to the kernel. The first tag must be a ATAG_CORE
317 * tag for the list to be recognised (to distinguish the tagged list from
318 * a param_struct). The list is terminated with a zero-length tag (this tag
319 * is not parsed in any way).
320 */
// 태그파싱.
// 부팅시 커널의 데이터를 파싱하는 새로운 방법 이다.
// 커널의 고정된 확실한 구조를 파싱한 후에, 커널 태그의 가변적인 크기의 리스트를 통과시킨다.
// 인식할 수 있는 리스트의 첫번째 태크는 ATAG_CORE 태그일 것이다.( aram_struct의 구별된 태그의 리스트 )
// zero-length태크(이 태그는 더이상 파싱되지 않는다.)는 리스트는 끝이다.
321 static int __init parse_tag_core(const struct tag *tag)
322 {
323 if (tag->hdr.size > 2) {
324 if ((tag->u.core.flags & 1) == 0)
325 root_mountflags &= ~MS_RDONLY;
326 ROOT_DEV = to_kdev_t(tag->u.core.rootdev);
327 }
328 return 0;
329 }
330
331 __tagtable(ATAG_CORE, parse_tag_core);
332
333 static int __init parse_tag_mem32(const struct tag *tag)
334 {
335 if (meminfo.nr_banks >= NR_BANKS) {
336 printk(KERN_WARNING
337 "Ignoring memory bank 0x%08x size %dKB\n",
338 tag->u.mem.start, tag->u.mem.size / 1024);
339 return -EINVAL;
340 }
341 meminfo.bank[meminfo.nr_banks].start = tag->u.mem.start;
342 meminfo.bank[meminfo.nr_banks].size = tag->u.mem.size;
343 meminfo.bank[meminfo.nr_banks].node = PHYS_TO_NID(tag->u.mem.start);
344 meminfo.nr_banks += 1;
345
346 return 0;
347 }
348
349 __tagtable(ATAG_MEM, parse_tag_mem32);
350
351 #if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE)
352 struct screen_info screen_info = {
353 orig_video_lines: 30,
354 orig_video_cols: 80,
355 orig_video_mode: 0,
356 orig_video_ega_bx: 0,
357 orig_video_isVGA: 1,
358 orig_video_points: 8
359 };
360
361 static int __init parse_tag_videotext(const struct tag *tag)
362 {
363 screen_info.orig_x = tag->u.videotext.x;
364 screen_info.orig_y = tag->u.videotext.y;
365 screen_info.orig_video_page = tag->u.videotext.video_page;
366 screen_info.orig_video_mode = tag->u.videotext.video_mode;
367 screen_info.orig_video_cols = tag->u.videotext.video_cols;
368 screen_info.orig_video_ega_bx = tag->u.videotext.video_ega_bx;
369 screen_info.orig_video_lines = tag->u.videotext.video_lines;
370 screen_info.orig_video_isVGA = tag->u.videotext.video_isvga;
371 screen_info.orig_video_points = tag->u.videotext.video_points;
372 return 0;
373 }
374
375 __tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
376 #endif
377
378 static int __init parse_tag_ramdisk(const struct tag *tag)
379 {
380 setup_ramdisk((tag->u.ramdisk.flags & 1) == 0,
381 (tag->u.ramdisk.flags & 2) == 0,
382 tag->u.ramdisk.start, tag->u.ramdisk.size);
383 return 0;
384 }
385
386 __tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
387
388 static int __init parse_tag_initrd(const struct tag *tag)
389 {
390 setup_initrd(tag->u.initrd.start, tag->u.initrd.size);
391 return 0;
392 }
393
394 __tagtable(ATAG_INITRD, parse_tag_initrd);
395
396 static int __init parse_tag_serialnr(const struct tag *tag)
397 {
398 system_serial_low = tag->u.serialnr.low;
399 system_serial_high = tag->u.serialnr.high;
400 return 0;
401 }
402
403 __tagtable(ATAG_SERIAL, parse_tag_serialnr);
404
405 static int __init parse_tag_revision(const struct tag *tag)
406 {
407 system_rev = tag->u.revision.rev;
408 return 0;
409 }
410
411 __tagtable(ATAG_REVISION, parse_tag_revision);
412
413 static int __init parse_tag_cmdline(const struct tag *tag)
414 {
415 strncpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
416 default_command_line[COMMAND_LINE_SIZE - 1] = '\0';
417 return 0;
418 }
419
420 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
421
--------------- kernel/arch/arm/kernel/setup.c인용 시작. ----------------
다시 main.c로 돌와와서
----------------- main.c파일의 인용 시작.-------------------
159 /* the command loop. endless, of course */
// 명령 무한루프
160 for(;;) {
// 프롬프트를 보여주는 부분이다 tbloader의 경우는 tbloader>가 나온는것 처럼. blob는 NULL로 아무것도 없나보다.
161 DisplayPrompt(NULL);
162
163 /* wait 10 minutes for a command */
164 numRead = GetCommand(commandline, 128, 600);
165
// blob에서 사용할 수 있는 명령어는
// boot, clock, download, flash, help,
// reblob, reboot, reload, reset, speed, status. 11개 이다.
// 위에 해당하지 않는 명령어는
// *** Unknown command: 메시지가 출력된다.
166 if(numRead > 0) {
167 if(MyStrNCmp(commandline, "boot", 4) == 0) {
168 boot_linux(commandline + 4);
169 } else if(MyStrNCmp(commandline, "clock", 5) == 0) {
170 SetClock(commandline + 5);
171 } else if(MyStrNCmp(commandline, "download ", 9) == 0) {
172 Download(commandline + 9);
173 } else if(MyStrNCmp(commandline, "flash ", 6) == 0) {
174 Flash(commandline + 6);
175 } else if(MyStrNCmp(commandline, "help", 4) == 0) {
176 PrintHelp();
177 } else if(MyStrNCmp(commandline, "reblob", 6) == 0) {
178 Reblob();
179 } else if(MyStrNCmp(commandline, "reboot", 6) == 0) {
180 Reboot();
181 } else if(MyStrNCmp(commandline, "reload ", 7) == 0) {
182 Reload(commandline + 7);
183 } else if(MyStrNCmp(commandline, "reset", 5) == 0) {
184 ResetTerminal();
185 } else if(MyStrNCmp(commandline, "speed ", 6) == 0) {
186 SetDownloadSpeed(commandline + 6);
187 }
188 else if(MyStrNCmp(commandline, "status", 6) == 0) {
189 PrintStatus();
190 } else {
191 SerialOutputString("*** Unknown command: ");
192 SerialOutputString(commandline);
193 SerialOutputByte('\n');
194 }
195 }
196 }
197
198 return 0;
199 } /* main */
----------------- main.c파일의 인용 끝.---------------------
만약 autoboot이 취소 되었다면, main()함수는 for루프를 돌면서 사용자의 입력에 따를 것이다. 먼저 DisplayPrompt() 함수를 호출하여 프롬프트를 보여주며 사용자가 입력하기를 기다리게 한다. 여기서 부터 명령어와 관련된 처리는 command.c 파일을 보기 바란다. GetCommand() 함수를 호출해서 내려진 명령을 commandline변수로 읽어서, 이 명령이 어떤 것인가를 비교해서(MySTRNCmp()), 해당하는 명령을 수행하는 loop를 돌게 된다. 명령의 종류로는 boot, clock, download, flash, help,reblob, reboot, reload, reset, speed, status. 11개가 있으며, 그외의 명령어에 대해서는 Unknown command를 출력한다. 먼저 DisplayPrompt()함수와 GetCommand()함수를 본 후, 각각의 명령어에 대해서 보도록 하자.
----------------- command.c파일의 인용 시작.---------------------
46 /* display a prompt, or the standard prompt if prompt == NULL */
47 void DisplayPrompt(char *prompt)
48 {
49 if(prompt == NULL) {
50 SerialOutputString(PACKAGE "> ");
51 } else {
52 SerialOutputString(prompt);
53 }
54 }
----------------- command.c파일의 인용 끝.---------------------
DisplayPrompt()함수는 단순히 ">"문자를 표시하는 역할을 한다. 만약 넘겨받은 prompt변수의 값이 NULL일 경우에는 ">"을 시리얼을 통해서 쓰게 되고, 그렇지 않고 특정한 prompt를 요구하면, 그것을 그대로 보여준다.
----------------- command.c파일의 인용 시작.---------------------
60 int GetCommand(char *command, int len, int timeout)
61 {
62 u32 startTime, currentTime;
63 char c;
64 int i;
65 int numRead;
66 int maxRead = len - 1;
67
68 TimerClearOverflow();
69
70 startTime = TimerGetTime();
71
72 for(numRead = 0, i = 0; numRead < maxRead;) {
73 /* try to get a byte from the serial port */
// 시리얼 포트로부터 한바이트를 얻기 위해 시도한다.
74 while(!SerialInputByte(&c)) {
75 currentTime = TimerGetTime();
76
77 /* check timeout value */ // timeout값을 체크한다.
78 if((currentTime - startTime) >
79 (timeout * TICKS_PER_SECOND)) {
80 /* timeout */
81 command[i++] = '\0';
82 return(numRead);
83 }
84 }
85
86 if((c == '\r') || (c == '\n')) {
87 command[i++] = '\0';
88
89 /* print newline */
90 SerialOutputByte('\n');
91 return(numRead);
92 } else if(c == '\b') { /* FIXME: is this backspace? */ // 이것은 백스페이스로 고정되어 있다.
93 if(i > 0) {
94 i--;
95 numRead--;
96 /* cursor one position back. */ // 커서의 한 포지션 뒤.
97 SerialOutputString("\b \b");
98 }
99 } else {
100 command[i++] = c;
101 numRead++;
102
103 /* print character */
104 SerialOutputByte(c);
105 }
106 }
107
108 return(numRead);
109 }
----------------- command.c파일의 인용 끝.---------------------
GetCommand()함수는 특정 시간동안 명령 input을 받는 함수이다. 먼저 TimerClearOverflow()함수를 호출해서, timer가 overflow가 되었다면 이를 지워준다. 정의는 time.c에 다음과 같이 되어 있다.
----------------- time.c파일의 인용 시작.---------------------
89 int TimerDetectOverflow(void)
90 {
91 return(OSSR & OSSR_M0);
92 }
93
94
95
96 void TimerClearOverflow(void)
97 {
98 if(TimerDetectOverflow())
99 numOverflows++;
100
101 OSSR = OSSR_M0;
102 }
----------------- time.c파일의 인용 끝.---------------------
TimerClearOverflow()함수는 TimerDetectOverflow()를 다시 호출해서 overflow가 일어 났는지를 확인하게 되고, 만약 그렇다면, numOverflows 변수를 하나 증가시키게되며, OSSR(OS Status Register)은 OSMR(OS Match Register) 4개의 레지스터 중에서 어떤 레지스터와 OSCR(OS Counter Register)이 일치 하였는지를 나타낸다. 따라서, 이 값이 OSMR0와 일치가 된다면(OSSR_MO = 0x00000001), overflow가 일어났다고 생각하도록 한다. 즉, 현재 우리가 사용하는 timer와 관련된 mach register로 앞에서 OSMR0를 사용하기에 이것만을 기준으로 삼고 있다(TimerInit()). for 루프를 돌기전에 미리 현재의 시간을 읽어서(TimerGetTime())startTime 변수에 저장하고, 이 값을 기준으로 얼마나 시간이 흘렀냐에 따라서 timeout을 시켜준다.
for 루프는 최대 읽기 허용 byte를 넘어서거나, timeout이 일어난다면 현재까지 읽은 byte를 돌려주고 복귀할 것이며, 만약 "\r"이나 "\n"과 같은 문자를 받더라도, 같은 일을 할 것이다. 문자열을 읽는 함수는 SerialInputByte()를 사용한다. 한 문자를 읽을 때마다, timeout이 계산되며, 허용된 timeout을 넘어서게 되면, 현재까지 읽은 byte수를 복귀 값으로 전달한다. 만약 읽은 문자가 "\b"(백스페이스)인 경우에는 읽은 문자의 수를 감소시켜주게 되며, 표시하는 문자에는 "\b"를 두번써서, 지우는 효과를 보이도록 한다. 그외의 문자에 대해서는 읽은 값을 그대로 echo시켜 준다.
참고문서 :
김효춘님의 ARM7어셈블러 강좌(하이텔)
권수호님의 BLOB문서.
작성일 : 2001. 10. 19. (금) 14:44:59 KST
작성자 : 박진호 (jhpark@doall.co.kr
'Embedded System > Software' 카테고리의 다른 글
Ziegler–Nichols method (0) | 2009.11.08 |
---|---|
커널 부팅 로고 이미지 바꾸기(출처 : FALinux) (0) | 2009.08.13 |
__attribute__ (0) | 2009.07.24 |
Linux 방화벽 해제 (0) | 2009.07.13 |
리눅스 한글 깨짐 변경(i18n) (0) | 2009.07.13 |
따라서 다른 프레임버퍼에서 이 구조체를 보시고 수정해 주셔야 합니다.
현재 위의 화면에서 콘솔 메세지가 이미지 위에 나오는 것을 볼 수 있습니다.
프레임 버퍼 커널 소스 수정이라는 부분은 아래와 같이 하여 콘솔을 제거할 경우에 실행되게 되어 있습니다.
#make menuconfig 에서
Device Drivers --->
Graphics support --->
Console display driver support --->
[ ] VGA text console
< > Framebuffer Console support
이렇게 Framebuffer Console support 체크를 없애주셔야 합니다.
이렇게 체크를 없애면
#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)
에서 !defined(CONFIG_FRAMEBUFFER_CONSOLE) 를 만족하게 됩니다.
Device Drivers --->
Graphics support --->
Console display driver support --->
Logo configuration --->
[*] Bootup logo
Bootup logo 를 체크하시면 defined(CONFIG_LOGO) 의 조건은 만족합니다.
따라서 위의 두 조건이 만족하면 이미지 위에 콘솔 메세지가 나오지 않고 이미지만 나오게 됩니다.
[참고]
강좌에서는 gif 로 설명이 되어 있지만 bmp, png, jpeg 등 여러가지 이미지포맷을 지원합니다.
리눅스 시스템에서 원하는 이미지 포맷일 있는지 확인해 보시고 작업을 해 보세요.
anytopnm bmptopnm fiascotopnm fitstopnm gemtopnm giftopnm jpegtopnm palmtopnm pngtopnm pstopnm rasttopnm rletopnm sgitopnm sirtopnm tifftopnm xwdtopnm zeisstopnm
[---]
EZ-S2440의 경우 Framebuffer Console support 체크를 없애면 화면에 표출이 되지 않습니다.
체크옵션을 선택해 주세요..