Welcome to My World (www.dgmayor.com)

소프트웨어/C & 자료구조 & 커널 & DB

19. 하드디스크 드라이버 읽기

dgmayor 2022. 6. 14. 11:25
728x90
 
 
 
19강 소스코드입니다.
 
 
 
--------------------------------------------------------------------------------
 

안녕하세요
 
Qemu 환경으로도 옮겼겠다 이제 하드디스크 드라이버를 구현하는 일밖에 없습니다.
 
미리 말했듯이 처음 보면 굉장히 난해합니다. 따라서 드라이버 구현을 몇 단계로 나누어 포스팅할 생각입니다.
 
17강에서 언급했던 흐름을 그대로 코드 구현할 겁니다. 일일이 주석을 달았으니 시간을 가지고 음미하시기 바랍니다(?)
 
먼저 오래전에 구현했던 Interrupt.c 코드에서 90 ~ 100 줄에 해당하는 소스를 다음과 같이 수정합시다.
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//  인터럽트 작동 시작
 
    __asm__ __volatile__("mov eax, %0"::"r"(&idtr));
    __asm__ __volatile__("lidt [eax]");
    __asm__ __volatile__
    (
        "mov al, 0x00;" //슬레이브 PIC의 모든 인터럽트를
        "out 0xA1, al;" // 열어둔다.
        "mov al, 0x00;"// 마스터 PIC의 모든 인터럽트를
        "out 0x21, al;" //열어둔다
    );
    __asm__ __volatile__("sti");
 
    return;
 
cs
 
 

주석으로 넣은 바와 같이 모든 인터럽트를 해방해버립니다. 그래야만 하드디스크의 인터럽트도 받을 수 있습니다. ( 하드디스크는 자신에게 떨어진 명령을 수행하고 나면 CPU로 인터럽트를 날립니다. )
 
그리고 function.c 코드에 하드디스크의 현재 상태를 읽어오는 함수를 만들어둡시다. 반복적으로 호출할 것이기 때문에 미리 만들어두면 좋습니다.
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
unsigned char HDDstatus()
{
    unsigned char value;
 
    __asm__ __volatile__
    (
        "mov dx,0x1F7;"
        "in al, dx;"
    );
    __asm__ __volatile__("mov %0, al;" :"=r"(value));
 
    return value;
 
}
 
int HDD_BSY()
{
    unsigned char status = HDDstatus();
 
    if ( (status & 0x80== 0x80return 1// busy
    else return 0// non-busy
 
}
 
int HDD_DRDY()
{
    unsigned char status = HDDstatus();
 
    if ((status & 0x40== 0x40return 1// ready
    else return 0// non-ready
}
 
int HDD_DRQ()
{
    unsigned char status = HDDstatus();
 
    if ((status & 0x08== 0x08return 1// Data Request
    else return 0// non-Data Request
}
 
int HDD_ERR()
{
    unsigned char status = HDDstatus();
 
    if ((status & 0x01== 0x01return 1// error!
    else return 0// non-error
}
 
 
cs
 
 
 
모든 준비가 끝났으니 이제 메인함수를 작성할 차례입니다. 다음과 같은 코드를 function.c 코드에 추가합시다. sector 번째 섹터를 buffer에 읽어오는 코드입니다.
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
void HDDread(unsigned int sector, char* buffer)
{
    
    unsigned char LBA_a = sector & 0xFF// sector의 [7:0] 비트 추출
    unsigned char LBA_b = ( sector >> 8 ) & 0xFF// sector의 [15:8] 비트 추출
    unsigned char LBA_c = ( sector >> 16& 0xFF// sector의 [23:16] 비트 추출
    unsigned char LBA_d = ( ( sector >> 24& 0x0F ) | 0xE0 ; 
    // sector의 [27:24] 비트 추출
 
    // HDD INT 활성화
    __asm__ __volatile__
    (
        "mov al, 0;"
        "mov dx, 0x3F6;"
        "out dx, al;"
    );
 
    while ( HDD_BSY() == 1); // HDD가 busy 하다면 계속 대기
    
    /////////////////////////////////////////////////
    // 하드디스크 셋팅 시작
    /////////////////////////////////////////////////
 
    // 드라이브/헤드 레지스터 초기화 + LBA 주소 [27:24] 4비트
    __asm__ __volatile__
    (
        "mov al, %0;"
        "mov dx, 0x1F6;"
        "out dx, al;"::"r"(LBA_d)
    );
 
    __asm__ __volatile__
    (
        "mov al, 0x01;"
        "mov dx,0x1F2;"
        "out dx, al;"
    ); // 섹터 1개 읽는다
 
    __asm__ __volatile__
    (
        "mov al, %0;"
        "mov dx,0x1F3;"
        "out dx, al;" ::"r"(LBA_a)
    ); // LBA 주소 [7:0] 8비트
 
    __asm__ __volatile__
    (
        "mov al, %0;"
        "mov dx,0x1F4;"
        "out dx, al;" ::"r"(LBA_b)
    ); // LBA 주소 [15:8] 8비트
 
    __asm__ __volatile__
    (
        "mov al, %0;"
        "mov dx,0x1F5;"
        "out dx, al;" ::"r"(LBA_c)
    ); // LBA 주소 [23:16] 8비트
 
    /////////////////////////////////////////////////
    // 하드디스크 셋팅 끝
    /////////////////////////////////////////////////
 
 
    // 읽기(0x20) 내리기 전 하드디스크 드라이버가 명령을 받을 수 있는지 체크
    while ((HDD_BSY() ==1 )|| (HDD_DRDY()==0));
 
 
    // 읽기(0x20) 명령 내리기
    __asm__ __volatile__
    (
        "mov al, 0x20;"
        "mov dx,0x1F7;"
        "out dx, al;"
    );
 
    // 명령 내렸는데 오류가 발생했다면 읽기를 중단한다.
    if (HDD_ERR() == 1)
    {
        kprintf("Error!!", videomaxline - 10);
        return;
    }
 
    while (HDD_DRQ() == 0); // 데이터를 다 읽을 때까지 대기
 
 
    // 읽기가 성공했으므로 Buffer에다가 512바이트만큼 데이터를 옮긴다.
    __asm__ __volatile__("mov dx,0x1F0;");
    __asm__ __volatile__("mov edi, %0;" : : "r"(buffer));
    __asm__ __volatile__("mov ecx, 256");
    __asm__ __volatile__("rep insw");
 
}
 
cs
 
 
 
먼저, sector 변수를 총 28비트로 분해합니다. 하드디스크에게 몇 번째 섹터를 읽을 것인지 알려줘야하는데 각 포트별로 비트를 분해해서 넣기 때문입니다.
 
그리고 인터럽트를 활성화하고 하드디스크 셋팅을 시작합니다. 각 포트가 요구하는 값에 맞게 값들을 집어넣습니다. 구체적으로 무엇을 요구하는지는 17강에서 설명해두었습니다. 병행해서 보시기 바랍니다.
 
셋팅이 끝나면 while 문으로 하드디스크가 명령을 받을 수 있는지 점검하고, 읽기(0x20) 명령을 내립니다. 그리고 다시 한 번 while 문으로 데이터가 제대로 읽혔는지 체크합니다.
 
마지막엔 모든 값들이 제대로 읽혔으므로 rep insw 명령어를 통해 512바이트를 읽어옵니다. insw는 ds:edi 주소에 dx 포트의 값을 2바이트 읽는 명령어입니다. rep는 ecx 값이 0이 될 때까지 앞의 명령어를 반복합니다. 따라서 insw는 256번 반복될 것이고, 한 번 실행할 때마다 0x1F0에 담긴 2바이트 정보를 buffer 에 저장하게 될 겁니다.
 
좋습니다. 구현은 이게 끝입니다. 근데 이게 제대로 구현됬는지 누가 알겠습니까? 검증하는 차원으로 hdd.img을 열어서 다음과 같이 수정해봅시다.
 
 
 
 
보시는 바와 같이 0번째 섹터와 1번째 섹터에 데이터를 적었습니다. 우리가 제대로 함수를 작성한 것이 맞다면, HDDread(0, diskbuffer), HDDread(1, diskbuffer)를 각각 호출했을 때 diskbuffer에 해당 문장들이 저장되어있을 겁니다.
 
이를 검증하기 위해 shell 명령어 하나를 추가합시다.
 
 
1
2
3
4
5
6
7
8
9
10
11
// shell.c 에 추가
// diskbuffer는 data.h에 "unsigned char diskbuffer[512];" 로 추가함
void sh_HDDread()
{
    HDDread(0, diskbuffer);
    kprintf(diskbuffer, ++curline, 0);
 
    HDDread(1, diskbuffer);
    kprintf(diskbuffer, ++curline, 0);
}
 
cs
 
 
1
2
3
4
5
6
7
8
9
10
11
// main.c에 있는 translate_shell() 함수에 read 명령어 추가
void translate_shell()
{
    if (keyboard[0== 0) { return; } // 명령어 없이 그냥 ENTER 침
    if (kstrcmp(keyboard, "clear")) { sh_clear(); return; }
    if (kstrcmp(keyboard, "version")) { sh_version(); return; }
    if (kstrcmp(keyboard, "read")) { sh_HDDread(); return; }
    
    kprintf("There is no such command.",++curline, 0);
}
 
cs
 
 
 
이제 준비는 끝났습니다. 컴파일 후에 Qemu 환경을 통해 read 명령어를 실행해봅시다! 어떤 결과가 나올까요???
 
 
 
 
 
 
하드디스크를 아주 잘 읽었습니다. 또, idt_ignore가 발생했습니다. 이는 하드디스크가 명령(읽기명령)을 끝냈을 때 낸 인터럽트입니다. 하지만 우리가 ISR 구현 당시(13강)에는 하드디스크 관련 ISR을 구현하지 않았었죠? 그래서 그냥 idt_ignore가 나오고 끝나버린 겁니다.
 
자 어찌되었던 하드디스크 특정 섹터를 읽는 함수를 구현했습니다. 하지만! 하드디스크 드라이버의 진가는 바로 쓰기에서 오는 것 아니겠습니까? 다음 강의에서는 쓰기 함수를 구현해보도록 하겠습니다.
 
감사합니다.
728x90