§ Для чего порты
Сами по себе эти инструкции несложные, их немного: 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.