Доступ к жесткому диску через порты АТА контроллера
Задача прежняя - получить информацию идентификации устройства и считать MBR.
Рассмотрим программный код.
Заголовочные файлы:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/hdreg.h>
Для работы с портами ввода/вывода определим несколько макросов:
#define OUT_P_B(val,port) \ asm( \ "outb %%al, %%dx" \ ::"a"(val),"d"(port) \ )
#define IN_P_B(val,port) \ asm( \ "inb %%dx, %%al" \ :"=a"(val) \ :"d"(port) \ )
#define IN_P_W(val,port) \ asm( \ "inw %%dx, %%ax" \ :"=a"(val) \ :"d"(port) \ )
Макрос OUT_P_B осуществляет запись байта в порт, макросы IN_P_B и IN_P_W - чтения байта/слова из порта.
Для работы с устройством определим несколько функций.
Функция проверки статуса устройства (занято/свободно):
void hd_busy() { unsigned char status;
do { IN_P_B(status,HD_STATUS); } while (status & 0x80); return; }
Проверка статуса устройства осуществляется проверкой значения бита 7 (BSY) регистра состояния. Если бит сброшен, устройство свободно и регистры контроллера доступны.
Функция проверки готовности устройства к восприятию команд:
void hd_ready() { unsigned char status;
do { IN_P_B(status,HD_STATUS); } while (!(status & 0x40)); return; }
Устройство готово, если бит 6 (DRDY) регистра состояния установлен.
Функция проверки готовности устройства к обмену данными:
int hd_data_request() { unsigned char status; IN_P_B(status,HD_STATUS); if(status & 0x8) return 1; return 0; }
Если бит 3 (DRQ) регистра состояния установлен, данные находятся в регистре данных и готовы для считывания.
Следующая функция проверяет, не произошла ли ошибка при работе устройства:
void check_error() { unsigned char a;
IN_P_B(a,HD_STATUS); if (a & 0x1) { perror("HD_STATUS"); exit(-1); } return; }
Установленный бит 0 (ERR) регистра состояния означает, то при выполнении последней операции произошла ошибка. Дополнительная информация содержится в регистре ошибок.
Задача прежняя - получить информацию идентификации устройства и считать MBR.
Рассмотрим программный код.
Заголовочные файлы:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/hdreg.h>
Для работы с портами ввода/вывода определим несколько макросов:
#define OUT_P_B(val,port) \ asm( \ "outb %%al, %%dx" \ ::"a"(val),"d"(port) \ )
#define IN_P_B(val,port) \ asm( \ "inb %%dx, %%al" \ :"=a"(val) \ :"d"(port) \ )
#define IN_P_W(val,port) \ asm( \ "inw %%dx, %%ax" \ :"=a"(val) \ :"d"(port) \ )
Макрос OUT_P_B осуществляет запись байта в порт, макросы IN_P_B и IN_P_W - чтения байта/слова из порта.
Для работы с устройством определим несколько функций.
Функция проверки статуса устройства (занято/свободно):
void hd_busy() { unsigned char status;
do { IN_P_B(status,HD_STATUS); } while (status & 0x80); return; }
Проверка статуса устройства осуществляется проверкой значения бита 7 (BSY) регистра состояния. Если бит сброшен, устройство свободно и регистры контроллера доступны.
Функция проверки готовности устройства к восприятию команд:
void hd_ready() { unsigned char status;
do { IN_P_B(status,HD_STATUS); } while (!(status & 0x40)); return; }
Устройство готово, если бит 6 (DRDY) регистра состояния установлен.
Функция проверки готовности устройства к обмену данными:
int hd_data_request() { unsigned char status; IN_P_B(status,HD_STATUS); if(status & 0x8) return 1; return 0; }
Если бит 3 (DRQ) регистра состояния установлен, данные находятся в регистре данных и готовы для считывания.
Следующая функция проверяет, не произошла ли ошибка при работе устройства:
void check_error() { unsigned char a;
IN_P_B(a,HD_STATUS); if (a & 0x1) { perror("HD_STATUS"); exit(-1); } return; }
Установленный бит 0 (ERR) регистра состояния означает, то при выполнении последней операции произошла ошибка. Дополнительная информация содержится в регистре ошибок.
А теперь рассмотрим функцию получения информации идентификации устройства.
void get_hd_identity(struct hd_driveid *hd) {
unsigned short a = 0; int i = 0;
unsigned short buff1[0x100]; memset(buff1,0,0x100);
В соответствии с протоколом взаимодействия проверяем статус устройства. Оно должно быть свободно:
hd_busy();
Как только устройство освободилось, в регистр номера устройства и головки заносим значение 0xA0 (10100000 в двоичном виде). Бит 4 (DEV) равен 0, следовательно, нами выбрано ведущее устройство. Режим адресации в данном случае роли не играет, бит 6 оставим нулевым:
OUT_P_B(0xA0,HD_CURRENT);
Ожидаем готовность устройства к восприятию команд:
hd_ready();
Итак, устройство готово. В регистр команд (HD_STATUS) записываем код команды идентификации устройства - 0xEC. Данная команда выполняется в режиме PIO. Полный перечень команд смотрите в спецификации:
OUT_P_B(0xEC,HD_STATUS);
В ответ на эту команду устройство установит бит DRQ и вернет блок данных, содержащих информацию идентификации. Для считывания информации организуем цикл:
do { hd_busy(); check_error(); IN_P_W(a,HD_DATA); if((i>=10 && i=27 && i
Дождавшись освобождения устройства, при помощи функции check_error() читаем регистр состояния. При этом мы сбрасываем прерывание от устройства и проверяем, не произошла ли ошибка. Затем считываем из регистра данных значение. Считывание производим до тех пор, пока установлен бит DRQ. Как только будет передан последний блок данных, устройство этот бит сбросит. Считанную информацию сохраним в буфере buff1.
Копируем полученную информацию из буфера buff1 в структуру struct hdreg hd:
memcpy(hd,(struct hdreg *)buff1,0x100);
Очищаем буфер и выходим:
memset(buff1,0,0x100); return; }
Следующая функция осуществляет чтение сектора в режиме адресации CHS.
void read_hd_sector_chs(unsigned short N, unsigned short s_sect, unsigned short s_cyl, unsigned short head, unsigned short *buff) {
int i = 0; unsigned short a;
if((!N) (!s_sect)) return;
Аргументы функции:
N - число секторов для чтения s_sect - стартовый сектор s_cyl - стартовый цилиндр head - номер головки buff - буфер, куда все помещается
Ожидаем освобождения устройства:
hd_busy();
В регистр номера устройства и головки заносим соответствующие данные. Бит 6 сброшен, что указывает на режим адресации CHS:
OUT_P_B(0xA0|head,HD_CURRENT);
Ждем готовность устройства к приему команд:
hd_ready();
В блок командных регистров заносим требуемые параметры:
OUT_P_B(N,HD_NSECTOR); OUT_P_B(s_sect,HD_SECTOR); OUT_P_B(s_cyl,HD_LCYL); OUT_P_B((s_cyl >> 8),HD_HCYL);
В регистр команд записываем код команды чтения секторов с повторами - 0x20. Данная команда выполняется в режиме PIO:
OUT_P_B(0x20,HD_STATUS);
Считываем блок данных в буфер buff:
do { hd_busy(); check_error(); IN_P_W(a,HD_DATA); buff[i++] = a; } while(hd_data_request());
Считываем последние 4 байта и выходим из функции:
IN_P_W(a,HD_DATA); buff[i++] = a;
IN_P_W(a,HD_DATA); buff[i] = a;
return; }
Функция чтения сектора в режиме адресации LBA.
void read_hd_sector_lba(unsigned short N, unsigned int lba, unsigned short *buff) {
int i = 0; unsigned short a;
if(!N) return;
Аргументы функции:
N - число секторов для чтения lba - номер блока buff - буфер, куда все помещается
Ожидаем освобождения устройства:
hd_busy();
Спецификацией АТА-2 в режиме LBA предусмотрен 28-битный адрес сектора размером 512 байт, при этом максимальный объем ограничивается значением 0,5 терабайт.
В регистре номера устройства и головки бит 6 устанавливаем в 1, а биты 3-0 будут содержать старшие биты логического адреса (27-24):
OUT_P_B(0xE0|((lba & 0x0F000000) >> 24),HD_CURRENT);
Ожидаем готовность устройства к приему команд:
hd_ready();
В блок командных регистров заносим требуемые параметры:
OUT_P_B(N,HD_NSECTOR);
В регистр номера сектора заносим биты 7-0 логического адреса:
OUT_P_B((lba & 0x000000FF),HD_SECTOR);
В регистр младшего байта номера цилиндра - биты 15-8 логического адреса:
OUT_P_B(((lba & 0x0000FF00) >> 8),HD_LCYL);
В регистр старшего байта номера цилиндра - биты 23-16 логического адреса:
OUT_P_B(((lba & 0x00FF0000) >> 16),HD_HCYL);
В регистр команд - команду чтения секторов с повторами:
OUT_P_B(0x20,HD_STATUS);
Получаем результат:
do { hd_busy(); check_error(); IN_P_W(a,HD_DATA); buff[i++] = a; } while(hd_data_request());
Считываем последние 4 байта и выходим:
IN_P_W(a,HD_DATA); buff[i++] = a;
IN_P_W(a,HD_DATA); buff[i] = a;
return; }
Рассмотрим главную функцию:
int main () {
Определим необходимые структуры и переменные:
struct hd_driveid hd;
int out; unsigned short N = 1; unsigned int sect, cyl, head, lba;
/* * N - число секторов для чтения * sect - номер сектора * cyl - номер цилиндра * head - номер головки * lba - номер логического блока * */
unsigned short buff[0x100*N];
memset(buff,0,0x100*N); memset(&hd,0,sizeof(struct hd_driveid));
Чтобы не схлопотать Segmentation fault, запросим у системы разрешения доступа к портам в диапазоне 0x1f0 - 0x1f7:
ioperm(0x1f0,8,1);
Вызовем функцию получения информации идентификации. Результат будет помещен в структуру struct hd_driveid hd:
get_hd_identity(&hd);
Отобразим результаты:
printf("Серийный номер - %s\n",hd.serial_no); printf("Модель - %s\n",hd.model); printf("Число цилиндров - %d\n",hd.cur_cyls); printf("Число головок - %d\n",hd.cur_heads); printf("Число секторов - %d\n",hd.cur_sectors); printf("Число логических блоков - %d\n",hd.lba_capacity);
А теперь прочитаем первый сектор устройства (MBR) в режиме CHS:
sect = 1; cyl = 0; head = 0;
read_hd_sector_chs(N,sect,cyl,head,buff);
Запишем в файл результат:
out=open("sect_chs", O_CREAT|O_RDWR, 0600); write(out,(unsigned char *)buff,0x200*N); close(out);
Тоже самое - в режиме LBA:
lba = 0; read_hd_sector_lba(N,lba,buff);
out=open("sect_lba", O_CREAT|O_RDWR, 0600); write(out,(unsigned char *)buff,0x200*N); close(out);
ioperm(0x1f0,8,0);
return (0); }
Весь вышеприведенный код сохраним в файле disk.c. Исполняемый модуль получим, введя команду:
gcc -o disk disk.c
Работоспособность кода была проверена для ОС Linux, версия ядра 2.4.20.
Литература.
1. Теренс Чан. Системное программирование на С++ для UNIX: Пер. с англ. - К.: Издательская группа BHV, 1999. - 592 с.
2. Гук М. Интерфейсы ПК: справочник - СПб: Питер Ком, 1999 - 416 с.
Впервые статья опубликована в журнале "Системный администратор"
А теперь рассмотрим функцию получения информации идентификации устройства.
void get_hd_identity(struct hd_driveid *hd) {
unsigned short a = 0; int i = 0;
unsigned short buff1[0x100]; memset(buff1,0,0x100);
В соответствии с протоколом взаимодействия проверяем статус устройства. Оно должно быть свободно:
hd_busy();
Как только устройство освободилось, в регистр номера устройства и головки заносим значение 0xA0 (10100000 в двоичном виде). Бит 4 (DEV) равен 0, следовательно, нами выбрано ведущее устройство. Режим адресации в данном случае роли не играет, бит 6 оставим нулевым:
OUT_P_B(0xA0,HD_CURRENT);
Ожидаем готовность устройства к восприятию команд:
hd_ready();
Итак, устройство готово. В регистр команд (HD_STATUS) записываем код команды идентификации устройства - 0xEC. Данная команда выполняется в режиме PIO. Полный перечень команд смотрите в спецификации:
OUT_P_B(0xEC,HD_STATUS);
В ответ на эту команду устройство установит бит DRQ и вернет блок данных, содержащих информацию идентификации. Для считывания информации организуем цикл:
do { hd_busy(); check_error(); IN_P_W(a,HD_DATA); if((i>=10 && i=27 && i
Дождавшись освобождения устройства, при помощи функции check_error() читаем регистр состояния. При этом мы сбрасываем прерывание от устройства и проверяем, не произошла ли ошибка. Затем считываем из регистра данных значение. Считывание производим до тех пор, пока установлен бит DRQ. Как только будет передан последний блок данных, устройство этот бит сбросит. Считанную информацию сохраним в буфере buff1.
Копируем полученную информацию из буфера buff1 в структуру struct hdreg hd:
memcpy(hd,(struct hdreg *)buff1,0x100);
Очищаем буфер и выходим:
memset(buff1,0,0x100); return; }
Следующая функция осуществляет чтение сектора в режиме адресации CHS.
void read_hd_sector_chs(unsigned short N, unsigned short s_sect, unsigned short s_cyl, unsigned short head, unsigned short *buff) {
int i = 0; unsigned short a;
if((!N) (!s_sect)) return;
Аргументы функции:
N - число секторов для чтения s_sect - стартовый сектор s_cyl - стартовый цилиндр head - номер головки buff - буфер, куда все помещается
Ожидаем освобождения устройства:
hd_busy();
В регистр номера устройства и головки заносим соответствующие данные. Бит 6 сброшен, что указывает на режим адресации CHS:
OUT_P_B(0xA0|head,HD_CURRENT);
Ждем готовность устройства к приему команд:
hd_ready();
В блок командных регистров заносим требуемые параметры:
OUT_P_B(N,HD_NSECTOR); OUT_P_B(s_sect,HD_SECTOR); OUT_P_B(s_cyl,HD_LCYL); OUT_P_B((s_cyl >> 8),HD_HCYL);
В регистр команд записываем код команды чтения секторов с повторами - 0x20. Данная команда выполняется в режиме PIO:
OUT_P_B(0x20,HD_STATUS);
Считываем блок данных в буфер buff:
do { hd_busy(); check_error(); IN_P_W(a,HD_DATA); buff[i++] = a; } while(hd_data_request());
Считываем последние 4 байта и выходим из функции:
IN_P_W(a,HD_DATA); buff[i++] = a;
IN_P_W(a,HD_DATA); buff[i] = a;
return; }
Функция чтения сектора в режиме адресации LBA.
void read_hd_sector_lba(unsigned short N, unsigned int lba, unsigned short *buff) {
int i = 0; unsigned short a;
if(!N) return;
Аргументы функции:
N - число секторов для чтения lba - номер блока buff - буфер, куда все помещается
Ожидаем освобождения устройства:
hd_busy();
Спецификацией АТА-2 в режиме LBA предусмотрен 28-битный адрес сектора размером 512 байт, при этом максимальный объем ограничивается значением 0,5 терабайт.
В регистре номера устройства и головки бит 6 устанавливаем в 1, а биты 3-0 будут содержать старшие биты логического адреса (27-24):
OUT_P_B(0xE0|((lba & 0x0F000000) >> 24),HD_CURRENT);
Ожидаем готовность устройства к приему команд:
hd_ready();
В блок командных регистров заносим требуемые параметры:
OUT_P_B(N,HD_NSECTOR);
В регистр номера сектора заносим биты 7-0 логического адреса:
OUT_P_B((lba & 0x000000FF),HD_SECTOR);
В регистр младшего байта номера цилиндра - биты 15-8 логического адреса:
OUT_P_B(((lba & 0x0000FF00) >> 8),HD_LCYL);
В регистр старшего байта номера цилиндра - биты 23-16 логического адреса:
OUT_P_B(((lba & 0x00FF0000) >> 16),HD_HCYL);
В регистр команд - команду чтения секторов с повторами:
OUT_P_B(0x20,HD_STATUS);
Получаем результат:
do { hd_busy(); check_error(); IN_P_W(a,HD_DATA); buff[i++] = a; } while(hd_data_request());
Считываем последние 4 байта и выходим:
IN_P_W(a,HD_DATA); buff[i++] = a;
IN_P_W(a,HD_DATA); buff[i] = a;
return; }
Рассмотрим главную функцию:
int main () {
Определим необходимые структуры и переменные:
struct hd_driveid hd;
int out; unsigned short N = 1; unsigned int sect, cyl, head, lba;
/* * N - число секторов для чтения * sect - номер сектора * cyl - номер цилиндра * head - номер головки * lba - номер логического блока * */
unsigned short buff[0x100*N];
memset(buff,0,0x100*N); memset(&hd,0,sizeof(struct hd_driveid));
Чтобы не схлопотать Segmentation fault, запросим у системы разрешения доступа к портам в диапазоне 0x1f0 - 0x1f7:
ioperm(0x1f0,8,1);
Вызовем функцию получения информации идентификации. Результат будет помещен в структуру struct hd_driveid hd:
get_hd_identity(&hd);
Отобразим результаты:
printf("Серийный номер - %s\n",hd.serial_no); printf("Модель - %s\n",hd.model); printf("Число цилиндров - %d\n",hd.cur_cyls); printf("Число головок - %d\n",hd.cur_heads); printf("Число секторов - %d\n",hd.cur_sectors); printf("Число логических блоков - %d\n",hd.lba_capacity);
А теперь прочитаем первый сектор устройства (MBR) в режиме CHS:
sect = 1; cyl = 0; head = 0;
read_hd_sector_chs(N,sect,cyl,head,buff);
Запишем в файл результат:
out=open("sect_chs", O_CREAT|O_RDWR, 0600); write(out,(unsigned char *)buff,0x200*N); close(out);
Тоже самое - в режиме LBA:
lba = 0; read_hd_sector_lba(N,lba,buff);
out=open("sect_lba", O_CREAT|O_RDWR, 0600); write(out,(unsigned char *)buff,0x200*N); close(out);
ioperm(0x1f0,8,0);
return (0); }
Весь вышеприведенный код сохраним в файле disk.c. Исполняемый модуль получим, введя команду:
gcc -o disk disk.c
Работоспособность кода была проверена для ОС Linux, версия ядра 2.4.20.
Литература.
1. Теренс Чан. Системное программирование на С++ для UNIX: Пер. с англ. - К.: Издательская группа BHV, 1999. - 592 с.
2. Гук М. Интерфейсы ПК: справочник - СПб: Питер Ком, 1999 - 416 с.
Впервые статья опубликована в журнале "Системный администратор"