왜 필요한가

Dual Boot 에 대한 설명은 여기서 특별히 언급하지 않겠다. 관련 문서를 참고 하기 바란다. 또한 이제부터는 Dual Boot 대신 듀얼부트라고 명명할 것이다.
간단히 듀얼부트에 대해서 설명하자면, 가장 흔한 예가 부트로더이다. LILO 나 GRUB 와 같은 부트로더는 멀티 부팅을 하게 도와준다. STB 역시 마찬가지인데, 크게 두 가지 모드로 부팅이 가능하게 해야 한다.

  1. Normal Mode
  2. Download Mode

우선 Normal Mode 의 경우, 일반적으로 부팅하는 경우에 사용하는 방법이다. 부트로더에서 바로 Main 프로그램 쪽으로 Jump 를 시켜서 일반적인 루틴을 실행시킨다.

두번째 Download Mode 의 경우는 펌웨어가 업그레이드가 되었을 때, 사용하는 방법이다. 이 때는 시리얼 포트를 통해서 새로운 프로그램의 HEX 파일을 전송해서 Flash 에 write 하게 된다.

일반 PC 에서는 부팅할 때 커서를 이용해서 각각의 부팅 모드를 선택하지만, STB 의 경우는 그럴 수가 없다. 그래서 나는 시리얼 포트에 꼽혔는지 여부를 판단해서 각 모드를 선택하기로 했다.

시리얼 포트에 꼽혀 있음 Download Mode
시리얼 포트에 꼽혀 있지 않음 Normal Mode

이것은 gpio 를 이용한다. 지금 까지는 듀얼 부트가 왜 필요하고, 어떤 기능이 있어야 하는지에 대해서 설명했다. 이제부터는 내가 만든 프로그램을 직접 분석하면서 알아보도록 한다.

프로그램 분석

듀얼 부트를 하기 위해서는 크게 두 개의 프로그램이 필요하다. 하나는 호스트 쪽(보내는 쪽)의 프로그램과 또 하나는 타겟보드 쪽(받는 쪽)의 프로그램이다. 호스트 쪽의 프로그램의 경우, 나는 기존에 사용하던, C로 만든 시리얼 프로그램을 이용했다. 이에 대한 설명은 관련 문서를 참고 하기 바란다. 여기서는 타겟보드 쪽의 프로그램을 중심으로 분석할 것이다.

지금 부터는 일종의 부트로더를 만드는 셈인데, 될 수 있으면 작은 용량으로 만드는 것이 좋다. 내가 생각하기에 UART 와 FLASH 까지의 디바이스만 open 시켜주면 될 것이라고 생각한다. 다음은 main 함수의 구조이다.
<code text>
BasicInfraStructure_Init(); // 초기화 부팅을 위한 디바이스 초기화
UARTTest(); // uart open
FLASHTest(); // flash open
Down_Bun(); // data download and flash write
</code>
위에서 유의할 점이 있다면, tbx 를 사용하면 안된다는 것이다. tbx 가 uart 위에서 실행되기 때문에, tbx 를 open 하면, uart 가 제대로 콘트롤이 안된다. 이 문제 때문에, uart api 를 통해서 메세지를 보낼 수 밖에 없다. 이제 각각의 함수들을 좀더 자세히 살펴보자!
===== BasicInfraStructure_Init() =====
여기서는 가장 중요한 uart 와 tbx 를 init 해주는 부분만을 살펴본다.
<code text>
ST_ErrorCode_t UART_Setup(void)
{
ST_ErrorCode_t ST_ErrorCode = ST_NO_ERROR;
U8 InitList[] = { SC1_UART_DEV, UART_END_OF_LIST }; // Download Mode 에서는 Front 를 사용하지 않기 때문에 굳이 초기화를 안해주어도 상관 없다(asc1 사용)
U8 index;

for ( index = 0; InitList[index] != UART_END_OF_LIST; index++ )
{
ST_ErrorCode

  }
DEVUART_Init();   // data 를 주고 받을 uart 초기화(asc2 사용)


ST_ErrorCode = uart_init(3);
return ( ST_ErrorCode );
}
</code>
다음은 tbx 의 초기화 루틴이다.
<code text>
ST_ErrorCode_t TBX_Setup(void)
{
ST_ErrorCode_t ST_ErrorCode = ST_NO_ERROR;
STTBX_InitParams_t STTBX_InitParams;

memset(&STTBX_InitParams, '\0', sizeof( STTBX_InitParams_t ) );

STTBX_InitParams.SupportedDevices = STTBX_DEVICE_DCU | STTBX_DEVICE_UART;
STTBX_InitParams.DefaultOutputDevice = STTBX_DEVICE_UART;
STTBX_InitParams.DefaultInputDevice = STTBX_DEVICE_UART;
STTBX_InitParams.CPUPartition_p = SystemPartition;
strcpy(STTBX_InitParams.UartDeviceName, UART_DeviceName[1]);
tbx 에서 사용할 uart 포트를 지정해주는 부분. 만일 앞에서 open 해준 uart 포트와 겹치면 안됨

  ST_ErrorCode = STTBX_Init(TBX_DeviceName, &STTBX_InitParams );


#ifdef REPORT_TO_CONSOLE

      printf("TBX_Setup(%s)=", TBX_DeviceName);
      if (ST_ErrorCode != ST_NO_ERROR)
          printf("%s\n", GetErrorText(ST_ErrorCode) );
      else
          printf("%s\n", STTBX_GetRevision() );

#endif

  return ( ST_ErrorCode );

}
</code>
위에서 주석처리한 부분만 주의 한다면, 각 디바이스의 초기화과정은 성공적으로 수행될 것이다.
===== UARTTest() =====
데이터 통신에 이용할 uart 를 open 시키고, 간단한 테스트를 하는 루틴이다.

void UARTTest(void)
{
 
	U8 uart_buf[14];
 
	uart_buf[0] = 'F';  // tbx 가 아닌 uart 만을 이용해서 출력문을 만들려면, 이런 식으로 배열을 만들어야 함
	uart_buf[1] = 'L';
	uart_buf[2] = 'A';
	uart_buf[3] = 'S';
	uart_buf[4] = 'H';
	uart_buf[5] = '_';
	uart_buf[6] = 'B';
	uart_buf[7] = 'U';
	uart_buf[8] = 'R';
	uart_buf[9] = 'N';
	uart_buf[10] = 'N';
	uart_buf[11]= 'E';
	uart_buf[12] = 'R';
	uart_buf[13] = 0;
 
 
	printf("Start UART TEST\n");
	if(Setup_Dev_Uart()==ST_NO_ERROR)  // uart 를 open 할 때, 설정 값을 세팅함(115200 bps, even parity bit, no hardware control)
		printf("UART OPEN [OK]\n");
	else
		printf("UART OPEN [FAIL]\n");
 
	STUARTremoconWritex(Dev_Uart_Handle,uart_buf,14);  // 터미널 화면에 앞에서 만든 스트링 배열의 문자를 출력
	STUART_Flush(Dev_Uart_Handle);
 
}


===== FLASHTest() =====
uart 로 받은 데이터를 flash 로 굽기위한 init 와 open 을 시켜주는 루틴이다.

void FLASHTest(void)
{
	ST_ErrorCode_t	ferror;
 
	ferror = FLASH_Initx();
 
	if (ferror != ST_NO_ERROR)	 printf("FLASH INIT [FAIL]\n");
 
	ferror = FLASH_Open( );
 
	if (ferror != ST_NO_ERROR)	 printf("FLASH OPEN [FAIL]\n");
 
	printf("FLASH TEST START\n");
 
}


아주 간단해 보인다. 각각 init 와 open 을 시켜준다.
===== Down_Bun() =====
부트로더 프로그램에서 가장 핵심적인 부분이라고 할 수 있겠다. 호스트로 부터 한 블럭의 데이터(63335)를 받으면, 역시 한 블럭의(65535) flash 를 지우고 쓴다. 이 때, 호스트에서는 한 블럭의 데이터를 보내고 나서 타겟보드에서 응답이 와야 다른 블럭의 데이터를 보낸다. 만일 그렇지 않으면, 데이터를 보내지 않는다.
또한 여기서는 호스트의 시리얼 프로그램의 문제점 때문에 추가하게된 루틴에 대해서 설명할 것이다.

void Down_Bun(void)
{
	ST_ErrorCode_t	uerr;
 
	U8 cbuf[1] = 0;
	U8 bbuf [65540];     // uart 로 부터 받은 데이터를 저장할 배열
 
	//U8 ebuf[16385];
	U8 ebuf[65540];      // 마지막 블럭의 데이터를 저장할 배열(호스트 쪽 시리얼 프로그램 때문에 필요하게 됨)
 
	U8 fbuf[16];     // 끝났음 알리기 위해 필요한 문자열 배열
 
	U8 check = 0;
 
 
	U32 array = 0, barray = 0;
	U32 read_len = 0;
 
	U32 xcount = 0;
	U32 ycount = 0;
	U32 zcount = 0;
 
	cbuf[0] = 'S';
 
	fbuf[0] = 'F';       // 작업이 끝나면, 터미널에 출력된다
	fbuf[1] = 'I';
	fbuf[2] = 'N';
	fbuf[3] = 'I';
	fbuf[4] = 'S';
	fbuf[5] = 'H';
	fbuf[6] = 'E';
	fbuf[7] = 'D';
	fbuf[8] = '_';
	fbuf[9] = 'B';
	fbuf[10] = 'U';
	fbuf[11] = 'R';
	fbuf[12] = 'N';
	fbuf[13] = 'N';
	fbuf[14] = 'E';
	fbuf[15] = 'R';
 
 
 
 
	for(array=0;array<65540;array++)
		{
		bbuf[array]= 0xff;   // 데이터를 저장하기 전에 배열을 모두 0xff 로 세팅한다. flash 는 지울 때 모두 0xff 로 변한다
		}
 
	for(barray=0;barray<65540;barray++)
		{
		ebuf[barray] = 0xff;   // 바로 위와 마찬가지.
		}
 
/*
	for(barray=0;barray<16385;barray++)       // 부트로더를 구울 때, 0x7fffffe4
		{
		ebuf[barray] = 0xff;
		}
*/
 
	for(check=0; check<35; check++)  // 2M flash 의 경우 모두 34 block 로 이루어져 있다
		{
 
		STUART_Flush(Dev_Uart_Handle);  // 데이터를 받기 전에 버퍼를 비운다
 
		uerr = STUARTremoconReadx(Dev_Uart_Handle, bbuf, &read_len);      // 65535 만큼의 데이터를 bbuf 에 저장
		if(uerr != ST_NO_ERROR)printf("\nUART READ FAIL\n");     
 
		if(bbuf[65535] == 0xcd && bbuf[65536] == 0x7a)   //  만일 LAST BLOCK 이라면, 수행
			{
 
				for(xcount=0; xcount < 65536; xcount++)
					{
					if(bbuf[xcount] == 0xcd && bbuf[xcount+1] == 0xcd && bbuf[xcount+2] == 0xcd)
						{
 
						ycount = xcount;  // 마지막 블럭과 부트 블럭이 붙어 나오기 때문에 부트 블럭이 몇 번째 배열인지 확인
						break;
						}
					}
 
				/*
				ebuf[16356] = bbuf[ycount -28];     // 부트 블럭만  새로운 배열에 입력 (부트로더 용 0x7fffffe4)
				ebuf[16357] = bbuf[ycount -27];
				ebuf[16358] = bbuf[ycount -26];
				ebuf[16359] = bbuf[ycount -25];
				ebuf[16360] = bbuf[ycount -24];
				ebuf[16361] = bbuf[ycount -23];
				ebuf[16362] = bbuf[ycount -22];
				ebuf[16363] = bbuf[ycount -21];
				ebuf[16364] = bbuf[ycount -20];
				ebuf[16365] = bbuf[ycount -19];
				ebuf[16366] = bbuf[ycount -18];
				ebuf[16367] = bbuf[ycount -17];
				ebuf[16368] = bbuf[ycount -16];
				ebuf[16369] = bbuf[ycount -15];
				ebuf[16370] = bbuf[ycount -14];
				ebuf[16371] = bbuf[ycount -13];
				ebuf[16372] = bbuf[ycount -12];
				ebuf[16373] = bbuf[ycount -11];
				ebuf[16374] = bbuf[ycount -10];
				ebuf[16375] = bbuf[ycount -9];
				ebuf[16376] = bbuf[ycount -8];
				ebuf[16377] = bbuf[ycount -7];
				ebuf[16378] = bbuf[ycount -6];
				ebuf[16379] = bbuf[ycount -5];
				ebuf[16380] = bbuf[ycount -4];
				ebuf[16381] = bbuf[ycount -3];
				ebuf[16382] = bbuf[ycount -2];
				ebuf[16383] = bbuf[ycount -1];
				*/
 
				ebuf[65508] = bbuf[ycount -28];    // 부트 블럭만  새로운 배열에 입력
				ebuf[65509] = bbuf[ycount -27];
				ebuf[65510] = bbuf[ycount -26];
				ebuf[65511] = bbuf[ycount -25];
				ebuf[65512] = bbuf[ycount -24];
				ebuf[65513] = bbuf[ycount -23];
				ebuf[65514] = bbuf[ycount -22];
				ebuf[65515] = bbuf[ycount -21];
				ebuf[65516] = bbuf[ycount -20];
				ebuf[65517] = bbuf[ycount -19];
				ebuf[65518] = bbuf[ycount -18];
				ebuf[65519] = bbuf[ycount -17];
				ebuf[65520] = bbuf[ycount -16];
				ebuf[65521] = bbuf[ycount -15];
				ebuf[65522] = bbuf[ycount -14];
				ebuf[65523] = bbuf[ycount -13];
				ebuf[65524] = bbuf[ycount -12];
				ebuf[65525] = bbuf[ycount -11];
				ebuf[65526] = bbuf[ycount -10];
				ebuf[65527] = bbuf[ycount -9];
				ebuf[65528] = bbuf[ycount -8];
				ebuf[65529] = bbuf[ycount -7];
				ebuf[65530] = bbuf[ycount -6];
				ebuf[65531] = bbuf[ycount -5];
				ebuf[65532] = bbuf[ycount -4];
				ebuf[65533] = bbuf[ycount -3];
				ebuf[65534] = bbuf[ycount -2];
				ebuf[65535] = bbuf[ycount -1];
 
 
				for(zcount = (ycount-28); zcount < 65536; zcount++)
					{
					bbuf[zcount] = 0xff;   // 마지막 블럭의 나머지를 모두 0xff 로 세팅
					}
 
				EraseBlock(check);      // flash 의 해당 블럭을 지움
				WriteBlock(bbuf,check);     // flash 의 해당 블럭에 bbuf 배열을 쓴다
				STUART_Flush(Dev_Uart_Handle);
 
				break;
 
			}else{	// 마지막 블럭이 아니고 LOCAL BLOCK 일 때 수행
 
				EraseBlock(check);
 
				WriteBlock(bbuf,check);
				STUART_Flush(Dev_Uart_Handle);
				STUARTremoconWritex(Dev_Uart_Handle,cbuf,1);  // 제대로 받았음을 알리는 메세지를 호스트로 전송
 
				}
 
		}
 
/*
	EraseBlock(34);    // 부트로더 용 (0x7fffffe4)
	WriteBlock(ebuf, 34);
*/
 
	EraseBlock(15);     // flash 의 부트 블럭을 지움
	WriteBlock(ebuf, 15);   // flash 의 부트 블럭에 씀
	//ReadFlash();
 
	/*                           */
 
	STUARTremoconWritex(Dev_Uart_Handle,fbuf,15);  // 성공적으로 끝났다는 메세지를 터미널로 전송
	STUART_Flush(Dev_Uart_Handle);
 
	printf("\nFinsh!!\n");
 
}


잘 이해가 될런지 모르겠다. 최대한 주석을 참고하기 바란다.
====== HEX 파일 생성 ======
컴파일을 수행하고, hex 파일을 만들어야 할 차례다. 현재 나는 총 2M 의 flash 를 이용할 것이다. 지금 생각은 2M 를 각각 1M 로 나누어서 사용할 것이다.
부트로더가 들어갈 자리는 0x7ff00000 ~ 0x7fffffff 이다. config/board/mb382_um.cfg 파일을 아래와 같이 수정해 주어야 한다.

    memory FLASH        0x7FF00000           (1*M)                ROM


이제 만들어 보자! 가장 처음 주소가 0x7ff00000 로 되어 있을 것이다. 이제는 기존의 flash_burnner 를 가지고 프로그램을 flash 쓴다. 그리고 시리얼을 통해서 호스트에서 타겟보드로 hex 파일을 보내보자!!
작업이 끝나고, 제대로 부팅메세지가 보이면 성공이다. ^^;
====== 남은 몇가지!!! ======
아직은 현재 정확히 정해진 것이 아무것도 없다. 위에서 보여준 것들은 단지 테스트를 위한 것들이다. 좀더 완벽한 듀얼 부트를 위해서는 몇가지 결정되어야 할 것들이 있다. 위의 소스에서 주석처리된 부분이 바로 아래의 항목이 정확히 정해지지 않아서 이다.
- 과연 몇 M 의 FLASH 를 사용할 것인가?
- FLASH 를 어떻게 파티셔닝 할 것인가?
- 구체적으로 어떻게 듀얼 부팅 시나리오를 구현할 것인가?

하지만, 앞에서 본 실험으로 하여금 나는 어느정도의 자신감을 얻을 수 있었다.

  • computer/digitalarena/dual_boot_프로그램_분석.txt
  • Last modified: 3 years ago
  • by likewind