Subversion Repositories Kolibri OS

Rev

Rev 1962 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. ; Copyright (c) 2008-2009, diamond
  2. ; All rights reserved.
  3. ;
  4. ; Redistribution and use in source and binary forms, with or without
  5. ; modification, are permitted provided that the following conditions are met:
  6. ;       * Redistributions of source code must retain the above copyright
  7. ;       notice, this list of conditions and the following disclaimer.
  8. ;       * Redistributions in binary form must reproduce the above copyright
  9. ;       notice, this list of conditions and the following disclaimer in the
  10. ;       documentation and/or other materials provided with the distribution.
  11. ;       * Neither the name of the <organization> nor the
  12. ;       names of its contributors may be used to endorse or promote products
  13. ;       derived from this software without specific prior written permission.
  14. ;
  15. ; THIS SOFTWARE IS PROVIDED BY Alexey Teplov aka <Lrz> ''AS IS'' AND ANY
  16. ; EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  17. ; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. ; DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
  19. ; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  20. ; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  21. ; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  22. ; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. ; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  24. ; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. ;*****************************************************************************
  26.  
  27.                                         Читай между строк - там никогда не бывает опечаток.
  28.  
  29. Бутсектор для FAT32-тома на носителе с размером сектора 0x200 = 512 байт.
  30.  
  31. =====================================================================
  32.  
  33. Есть две версии в зависимости от того, поддерживает ли носитель LBA,
  34. выбор осуществляется установкой константы use_lba в первой строке исходника.
  35. Требования для работы:
  36. 1) Сам бутсектор, первая копия FAT и все используемые файлы
  37. должны быть читабельны. (Если дело происходит на носителе с разбиением на
  38. разделы и загрузочный код в MBR достаточно умный, то читабельности резервной
  39. копии бутсектора (сектор номер 6 на томе) достаточно вместо читабельности
  40. самого бутсектора).
  41. 2) Минимальный процессор - 80386.
  42. 3) В системе должно быть как минимум 584K свободной базовой памяти.
  43.  
  44. =====================================================================
  45.  
  46. Документация в тему (ссылки проверялись на валидность 15.05.2008):
  47.         официальная спецификация FAT: http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx
  48.                 в формате PDF: http://staff.washington.edu/dittrich/misc/fatgen103.pdf
  49.                 русский перевод: http://wasm.ru/docs/11/fatgen103-rus.zip
  50.         официальная спецификация расширения EDD BIOS 3.0: http://www.phoenix.com/NR/rdonlyres/19FEBD17-DB40-413C-A0B1-1F3F560E222F/0/specsedd30.pdf
  51.                 то же, версия 1.1: http://www.phoenix.com/NR/rdonlyres/9BEDED98-6B3F-4DAC-BBB7-FA89FA5C30F0/0/specsedd11.pdf
  52.         описание функций BIOS: Interrupt List by Ralf Brown: http://www.cs.cmu.edu/~ralf/files.html
  53.         официальная спецификация Boot BIOS: http://www.phoenix.com/NR/rdonlyres/56E38DE2-3E6F-4743-835F-B4A53726ABED/0/specsbbs101.pdf
  54.  
  55. =====================================================================
  56.  
  57. Схема используемой памяти:
  58.         ...-7C00        стек
  59.         7C00-7E00       код бутсектора
  60.         7E00-8200       вспомогательный файл загрузчика (kordldr.f32)
  61.         8400-8C00       информация о кэше для таблицы FAT: 100h входов по 8
  62.                         байт: 4 байта (две ссылки - вперёд и назад) для
  63.                         организации L2-списка всех прочитанных секторов в
  64.                         порядке возрастания последнего времени использования
  65.                         + 4 байта для номера сектора; при переполнении кэша
  66.                         выкидывается элемент из головы списка, то есть тот,
  67.                         к которому дольше всех не было обращений
  68.         60000-80000     кэш для таблицы FAT (100h секторов)
  69.         80000-90000     текущий кластер текущей рассматриваемой папки
  70.         90000-...       кэш для содержимого папок (каждой папке отводится
  71.                         2000h байт = 100h входов, одновременно в кэше
  72.                         может находиться не более 8 папок;
  73.                         точный размер определяется размером доступной
  74.                         физической памяти - как правило, непосредственно
  75.                         перед A0000 размещается EBDA, Extended BIOS Data Area)
  76.  
  77. =====================================================================
  78.  
  79. Основной процесс загрузки.
  80. Точка входа (start): получает управление от BIOS при загрузке, при этом
  81.         dl содержит идентификатор диска, с которого идёт загрузка
  82. 1. Настраивает стек ss:sp = 0:7C00 (стек располагается непосредственно перед
  83.         кодом), сегмент данных ds = 0, и устанавливает ss:bp на начало
  84.         бутсектора (в дальнейшем данные будут адресоваться через [bp+N] -
  85.         это освобождает ds и экономит на размере кода). Сохраняет в стеке
  86.         идентификатор загрузочного диска для последующего обращения
  87.         через byte [bp-2].
  88. 2. LBA-версия: проверяет, поддерживает ли носитель LBA, вызовом функции 41h
  89.         прерывания 13h. Если нет, переходит на код обработки ошибок с
  90.         сообщением об отсутствии LBA.
  91. CHS-версия: определяет геометрию носителя вызовом функции 8 прерывания 13h и
  92.         записывает полученные данные поверх BPB. Если вызов завершился ошибкой,
  93.         предполагает уже существующие данные корректными.
  94. 3. Вычисляет начало данных FAT-тома, сохраняет его в стек для последующего
  95.         обращения через dword [bp-10]. В процессе вычисления узнаёт начало
  96.         первой FAT, сохраняет и его в стек для последующего обращения через
  97.         dword [bp-6].
  98. 4. (Заканчивая тему параметров в стеке) Помещает в стек dword-значение -1
  99.         для последующего обращения через dword [bp-14] - инициализация
  100.         переменной, содержащей текущий сектор, находящийся в кэше FAT
  101.         (-1 не является валидным значением для номера сектора FAT).
  102. 5. Ищет в корневой папке элемент kordldr.f32. Если не находит - переходит на
  103.         код обработки ошибок с сообщением о ненайденном загрузчике.
  104.         Замечание: на этом этапе загрузки искать можно только в корневой
  105.         папке и только имена, заданные в формате файловой системе FAT
  106.         (8+3 - 8 байт на имя, 3 байта на расширение, все буквы должны
  107.         быть заглавными, при необходимости имя и расширение дополняются
  108.         пробелами, разделяющей точки нет, завершающего нуля нет).
  109. 6. Загружает первый кластер файла kordldr.f32 по адресу 0:7E00 и передаёт
  110.         ему управление. При этом в регистре eax оказывается абсолютный
  111.         номер первого сектора kordldr.f32, а в cx - число считанных секторов
  112.         (равное размеру кластера).
  113.  
  114. Вспомогательные процедуры бутсектора.
  115. Код обработки ошибок (err):
  116. 1. Выводит строку с сообщением об ошибке.
  117. 2. Выводит строку "Press any key...".
  118. 3. Ждёт нажатия any key.
  119. 4. Вызывает int 18h, давая шанс BIOSу попытаться загрузиться откуда-нибудь ещё.
  120. 5. Для подстраховки зацикливается.
  121.  
  122. Процедура чтения кластера (read_cluster):
  123. на входе должно быть установлено:
  124.         ss:bp = 0:7C00
  125.         es:bx = указатель на начало буфера, куда будут прочитаны данные
  126.         eax = номер кластера
  127. на выходе: ecx = число прочитанных секторов (размер кластера),
  128.         es:bx указывает на конец буфера, в который были прочитаны данные,
  129.         eax и старшие слова других 32-битных регистров разрушаются
  130. Загружает в ecx размер кластера, перекодирует номер кластера в номер сектора
  131. и переходит к следующей процедуре.
  132.  
  133. Процедура чтения секторов (read_sectors32 и read_sectors2):
  134. на входе должно быть установлено:
  135.         ss:bp = 0:7C00
  136.         es:bx = указатель на начало буфера, куда будут прочитаны данные
  137.         eax = стартовый сектор (относительно начала логического диска
  138.                 для read_sectors32, относительно начала данных
  139.                 для read_sectors2)
  140.         cx = число секторов (должно быть больше нуля)
  141. на выходе: es:bx указывает на конец буфера, в который были прочитаны данные
  142.         старшие слова 32-битных регистров могут разрушиться
  143. 0. Если вызывается read_sectors2, она переводит указанный ей номер сектора
  144.         в номер относительно начала логического диска, прибавляя номер сектора
  145.         начала данных, хранящийся в стеке как [bp-10].
  146. 1. Переводит стартовый сектор (отсчитываемый от начала тома) в сектор на
  147.         устройстве, прибавляя значение соответствующего поля из BPB.
  148. 2. В цикле (шаги 3-6) читает секторы, следит за тем, чтобы на каждой итерации
  149.         CHS-версия: все читаемые секторы были на одной дорожке.
  150.         LBA-версия: число читаемых секторов не превосходило 7Fh (требование
  151.         спецификации EDD BIOS).
  152. CHS-версия:
  153. 3. Переводит абсолютный номер сектора в CHS-систему: сектор рассчитывается как
  154.         единица плюс остаток от деления абсолютного номера на число секторов
  155.         на дорожке; дорожка рассчитывается как остаток от деления частного,
  156.         полученного на предыдущем шаге, на число дорожек, а цилиндр - как
  157.         частное от этого же деления. Если число секторов для чтения больше,
  158.         чем число секторов до конца дорожки, уменьшает число секторов для
  159.         чтения.
  160. 4. Формирует данные для вызова int 13h (ah=2 - чтение, al=число секторов,
  161.         dh=головка, (младшие 6 бит cl)=сектор,
  162.         (старшие 2 бита cl и весь ch)=дорожка, dl=диск, es:bx->буфер).
  163. 5. Вызывает BIOS. Если BIOS рапортует об ошибке, выполняет сброс диска
  164.         и повторяет попытку чтения, всего делается не более трёх попыток
  165.         (несколько попыток нужно в случае дискеты для гарантии того, что
  166.         мотор раскрутился). Если все три раза происходит ошибка чтения,
  167.         переходит на код обработки ошибок с сообщением "Read error".
  168. 6. В соответствии с числом прочитанных на текущей итерации секторов
  169.         корректирует текущий сектор, число оставшихся секторов и указатель на
  170.         буфер (в паре es:bx корректируется es). Если всё прочитано, заканчивает
  171.         работу, иначе возвращается на шаг 3.
  172. LBA-версия:
  173. 3. Если число секторов для чтения больше 7Fh, уменьшает его (для текущей
  174.         итерации) до 7Fh.
  175. 4. Формирует в стеке пакет для int 13h (кладёт все нужные данные командами
  176.         push, причём в обратном порядке: стек - структура LIFO, и данные в
  177.         стеке хранятся в обратном порядке по отношению к тому, как их туда
  178.         клали).
  179. 5. Вызывает BIOS. Если BIOS рапортует об ошибке, переходит на код обработки
  180.         ошибок с сообщением "Read error". Очищает стек от пакета,
  181.         сформированного на предыдущем шаге.
  182. 6. В соответствии с числом прочитанных на текущей итерации секторов
  183.         корректирует текущий сектор, число оставшихся секторов и указатель на
  184.         буфер (в паре es:bx корректируется es). Если всё прочитано, заканчивает
  185.         работу, иначе возвращается на шаг 3.
  186.  
  187. Процедура поиска элемента в папке (lookup_in_dir):
  188. на входе должно быть установлено:
  189.         ss:bp = 0:7C00
  190.         ds:si = указатель на имя файла в формате FAT (см. выше)
  191.         eax = начальный кластер папки
  192.         bx = 0
  193. на выходе: флаг CF определяет, удалось ли найти файл; если удалось, то
  194.         CF сброшен и es:di указывает на элемент папки
  195. В цикле считывает кластеры папки и ищет запрошенный элемент в прочитанных
  196. данных. Для чтения кластера использует уже описанную процедуру read_clusters,
  197. для продвижения по цепочке кластеров - описанную далее процедуру
  198. get_next_clusters. Данные читаются в область памяти, начинающуюся с адреса
  199. 8000:0000, при этом первые 2000h байт из данных папки (может быть, меньше,
  200. если чтение прервётся раньше) не перекрываются последующими чтениями
  201. (это будет использовано позднее, в системе кэширования из kordldr.f32).
  202. Выход осуществляется в любом из следующих случаев: найден запрошенный элемент;
  203. кончились элементы в папке (первый байт очередного элемента нулевой);
  204. кончились данные папки в соответствии с цепочкой кластеров из FAT.
  205.  
  206. Процедура вывода на экран ASCIIZ-строки (out_string):
  207. на входе: ds:si -> строка
  208. В цикле, пока не достигнут завершающий ноль, вызывает функцию int 10h/ah=0Eh.
  209.  
  210. =====================================================================
  211.  
  212. Работа вспомогательного загрузчика kordldr.f32:
  213. 1. Определяет, был ли он загружен CHS- или LBA-версией бутсектора.
  214.         В зависимости от этого устанавливает смещения используемых процедур
  215.         бутсектора. Критерий проверки: в CHS-версии по адресу err находится
  216.         байт 0xE8 (машинная команда call), в LBA-версии по тому же адресу
  217.         находится байт 0x14, а адрес процедуры err другой.
  218. 2. Узнаёт размер свободной базовой памяти (т.е. свободного непрерывного куска
  219.         адресов памяти, начинающегося с 0) вызовом int 12h. В соответствии с
  220.         ним вычисляет число элементов в кэше папок. Хотя бы для одного элемента
  221.         место должно быть, отсюда ограничение в 592 Kb (94000h байт).
  222.         Замечание: этот размер не может превосходить 0A0000h байт и
  223.         на практике оказывается немного (на 1-2 килобайта) меньшим из-за
  224.         наличия  дополнительной области данных BIOS "вверху" базовой памяти.
  225. 3. Инициализирует кэширование папок. Бутсектор уже загрузил какую-то часть
  226.         данных корневой папки; копирует загруженные данные в кэш и запоминает,
  227.         что в кэше есть корневая папка.
  228. 4. Инициализирует кэширование FAT. Бутсектор имеет дело с FAT в том и только
  229.         том случае, когда ему приходится загружать данные корневой папки,
  230.         не поместившиеся в один кластер. В этом случае в памяти присутствует
  231.         один сектор FAT (если было несколько обращений - последний из
  232.         использованных).
  233. 5. Если кластер равен сектору, то бутсектор загрузил только часть файла
  234.         kordldr.f32, и загрузчик подгружает вторую свою часть, используя
  235.         значения регистров на входе в kordldr.f32.
  236. 6. Загружает вторичный загрузчик kord/loader по адресу 1000:0000. Если файл не
  237.         найден,   или оказался папкой, или оказался слишком большим, то переходит
  238.         на код обработки ошибок из бутсектора с сообщением
  239.         "Fatal error: cannot load the secondary loader".
  240.         Замечание: на этом этапе имя файла уже можно указывать вместе с путём
  241.         и в формате ASCIIZ, хотя поддержки длинных имён и неанглийских символов
  242.         по-прежнему нет.
  243. 7. Изменяет код обработки ошибок бутсектора на переход на метку hooked_err.
  244.         Это нужно, чтобы последующие обращения к коду бутсектора в случае
  245.         ошибок чтения не выводил соответствующее сообщение с последующей
  246.         перезагрузкой, а рапортовал об ошибке чтения, которую могло бы
  247.         как-нибудь обработать ядро.
  248. 8. Если загрузочный диск имеет идентификатор меньше 0x80,
  249.         то устанавливает al='f' ("floppy"), ah=идентификатор диска,
  250.         иначе al='h' ("hard"), ah=идентификатор диска-0x80 (номер диска).
  251.         (Говорите, дискеток с FAT32 не бывает? В чём-то Вы правы... но
  252.         уверены ли Вы, что нет загрузочных устройств, подобных дискетам,
  253.         но большего размера, и для которых BIOS-идентификатор меньше 0x80?)
  254.         Устанавливает bx='32' (тип файловой системы - FAT32).
  255.         Устанавливает si=смещение функции обратного вызова. Поскольку в этот
  256.         момент ds=0, то ds:si образуют полный адрес.
  257. 9. Передаёт управление по адресу 1000:0000.
  258.  
  259. Функция обратного вызова для вторичного загрузчика:
  260.         предоставляет возможность чтения файла.
  261. Вход и выход описаны в спецификации на загрузчик.
  262. 1. Сохраняет стек вызывающего кода и устанавливает свой стек:
  263.         ss:sp = 0:(7C00-10), bp=7C00: пара ss:bp при работе с остальным
  264.         кодом должна указывать на 0:7C00, а -10 берётся от того, что
  265.         инициализирующий код бутсектора уже поместил в стек 10 байт параметров,
  266.         и они должны сохраняться в неизменности. (Значение [ebp-14],
  267.         "текущий сектор, находящийся в кэше FAT", не используется после
  268.         инициализации кэширования в kordldr.f32.)
  269. 2. Разбирает переданные параметры и вызывает нужную из вспомогательных
  270.         процедур (загрузки файла либо продолжения загрузки файла).
  271. 3. Восстанавливает стек вызывающего кода и возвращает управление.
  272.  
  273. Вспомогательные процедуры kordldr.f32.
  274. Процедура получения следующего кластера в FAT (get_next_cluster):
  275. 1. Вычисляет номер сектора в FAT, в котором находится запрошенный элемент.
  276.         (В секторе 0x200 байт, каждый вход занимает 4 байта.)
  277. 2. Проверяет, есть ли сектор в кэше. Если есть, пропускает шаги 3 и 4.
  278. 3. Если нет, то в кэш нужно вставить новый элемент. Если кэш ещё не заполнен,
  279.         выделяет очередной элемент в конце кэша. Если заполнен, удаляет
  280.         самый старый элемент (тот, к которому дольше всего не было обращений);
  281.         для того, чтобы отслеживать порядок элементов по времени последнего
  282.         обращения, все (выделенные) элементы кэша связаны в двусвязный список,
  283.         в котором первым элементом является самый старый, а ссылки вперёд
  284.         указывают на следующий по времени последнего обращения.
  285. 4. Читает соответствующий сектор FAT с диска.
  286. 5. Корректирует список: текущий обрабатываемый элемент удаляется с той позиции,
  287.         где он находится, и добавляется в конец. (В случае со свежедобавленными
  288.         в кэш элементами удаления не делается, поскольку их в списке ещё нет.)
  289. 6. Считывает нужный вход в FAT, сбрасывая старшие 4 бита.
  290. 7. Сравнивает прочитанное значение с пределом: если оно строго меньше
  291.         0x0FFFFFF7, то оно задаёт номер следующего кластера в цепочке;
  292.         в противном случае цепочка закончилась.
  293.  
  294. Процедура загрузки файла (load_file):
  295. 1. Текущая рассматриваемая папка - корневая. В цикле выполняет шаги 2-4.
  296. 2. Конвертирует имя текущего рассматриваемого компонента имени (компоненты
  297.         разделяются символом '/') в FAT-формат 8+3. Если это невозможно
  298.         (больше 8 символов в имени, больше 3 символов в расширении или
  299.         больше одной точки), возвращается с ошибкой.
  300. 3. Ищет элемент с таким именем в текущей рассматриваемой папке.
  301.         а) Проверяет, есть ли такая папка в кэше папок. (Идентификация папок
  302.         осуществляется по номеру начального кластера.) Если такой папки ещё
  303.         нет, добавляет её в кэш; если тот переполняется, выкидывает папку,
  304.         к которой дольше всего не было обращений. (Для каждого элемента кэша
  305.         хранится метка от 0 до (размер кэша)-1, определяющая его номер при
  306.         сортировке по давности последнего обращения. При обращении к какому-то
  307.         элементу его метка становится нулевой, а те метки, которые меньше
  308.         старого значения, увеличиваются на единицу.)
  309.         б) Просматривает в поисках запрошенного имени все элементы из кэша,
  310.         используя процедуру из бутсектора. Если обнаруживает искомый элемент,
  311.         переходит к шагу 4. Если обнаруживает конец папки, возвращается из
  312.         процедуры с ошибкой.
  313.         в) В цикле считывает папку посекторно. При этом пропускает начальные
  314.         секторы, которые уже находятся в кэше и уже были просмотрены. Каждый
  315.         прочитанный сектор копирует в кэш, если там ещё остаётся место,
  316.         и просматривает в нём все элементы. Работает, пока не случится одно из
  317.         трёх событий: найден искомый элемент; кончились кластеры (судя по
  318.         цепочке кластеров в FAT); очередной элемент папки сигнализирует о конце
  319.         (первый байт нулевой). В двух последних случаях возвращается с ошибкой.
  320. 4. Проверяет тип найденного элемента (файл/папка): последний элемент в
  321.         запрошенном имени должен быть файлом, все промежуточные - папками.
  322.         Если текущий компонент имени - промежуточный, продвигает текущую
  323.         рассматриваемую папку и возвращается к пункту 2.
  324. 5. Проходит по цепочке кластеров в FAT и считывает все кластеры в указанный
  325.         при вызове буфер последовательными вызовами функции бутсектора;
  326.         при этом если несколько кластеров файла расположены на диске
  327.         последовательно, то их чтение объединяется в одну операцию.
  328.         Следит за тем, чтобы не превысить указанный при вызове процедуры
  329.         лимит числа секторов для чтения.
  330.  
  331. Процедура продолжения загрузки файла (continue_load_file): встроена
  332.         внутрь шага 5 load_file; загружает в регистры нужные значения (ранее
  333.         сохранённые из load_file) и продолжает шаг 5.
  334.