|
|
Le protocole IDE (Integrated Drive Electronics) est un des standards les plus répandus en ce qui concerne les disques durs. Tout système digne de ce nom doit donc impérativement supporter ce protocole par le biais d'un pilote. Bien sûr, il serait plus simple d'utiliser le BIOS qui fournit déjà des fonctions d'accès aux disques durs (interruption 13h) mais ces fonctions sont limitées, lentes, et parfois boguées. Se baser sur le BIOS ne permet donc pas d'avoir un système fiable sans compter qu'en mode protégé, c'est impossible. C'est pourquoi il nous faut réécrire les routines d'accès aux disques.
Ce document présente le protocole IDE. Il n'est pas exhaustif et de nombreuses fonctions sont évoquées sans être détaillées. Néanmoins, les informations contenues dans ce document suffisent pour les opérations de base, d'ailleurs illustrées par du code.
Attention : ce document ne doit servir que d'introduction. Afin d'aller plus loin dans la connaissance d'ATA et d'ATAPI, il est préférable de consulter des documents de meilleure qualité, idéalement les spécifications officielles.
Les disques sont branchés sur des contrôleurs. Sur les anciennes cartes, il n'y en a qu'un mais actuellement la plupart des cartes mères proposent 2 contrôleurs, voire 4. Chaque contrôleur permet de brancher 2 disques dur : un maître et un esclave. Une carte mère disposant de 2 contrôleurs IDE permet donc de brancher 4 disques durs. La distinction maître/esclave permet simplement de reconnaître les disques mais l'utilisation de chacun d'eux est similaire.
Le protocole IDE correspond en réalité au protocole ATA/ATAPI.
La principale différence entre les deux protocoles réside dans l'existence, dans ATAPI, de l'extension Packet Interface qui implémente le jeu d'instructions Packet. De plus, de nombreuses commandes ATA sont interdites si ce jeu d'instructions est présent.
Dans les sections suivantes, les commandes réservées à ATA ou à ATAPI seront indiquée. Les commandes communes aux deux protocoles ne porteront pas de mention spéciale.
Ce jeu d'instructions constitue la principale différence entre ATA et ATAPI. Il implémente les deux commandes suivantes :
Dans le cas des CD-ROM et des DVD, ces commandes sont définies par le T10 (Technical Committee T10, dépendant de NCITS (National Committee for Information and Technology Standards) chargé de SCSI) dans les MMC (Multimedia Commands 1, 2 et 3 actuellement).
Quelques-unes des commandes sont décrites dans ce document.
Voici un récapitulatif des ports utilisés par le protocole IDE.
Présentation
ATA et ATAPI
ATAPI (ATA with Packet Interface extension) est en fait une extension de ATA (AT Attachement). Ce dernier est le protocole utilisé par les disques durs IDE tandis qu'ATAPI est plutôt utilisé par les lecteurs et graveurs de CD-ROM et DVD ainsi que par quelques lecteurs de disquettes spéciales de type ZIP par exemple.Jeu d'instructions Packet
Ces commandes servent d'interface à un jeu d'instructions spéciales spécifiques au type de périphérique (CD-ROM, CD-R/RW, DVD, ...). Ces commandes ne sont pas définies par le protocole ATAPI.
Note : Ces commandes étaient, pour les CD-ROM, définies dans le document SFF-8020i, maintenant obsolète.Ports et commandes
Récapitulatif
1F0 + X Registres de commande du 1e contrôleur 3F0 + Y Registres de contrôle du 1e contrôleur 170 + X Registres de commande du 2e contrôleur 370 + Y Registres de contrôle du 2e contrôleur F0 + X Registres de commande du 3e contrôleur 2F0 + Y Registres de contrôle du 3e contrôleur 70 + X Registres de commande du 4e contrôleur 270 + Y Registres de contrôle du 4e contrôleur
X Signification Lecture (entrée) 0 Données 1 Registre d'erreur 2 Nombre de secteurs 3 Secteur 4 Cylindre inférieur 5 Cylindre supérieur 6 Lecteur et tête 7 État Écriture (sortie) 0 Données 1 Précompensation d'écriture 2 Nombre de secteurs 3 Secteur 4 Cylindre inférieur 5 Cylindre supérieur 6 Lecteur et tête 7 Commande
Y Signification Lecture (entrée) 6 État du disque dur 7 Registre d'adresses Écriture (sortie) 6 Registre de contrôle
| Indice | Taille | Signification |
| 2h | mot | Nombre total de cylindres logiques ou 16383 si le double-mot à l'indice 78h dépasse 16515072 |
| 6h | mot | Nombre total de têtes logiques ou 16 si le double-mot à l'indice 78h dépasse 16515072 |
| Ch | mot | Nombre total de secteurs logiques par piste logique ou 63 si le double-mot à l'indice 78h dépasse 16515072 |
| 14h | 10 mots | Numéro de série |
| 36h | 20 mots | Modèle |
| 78h | double-mot | Nombre total de secteurs adressables en mode LBA |
| A0h | mot | Numéro majeur de révision des spécifications ATA |
| A2h | mot | Numéro mineur de révision des spécifications ATA |
Commande interdite si le jeu de commandes Packet est présent.
A1h - Obtenir les informations sur le disque (Jeu d'instructions Packet)
Cette commande est similaire à la commande précédente mais est réservée au jeu d'instructions Packet. Les informations renvoyées sont toutefois différentes. Pour une description complète, reportez-vous aux spécifications officielles.
| Indice | Taille | Signification |
| 14h | 10 mots | Numéro de série |
| 36h | 20 mots | Modèle |
| A0h | mot | Numéro majeur de révision des spécifications ATA |
| A2h | mot | Numéro mineur de révision des spécifications ATA |
Commande interdite si le jeu de commandes Packet est absent.
A0h - Envoyer une commande Packet
Cette commande permet d'envoyer une commande Packet au contrôleur.
Voici comment utiliser cette commande :
Lancer la commande
| Features (0x1f1, 0x171, 0xf1, 0x71) | 0 |
| Sector Count (0x1f2, 0x172, 0xf2, 0x72) | 0 |
| Sector Number (0x1f3, 0x173, 0xf3, 0x73) | 0 |
| Byte Low (0x1f4, 0x174, 0xf4, 0x74) | (1) |
| Byte High (0x1f5, 0x175, 0xf5, 0x75) | (1) |
| Head and Disk (0x1f6, 0x176, 0xf6, 0x76) | (2) |
| Command (0x1f7, 0x177, 0xf7, 0x77) | 0xa0 |
(1) Ces deux registres contiennent le nombre maximum d'octet qui vont être transférés (ex. 4096 : Byte High = 0x10 ; Byte Low = 0)
(2) Ce registre s'utilise comme pour toutes les commandes.
Lorsque le contrôleur est prê:t à recevoir la commande, Status & 0x8
Lorsque le contrôleur est prêt à envoyer ou recevoir des données (dans le cas d'une commande nécessitant un transfert), Status & 0x8
Enfin, Interrupt Reason (Sector Count) & 0x7 == 3 (REL=0; I/O=1 ; C/D=1) pour indiquer que la commande est terminée (avec ou sans erreur). Dans le cas d'une erreur, le registre d'erreur contient dans ses 4 bits de poids forts le numéro Sense Key de l'erreur. L'erreur complète peut être connue grâce à la commande Request Sense (non traitée dans ce document).
Commande interdite si le jeu de commandes Packet est absent.
Dans cette section, tous les codes sont écrits en assembleur AT&T (par exemple Gnu ASsembler). Ils sont susceptibles de contenir quelques erreurs car ils n'ont pas été testé tels qu'ils sont présentés ici (puisqu'ils ne sont pas complets). Ils constituent néanmoins une bonne base.
Pour savoir si un contrôleur est présent, il faut tester ces ports. Sur certains système, un port non attribué renverra toujours 0xff. Malheureusement, un port attribué peut aussi renvoyer 0xff et sur certains systèmes, la valeur est aléatoire. Vérifier cette valeur n'est donc pas un moyen sûr de détecter la présence d'un contrôleur.
Une autre méthode est de modifier la valeur d'un port en lecture/écriture et de vérifier ensuite que la modification a été effectuée.
Le registre de lecteur et de tête est en E/S. Aussi, nous pouvons l'utiliser pour tenter de détecter un contrôleur :
AL contient maintenant 0 (Maitre) ou 1 (Esclave)
AL contient maintenant 0 (Maitre) ou 1 (Esclave)
adapter_found: // contrôleur présent
La détection des disques installés sur un contrôleur est à mon sens plus difficile.
En fait, avec cette méthode, il faut bien choisir la commande à envoyer et le résultat qu'on en attend.
movw $PORT_HEAD_AND_DISK, %dx // 0x1f6, 0x176, 0xf6 ou 0x76
movl $TIMEOUT, %ecx // Nombre de tentatives de lecture de l'état du contrôleur
disque_present:
Une autre méthode, plus simple à mettre en œuvre et plus rapide, mais moins sûre, est de tenter de détecter les protocoles utilisés par les disques. Cette méthode est moins efficace car si un contrôleur n'existe pas, les valeurs que les ports peuvent retourner dépendent des machines et peuvent être totalement aléatoire, et donc, pourquoi pas, correspondre aux valeurs du standard sans qu'aucun disque ne soit présent.
L'idéal à mon avis est de commencer par la deuxième méthode puis, pour les disques détecté, vérifier avec la première. Dans la plupart des cas, le disque existe vraiment et cette vérification est assez rapide.
La commande 0xec permet d'obtenir des informations sur le disque ATA sélectionné. Cette commande n'a besoin que d'une information : le disque sélectionné. En retour, elle envoie 256 mots (double-octets) sur le registre de données (0x1f0, 0x170, 0xf0 ou 0x70).
movw $PORT_HEAD_AND_DISK, %dx // 0x1f6, 0x176, 0xf6 ou 0x76
movl $TIMEOUT, %ecx // Nombre de tentatives de lecture de l'état du contrôleur
transf: // effectuer le transfert
Il ne reste plus ici qu'à interpréter les valeurs lues.
Pour cela, je ne propose pas de code mais seulement des explications.
Pour initialiser un contrôleur, il faut utiliser le registre de contrôle (0x3f6, 0x376, 0x2f6 ou 0x276 en écriture seule).
Une question qu'il est judicieux de se poser est : "Pourquoi initialiser ?". En effet, il est possible, du moins d'après mes constatations, d'accèder aux disques même sans initialisation. En fait, cette opération permet ensuite de savoir si les disques sont des disques ATA ou ATAPI.
Depuis un disque ATA
La lecture fonctionne de la même manière que la lecture d'informations concernant le disque (voir plus haut) mais n'utilise pas les mêmes paramètres d'entrée. Voici ceux concernant la lecture :
En sortie, le disque envoie sur le port de donnée n*256 mots où n est le nombre de secteur à lire.
Depuis un disque ATAPI
Il faut utiliser la commande Packet suivante :
Le contrôleur renvoie les données lues ((2048 * nombre de secteurs) octets).
Note : L'adresse est le nombre de secteur est indiqué avec la norme Big Endian :
Vers un disque ATA
L'écriture se passe exactement de la même manière que la lecture si ce n'est que la commande est 0x30 et que les données ne sont pas transférées du disque à la mémoire mais de la mémoire au disque.
Vers un disque ATAPI
Dans le cas d'un CD-ROM, cette commande ne peut être utilisée que pour un graveur. N'ayant jamais implémenté cette commande, je préfère ne pas la détailler.
Une bonne prise en charge des erreurs est également un point important pour un pilote fonctionnel.
Pour cela, il faut lire le port d'erreur (0x1f1, 0x171, 0xf1 ou 0x71) après chaque commande. Le contenu de ce port est décrit plus haut. La documentation officielle est plus exhaustive sur ce port, dont la valeur peut parfois dépendre de la dernière commande.
Pour les commandes Packet, il faut utiliser la commande (Packet) Request Sense pour connaitre la valeur de la dernière erreur (cette commande n'est pas traitée dans ce document).
Les disques ATA et ATAPI fonctionnant différemment, il est important de pourvoir détecter le protocole utilisé par un disque. Cela se réalise de la manière suivante :
Attention, toutefois, si un seul disque se trouve sur un contrôleur, j'ai pu constaté que les valeurs pour le disque qui n'existe pas peuvent être éronnée. Cette méthode ne permet donc pas de détecter un disque.
Dans le cas d'un disque dur ATA, cette donnée, fixe, est fournie par l'instruction Identify Device (ECh) sous la forme du nombre de secteurs adressable en mode LBA ou, dans le cas d'un disque ne supportant pas ce mode, de la structure CHS logique du disque.
Au contraire, dans le cas d'un lecteur de CD-ROM ATAPI, dont le média est amovible, la capacité varie selon le média actuellement dans le lecteur. Il existe donc une commande Packet Read Capacity qui donne le numéro logique du dernier bloc de donné. Cette commande n'indique pas la capacité totale du CD, mais la quantité de données pressées (CD-ROM) ou gravées (CD-R/RW).
Voici la structure de cette commande :
La commande renvoie 8 octets de réponse :
ATAPI
Cette opération se réalise grâce à la commande Prevent Allow Medium Removal définies dans les SPC-2 (SCSI Primary Commands).
La structure de cette commande est la suivante :
Cette commande n'envoie pas de réponse.8 (DRQ bit - Data ReQuest) et Interrupt Reason (Sector Count) & 0x7
1 (REL=0; I/O=0 ; C/D=1). Le paquet de commande peut alors être envoyé par le port de données (0x1f0, 0x170, 0xf0, 0x70).8 (DRQ bit - Data ReQuest) et Interrupt Reason (Sector Count) & 0x7
0 (REL=0; I/O=0 ; C/D=0) si le contrôleur reçoit, ou Status & 0x8 8 (DRQ bit - Data ReQuest) et Interrupt Reason (Sector Count) & 0x7
2 (REL=0; I/O=1 ; C/D=0) si le contrôleur envoie.Quelques opérations courantes
Détecter les contrôleurs
movw $PORT_HEAD_AND_DISK, %dx // 0x1f6, 0x176, 0xf6 ou 0x76 selon le contrôleur testé
inb %dx, %al
movb %al, %bl
andb $0x10, %bl
shrb $4, %bl
cmpb $0, %bl
jz setslave
jmp setmaster
setmaster:
andb $0xef, %al
jmp set
setslave:
orb $0x10, %al
set:
outb %al, %dx
inb %dx, %al
andb $0x10, %al
shrb $4, %al
cmpb %bl, %al
jz no_adapter
jmp adapter_found
no_adapter: // contrôleur absentDétecter les disques
La méthode la plus sûre à mon avis est d'envoyer une commande au disque et d'attendre. Si la réponse tarde trop, on suppose que le disque testé n'existe pas. Cette méthode n'est sûr que si l'attente est suffisamment grande. Il faut noter que même le BIOS peut mettre quelques secondes à détecter l'absence d'un disque donc cette méthode n'est peut-être pas si mauvaise. Il faut faire attention à la commande que l'on choisit. En effet, selon que l'on veut détecter uniquement les disques ATA, ATAPI ou les deux, il faut choisir une commandes qui corresponde. En choisissant la commande 0xec et en attendant que le contrôleur soit prèt à recevoir des données, les disques ATAPI répondront mais ne seront jamais prêts pour un transfert. Le programme déduira donc que le disque n'existe pas.
movb $HEAD_AND_DISK, %al // 0xa0 pour tester le maitre, 0xb0 pour l'esclave
outb %al, %dx
movw $PORT_COMMAND, %dx // 0x1f7, 0x177, 0xf7 ou 0x77
movb $COMMAND, %al // Commande à utiliser (personnellement, j'utilise la commande 0xec, ce qui me permet du même coup de lire les informations du disque s'il est présent, dans ce cas, il ne faut pas oublier d'ajouter le code nécessaire à la récupération de ces informations)
outb %al, %dx
movw $PORT_STAT, %dx // 0x1f7, 0x177, 0xf7, 0x77
detect_disk_loop:
inb %dx, %al
andb $0xfe, %al // Contrôleur et disque
cmpb $0x50, %al // prêts ?
je disque_present
loop detect_disk_loop
disque_absent:
De même, si un contrôleur existe mais n'est branché qu'à un seul disque, les valeurs seront les mêmes quelques soit le disque sélectionné, comme s'il y avait deux disques.Obtenir quelques informations sur un disque
Pour les disque ATAPI, il faut utiliser la commande 0xa1 et ne pas oublier que les valeurs retournées n'ont pas toutes la même signification.
movb $HEAD_AND_DISK, %al // 0xa0 ou 0xb0
outb %al, %dx
movw $PORT_COMMAND, %dx // 0x1f7, 0x177, 0xf7 ou 0x77
movb $0xec, %al // ou 0xa1 pour les disques ATAPI
outb %al, %dx
movw $PORT_STAT, %dx // 0x1f7, 0x177, 0xf7, 0x77
wait_loop:
inb %dx, %al
andb $0xd8, %al // Contrôleur et disque
cmpb $0x58, %al // prêts à transférer ?
je transf
loop wait_loop
timeout: // Le disque n'est pas prêt
movl $OFFSET_BUFFER, %edi
movw $SEGMENT_BUFFER, %es
movl 0x100, %ecx
movw $PORT_DATA, %dx // 0x1f0, 0x170, 0xf0 ou 0x70
rep inswInitialiser un contrôleur
Il faut en effet positionner le bit 2 (le 3e bit en partant de la droite) pendant 4.8 microsecondes ou plus, puis le vider.
La procédure n'a donc rien de complexe en soit mais l'attente doit être programmé selon le système d'exploitation utilisé (et donc les fonctions d'API disponibles).Lire
0x1f2, 0x172, 0xf2 ou 0x72 Nombre de secteurs à lire 0x1f3, 0x173, 0xf3 ou 0x73 Numéro de secteur du 1e secteur à transférer 0x1f4, 0x174, 0xf4 ou 0x74 Octet inférieur du numéro de cylindre du 1e secteur à transférer 0x1f5, 0x175, 0xf5 ou 0x75 Octet supérieur du numéro de cylindre du 1e secteur à transférer 0x1f6, 0x176, 0xf6 ou 0x76 Disque et numéro de tête du 1e secteur à transférer Numéro de commande 0x20
Octet 1. 0xA8
Octet 2. 0
Octets 3-6. Adresse LBA du 1e secteur à lire
Octets 7-10. Nombre de secteurs.
Octet 11. 0
Octet 12. 0
Octet 3 = ( lba >> 24 ) & 0xff;
Octet 4 = ( lba >> 16 ) & 0xff;
Octet 5 = ( lba >> 8 ) & 0xff;
Octet 6 = lba & 0xff;
Octet 7 = ( count >> 24 ) & 0xff;
Octet 8 = ( count >> 16 ) & 0xff;
Octet 9 = ( count >> 8 ) & 0xff;
Octet 10 = count & 0xff;Écrire
Prise en charge des erreurs
Détecter le protocole
Après avoir obtenu le protocole d'un disque, on peut sélectionner l'autre disque pour en déterminer le protocole sans avoir à réinitialiser (tant que les valeurs n'ont pas été modifiées).Déterminer la capacité
0 Code opération : 25h
1 0h 2 0h 3 0h 4 0h 5 0h 6 0h 7 0h 8 0h 9 0h 10 0h 11 0h
0
Most Significant Byte
Adresse LBA du dernier
secteur adressable
Least Significant Byte
1 2 3 4
Most Significant Byte
Nombre d'octet dans un
secteur (normalement 2048)
Least Significant Byte
5 6 7 Interdire ou autoriser le retrait d'un média
0 Code opération : 1eh
1 0h 2 0h 3 0h 4 0h pour autoriser et 3h pour interdire 5 0h 6 0h 7 0h 8 0h 9 0h 10 0h 11 0h
| Registre | Mode CHS | Mode LBA |
| Registre de lecteur et tête, bit 6 | 0 | 1 |
| Numéro de secteur | Numéro du secteur | Bits 0 à 7 de l'adresse LBA |
| Numéro de cylindre, octet de poids faible | Numéro de cylindre, octet de poids faible | Bits 8 à 15 de l'adresse LBA |
| Numéro de cylindre, octet de poids fort | Numéro de cylindre, octet de poids fort | Bits 16 à 23 de l'adresse LBA |
| Registre de lecteur et tête, bits 0 à 3 | Numéro de tête | Bits 24 à 27 de l'adresse LBA |
Pour le reste, tout est identique.
Conversion d'une adresse CHS en adresse LBA et inversement
adresse logique = (numero de secteur - 1) + (numero de tête * nombre de secteurs par cylindre) + (numero de cylindre * nombre de secteurs par cylindre * nombre de têtes)
secteur CHS = entier(1 + reste de (adresse logique / nombre de secteurs par pistes))
tête CHS = entier(reste de ((adresse logique / nombre de secteurs par pistes) / nombre de têtes))
piste CHS = entier(adresse logique / (nombre de secteurs par cylindre * nombre de faces))
Considérons lba l'adresse logique, c le cylindre, h la tête, s le secteur, H le nombre de têtes et S le nombre de secteurs par cylindre, voici les mêmes formules dans une syntaxe de style C (types entiers) :
lba = (s - 1) + (h * S) + (c * S * H);
s = 1 + (lba % S);
h = (lba / S) % H;
c = lba / (S * H);
Les informations contenues dans ce document sont directement inspirées des spécifications officielles, mais sont très simplifiées. La section sources et références bibliographiques vous permettra d'approfondir votre connaissance des standard ATA/ATAPI.
Conclusion
Sources et références bibliographiques