From daa5a43c4d1e4bcf62daf948248aed10c77ac5a5 Mon Sep 17 00:00:00 2001 From: Thierno-Souleymane BAH <thiernosouleymane.bah.etu@univ-lille.fr> Date: Wed, 10 Feb 2021 11:20:22 +0100 Subject: [PATCH] feat(master mode and user mode scenario test done) --- MMUConfig.h | 8 +++ MMUConfig.ini | 135 +++++++++++++++++++++++++++++++++++++++ Makefile | 7 +- mi_kernel.c | 85 ++++++++++++++++++++++++ process.h => mi_kernel.h | 24 +++---- mi_syscall.h | 11 ++++ mi_user.c | 38 +++++++++++ mi_user.h | 12 ++++ process.c | 40 ------------ x86-64/lib/libhardware.a | Bin 118486 -> 119034 bytes 10 files changed, 307 insertions(+), 53 deletions(-) create mode 100644 MMUConfig.h create mode 100644 MMUConfig.ini create mode 100644 mi_kernel.c rename process.h => mi_kernel.h (68%) create mode 100644 mi_syscall.h create mode 100644 mi_user.c create mode 100644 mi_user.h delete mode 100644 process.c diff --git a/MMUConfig.h b/MMUConfig.h new file mode 100644 index 0000000..b5b8ee5 --- /dev/null +++ b/MMUConfig.h @@ -0,0 +1,8 @@ +#define MMU_ENABLE 1 +#define MMU_IRQ 13 +#define MMU_CMD 0x66 +#define MMU_FAULT_ADDR 0xCD +#define TLB_ADD_ENTRY 0xCE +#define TLB_DEL_ENTRY 0xDE +#define TLB_SIZE 32 +#define TLB_ENTRIES 0x800 diff --git a/MMUConfig.ini b/MMUConfig.ini new file mode 100644 index 0000000..a35ae17 --- /dev/null +++ b/MMUConfig.ini @@ -0,0 +1,135 @@ +# $Id: hardware.ini 114 2009-12-01 13:06:43Z simon_duquennoy $ +#------------------------------------------------------------ + +# +# Hardware.cfg +# Fichier de configuration du simulateur de mat�riel +# + +# +# Trace de Debug +# +# define DEBUG_SETUP 0x0001 /* trace hardware setup */ +# define DEBUG_IT 0x0010 /* trace interruptions generation */ +# define DEBUG_REG 0x0100 /* trace hardware register access */ +# define DEBUG_WARNING 0x1000 /* trace hardware warning messages */ +DEBUG = 0x0000 + +# +# Configuration des param�tres de base du simulateur +# +SYSTICK = 1000 # delais entre deux ticks du simulateur (en micro-seconde) + +# +# Configuration des mat�riels r�seau +# +# configuration des ports COM (port serie de type RS232) +# > Port Serie n�1 +SL1_ENABLE = 0 # SL1_ENABLE = 0 => simulation lien serie d�sactiv�e +SL1 = "chimay.lifl.fr" # Machine connect� � la sortie du lien serie n�1 +SL1_COM = 1 # destinataire 1 = lien serie "com1", 2 = lien serie "com2" +SL1_NOISE = 500 # bruit de la ligne 0-999 (exprim� en /1000 d'erreur) +SL1_IRQ = 4 # niveau d'interruption de l'UART serie n�1 +SL1_UARTSR = 0x3F8 # registre de status de l'UART serie n�1 +SL1_UARTDATA = 0x3FA # registre d'entr�e sortie de l'UART serie n�1 +SL1_UDPPORT = 1500 # port UDP "r�el" utiliser pour simulation serie n�1 +# > Port Serie n�2 +SL2_ENABLE = 0 # SL2_ENABLE = 0 => simulation lien serie d�sactiv�e +SL2 = "ldx2" # Machine connect� � la sortie du lien serie n�2 +SL2_COM = 2 # destinataire 1 = lien serie "com1", 2 = lien serie "com2" +SL2_NOISE = 0 # bruit de la ligne 0-999 (exprim� en /1000 d'erreur) +SL2_IRQ = 5 # niveau d'interruption de l'UART serie n�2 +SL2_UARTSR = 0x3FC # registre de status de l'UART serie n�2 +SL2_UARTDATA = 0x3FE # registre d'entr�e sortie de l'UART serie n�2 +SL2_UDPPORT = 1501 # port UDP "r�el" utiliser pour simulation serie n�2 +# > Configuration de la carte Ethernet +ENABLE_ETHERNET = 0 # ENABLE_ETHERNET = 0 => simulation ethernet d�sactiv�e +Eth0_Link = 1 # num de cable de connexion de la carte Eth0 +Eth0_DMASR = 0xE800 # registre de status de la carte Ethernet +Eth0_DMABASE = 0xE804 # adresse de base pour le vidage d'un paquet +Eth0_DMASIZE = 0xE808 # adresse limite pour le vidage d'un paquet +Eth0_IRQ = 9 # niveau d'interruption de la carte. +Eth0_MCADR = "225.0.0.1" # adresse multicast utilis�e pour la simulation ethernet +Eth0_UDPPORT = 1502 # port UDP utilis� pour la simulation ethernet + +# +# Configuration des disques durs +# + +# > Disque dur IDE Maitre +ENABLE_HDA = 1 # ENABLE_HD = 0 => simulation du disque d�sactiv�e +HDA_FILENAME = "vdiskA.bin" # nom du fichier de stockage du disque simul� +HDA_CMDREG = 0x3F6 # registre de commande du disque maitre +HDA_DATAREGS = 0x110 # base des registres de donn�es (r,r+1,r+2,...r+7) +HDA_IRQ = 14 # Interruption du disque +HDA_MAXCYLINDER = 16 # Nombre de pistes du disque ma�tre +HDA_MAXSECTOR = 16 # Nombre de secteurs du disque ma�tre +HDA_SECTORSIZE = 32 # Taille (en octet) d'un secteur du disque ma�tre +HDA_STPS = 2 # nombre de SYSTICK pour changer de secteur +HDA_STPC = 1 # nombre de SYSTICK pour changer de piste +HDA_PON_DELAY = 30 # nombre de SYSTICK avant amorce du disque +HDA_POFF_DELAY = 30 # nombre de SYSTICK avant arret du disque + +# > Disque dur IDE Esclave +ENABLE_HDB = 1 # ENABLE_HD = 0 => simulation du disque d�sactiv�e +HDB_FILENAME = "vdiskB.bin" # nom du fichier de stockage du disque simul� +HDB_CMDREG = 0x376 # registre de commande du disque esclave +HDB_DATAREGS = 0x170 # base des registres de donn�es (r,r+1,r+2,...r+7) +HDB_IRQ = 15 # Niveau d'interruption du disque +HDB_MAXCYLINDER = 16 # Nombre de pistes du disque esclave +HDB_MAXSECTOR = 16 # Nombre de secteurs du disque esclave +HDB_SECTORSIZE = 512 # Taille (en octet) d'un secteur du disque esclave +HDB_STPS = 2 # nombre de SYSTICK pour changer de secteur +HDB_STPC = 3 # nombre de SYSTICK pour changer de piste +HDB_PON_DELAY = 30 # nombre de SYSTICK avant amorce du disque +HDB_POFF_DELAY = 30 # nombre de SYSTICK avant arret du disque + +# +# Configuration de l'horologe interne +# +TIMER_CLOCK = 0xF0 # registre de lecture de la date courante (en ms) +TIMER_PARAM = 0xF4 # registre de configuration du TIMER + # bit 7 : RESET general (=1) + # bit 6 : Alarm ON = 1, Alarm OFF = 0 + # bit 5 : Declanche la division Hz du Timer (=1) + # bit 4 \ Si le division Hz du timer est demand� : + # bit 3 / 00: 1 top d'alarme pour 1 tops d'horloge , + # 01: 1 top d'alarme pour 8 tops d'horloge , + # 10: 1 top d'alarme pour 64 tops d'horloge , + # 11: 1 top d'alarme pour 512 tops d'horloge. + # bit 2 - R.F.U. - + # bit 1 \ Lecture d'un �tat interne de l'alarme + # bit 0 / 00: Alarme Courante, + # 01: Division Hz, + # 10: Ticks/Sec + # 11: niveau d'interruption de l'horloge +TIMER_ALARM = 0xF8 # registre de generation d'interruption +TIMER_IRQ = 2 # Niveau d'interruption de l'horologe +TIMER_TICKS = 1 # Nombre de SYSTICKS par tick d'horloge + +# +# Configuration de la MMU +# +MMU_ENABLE = 1 # MMU_ENABLE = 0 => simulation de la MMU d�sactiv�e +MMU_IRQ = 13 # Niveau d'interruption de la MMU +MMU_CMD = 0x66 # Registre de commande de la MMU +MMU_FAULT_ADDR_HI = 0xCD # Registre contenant l'adresse m�moire ayant provoqu� une faute +MMU_FAULT_ADDR_LO = 0xCC +TLB_ADD_ENTRY = 0xCE # Registre de commande d'ajout d'entr�e dans la TLB + # attend une valeur de la forme : + # struct tlb_entry_s { + # unsigned unused: 8; + # unsigned virt_page: 12; + # unsigned phys_page: 8; + # unsigned access_type: 3; + # unsigned is_active: 1; + #}; + # le nouveau mapping est ajout� dans la TLB, + # provoquant �ventuellement l'�crasement d'une ancienne entr�e +TLB_DEL_ENTRY = 0xDE # Registre de commande de suppression d'entr�e dans la TLB + # seul phys_page est lu, et toutes les entr�e + # correspondant � cette page physicique sont supprim�es de la TLB +TLB_SIZE = 32 # Number of entries in the TLB +TLB_ENTRIES = 0x800 # Registre contenant les entr�es de la TLB (32 bits par entr�e) + # Accessible en lecture comme en �criture avec le m�me format que + # celui utilis� par TLB_ADD_ENTRY diff --git a/Makefile b/Makefile index f1c8d50..9512dba 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ INC=-I/vagrant/ASE/ordonnancement/x86-64/include LIB=-L/vagrant/ASE/ordonnancement/x86-64/lib -lhardware -all: try_mul pingpong pingpongpang +all: try_mul pingpong pingpongpang mi_kernel display_stack : display_stack.o gcc -o $@ $^ $(LIB) @@ -15,8 +15,11 @@ pingpong : try.o pingpong.o pingpongpang : try.o pingpongpang.o gcc -o $@ $^ $(LIB) +mi_kernel : mi_user.o mi_kernel.o + gcc -o $@ $^ $(LIB) + %.o:%.c gcc $(INC) -c $< clean: - rm -f *.o *.s display_stack try_mul pingpong pingpongpang vdisk?.bin \ No newline at end of file + rm -f *.o *.s display_stack try_mul pingpong pingpongpang vdisk?.bin mi_kernel \ No newline at end of file diff --git a/mi_kernel.c b/mi_kernel.c new file mode 100644 index 0000000..f2ff8a1 --- /dev/null +++ b/mi_kernel.c @@ -0,0 +1,85 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "hardware.h" +#include "mi_kernel.h" +#include "mi_user.h" +#include "MMUConfig.h" +#include "mi_syscall.h" + +int current_process = 0; + +void mmu_handler() +{ + tlb_entry_t tlb; + long int vaddr = ((long int)_in(MMU_FAULT_ADDR_HI) << 32) | (_in(MMU_FAULT_ADDR_LO) & 0xFFFFFFFF); + long int vpage = (vaddr >> 12) & 0xFFF; + long int ppage = ppage_of_vpage(current_process, vpage); + + //printf("%lx, %lx, %lx, %lx, %d\n", vpage, ppage, vaddr, (long int)virtual_memory, current_process); + if (vaddr < ((long int)virtual_memory) || vaddr > ((long int)virtual_memory) + VM_SIZE || ppage == -1) + { + fprintf(stderr, "Echec"); // TODO + exit(EXIT_FAILURE); + } + + tlb.s.tlb_vpage = vpage; + tlb.s.tlb_ppage = ppage; + tlb.s.tlb_rwx = 7; + tlb.s.tlb_used = 1; + + _out(TLB_ADD_ENTRY, tlb.i); + //_out(TLB_ADD_ENTRY, *(int *)(&tlb)); +} + +void switch_to_process0(void) +{ + current_process = 0; + _out(MMU_CMD, MMU_RESET); +} + +void switch_to_process1(void) +{ + current_process = 1; + _out(MMU_CMD, MMU_RESET); +} + +void init() +{ + if (init_hardware("MMUConfig.ini") == 0) + { + fprintf(stderr, "ERROR INITHARDWARE"); + exit(EXIT_FAILURE); + } + IRQVECTOR[16] = switch_to_process0; + IRQVECTOR[17] = switch_to_process1; + IRQVECTOR[MMU_IRQ] = mmu_handler; +} + +int ppage_of_vpage(int process, unsigned vpage) +{ + if (vpage < N) + { + if (process == PROCESS_0) + return 2 * vpage + 2; + if (process == PROCESS_1) + return 2 * vpage + 1; + } + + return -1; +} + +void main_master(void) +{ + init(); + IRQVECTOR[16] = switch_to_process0; + IRQVECTOR[17] = switch_to_process1; +} + +int main(int argc, char **argv) +{ + + main_master(); + _mask(0x1001); // Basculer mode utilisateur + main_user(); +} diff --git a/process.h b/mi_kernel.h similarity index 68% rename from process.h rename to mi_kernel.h index ea0b40b..04dd604 100644 --- a/process.h +++ b/mi_kernel.h @@ -1,14 +1,12 @@ -#if !defined(PROCESS) -#define PROCESS - -#define PROCESS_1 1 -#define PROCESS_0 0 -#define N 127 -#define VM_SIZE 160000 * 8 +#if !defined(MI_KERNEL) +#define MI_KERNEL +#define VM_SIZE 16000000 #define MMU_FAULT_ADDR_LO 0xCC #define MMU_FAULT_ADDR_HI 0xCD -#define TLB_ADD_ENTRY 0xCE + +#define PROCESS_1 1 +#define PROCESS_0 0 struct tlb_entry_s { @@ -27,8 +25,12 @@ union tlb_entry_u typedef union tlb_entry_u tlb_entry_t; -int ppage_of_vpage(int process, unsigned vpage); - void mmu_handler(); -#endif // PROCESS +void switch_to_process0(void); + +void switch_to_process1(void); + +void main_master(void); + +#endif // MI_KERNEL diff --git a/mi_syscall.h b/mi_syscall.h new file mode 100644 index 0000000..9972004 --- /dev/null +++ b/mi_syscall.h @@ -0,0 +1,11 @@ +#if !defined(MI_SYSCALL) +#define MI_SYSCALL + +#define SYSCALL_SWTCH_0 16 +#define SYSCALL_SWTCH_1 17 + +#define N 127 + +void init(void); + +#endif // MI_SYSCALL diff --git a/mi_user.c b/mi_user.c new file mode 100644 index 0000000..1f76de3 --- /dev/null +++ b/mi_user.c @@ -0,0 +1,38 @@ +#include <stdio.h> +#include <string.h> +#include "mi_user.h" +#include "mi_syscall.h" +#include "hardware.h" + +int sum(void *ptr) +{ + int i; + int sum = 0; + + for (i = 0; i < PAGE_SIZE * N / 2; i++) + sum += ((char *)ptr)[i]; + return sum; +} + +void main_user(void) +{ + void *ptr; + int res; + + ptr = virtual_memory; + + _int(SYSCALL_SWTCH_0); + memset(ptr, 1, PAGE_SIZE * N / 2); + + _int(SYSCALL_SWTCH_1); + memset(ptr, 3, PAGE_SIZE * N / 2); + + _int(SYSCALL_SWTCH_0); + res = sum(ptr); + + printf("Resultat du processus 0 : %d\n", res); + _int(SYSCALL_SWTCH_1); + + res = sum(ptr); + printf("Resultat processus 1 : %d\n", res); +} \ No newline at end of file diff --git a/mi_user.h b/mi_user.h new file mode 100644 index 0000000..a5e8ac5 --- /dev/null +++ b/mi_user.h @@ -0,0 +1,12 @@ +#if !defined(MI_USER) +#define MI_USER + +#define PAGE_SIZE 4000 + +int ppage_of_vpage(int process, unsigned vpage); + +int sum(void *ptr); + +void main_user(void); + +#endif // MI_USER diff --git a/process.c b/process.c deleted file mode 100644 index 60ff9e7..0000000 --- a/process.c +++ /dev/null @@ -1,40 +0,0 @@ -#include <stdlib.h> -#include <stdio.h> -#include "hardware.h" -#include "process.h" - -int current_process = NULL; - -int ppage_of_vpage(int process, unsigned vpage) -{ - if (vpage < N) - { - if (process == PROCESS_0) - return 2 * vpage + 2; - if (process == PROCESS_1) - return 2 * vpage + 1; - } - - return -1; -} - -void mmu_handler() -{ - tlb_entry_t tlb; - unsigned int vaddr = ((long int)_in(MMU_FAULT_ADDR_HI) << 32) | (_in(MMU_FAULT_ADDR_LO) & 0xFFFFFFFF); - unsigned int vpage = (vaddr >> 12) & 0xFFF; - unsigned int ppage = ppage_of_vpage(current_process, vpage); - - if (vaddr < virtual_memory || vaddr > virtual_memory + VM_SIZE || vpage == -1) - { - fprintf(stderr, "Echec"); - exit(EXIT_FAILURE); - } - - tlb.s.tlb_vpage = vpage; - tlb.s.tlb_ppage = ppage; - tlb.s.tlb_rwx = 7; - tlb.s.tlb_used = 1; - - _out(TLB_ADD_ENTRY, tlb.i); -} diff --git a/x86-64/lib/libhardware.a b/x86-64/lib/libhardware.a index 7b1a9ec9aef8e1f6016442b01674e62265a07f6a..738b2688a371f209faf966aee457234e0d5b4443 100644 GIT binary patch delta 5740 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 Mjkmu&!1$6E0G>WdM*si- delta 4274 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 -- GitLab