§ Для чего порты
Сами по себе эти инструкции несложные, их немного: IN - ввод данных из порта от 0 до 65535 и OUT - вывод данных в порт от 0 до 65535. Стоит заметить, что скорость работы портов крайне низкая. Буквально 100 наносекунд тратится на то, чтобы записать или прочитать из порта. А 100 нс - это невероятно медленная скорость для современных компьютеров.Порты предназначены для общения с внешним миром. Процессор - это такая вещь в себе, но ему нужно знать, что вокруг происходит.
§ Реализация инструкции
Сначала надо написать две функции, для ввода данных из порта и вывода в портuint8_t io_ports[65536]; // Ввод данных uint8_t port_in(uint16_t port) { return io_ports[port]; } // Вывод void port_out(uint16_t port, uint8_t data) { io_ports[port] = data; }Функции пока что не заполнены, но будут заполняться по мере написания для них устройств. Главные устройства это конечно таймер, видеоадаптер, клавиатура. Реализовано будет далее. А сейчас надо реализовать инструкции и это сделать очень просто.
// in case 0xE4: case 0xE5: case 0xEC: case 0xED: i_tmp = opcode_id & 8 ? regs16[REG_DX] : fetch(1); regs[REG_AL] = port_in(i_tmp); if (opcode_id & 1) regs[REG_AH] = port_in(i_tmp); break; // out case 0xE6: case 0xE7: case 0xEE: case 0xEF: i_tmp = opcode_id & 8 ? regs16[REG_DX] : fetch(1); port_out(i_tmp, regs[REG_AL]); if (opcode_id & 1) port_out(i_tmp, regs[REG_AH]); break;
§ Таймер
Первым делом запрограммирую таймер. Для этого придется добавить несколько новых строк в заголовочные файлы:int io_hi_lo; int irq8_prevtime; int pit_frequency; int pit_interval;И выполнить их инициализацию
0; pit_frequency = 65536; pit_interval = pit_frequency;io_hi_lo = Переменная io_hi_lo принимает значение 0 - младший байт или 1 - старший байт, для записи в регистр pit_frequency, который является 16 битным и здесь записана скорость вызова таймера. При pit_frequency = 0 скорость таймера будет равна 18.2065 Гц (IRQ вызывается каждые 54.9254 с).
Также необходимо помнить, что существует программируемый контроллер прерываний (PIC), который тоже надо сделать, но пока сделаю все по-простому. Будем считать что таймер будет вызываться всегда, если есть IF=1. Просто дело в том, что можно заблокировать вызов IRQ 8 аппаратно.
// Вызов INT 8 void call_interrupt8() { // Каждые 1..65535 инструкции вызывать проверки if (--pit_interval <= 0) { ftime(&ms_clock); pit_interval = pit_frequency; int time_curr = ms_clock.millitm; int time_diff = time_curr - irq8_prevtime; if (time_diff < 0) time_diff += 1000; // 54.9254 ms = 65536 int time_pit = pit_frequency / 1191; // Вызов прерывания по истечению времени if (time_diff >= time_pit) { irq8_prevtime = time_curr; interrupt(8); } } }Это вспомогательная процедура, которая вызывается на каждой инструкции при включенном IF=1. Вызывается проверка истечения времени таймера каждые pit_frequency раз, при этом используя pit_interval, который уменьшается с каждой инструкцией на 1. После того как pit_interval достиг 0, происходит его обновление и запуск процедуры проверки истечения времени. Берется текущее время, вычисляется разность между предыдущим временем запроса INT8 и если она равна pit_frequency / 1191, то тогда обновляется новое время и делается запрос на INT8.