From e4db0a38d87e99577d537cfae4bf6a1de96c4cb6 Mon Sep 17 00:00:00 2001 From: rick Date: Thu, 14 Sep 2023 22:47:59 +0200 Subject: [PATCH] add serial port --- Makefile | 2 +- include/serial.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ src/serial.c | 89 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 include/serial.h create mode 100644 src/serial.c diff --git a/Makefile b/Makefile index 5302c10..6ed38d0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -OBJECTS = loader.o framebuffer.o io.o kmain.o +OBJECTS = loader.o serial.o framebuffer.o io.o kmain.o CC = gcc CFLAGS = -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector \ diff --git a/include/serial.h b/include/serial.h new file mode 100644 index 0000000..f1754b4 --- /dev/null +++ b/include/serial.h @@ -0,0 +1,94 @@ +/** @file serial.h + * Fichier d'en-tête pour les ports séries. + * + * Références: + * - documentation sur les ports séries : + * - https://wiki.osdev.org/Serial_Ports + * - https://devse.wiki/x86_64/p%C3%A9riph%C3%A9riques/com + * - https://www.sci.muni.cz/docs/pc/serport.txt + * - `LCR (Line Control Register)` + * - `FCR (FIFO Control Register)` + * - `Excursion: Why and how to use the FIFOs (by Scott C. Sadow)` + */ + +#ifndef SERIAL_H +#define SERIAL_H + +#define COM1_PORT 0x03F8 + +#define COM_DATA(base) (base) +#define COM_INTERRUPT(base) (base + 1) +#define COM_FIFO(base) (base + 2) +#define COM_LINE_CONTROL(base) (base + 3) +#define COM_MODEM_CONTROL(base) (base + 4) +#define COM_LINE_STATUS(base) (base + 5) +#define COM_MODEM_STATUS(base) (base + 6) +#define COM_SCRATCH(base) (base + 7) + +#define DLAB_BYTE 0x80 + +/** + * Fonction pour définir la vitesse de transmission du port série. + * + * L'UART (le controleur de série) a une horloge interne tournant à 115200 + * ticks par seconde. On peut modifier cette vitesse en la divisant. Cela se + * fait en activant le DLAB. + * + * @param port le port série à configurer + * @param div le diviseur du nombre de tick + */ +void serial_set_baud(unsigned short port, unsigned short div); + +/** + * Configuration du port série. + * + * On configure le port série via le canal de controle de ligne. Il prend un + * byte découpé en plusieurs sous parties : (7) DBPPPSTT (0). + * + * - D = bit pour activer le DLAB; + * - B = active le break (cf. section `LCR (Line Control Register)` de + * https://www.sci.muni.cz/docs/pc/serport.txt); + * - P = parité (cf. https://wiki.osdev.org/Serial_Ports#Parity); + * - S = nombre de bits de stop (0 = 1, 1 = 1.5/2); + * - T = taille de la communication (0 = 5; 3 = 8) + * + * Notre configuration étant basique, nous nous contentons d'utiliser la taille + * maximale de données, sans parité et avec un bit de stop. + * + * @param port le port à configurer + */ +void serial_config(unsigned short port); + +/** + * Vérifie si la file d'envoie est vide. + * + * @param port le port à vérifier + * @return 1 si la file est vide, 0 sinon + */ +int serial_is_fifo_empty(unsigned short port); + +/** + * Envoie une chaine de caractère à un port série. + * + * La fonction fait une attente active lorsque la chaine est trop grosse. À + * corriger dans le futur. + * + * @param port le port où envoyer la chaine. Il doit être configuré avant. + * @param buf la chaine de caractère + * @param len la longueur de la chaine + * @return la taille de la chaine envoyée + */ +unsigned int serial_write(unsigned short port, char *buf, unsigned int len); + +/** + * Vérifie si le port série marche bien. + * + * Configure le port avec un diviseur de 5 et active le drapeau de boucle du + * modem. + * + * @param port le port où tester la configuration + * @return 0 si tout est OK, un chiffre positif sinon + */ +int serial_test_configuration(unsigned short port); + +#endif diff --git a/src/serial.c b/src/serial.c new file mode 100644 index 0000000..0430fd6 --- /dev/null +++ b/src/serial.c @@ -0,0 +1,89 @@ +#include "io.h" + +void serial_set_baud(unsigned short port, unsigned short div) +{ + outb(COM_LINE_CONTROL(port), DLAB_BYTE); + outb(COM_DATA(port), (div >> 8) & 0x00FF); + outb(COM_INTERRUPT(port), div & 0x00FF); + outb(COM_LINE_CONTROL(port), 0); +} + +void serial_config(unsigned short port) +{ + /* configuration de la façon d'envoyer des données. Ici, on envoie des + caractères de 8 bits, avec 1 bit de stop et aucune parité. */ + outb(COM_LINE_CONTROL(port), 0x03); + + /* configuration de la file FIFO. Lorsqu'on essaie d'envoyer trop de + * caractères et que la vitesse du port ne suit pas, les caractères en trop + * sont stockés dans cette file. Ici, on vide la file de réception et + * d'envoie, on stocke 14 bytes dans les files et on l'active. cf. FCR (FIFO + * Control Register) : https://www.sci.muni.cz/docs/pc/serport.txt + */ + outb(COM_FIFO(port), 0xC7); + + /* configuration du modem, on active la réception et l'envoie de données */ + outb(COM_MODEM_CONTROL(port), 0x03); +} + +int serial_is_fifo_empty(unsigned short port) +{ + return inb(COM_LINE_STATUS(port)) & 0x20; +} + +unsigned int serial_write(unsigned short port, char *buf, unsigned int len) +{ + unsigned int ret; + char tmp; + + ret = 0; + + while (ret < len) + { + tmp = *(buf + ret); + + /* pas ouf, à trouver un meilleur moyen d'attendre lorsque la file est + * pleine */ + while (!serial_is_fifo_empty(port)); + outb(COM_DATA(port), tmp); + + ret++; + } + + return ret; +} + +int serial_test_configuration(unsigned short port) +{ + char *tmp; + char t; + int i, ret; + + serial_set_baud(port, 5); + serial_config(port); + /* activation du retour pour pouvoir tester */ + outb(COM_MODEM_CONTROL(port), 0x13); + + i = 0; + tmp = "coucou"; + + do + { + outb(COM_DATA(port), *(tmp + i)); + i++; + } + while (*(tmp + i) != '\0'); + + ret = 0; + i = 0; + + do + { + t = inb(COM_DATA(port)); + if (t != *(tmp + i)) ret++; + i++; + } + while (t != '\0'); + + return ret; +}