Модуль boot asm


UEFI Programming — First Steps

vid , 2009-03-31 Revision: 1.0

In this article, I will describe steps needed to start on with development of real UEFI applications on x86 PC, and share some practical experiences with problems doing so. I will focus on 64-bit version of UEFI, because the 32-bit version isn’t much used in this area (most likely due to Microsoft decision not to support UEFI in 32-bit Vista). So, to follow some of my steps here, you’ll need a 64-bit CPU (but not 64-bit OS, you can use any 32-bit OS as well). We will finish this article with EFI Hello World application.

This article is continuation of my previous article Introduction to UEFI. Make sure to understand things described there, before reading on.

Of course, anything you try according to this article, you are doing at your own risk.

Getting the hardware

To start UEFI development, first of all you need to get a motherboard whose BIOS has UEFI support. (more precisely we should probably say «whose firmware has UEFI support», but I will use this form). Finding whether particular BIOS has UEFI support often turns out to be quite complicated task. Motherboard manufacturers license BIOS from other companies, usually from AMI (Aptio, AMIBIOS), Phoenix (SecureCore, TrustedCore, AwardCore) or Insyde (InsydeH20). Forget about determining UEFI support just by end-user stats you see in most shops. Since UEFI support is still only in somewhat experimental state, in many cases it isn’t even listed in motherboard technical specification. In such case you are left to googling and asking on forums, where you often get only internal brand name that is often hard to match with end-user product designation.

One trick that I found out to work for Intel boards (but it may very well work for other boards as well) is to look at BIOS Update Release Notes, e. g. the document which lists changes and fixes of BIOS. If board has UEFI support, you will probably find UEFI mentioned there (and only there in case of Intel).

In short, determining UEFI support is much harder than it may seem. Some machines that have this technology are listed here. I use Intel DG33BU board (it was marketed as Intel DG33BUC for some reason).

You will also need some place to boot from. In theory just USB pen should be enough, but in practice none of 4 brands I tried worked with my board’s UEFI implementation. So you may have to use harddrive. I strongly suggest IDE drive, because SATA drives may need some tweaking of BIOS settings, or they may not work at all. Like the USB pens, USB keyboard might be a problem too. I wouldn’t fear this that much, but if you can use PS/2 keyboard, do so.

Getting the software

To go on with UEFI development, you will need two development packages: (EDK ) and .

First package, the EFI Development Kit contains the TianoCore (public part of reference UEFI implementation by Intel) source code, along with many examples and binaries of EFI Shell (we’ll talk about this later), all ready to be built with nice make system. It even can be built into a Win32 UEFI emulator for more convinient development and testing, but I won’t cover that here. I also won’t demonstrate usage of EDK build system in this article. Even though it might be a good idea for real world project, I want to give you bit more insight about what goes «under the hood» here.

Second package, the EFI Toolkit is set of extra UEFI applications like FTP client, Python port, text editor, etc. Strictly speaking, we don’t really need this, but it has a set of C headers that is somewhat easier to use than those of EDK (unless you take advantage of EDK build system for your project). However, note that it doesn’t contain all headers yet — you will quickly run into this problem if you try some real development with it.

Along with headers, you also will need documentation of the UEFI interface. That can be downloaded after filling simple form at http://www.uefi.org/specs. This is the official .

Except for UEFI specification, there is also another document called . This describes implementation of UEFI initialization stages (before drivers are loaded and applications can be executed), and more importantly for us, it also describes interface of routines used to implement UEFI. We can understand this Platform Initialization Specification as description of Tiano UEFI implementation. It provides more lowlevel control than UEFI, in cases when such control is needed. Strictly speaking, someone may implement UEFI Specification without implementing anything from Platform Initialization Specification, but that’s not very likely to happen in real world.

Last but not least, you will need 64-bit C compiler. I suggest Microsoft Visual C++, whose 64-bit version is freely available in Windows DDK. You can get it here.

Booting into EFI Shell

Up to this point, you may have lacked any visual idea about UEFI. It was just a programatic interface, after all. Purpose of this chapter would be to overcome this, by booting the so-called EFI Shell. EFI shell is much like any other shell you know: you have a command line, through which you can enter commands to browse disk, run applications, configure system, etc. Only difference is that EFI shell is only built using UEFI services, and as such it doesn’t require any operating system to run. Working in EFI shell feels much like working in some very simple operating system. From this shell you will also run your applications (later in this article).

Actual steps of booting EFI shell might vary a lot among different BIOS brands. Some BIOSes (mostly on MACs where EFI is the primary standard) have extra options for specifying file to boot, or can even have EFI shell built-in inside ROM. However, I only have experience with non-MAC Intel UEFI implementation, that only seems to support the very minimum of features required to install EFI versions of Windows. Microsoft requirements on UEFI implementation, among other things, specify that UEFI boot loader must use fixed path to file to boot, and if it can’t boot UEFI for some reason, it must silently switch to default boot. That causes lot of headache when your UEFI doesn’t boot as it should, and you must find out what the problem is without any information from loader.

For 64-bit UEFI implementations, the path to file that is booted is \EFI\BOOT\BOOTX64.EFI . UEFI boot loader searches all filesystems it can access for this file, and when it finds it, it is executed. As I already said, if the file isn’t found, booting continues with legacy BIOS. UEFI boot loader can read MBR or GPT partition tables, and can only read FAT32 partitions. This includes USB drives, so it is possible to boot EFI shell from FAT32-formatted USB pen. Unfortunately, in my tests 3 of 4 USB pens didn’t work, and the 4th stopped working too after BIOS update. I had similar problem with one of two SATA drives not working with UEFI. Therefore, I strongly suggest to use IDE drive, if you have any problems. If you already have at least one FAT32 partition on your drive, you can use it, otherwise you need to create fresh new one.

Now you need to copy binary of EFI shell to that partition. You can find 64-bit binary of EFI shell in EFI Development Kit: \Other\Maintained\Application\UefiShell\bin\x64\Shell_full.efi . Copy it to your FAT32 partition as \EFI\BOOT\BOOTX64.EFI .

Now reboot, and enter into BIOS settings. In the Boot tab, you should see an UEFI Boot option, enable it. If everything is allright (likely not), now after reboot you should end up in the EFI Shell. If you did, congratulations. If you didn’t, and instead your regular OS booted as usual, it most likely means that UEFI boot manager wasn’t able to access the drive.

First try to enter Boot Menu during BIOS screen ( F10 on my machine). If the EFI Shell on FAT32 partition was detected, but didn’t boot, you will see it as one of option (in my case [Internal EFI Shell—Harddrive] ). If you see it, just run it. This might possibly happen if you already have some EFI operating system installed, and it has written itself as default EFI boot entry to NVRAM.

If you don’t see EFI Shell in the Boot Menu, it means UEFI Boot Loader wasn’t able to find any FAT32 drive with \EFI\BOOT\BOOTX64.EFI . If you are trying to boot EFI shell from USB key, try tweaking USB emulation settings in BIOS. Same applies to SATA disks and SATA/IDE settings. If none of settings work, or your machine failed to boot from IDE drive, I can’t help you any more than to doublecheck everything I wrote so far (especially typos in path to EFI shell).

If you have some experience with booting EFI shell not covered in this chapter, please drop me a message, so I can update this article.

Building an UEFI application

So, we are in EFI shell. That means we can finally test any (64-bit) EFI application we write. Time to start writing one. We will of course write and compile applications in normal operating system, not in EFI shell.

EFI application or driver is just a plain Windows PE DLL file, just with different subsystem value in header. There are 3 new values: EFI application = 10, EFI boot service driver = 11, efi runtime driver = 12 (numbers are decimal). Question how to set subsystem will be answered later, for now let’s focus on the DLL file.

EFI PE application doesn’t have any fancies we have in Windows PEs, like symbol tables, exports, static exception handling data, etc. It does not even have imports — all you will ever need in EFI application is passed as argument to entry point function. Only thing needed apart from data and code are relocations. So, this is simplest EFI application:

Compiling it with MS Visual C (supply your own path to EFI toolkit):

Here we set path to common EFI headers, and to platform-specific EFI headers. The /c switch disables linking (we will link separately for better readability), and /Zl disables dependency on default libc libraries.

The /entry:main overrides default libc entry point to our main() function. The /dll forces creation of relocations. And the IGNORE:4086 disables warning LNK4086 , that is caused by nonstandard main() arguments.

Now we have windows DLL, we just need to change PE subsystem value to EFI. For that, EFI Toolkit contains simple (buggy) utility that changes subsystem to one of 3 EFI values. We find this utility in EFI_Toolkit\build\tools\bin\fwimage.exe . To set subsystem to EFI app, we’ll use it like this:


Produced file hello.efi should now be functional empty EFI application. We just need to copy it to our FAT32 partition, reboot to EFI shell, and test it:

If we don’t get any error message, application worked.

UEFI Programming

Now we finally can get deeper into UEFI programming. Your main source for this information should be the UEFI specification, I will only sum up most basic points.

First of all, we should know something about environemnt of UEFI applications. I will describe only environment of 64-bit x86 UEFI here (other to be found in UEFI specification).

UEFI runs in uniprocessor flat 64-bit long mode. Usually UEFI runs with memory mapping disabled (physical RAM address = virtual RAM address), but since 64-bit x86 mode requires mapping to be enabled, UEFI maps entire memory so that virtual address is same as physical (i. e. mapping is transparent). Calling convention is usual 64-bit fastcall (first 4 arguments in RCX , RDX , R8 , R9 with space reserved on stack; rest of arguments passed by stack; RAX , R10 , R11 and XMM0 — XMM5 not preserved by called function), so you don’t need to worry about special compiler settings. Notable feature of EFI is that for every supported architecture, it defines exact binary interface (ABI).

Now let’s look at how our application interacts with UEFI services. First, UEFI provides set of services, called . These are available to EFI drivers, applications, and to OS boot loader during boot. At some point during OS booting, OS loader can decide to drop them, and after that point those services become unavailable. There is also a little number of services that always remain available, called «Runtime Services». Apart from these two sets of services, all that UEFI offers is available through so-called . Protocol is very much like a class in object oriented programming. UEFI defines set of protocols itself (for example protocols handling USB, filesystem, compression, network, …), and application can define its own protocols (hence the «Extensible» in «Unified Extensible Firmware Interface»). Protocols are identified by GUID (, google it if you don’t know what it is). Only very few protocols are mandatory in UEFI specification, and all the rest may or may not be implemented in particular firmware. If protocol isn’t implemented in firmware, you can load a driver that implements it, or even write such driver yourself.

Now let’s look at how to access these services. As I already explained, all you ever need is passed as argument to entry point function. Prototype of entry point function looks like this:

First argument is handle of our process, nothing extra to say about it. Second is pointer to EFI_SYSTEM_TABLE , the top-level EFI structure, which keeps references to everything there is: boot/runtime services, drivers, protocol implementations, memory maps, etc. It is good idea to always save both these arguments in a global variable, so you can access them from anywhere in source code. You can find detailed description of EFI System Table in UEFI Specification chapter 4 — EFI System Table. Its C definition looks like this:

Here we see references to boot and runtime services, three standard I/O handles (as implementations of SIMPLE_TEXT_OUTPUT and SIMPLE_INPUT protocols), and pointer to . Configuration Table holds references to all other protocol implementations currently active in system.

First we will show example of using Boot Service. The EFI_BOOT_SERVICES is just a structure that holds pointers to functions described in UEFI Specification chapter 6: Services — Boot Services. For now we will use only simple Exit() function, that terminates current EFI application immediately.

Now, we will show simple Hello World application, using the ConOut implementation of SIMPLE_TEXT_OUTPUT protocol. This protocol is described in UEFI Specification chapter 11.4 — Simple Text Output Protocol. Its C header looks like this:

We are of course interested in OutputString() function, whose prototype is:

Note that UEFI uses Unicode strings only, hence the CHAR16 *String . This pointer meaning is exactly same as in any object oriented programming. With this info, we should be able to write Hello World app easily:

Also note that UEFI uses CRLF line terminators ( \r\n ) instead of just LF ( \n ), and when we use native EFI functions, there is no layer which reinterprets LF to CRLF. Normally, applications use additional library called which does the LF->CRLF transform.

UEFI Programming with FASM

As an extra, I will also demonstrate same Hello World example in assembly (using FASM, that currently has experimental UEFI support since version 1.67.28):

First we need some to create simple UEFI headers (efi.inc):

And here is the assembly code itself (hello.asm):

Compile and link it with fasm.exe hello_world.asm .

That’s all for now, hope you enjoyed yourselves.

Writing Hello World Bootloader

Introduction

Bootloader is a piece of code that is executed once the system is booted. It is 512 byte long code which resides on 1st sector of booting device (floppy, hard drive etc). I recommend to read the tutorial The Booting Process before proceeding this tutorial. We will make a small bootloader that will print Hello World on screen. We will use a floppy for booting (I know you dont want to crash your harddrive for this). We will use normal 3.5 inch floppy for this.

Getting Tools

For making a bootloader we will need some tools which will be used to compile and write the boot loader.

The assembler used here is NASM (Netwide Assembler). NASM is an 80×86 assembler designed for portability and modularity. It supports a range of object file formats, including Linux and NetBSD/FreeBSD a.out, ELF, COFF, Microsoft 16-bit OBJ and Win32. It will also output plain binary files. Its syntax is designed to be simple and easy to understand, similar to Intel’s but less complex. It supports Pentium, P6, MMX, 3DNow!, SSE and SSE2 opcodes, and has macro capability.

  • Nasm 0.98.35 for Windows(ZIP, 218KB)
  • Nasm 0.98.35 for i386 Linux(RPM, 133KB)

Partcopy

Windows user can use Partcopy to write bootsector on floppy/hdd disks.

  • Partcopy (ZIP, 10KB)
Илон Маск рекомендует:  Песнь о cgi скриптах

3.5 inch Floppy Disk

You dont want to damage your harddisk so it is recommended to use floppy for this work. Please use a floppy which does not have any important data (because you will be going to damage that one). So use a fresh formated floppy disk.

Creating Bootloader


Now its time to start coding our bootloader. For this you will require some prior knowledge of Assembly. I assume that you know assembly level language.

First try: Hanging Bootloader

We will start with a bootloder that will do nothing. Neither it will print anything. Our system will be just booted from this and it will be hanged. Open your favourite text editor and write following code and save the file as ourbootloader.asm.

[BITS 16] : Our code starts with [BITS 16] which is an assembler directive. This will tell assembler that our code is a 16 bit code.

[ORG 0x7C00] : Then we have used [ORG 0x7C00] which tell assembler to assemble the instructions from Origin 0x7C00. BIOS loads bootloader at physical address 0x7C00 hence we have assemble our bootloader starting from that location.

JMP $ : JMP at location $ means jumping to the same location. Thus this nothing but an infinite loop. We just want to hang our code here.

TIMES 510 — ($ — $$) db 0 : As bootloader is always of length 512 bytes, our code does not fit in this size as its small. We need to use rest of memory and hence we clear it out using TIMES directive. $ stands for start of instruction and $$ stands for start of program. Thus ($ — $$) means length of our code.

DW 0xAA55 : This is boot signature. This tells the BIOS that this is a valid bootloader. If bios does not get 0x55 and 0xAA at the end of the bootloader than it will treat bootloader as invalid. Thus we provide this two bytes at the end of our bootloader.

Compiling / Assembling

Its time to compile our bootloader using NASM. For that you need to type:

This will create a file boot.bin. Now check the size of this file. It must be 512 bytes. If its not that then there must be some problem. Our bootloader may not be compiled properly. Check the code once again.

Copying bootsector to floppy disk

Windows user can use PARTCOPY for writing their bootloader to floppy disk. For this you need to write command:

Linux users can use dd command for this. For this Insert a floppy but dont mount it and then write following command:

Congratulation. You have just made your bootloader. Now you are ready to boot your system using this floppy.

So insert this floppy in floppy drive and boot your system. Dont forget to select floppy as your 1st boot device from BIOS. Once booted with this floppy your system will not do anything but it will get hanged.

Second try: Print a character Bootloader

We have successfully created our hanging bootloader. Now we will add some code in this bootloader to print a character ‘A’ on screen. For printing we will use BIOS video interrupt int 0x10.

So its time to write code for a bootloader that will print ‘A’ on screen and then it will be hanged. The code for this is:

The code is pretty self explainatory. Here we have made a procedure PrintCharater which will print a character on screen. It assumes that we provide the ASCII value in AL register.

Compile this code as mention above and write the bootloader on floppy disk. Now boot your system using this floppy and voila!! There is an ‘A’ on the screen.

Third try: Hello World Bootloader

Its time to create our final Hello World bootloader. We have enough expirence now and can code it without wasting a second. So once again start your favourite text editor and start writing following code:

Now once again compile the bootloader and write it into floppy disk and boot the system using it. You will be once again surprised to see ‘Hello World’ on screen.

Disclaimer

Author of this article is not responsible for anything that happens due to it. Please read all the instruction carefully and then try. Dont use your hard disk for booting your bootloader.

AterLux › Блог › UART-Бутлоадер для AVR размером 512байт

Иногда приходится делать схемки на МК, которые ведут обмен с компьютером посредством универсального последовательного порта (UART). Но кроме передачи разных настроечных параметров, было бы полезно этот самый порт использовать для обновления прошивки. Во-первых не нужен будет ни разъём ISP, ни программатор для внесения мелких исправлений, во-вторых сам процесс значительно упрощается, можно даже доверить пользователю загружать новые прошивки.

Для этого в микроконтроллерах предусмотрен так называемый загрузчик — бутлоадер (boot loader). Это изначально зашитая маленькая программа, располагающаяся где-то в конце флеш-памяти, которая может установить связь с внешним компьютером и загрузить обновление основной прошивки по любому каналу связи.

Я уже делал экзотические бутлоадеры для обновления прошивки по радио-каналу, через nRF24L01, или через wi-fi, но схемы подключающиеся по простому UART встречаются довольно часто, да ещё пару таких схем нарисовались в голове в планах на разработку.

Поэтому я решил сделать универсальный компактный бутлоадер, который покроет большинство моих нужд.

Что значит универсальный? Это значит, что для адаптации его к очередной схеме на МК, нужно будет изменить пару констант и перекомпилировать. В его коде учитываются особенности разных МК, например, ATmega8 и ATmega88, у которых разительно отличается расположение регистров: например, регистры USART у ATmega8 находятся в нижней части пространства регистров, и доступ к ним осуществляется через in/out, в то время как у 88 и ему подобных эти регистры доступны только через их отображение в адресном пространстве, через lds/sts.

Итак, представляю результат моей разработки: ассемблерный код бутлоадера, который компилируется в не более чем 512 байт, работает почти на всех МК из семейства ATmega от 8 до 32кб (у МК с менее 8кб нет бутлоадера, а более 32кб — бутлоадер минимум 1кб). Главное, что работает с моим любимым семейством: ATmega88PA, 168PA, 328P

При сбросе или старте микроконтроллера, сначала запускается программа бутлоадера.
Бутлоадер проверяет наличие условий на принудительный запуск программы загрузки. Таких условий три:
1) если управление на программу бутлоадера было передано из основной программы;
2) если загрузка прошивки не была завершена должным образом;
3) если при запуске выполняется некоторое внешнее условие (определённый логический уровень на одной из ножек, или перемычка между парой ножек).


Чтобы узнать, откуда было передано управление, бутлоадер смотрит на содержимое регистра MCUCSR (или MCUSR в некоторых МК). В этом регистре хранится информация о том, что привело микроконтроллер к перезагрузке: низкое напряжение, сторожевой таймер, сброс через ножку RESET, включение питания, или JTAG. Если содержимое этого регистра равно нулю, значит переход был осуществлён из основной программы, т.е. программа либо сознательно вызвала бутлоадер, либо она поломана и адрес исполнения сам докатился до бутлоадера. В любом случае, нулевое значение — это сигнал, чтобы не переходить к основной программе, а включить режим загрузки и ожидать новой прошивки по UART.

Если же оказалось, что содержимое этого регистра было не нулевым, то его содержимое обнуляется. На случай, если основной программе зачем-то нужно будет изначальное значение этого регистра, оно сохраняется в регистре GPIOR0, а на МК где этого регистра нет — в первой ячейке SRAM памяти (как правило, это ячейка с адресом 0x0100)

Одна из ячеек (обычно — самая последняя) энергонезависимой памяти (EEPROM) отводится для хранения маркера об успешном окончании процесса прошивки. Когда начинается процесс прошивки, значение этой ячейки сбрасывается — в неё записывается 0xFF. Когда процесс прошивки завершён, то в неё записывается определённое значение. В зависимости от настроек, это делает либо сам бутлоадер, либо основная программа самостоятельно. При старте бутлоадер считывает значение этой ячейки, и если оно не равно заданному значению, то включается программа загрузки прошивки. Этот контроль можно отключить.
Возможность установки этого значения самой программой может быть полезна, чтобы обеспечить дополнительный контроль. Во-первых программа может определить что она была обновлена и выполнить некоторые действия (например, изменить сохранённые параметры должным образом), во-вторых запись значения в эту ячейку программа может осуществить после специальной команды от компьютера, тем самым позволяя лишний раз убедиться, что загруженная прошивка правильно работает и принимает команды с UART-порта, а значит позволит вызвать бутлоадер штатным образом.

И, наконец, третий контроль — проверка логических уровней на входе. Позволяет принудительно запустить бутлоадер после включения питания или сброса, путём удерживания специальной кнопки, или установки перемычки. Этот контроль может быть отключен, либо работать в одном из четырёх режимов:
1) низкий уровень на входе с включением подтягивающего резистора. На указанном входе включается подтягивающий резистор, и проверяется, что логический уровень остаётся низким в течение некоторого времени. Эта опция для типичного подключения кнопки.
2) высокий уровень — аналогично, но проверяется высокий уровень и подтягивающий резистор не включается. Должен существовать внешний стягивающий резистор.
3) Перемычка между двумя входами. Поочерёдно на двух входах включаются либо подтягивающие резисторы, либо выводится низкий уровень и замеряются логические уровни. Если изменение уровней свидетельствует о наличие перемычки между ними, то запускается загрузка прошивки. Эта опция полезна, если особенности подключения входов изначально не удаётся определить.
4) Низкий уровень на двух входах. Аналогично режиму 1, на обоих входах включаются подтягивающие резисторы, низкий уровень должен определятся на обоих входах в течение нескольких замеров подряд.

На деле, все эти проверки проходят практически мгновенно и не задерживают переход к основной прошивке.

Кроме того, есть возможность включить индикацию работы миганиями светодиодов.

Если какой-то контроль стрельнул, и переход к основному ПО не произошёл, то бутлоадер включает UART и ждёт команды.

Команд всего три:
BLST — вход в режим программирования, эта команда может быть выполнена в любой момент и начинает процесс загрузки сначала.
BLPG — запись страницы. Эта команда не может быть выполнена, если не выполнялась BLST.
BLXT — завершение процесса прошивки (если он был начат) и переход к основному ПО.

Запрос: 0x42 0x4C 0x53 0x54 (BLST)
Ответ: 0x62 0x6C 0x73 0x74 (blst)
После 4х символов blst в ответе следуют 16 байт, первый из которых указывает размер страницы в словах (т.е. парах байт), второй — количество страниц, доступных для прошивки (т.е. размер флеш-памяти в страницах, не считая самого загрузчика), а последующие 14 байт — это произвольная строка, идентифицирующая схему, которая может использоваться для предотвращения попыток загрузить неподходящую прошивку.

Запрос: 0x42 0x4C 0x50 0x47 (BLPG)
Ответ: 0x62 0x6C 0x70 0x77 (blpg) — если страница успешно загружена;
либо: 0x62 0x6C 0x65 0x72 (bler) — если не была вызвана BLST, неправильный номер страницы, или не совпал контроль.
Команде загрузки страницы должен предшествовать вызов BLST
— номера страниц должны идти по порядку и начинаться с нуля
— данные страницы. Количество данных точно равно размеру страницы в байтах (ответ blst возвращает её в словах, т.е. нужно умножить на 2)
— это CRC с полиномом x^8 +x^7 +x^6 +x^3 +x^2 +x +1 (полином выбран из списка users.ece.cmu.edu/

koopman/crc/crc8.html) по всем байтам в поле data, от старших битов к младшим. Инициализируется значением 0xFF (способ расчёта контрольной суммы изменён 24.09.2020):
cc = 0xFF;
for (i = 0; i

Все необходимые настройки задаются в inc-файле (по умолчанию default_definitions.inc)

HARDWARE_ID — это просто строка из ровно 14 символов, которая возвращается на запрос BLST и позволяет идентифицировать схему. Рекомендую составлять её из имени автора и названия схемы, например: «VPupkinMigalka» (убедитесь, что символов ровно 14)

F_CPU — частота МК. Здесь должна быть указана действительная частота, т.к. через это значение вычисляются настройки скорости для модуля USART.

UART_SPEED — скорость обмена по UART, например 9600. Следует помнить, что далеко не все скорости точно могут быть выдержаны модулем USART, действительная скорость будет несколько отличаться. В коде настроены макросы, которые проверяют различие действительной и заданной скорости, выдадут предупреждения если разница более 2,1%, 3%, и прервут компиляцию, если разница более 4,5%.

UART_2X — включние опции 2X модуля USART
Подробнее об опции 2X и скоростях UART читайте в документации на МК.

UART_PARITY — режим контроля чётности: 0 — бит чётности отсутствует; 1 — нечет (odd), 2 — чёт (even)

UART_STOP_BITS — количество стоповых бит передатчика: 1, или 2

BLINKER — включение мигания светодиода, значения 1 (включено), или 0 (отключено).
Если опция включена то необходимо определить BLINKER_DDR, BLINKER_PORT — регистры для доступа к порту, и BLINKER_PIN_NUM — номер вывода, на котором расположен светодиод.

BLINKER_PATTERN_ … — шаблоны для мигания светодиода. Для мигания используется некий циклический счётчик, скорость изменения которого пропорциональна скорости МК, и для 8МГц составляет примерно 16 изменений в секунду. Значение счётчика накладывается операцией битового И на значение указанного шаблона, и если результат равен нулю — светодиод зажигается (высокий уровень на выходе), иначе — тушится (низкий уровень). Таким образом, в зависимости от значения шаблона, можно задать быстрые мигания, медленные мигания, короткие вспышки с большим интервалом между ними, постоянно включенный светодиод, серии по две, по четыре вспышки и т.д.
По умолчанию запуск загрузчика обозначается медленными миганиями, готовность к загрузке страницы (т.е. после выполнения BLST) — сериями по две вспышки, и процесс загрузки страниц прошивки — быстрыми короткими вспышками.

MAGIC — значение, сохраняющееся в EEPROM и указывающее на успешную прошивку. Не может равняться 0xFF. Если этот контроль не нужен, закомментируйте строку.

FORCE_METHOD — способ контроля по уровню на входе. 0 — отключен, 1 — бутлоадер по низкому уровню, 2 — по высокому, 3 — по перемычке, 4 — по низкому уровню одновременно на двух входах.
в FORCE_PORT, FORCE_DDR, FORCE_PIN нужно указать регистры для доступа к соответствующему порту и FORCE_PIN_NUM — номер вывода на этом порту.
Если выбран метод 3 или 4, то аналогично для второго вывода FORCE_PORT2, FORCE_DDR2, FORCE_PIN2, FORCE_PIN_NUM2.

После настроек код можно скомпилировать нажатием F7.

Перед загрузкой кода необходимо настроить должным образом фьюзы.
1) Фьюзы BOOTSZ1: BOOTSZ0 должны быть выбраны таким образом, чтобы размер загрузчика равнялся 256 словам (words), обычно для МК с 8кб флеш их значения 10, для МК с 16 и 32 кб — 11. Сверьтесь с документацией на МК.
2) Необходимо запрограммировать (установить значение 0) фьюз BOOTRST, в результате чего после сброса будет запускаться не основная программа по адресу 0x0000, а загрузчик по выбранному адресу.
3) Остальные фьюзы (тактирование, BOD, CKDIV8 и т.д.) — сконфигурируйте под нужды своего проекта.

После того, как фьюзы прошиты, остаётся загрузить бутлоадер, подключить схему к компьютеру и проверять что получилось.
Для загрузки прошивок через бутлоадер прилагаю программку.

Чтобы избегать путаницы, рекомендую имена файлов начинать со значения HARDWARE_ , то имена файлов с прошивкой делать вида: VPupkinMigalka_v1.0_beta.hex

Как вызвать бутлоадер из программы

Допустим, программа во время работы приняла команду на обновление прошивки, ей нужно передать управление бутлоадеру. Для этого нужно просто перейти на адрес бутлоадера, то есть РАЗМЕР_ФЛЕШ — РАЗМЕР_БУТЛОАДЕРА. Т.к. бутлоадер у нас 512 байт, то из Си переход может выглядеть вот таким образом:

Формально, это не переход, а вызов процедуры без параметров, но разницы в результате нет.

Через avr/io.h определяется FLASHEND и указывает адрес последнего БАЙТА флеш-памяти. Для вызовов адреса должны быть в словах, поэтому делим на два.

С этим нужно быть внимательным, потому как в ассемблерных проектах FLASHEND определяется в СЛОВАХ и адреса переходов также задаются в словах. Т.е. вызов бутлоадера будет выглядеть как

rjmp FLASHEND — 255 // для устройств с 8кб флеша, у которых нет команды jmp
или
jmp FLASHEND — 255 // для устройств с 16 и 32кб флеша

Перед переходом в бутлоадер необходимо не забыть завершить работу всяких периферийных устройств и подготовить соответствующим образом внешнее оборудование, а также остановить сторожевой таймер, если он запущен.

Изменения от 24.09.2020


1) Теперь все настроечные определения хранятся в отдельном файле. Так удобнее использовать загрузчик для разных проектов.
2) Добавлен новый метод 4 проверки для принудительного запуска: две кнопки (низкий уровень на двух входах при включенных подтягивающих резисторах)
3) Исправлена некритичная ошибка в коде
4) Изменён способ расчёта контрольной суммы!. Теперь вместо простого исключающего или идёт расчёт полноценного 8-битного CRC. Это снизит вероятность ошибок при загрузке страницы. Но теперь, если включен метод 3 контроля запуска, включена запись маркера и мигание светодиодом, то на МК с большим количеством GPIO (т.е. у тех, у которых есть PORTE PORTF PORTG) код загрузчика может не поместиться в 512 байт. Если такая ситуация возникает, то загрузчик не будет обнулять все регистры PORTn и DDRn, а только те, которые используются самим загрузчиком.

Модуль boot asm

Приветствую всех своих читателей!

В этом выпуске мы приступим к написанию не очень сложной, но достаточно важной части любой операционной системы — начального загрузчика. Именно эта часть обычно присутствует абсолютно во всех рассылках, но её не стоит пропускать, потому что без загрузчика мы не сможем производить какие-либо действия. Можно очень много рассуждать о структуре ядра ОС, однако без загрузчика мы никак не сможем проверить свои идеи на практике. Конечно, существуют универсальные загрузчики вроде GRUB, но хотелось бы рассмотреть аспект написания системы наиболее полно. Тем, кому интересует исключительно программирование самого ядра, придётся подождать пока мы научимся его запускать :-)

Теория

Итак, начнём с теоретической части. Весь обмен данными с носителями информации вроде дискет, жёстких дисков и флешек осуществляется только блоками фиксированными размера — секторами. Как в памяти компьютера нельзя непосредственно обратиться к единице меньшей, чем байт, так на диске все операции чтения и записи выполняются посекторно. Самый часто используемый размер сектора — 512 байт, хотя есть и немногочисленные исключения — на CD-дисках, например размер сектора 2 килобайта. Но сейчас мы не рассматриваем загрузку с последних, так что можно считать, что размер сектора ровно 512 байт, не больше и не меньше.

После включения компьютера управление получает BIOS — Basic Input-Output System. Он проводит первичное тестирование оборудования, предоставляет интерфейс для настройки некоторых компонентов (например, часов) и наконец загружает 0-ой сектор загрузочного диска (определяется в настройках), передавая ему управление. Поскольку 512 байт слишком мало, чтобы там можно было разместить полноценный драйвер для работы с диском, BIOS предоставляет все необходимые функции для работы с экраном, клавиатурой и мышью. Их более чем достаточно для любого начального загрузчика.

Илон Маск рекомендует:  Что такое код filepro_retrieve

В последнее время внедряется альтернативная система инициализации — EFI (Extensible Firmware Interface). Возможно, когда-нибудь мы рассмотрим и её, но не раньше, чем напишем ядро ОС. С одной стороны эта система отбрасывает некоторые устаревшие технологии, но с другой стороны требует больших теоретических знаний для начала работы. К тому же подавляющее большинство версий EFI на сегодняшний день поддерживают режим эмуляции BIOS и наша система сможет работать и с ними. Да и пока достаточно мало виртуальных машин поддерживают загрузку EFI-совместимых систем.

Практика

Приступим к написанию начального загрузчика (boot loader) для нашей системы. Поскольку мне отнюдь не симпатизирует идея написания жёсткого монолита (скорее я предпочитаю микроядра), в задачу загрузчика будет входить не только загрузка ядра, но и модулей, необходимых для дальнейшей инициализации (например, драйвер жёсткого диска), которые содержатся в отдельных файлах.

Пока для простоты будем рассматривать загрузку с диска без таблицы разделов. Скажем, дискеты, хотя теоретически ничто не мешает реализовать такую же структуру и на других носителях (хотя это и не совсем правильно).

Как я уже сказал выше, BIOS загружает в оперативную память первые 512 байт диска и передаёт им управление. Наш код оказывается в реальном режиме работы процессора (те, кто не знают что это, идут читать какой-нибудь учебник по Assembler) по адресу 0000:7C00. Прерывания запрещены, в регистре DL находится номер загрузочного диска (например, 0 и 1 для дискет, начиная с 0x80 идут все прочие виды дисков). Некоторые сегментные регистры указывают на область данных BIOS, другие регистры также могут содержать дополнительную информацию, но на это лучше не рассчитывать, потому что многое зависит от деталей реализации конкретного BIOS. Также стоит отметить, что последние два байта начального загрузчика должны содержать сигнатуру 0x55,0xAA, иначе многие BIOS посчитают такой загрузчик некорректным и откажутся запускать.

Начнём с заготовки загрузчика, который способен просто запуститься, настроить всё окружение, вывести пару строчек, подождать нажатия на клавиатуру и перезагрузить машину при любом нажатии. На этом пока и остановимся, иначе этот выпуск рассылки получится слишком длинным. Загрузчик будет выполнять некоторые ненужные сейчас действия (например, запоминание номера диска, с которого мы стартовали), но они нам пригодятся, когда в следующей части мы напишем его полноценную (способную что-то загрузить) версию.

Опишу некоторые аспекты работы этого кода.

Обычно, после первых 3-4 байт загрузчика размещает заголовок некоторых файловых систем (например, FAT), поэтому первым делом мы обходим все данные и прыгаем на истинную точку входа начального загрузчика. Специальное слово word говорит flat assembler не пытаться оптимизировать размер перехода и в любом случае положить адрес перехода в 2 байта, даже если можно в 1. Таким образом наш jmp должен гарантированно занять 3 байта.

Также в результате этого действия у нас первые 3 байта загрузчика содержат ненужные для дальнейшей работы данные, поэтому мы можем сэкономить 3 байта (512 это очень мало и любую возможность оптимизировать размер без потери функционала не следует упускать), разместив там какие-нибудь неинициализированные переменные. Пока она одна: номер загрузочного диска — байтовая переменная disk_id.

Также в области данных я разместил две строки, которые будут использоваться в нашем загрузчике — приветственное сообщение с его названием и версией и предложение нажать любую клавишу для перезагрузки. Вы можете легко изменить выводимый текст, но помните, что у нас в распоряжении нет русских букв.

Первая из описанных функций предназначена для вывода текстовых сообщений на экран. Она принимает единственный параметр в паре регистров DS:SI. Строка должна оканчиваться нуль-символом (использование null-terminated строк сейчас является стандартом в подавляющем большинстве операционных систем и языков программирования, и это вполне оправданно — символ с кодом 0 не нужен для простых текстовых данных). Вывод осуществляется с помощью сервиса BIOS.

BIOS предоставляет достаточно много функций, все они вызываются с помощью программных прерываний, для осуществления которых служит ассемблерная инструкция int. Она принимает в качестве аргумента номер прерывания. За каждым прерыванием закреплён адрес функции обработки. Она принимает параметры в регистрах процессора, выполняет действие и возвращает управление нашему коду. В данном случае нас интересует сервис с кодом 0x10. Это прерывание служит для управления экраном. Номер нужной функции прерывания передаётся в регистре AH. Функция write_str использует функцию с кодом 0x0E, которая просто выводит символ из AL на экран со сдвигом курсора (воспринимаются также различные управляющие коды вроде последовательности 13,10 — перевод строки и возврат каретки). Наш код сохраняет оба модифицируемых во время работы регистра — AX и SI, поэтому его можно вызывать из любых мест кода не беспокоясь о последствиях.

Вторая функция — error. Она не используется в текущем коде, не пригодится в будущем. Это один из примеров оптимизации размера. Как известно, команда call помещает в стек адрес возврата, то есть адрес, следующий за этой инструкцией. Но если возврат из функции не предполагается, то там могут находиться произвольные данные, к которым можно получить доступ вытолкнув адрес из стека. В этом случае мы экономим на команде запихивания в стек этого адреса, потому что этим занимается процессор. Функция error нужна для обработки критических ошибок загрузки (а других на этом этапе и не бывает :-) ), например ошибки чтения диска. Использовать эту функцию следует так:

В итоге будет выведено сначала сообщение об ошибке, а потом предложение нажать любую клавишу для перезагрузки. За последнее действие отвечает функция reboot. Она выводит сообщение «Press any key. «, а затем ждёт нажатия на любую клавишу (этим занимается нулевая функция прерывания BIOS 0x16). После того, как нажата клавиша, происходит прыжок на точку входа в BIOS — FFFF:0000. Возможно, было бы корректнее вызывать прерывание 0x18, но на многих машинах оно лишь приводит к попытке загрузки со следующего устройства, я же хочу эффект аналогичный нажатию Reset.

Основной код располагается за меткой boot. Первым делом он обнуляет все сегментые регистры, чтобы правильно работала адресация данных. Затем он настраивает стек и разрешает прерывания. Стеком будет считаться область от данных BIOS (0000:0400) до нашего кода. Это более 30 килобайт, что вполне достаточно.

Далее с помощью write_str выводится название загрузчика и наконец работа завершается переходом на reboot (нам больше нечего делать — мы пока ничего не умеем загружать).

Компиляция и запуск

Для компиляции достаточно просто выполнить команду:

В итоге должен получиться файл boot.bin размером 512 байт. Некоторые эмуляторы (например, Bochs) позволяют подключать неполные образы дискет. Просто отсутствующие сектора не будут загружаться.

Затем можно запускать всё это в Bochs и, если не было ошибок, вы увидите две строки.

Конфиг для Bochs:

Могу вас поздравить: вы написали свой первый начальный загрузчик!

Заключение

В этом выпуске мы рассмотрели основные теоретические аспекты начальной загрузки компьютера, а также написали простейший загрузчик. В следующем выпуске я расскажу про файловые системы и мы научим наш загрузчик действительно что-нибудь загружать.

GNU assembly boot loader won’t boot

I’ve been going through the following tutorial:


Unfortunately, it uses NASM instead of the GNU Assembler and because I typically work with the GNU toolchain I rather use that instead. The tutorial has the following trivial hello world program:

which provides the multiboot header as well as the following:

These are linked together with the the following linker.ld file:

Compiling and running all of this can be done by:

Check the tutorial how to make an iso image with the kernel and how to boot is using qemu. Now I’ve ported the source code as follows to use the GNU Assembler:

I’ve compiled and linked these together by:

objdump shows that the kernel.bin that is output by both cases is identical with respect to code in each segment. The only difference is that offsets where the segments are located are slightly different. The main difference is though that the nasm version boots while the GNU assembler version does not.

The GNU assembler version gives me error: no multiboot header found.

Here’s the objdump output from the two created binaries:

Here’s the version created by using the GNU assembler:

Now that I’m looking at it, it looks like the GNU Assembler version has an overlapping .boot and .text segment, so grub while loading the file probably overwrites the .boot section with the data of the .text section. Anyone have an idea how to fix this?

dev64

Programming

Загрузка операционной системы Hello world

Галерея

Оглавление

Что же такое “загрузка Операционной системы”? Как уже упоминалось выше, производители компьютеров поставляют компьютер с предустановленной программой, называемой BIOS. Процессор компьютера, при включении питания или нажатии на кнопку Reset, начинает выполнять программу из BIOS автоматически. Сам по себе BIOS мало полезен пользователю, он позволяет лишь посмотреть список устройств подключенных к компьютеру и произвести некоторые настройки. Более того. Обычному пользователю компьютера, как правило, не приходится в эти настройки “лазить” и что-либо в них менять. Чтобы компьютер превратился во что-то полезное, нужно, чтобы процессор начал выполнять какую-то более полезную программу, чем BIOS. Эта программа и есть Операционная система. BIOS умеет стартовать из себя только одну программу — Операционную систему. Операционная система, в свою очередь, предоставляет пользователю возможность запуска множества пользовательских программ. Процесс передачи управления от BIOS к Операционной системе и есть — загрузка Операционной системы. Главная сложность этой передачи управления в том, что с точки зрения процессора при загрузке Операционной системы ничего особенного не происходит. Процессор просто продолжает выполнять набор инструкций, пока его не выключат или не встретится специальная инструкция HLT.

Компьютеры и BIOS-ы создают одни компании, а Операционные системы пишут другие компании. Поэтому, чтобы процессор перешел от выполнения инструкций BIOS к операционной системе, предусмотрена не простая передача управления, а целая процедура (программа). Эту процедуру пишут разработчики Операционной системы, а не производители компьютера. Эта процедура называется Boot Loader (Загрузчик), хранящийся на носителе, с которого загружается Операционная система. Наиболее известными из до сих пор поддерживаемых носителей, с которых может загружаться Операционная система, являются Floppy Disk-и (дискеты). Сегодня, правда, Floppy Disk-и вытеснены новыми устройствами, типа CD/DVD приводов или USB Drive-ами. При экспериментах удобно использовать не настоящий компьютер, а виртуальную машину. Виртуальная машина вместо настоящих дискет использует обычные файлы, в которых хранится содержимое загрузочной дискеты, поэтому несмотря на то, что дискеты устарели, с ними (виртуальными), по прежнему удобно экспериментировать. На картинке настройки моей виртуальной машины. Видно что в качестве floppy-диска подключен файл “D:\VM\hw.flp”.

Первый эксперимент

Хороший начальный эксперимент описан в статье: http://habrahabr.ru/tag/Пишем свою ОС. Эксперимент заключается в подмене Boot Loader простой программой, которая выведет на экран строчку “Hello World!”.

Программа непосредственно из статьи:

Для выполнения эксперимента нужен ассемблер: nasm или yasm. Оба доступны для свободного скачивания в интернете. Также проще экспериментировать с виртуальной машиной. Для этого подойдет VMWare или Bochs. Вochs — Open Source — проект. Преимущество Bochs — маленький размер и возможность отладки вашей операционной системы начиная с момента загрузки. Всего несколько мегабайт. VMWare — коммерческий продукт, но для разнообразных экспериментов предоставляет для свободного использования VMWare Player. (Возможно еще какие-то продукты, политика неоднократно менялась). Преимущество VMWare — скорость. Работает очень быстро.

Я поместил этот исходный код в файл hw.asm (hello world), и скомпилировал:

Полученный файл “hw” скопировал по пути D:\VM\hw.flp, сконфигурированному в качестве виртуального флоппи диска в VMWare. Стартовал виртуальную машину и действительно получил желаемое сообщение “Hello World!”:

Загрузка процессора моего компьютера, на котором работает VMWare оказалась 50%:

Стоит отметить, что на ноутбуке, на котором я тестируюсь, установлен двухядерный процессор Intel. 50% загрузка означает 100%-ю загрузку одного из ядер. Случайно ли это?

Код примера заканчивается бесконечным циклом
jmp $ ; вечный цикл

Этот цикл и грузит одно из ядер процессора на 100%. Как этого избежать? Вспомним утверждение “Процессор просто выполняет последовательность инструкций, пока его не выключат или не встретится специальная инструкция HLT”.

Проверю это утверждение. Заменю вечный цикл на 2 инструкции:

cli ; запрещаю аппаратные прерывания
hlt ; останавливаю процессор до возникновения аппаратного прерывания

hlt приводит к остановке выполнения команд процессором до ближайшего аппаратного прерывания. Но, так как аппаратные прерывания запрещены предыдущей командой, то процессор просто прекращает выполнение программы. Останавливается. Тест показывает, что полученная на экране картинка всё та же: “Hello World!”. Зато загрузка процессора 0%.

Скомпилированный hw.flp выглядит примерно так (я использую для просмотра far).

В 512-байтном пространстве Boot Loader-a остается ещё много места для размещения настоящего Boot Loader-a, который загрузит “настоящую” Операционную систему.

Некоторые шаги экспериментов (например контроль загрузки процессора) легко сделать только в условиях виртуальной машины. На настоящей машине, если загрузчик подменен на вышеописанную программу “Hello World!”, загрузку процессора можно оценить только по громкости вентилятора процессора. (Поэтому сложно проверить остановку процессора инструкциями cli; hlt).


Что дальше?

Для начала подведем итоги. Что известно про Boot Loader:
Известно, что программа эта загружается по адресу 0x7c00
Известно, что размер загрузчика 0x200 (512) байт
Известно, что процессор Intel работает в реальном режиме
Известно, что загрузчику доступны для использования функции BIOS
К функциям BIOS можно обратиться с помощью присвоения значений определенным регистрам и последующим вызовом прерываний.

Например, в приведенном примере вывод на экран осуществляется с помощью прерывания int 0x10. Последовательность команд выведет символ ‘H’ в текущую позицию курсора.

В дополнение к вышеприведенным утверждениям, я думаю (на текущий момент), что функции BIOS доступны только из реального режима процессора. Соответственно, сначала загрузчик должен загрузить ядро, пользуясь функциями BIOS, потом передать управление на загруженное ядро. Пока не вижу ограничений на точку входа. И ещё один интересный момент — куда указывает стек, в момент загрузки? Каков его размер.

Изучение исходников разных Boot Loader-ов показало, что неизвестно куда указывает стек в момент перехода на адрес 0x7c0:0. Поэтому загрузчик в своем коде устанавливает регистр стека как удобно. (Например сразу перед загрузчиком).

В последствии, я провел эксперимент по определению состояния регистров процессора, в момент старта Boot Loader-a. См. статью по этой ссылке.. Оказалось, что в VMWare, например, при загрузке с дискеты стек указывает 0:3EC, что соответствует верхней части таблицы прерываний. ax=0x7C03 ds=0x40 sp=0:3EC, остальные регистры равны 0. В регистре dl BIOS передает идентификатор диска с которого загружался загрузочный сектор. Для диска A: (дискеты) это значение 0.

U-Boot и uClinux

U-Boot и uClinux.

U-Boot и uClinux.

U-Boot.

U-Boot — универсальный загрузчик ориентированный на встраивание систем на базе архитектур ARM, NIOS II, MIPS и других. Может быть установлен в загрузочную ПЗУ и от туда запущен, является загрузчиком второго уровня (в основном). U-boot ориентирован на запуск Linux, для микроконтроллеров uClinux(может быть использован для загрузки обычных прошивок).

Статья посвящена U-Boot и uClinux. Основной тип операционных систем используемых для микроконтроллеров остается RTOS. При наличии большой бизнес логики используют Linux. Для обеспечения реального времени применяют AMP, RTLinux и др. U-Boot (Universal bootloader) — универсальный загрузчик.

Запуск U-boot на одной из поддерживаемых им плат обычно не требует редактирования исходного кода. Зная, что файл настроек include/configs/»Платформа.h» уже составлен, необходимо выбрать нужную конфигурацию, скомпилировать и записать получившийся образ на загрузочный носитель (NAND, NOR т.д.).

Что касается микроконтроллеров, необходимо значительный объем памяти на микроконтроллере, или поддержка нативной адресации ПЗУ (ROM) со стороны внешней памяти микроконтроллера, в случае отсутствии последнего, bin файл загружается из внешней памяти (NOR, SD и др.) в RAM с нативной адресацией.

Важность Das U-Boot в Embedded Linux системах достаточно лаконично изложена в книге Embedded Linux системы, Карим Ягмур. Текст о U-Boot начинается словами: «Хотя существует довольно много других загрузчиков, Das U-Boot, универсальный загрузчик, возможно, является самым богатым, самым гибким и наиболее активно развивающимся из загрузчиков с открытым исходным кодом». U-boot может использовать следующие файловые системы:

Для работы с U-Boot и uClinux для микроконтроллеров понадобиться репозиторий с github u-boot.

Все эксперименты будут происходит на базе данного репозитория, в качестве отладочной платы используется stm32f746g-disco и операционная система ubuntu 14.04 (можно использовать иные дистрибутивы Linux).

Для сборки u-boot нам понадобиться GNU Cross-Build Tools (возможно вам придется еще установить ia32-libs).

Модуль boot asm

Всем привет. Как бы это не звучало смешно, НО стоит призадуматся, а почему бы нет ? Я сам лично тоже изучаю, поэтому абсолютно на все вопросы дать ответы не смогу но буду стараться. Излагать буду не грамоздко, а как возможно компактнее.

Причины которые сподвигли меня на это:
1. Разобраться как все устроено в ПК.
2. Довести до ума мои знания Asm и C.
3. Существует ряд статей в сети на эту тему, но все они не дают полного понимания того, что просиходит в коде.
4. Просто это интересно

По сути последнии и самые популярные ОС, всем известные Windows и Linux основаны на идеях еще 20го века
И так главная идея всей этой задумки, это просто: «Для изучения и понимания». А в будущем будет видно что из этого выйдет. Начало будет обычным, а дальше будем думать уже вместе.

1. Софт
Для разработки нашей ОС понадобится ( выбран софт, который я использую, можете выбрать какой вам угоден ):
1. Виртуальная машина для теста Bochs
2. Ассемблер Nasm
3. Для создания кода на языке С понадобится MinGW
4. Так же информация о Asm, C, Железе, Прерываниях BIOS HelpPC

2. Теория
Так мы начинающие в этом деле то будем начинать с самого малого. Этапы запуска нашей ОС это
1. Загрузка ( в нашем случае будем грузиться с флоппика, в будущем будет видно )
2. Проверка и инициализация оборудования
3. Загрузка всех необходимых данных для нашей ОС и запуск «диалога» с пользователем.

3. Загрузчик
И так забываем о файловых системах, и вообще о понятии «файл». Флоппик ( дискета ) делится на сектора. Каждый сектор это 512 байт. При запуске ПК BIOS инициализирует все оборудование, проверяет его на ошибки. Дальше читает дискету, если она будет, то читаются первые 512 байт ( т.е. первый сектор ) по адресу 0000:07c0 и иму передается управление. Именно эти 512 байт и будут «первичным» загрузчиком. Почему первичным т.к. именно в этих 512 байт мы должны будем вместить код для передачи управления коду который будет распалогаться далее на дискете после первого сектора. Т.к. мы только загружаемся у нас есть BIOS и не более, поэтому начнем с Nasm.

UEFI Programming — First Steps

vid , 2009-03-31 Revision: 1.0

In this article, I will describe steps needed to start on with development of real UEFI applications on x86 PC, and share some practical experiences with problems doing so. I will focus on 64-bit version of UEFI, because the 32-bit version isn’t much used in this area (most likely due to Microsoft decision not to support UEFI in 32-bit Vista). So, to follow some of my steps here, you’ll need a 64-bit CPU (but not 64-bit OS, you can use any 32-bit OS as well). We will finish this article with EFI Hello World application.

Илон Маск рекомендует:  Видеоадаптер как выйти за предел 256 кбайт

This article is continuation of my previous article Introduction to UEFI. Make sure to understand things described there, before reading on.

Of course, anything you try according to this article, you are doing at your own risk.

Getting the hardware

To start UEFI development, first of all you need to get a motherboard whose BIOS has UEFI support. (more precisely we should probably say «whose firmware has UEFI support», but I will use this form). Finding whether particular BIOS has UEFI support often turns out to be quite complicated task. Motherboard manufacturers license BIOS from other companies, usually from AMI (Aptio, AMIBIOS), Phoenix (SecureCore, TrustedCore, AwardCore) or Insyde (InsydeH20). Forget about determining UEFI support just by end-user stats you see in most shops. Since UEFI support is still only in somewhat experimental state, in many cases it isn’t even listed in motherboard technical specification. In such case you are left to googling and asking on forums, where you often get only internal brand name that is often hard to match with end-user product designation.

One trick that I found out to work for Intel boards (but it may very well work for other boards as well) is to look at BIOS Update Release Notes, e. g. the document which lists changes and fixes of BIOS. If board has UEFI support, you will probably find UEFI mentioned there (and only there in case of Intel).

In short, determining UEFI support is much harder than it may seem. Some machines that have this technology are listed here. I use Intel DG33BU board (it was marketed as Intel DG33BUC for some reason).


You will also need some place to boot from. In theory just USB pen should be enough, but in practice none of 4 brands I tried worked with my board’s UEFI implementation. So you may have to use harddrive. I strongly suggest IDE drive, because SATA drives may need some tweaking of BIOS settings, or they may not work at all. Like the USB pens, USB keyboard might be a problem too. I wouldn’t fear this that much, but if you can use PS/2 keyboard, do so.

Getting the software

To go on with UEFI development, you will need two development packages: (EDK ) and .

First package, the EFI Development Kit contains the TianoCore (public part of reference UEFI implementation by Intel) source code, along with many examples and binaries of EFI Shell (we’ll talk about this later), all ready to be built with nice make system. It even can be built into a Win32 UEFI emulator for more convinient development and testing, but I won’t cover that here. I also won’t demonstrate usage of EDK build system in this article. Even though it might be a good idea for real world project, I want to give you bit more insight about what goes «under the hood» here.

Second package, the EFI Toolkit is set of extra UEFI applications like FTP client, Python port, text editor, etc. Strictly speaking, we don’t really need this, but it has a set of C headers that is somewhat easier to use than those of EDK (unless you take advantage of EDK build system for your project). However, note that it doesn’t contain all headers yet — you will quickly run into this problem if you try some real development with it.

Along with headers, you also will need documentation of the UEFI interface. That can be downloaded after filling simple form at http://www.uefi.org/specs. This is the official .

Except for UEFI specification, there is also another document called . This describes implementation of UEFI initialization stages (before drivers are loaded and applications can be executed), and more importantly for us, it also describes interface of routines used to implement UEFI. We can understand this Platform Initialization Specification as description of Tiano UEFI implementation. It provides more lowlevel control than UEFI, in cases when such control is needed. Strictly speaking, someone may implement UEFI Specification without implementing anything from Platform Initialization Specification, but that’s not very likely to happen in real world.

Last but not least, you will need 64-bit C compiler. I suggest Microsoft Visual C++, whose 64-bit version is freely available in Windows DDK. You can get it here.

Booting into EFI Shell

Up to this point, you may have lacked any visual idea about UEFI. It was just a programatic interface, after all. Purpose of this chapter would be to overcome this, by booting the so-called EFI Shell. EFI shell is much like any other shell you know: you have a command line, through which you can enter commands to browse disk, run applications, configure system, etc. Only difference is that EFI shell is only built using UEFI services, and as such it doesn’t require any operating system to run. Working in EFI shell feels much like working in some very simple operating system. From this shell you will also run your applications (later in this article).

Actual steps of booting EFI shell might vary a lot among different BIOS brands. Some BIOSes (mostly on MACs where EFI is the primary standard) have extra options for specifying file to boot, or can even have EFI shell built-in inside ROM. However, I only have experience with non-MAC Intel UEFI implementation, that only seems to support the very minimum of features required to install EFI versions of Windows. Microsoft requirements on UEFI implementation, among other things, specify that UEFI boot loader must use fixed path to file to boot, and if it can’t boot UEFI for some reason, it must silently switch to default boot. That causes lot of headache when your UEFI doesn’t boot as it should, and you must find out what the problem is without any information from loader.

For 64-bit UEFI implementations, the path to file that is booted is \EFI\BOOT\BOOTX64.EFI . UEFI boot loader searches all filesystems it can access for this file, and when it finds it, it is executed. As I already said, if the file isn’t found, booting continues with legacy BIOS. UEFI boot loader can read MBR or GPT partition tables, and can only read FAT32 partitions. This includes USB drives, so it is possible to boot EFI shell from FAT32-formatted USB pen. Unfortunately, in my tests 3 of 4 USB pens didn’t work, and the 4th stopped working too after BIOS update. I had similar problem with one of two SATA drives not working with UEFI. Therefore, I strongly suggest to use IDE drive, if you have any problems. If you already have at least one FAT32 partition on your drive, you can use it, otherwise you need to create fresh new one.

Now you need to copy binary of EFI shell to that partition. You can find 64-bit binary of EFI shell in EFI Development Kit: \Other\Maintained\Application\UefiShell\bin\x64\Shell_full.efi . Copy it to your FAT32 partition as \EFI\BOOT\BOOTX64.EFI .

Now reboot, and enter into BIOS settings. In the Boot tab, you should see an UEFI Boot option, enable it. If everything is allright (likely not), now after reboot you should end up in the EFI Shell. If you did, congratulations. If you didn’t, and instead your regular OS booted as usual, it most likely means that UEFI boot manager wasn’t able to access the drive.

First try to enter Boot Menu during BIOS screen ( F10 on my machine). If the EFI Shell on FAT32 partition was detected, but didn’t boot, you will see it as one of option (in my case [Internal EFI Shell—Harddrive] ). If you see it, just run it. This might possibly happen if you already have some EFI operating system installed, and it has written itself as default EFI boot entry to NVRAM.

If you don’t see EFI Shell in the Boot Menu, it means UEFI Boot Loader wasn’t able to find any FAT32 drive with \EFI\BOOT\BOOTX64.EFI . If you are trying to boot EFI shell from USB key, try tweaking USB emulation settings in BIOS. Same applies to SATA disks and SATA/IDE settings. If none of settings work, or your machine failed to boot from IDE drive, I can’t help you any more than to doublecheck everything I wrote so far (especially typos in path to EFI shell).

If you have some experience with booting EFI shell not covered in this chapter, please drop me a message, so I can update this article.

Building an UEFI application

So, we are in EFI shell. That means we can finally test any (64-bit) EFI application we write. Time to start writing one. We will of course write and compile applications in normal operating system, not in EFI shell.

EFI application or driver is just a plain Windows PE DLL file, just with different subsystem value in header. There are 3 new values: EFI application = 10, EFI boot service driver = 11, efi runtime driver = 12 (numbers are decimal). Question how to set subsystem will be answered later, for now let’s focus on the DLL file.

EFI PE application doesn’t have any fancies we have in Windows PEs, like symbol tables, exports, static exception handling data, etc. It does not even have imports — all you will ever need in EFI application is passed as argument to entry point function. Only thing needed apart from data and code are relocations. So, this is simplest EFI application:

Compiling it with MS Visual C (supply your own path to EFI toolkit):

Here we set path to common EFI headers, and to platform-specific EFI headers. The /c switch disables linking (we will link separately for better readability), and /Zl disables dependency on default libc libraries.

The /entry:main overrides default libc entry point to our main() function. The /dll forces creation of relocations. And the IGNORE:4086 disables warning LNK4086 , that is caused by nonstandard main() arguments.

Now we have windows DLL, we just need to change PE subsystem value to EFI. For that, EFI Toolkit contains simple (buggy) utility that changes subsystem to one of 3 EFI values. We find this utility in EFI_Toolkit\build\tools\bin\fwimage.exe . To set subsystem to EFI app, we’ll use it like this:

Produced file hello.efi should now be functional empty EFI application. We just need to copy it to our FAT32 partition, reboot to EFI shell, and test it:

If we don’t get any error message, application worked.

UEFI Programming

Now we finally can get deeper into UEFI programming. Your main source for this information should be the UEFI specification, I will only sum up most basic points.

First of all, we should know something about environemnt of UEFI applications. I will describe only environment of 64-bit x86 UEFI here (other to be found in UEFI specification).

UEFI runs in uniprocessor flat 64-bit long mode. Usually UEFI runs with memory mapping disabled (physical RAM address = virtual RAM address), but since 64-bit x86 mode requires mapping to be enabled, UEFI maps entire memory so that virtual address is same as physical (i. e. mapping is transparent). Calling convention is usual 64-bit fastcall (first 4 arguments in RCX , RDX , R8 , R9 with space reserved on stack; rest of arguments passed by stack; RAX , R10 , R11 and XMM0 — XMM5 not preserved by called function), so you don’t need to worry about special compiler settings. Notable feature of EFI is that for every supported architecture, it defines exact binary interface (ABI).

Now let’s look at how our application interacts with UEFI services. First, UEFI provides set of services, called . These are available to EFI drivers, applications, and to OS boot loader during boot. At some point during OS booting, OS loader can decide to drop them, and after that point those services become unavailable. There is also a little number of services that always remain available, called «Runtime Services». Apart from these two sets of services, all that UEFI offers is available through so-called . Protocol is very much like a class in object oriented programming. UEFI defines set of protocols itself (for example protocols handling USB, filesystem, compression, network, …), and application can define its own protocols (hence the «Extensible» in «Unified Extensible Firmware Interface»). Protocols are identified by GUID (, google it if you don’t know what it is). Only very few protocols are mandatory in UEFI specification, and all the rest may or may not be implemented in particular firmware. If protocol isn’t implemented in firmware, you can load a driver that implements it, or even write such driver yourself.

Now let’s look at how to access these services. As I already explained, all you ever need is passed as argument to entry point function. Prototype of entry point function looks like this:

First argument is handle of our process, nothing extra to say about it. Second is pointer to EFI_SYSTEM_TABLE , the top-level EFI structure, which keeps references to everything there is: boot/runtime services, drivers, protocol implementations, memory maps, etc. It is good idea to always save both these arguments in a global variable, so you can access them from anywhere in source code. You can find detailed description of EFI System Table in UEFI Specification chapter 4 — EFI System Table. Its C definition looks like this:


Here we see references to boot and runtime services, three standard I/O handles (as implementations of SIMPLE_TEXT_OUTPUT and SIMPLE_INPUT protocols), and pointer to . Configuration Table holds references to all other protocol implementations currently active in system.

First we will show example of using Boot Service. The EFI_BOOT_SERVICES is just a structure that holds pointers to functions described in UEFI Specification chapter 6: Services — Boot Services. For now we will use only simple Exit() function, that terminates current EFI application immediately.

Now, we will show simple Hello World application, using the ConOut implementation of SIMPLE_TEXT_OUTPUT protocol. This protocol is described in UEFI Specification chapter 11.4 — Simple Text Output Protocol. Its C header looks like this:

We are of course interested in OutputString() function, whose prototype is:

Note that UEFI uses Unicode strings only, hence the CHAR16 *String . This pointer meaning is exactly same as in any object oriented programming. With this info, we should be able to write Hello World app easily:

Also note that UEFI uses CRLF line terminators ( \r\n ) instead of just LF ( \n ), and when we use native EFI functions, there is no layer which reinterprets LF to CRLF. Normally, applications use additional library called which does the LF->CRLF transform.

UEFI Programming with FASM

As an extra, I will also demonstrate same Hello World example in assembly (using FASM, that currently has experimental UEFI support since version 1.67.28):

First we need some to create simple UEFI headers (efi.inc):

And here is the assembly code itself (hello.asm):

Compile and link it with fasm.exe hello_world.asm .

That’s all for now, hope you enjoyed yourselves.

Абонентский модуль ASM и RASM

Функциональное назначение блоков

Модули ASM и RASM позволяют подключать к станции аналоговые абонентские линии. Емкость абонентского модуля составляет 239 индивидуальных телефонных аппаратов или 478 спаренных телефонных аппаратов. Один модуль состоит из трех секций.

Аппаратные средства модуля ASM и RASM обеспечивают:

• подключение аналоговых абонентских линий;

• концентрацию линий в направлении группового переключателя в соотношении 239/30;

• генерирование тарифных сигналов и их передачу абонентам;

• генерирование акустических сигналов и вызывного тока;

• декадный и частотный набор номера;

• межпроцессорную связь (IPC) с остальными модулями (GSM);

• преобразование аналоговых речевых сигналов в цифровые и наоборот;

• синхронизацию модуля от группового переключателя;

• доступ к точкам подключения с целью выполнения испытаний, включая
линии, телефонные аппараты и абонентские комплекты;

В состав каждого модуля входят следующие блоки:

PLC — абонентский комплект , который предназначен для подключения абонентских линий как от индивидуальных, так и от спаренных ТА. Здесь выполняются функции BORSCHT.

МХС – пространственный коммутатор, т.е. коммутационное поле модуля.Коммутационное поле абонентского модуля является аналоговым, здесь осуществляется соединение всех блоков модуля между собой. Процессор управляет соединением в модуле программным способом, посредством управления коммутационными точками между горизонталью и вертикалью коммутационного поля. Вертикали коммутационного поля всех съемных блоков МХС в модуле соединены между собой и подключены к съемному блоку ADC, на котором выполняется аналого-цифровое преобразование речевого сигнала.

SIN — интерфейс секции статива, обеспечивает в каждой секции распознавание данных для своей секции. Интерфейс SIN распознает все данные, поступающие от процессора и предназначенные для периферийных съемных блоков МХС или PLC в данной секции, и передает эти данные, преобразованные в соответствующую форму, на шину секции.

DTMF- приемник и передатчик сигналов частотного набора номера , имеется только в центральной секции, осуществляют прием номера многочастотным кодом «2 из 8» .

SCC – Процессор, который управляет периферийными комплектами и распознает изменения их состояний посредством периферийного интерфейса PIN и интерфейса секции стативаSIN.

ADC – аналого – цифровой преобразователь. Все речевые сигналы, преобразованные в цифровую форму в блоке ADC и размещенные во временные канальные интервалы, передаются по 30 разговорным каналам ИКМ через универсальный интерфейс UPI к групповому переключателю в модуле GSM. Между модулем и GSM дополнительно передается синхросигнал в О-о м канале и данные межпроцессорной связи (IPC) в 16-ом канале.

RTG – генератор тональных и вызывных сигналов.

Не нашли то, что искали? Воспользуйтесь поиском:

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL