§ Для чего порты

Сами по себе эти инструкции несложные, их немного: IN - ввод данных из порта от 0 до 65535 и OUT - вывод данных в порт от 0 до 65535. Стоит заметить, что скорость работы портов крайне низкая. Буквально 100 наносекунд тратится на то, чтобы записать или прочитать из порта. А 100 нс - это невероятно медленная скорость для современных компьютеров.
Порты предназначены для общения с внешним миром. Процессор - это такая вещь в себе, но ему нужно знать, что вокруг происходит.

§ Реализация инструкции

Сначала надо написать две функции, для ввода данных из порта и вывода в порт
1uint8_t io_ports[65536];
2
3// Ввод данных
4uint8_t port_in(uint16_t port) {
5    return io_ports[port];
6}
7
8// Вывод
9void port_out(uint16_t port, uint8_t data) {
10    io_ports[port] = data;
11}
Функции пока что не заполнены, но будут заполняться по мере написания для них устройств. Главные устройства это конечно таймер, видеоадаптер, клавиатура. Реализовано будет далее. А сейчас надо реализовать инструкции и это сделать очень просто.
1// in
2case 0xE4: case 0xE5:
3case 0xEC: case 0xED:
4
5    i_tmp = opcode_id & 8 ? regs16[REG_DX] : fetch(1);
6    regs[REG_AL] = port_in(i_tmp);
7    if (opcode_id & 1) regs[REG_AH] = port_in(i_tmp);
8    break;
9
10// out
11case 0xE6: case 0xE7:
12case 0xEE: case 0xEF:
13
14    i_tmp = opcode_id & 8 ? regs16[REG_DX] : fetch(1);
15    port_out(i_tmp, regs[REG_AL]);
16    if (opcode_id & 1) port_out(i_tmp, regs[REG_AH]);
17    break;

§ Таймер

Первым делом запрограммирую таймер. Для этого придется добавить несколько новых строк в заголовочные файлы:
1int      io_hi_lo;
2int      irq8_prevtime;
3int      pit_frequency;
4int      pit_interval;
И выполнить их инициализацию
1io_hi_lo = 0;
2pit_frequency = 65536;
3pit_interval  = pit_frequency;
Переменная io_hi_lo принимает значение 0 - младший байт или 1 - старший байт, для записи в регистр pit_frequency, который является 16 битным и здесь записана скорость вызова таймера. При pit_frequency = 0 скорость таймера будет равна 18.2065 Гц (IRQ вызывается каждые 54.9254 с).
Также необходимо помнить, что существует программируемый контроллер прерываний (PIC), который тоже надо сделать, но пока сделаю все по-простому. Будем считать что таймер будет вызываться всегда, если есть IF=1. Просто дело в том, что можно заблокировать вызов IRQ 8 аппаратно.
1// Вызов INT 8
2void call_interrupt8() {
3
4    // Каждые 1..65535 инструкции вызывать проверки
5    if (--pit_interval <= 0) {
6
7        ftime(&ms_clock);
8
9        pit_interval  = pit_frequency;
10        int time_curr = ms_clock.millitm;
11        int time_diff = time_curr - irq8_prevtime;
12        if (time_diff < 0) time_diff += 1000;
13
14        // 54.9254 ms = 65536
15        int time_pit = pit_frequency / 1191;
16
17        // Вызов прерывания по истечению времени
18        if (time_diff >= time_pit) {
19
20            irq8_prevtime = time_curr;
21            interrupt(8);
22        }
23    }
24}
Это вспомогательная процедура, которая вызывается на каждой инструкции при включенном IF=1. Вызывается проверка истечения времени таймера каждые pit_frequency раз, при этом используя pit_interval, который уменьшается с каждой инструкцией на 1. После того как pit_interval достиг 0, происходит его обновление и запуск процедуры проверки истечения времени. Берется текущее время, вычисляется разность между предыдущим временем запроса INT8 и если она равна pit_frequency / 1191, то тогда обновляется новое время и делается запрос на INT8.