mardi 18 septembre 2007

Un driver caractère pour linux

Pour faire suite à l'article précédent, je vais vous présenter un deuxième driver "hello world", qui lui est "un vrai" pilote de périphérique.

Le source



#include linux/fs.h
#include linux/sched.h
#include linux/errno.h
#include linux/init.h
#include linux/module.h
#include linux/kernel.h
#include asm/current.h
#include asm/segment.h
#include asm/uaccess.h

/* le message à retourner */
char message[80]="bonjour monde";

/* ma fonction open. */
int mon_open(struct inode *inode,struct file *filep)
{
return 0;
}

/* ma fonction close. */
int mon_release(struct inode *inode,struct file *filep)
{
return 0;
}

/* ma fonction lecture. */
ssize_t mon_read(struct file *filep,char *buff,size_t count,loff_t *offp )
{
/* fonction pour copier le tampon depuis l'espace du noyau vers l'espace utilisateur*/
if ( copy_to_user(buff,message,strlen(message)) != 0 )
printk( "Kernel -> Erreur copie vers espace utilisateur\n" );
return strlen(message);

}

/* ma fonction lecture */
ssize_t mon_write(struct file *filep,const char *buff,size_t count,loff_t *offp )
{
/* On ne fait rien en écriture */
return 0;
}

/* Cette structure permet de définir les 4 fonctions de base que doit remplir */
/* un pilote caractère. Ces 4 fonctions sont celles décrites par les prototypes */
/* que l'on vient de décrire. */
/* Ces fonctions sont l'ouverture du périphérique, sa fermeture, l'écriture et */
/* la lecture. */
struct file_operations mon_fops={
open: mon_open,
read: mon_read,
write: mon_write,
release:mon_release,
};

MODULE_AUTHOR("olivier thebault");
MODULE_DESCRIPTION("Hello world");
MODULE_LICENSE("GPL");

static int hello_init(void) {
/* enregistre le driver auprès du système. */
/* On précise son numéro majeur, son nom */
/* et la structure file_operations qui */
/* contient les références aux fonction de */
/* base du driver. */
/* Ce driver ne fait que retourner le message */
/* "message". */
if(register_chrdev(234,"Bonjour Monde",&mon_fops)){
printk("<1>déclaration impossible");
}
return 0;
}


static void hello_exit(void) {
/* On désenregistre le driver */
unregister_chrdev(234,"Bonjour Monde");
}

module_init(hello_init);
module_exit(hello_exit);



L'installation

Une fois la commande insmod appelée, il faut déclarer le chemin /dev du périphérique avec
la commande mknod /dev/bonjour c 234 0.

Test

La commande cat /dev/bonjour devrait vous permettre de voir le message Bonjour Monde.

Comment écrire un module pour linux ?

Après avoir fait des essais autour d'un driver windows, j'étais quelque peu échaudé par ce genre d'activité.

C'est pourquoi, je me suis contenté de faire un petit driver expérimental sous linux. Rien de
fabuleux, juste pour m'habituer à la démarche. Pour linux, un driver peut se présenter sous le forme
d'un module, ou directement être compilé dans le noyau.

Préparation
Pour commencer il faut bien avoir installer le compilateur gcc et make. Ensuite il faut s'assurer que les headers du kernel soient présents. (j'ai fait mes premiers pas sur un noyau 2.6.18)
Ensuite à partir de là, il s'agit de créer des liens symboliques sur les entêtes du noyau, de la manière suivante :

ln -s /lib/modules/2.6.18-5-686/build/ /usr/src/linux
ln -s /usr/src/linux-headers-2.6.18-5-686 /lib/modules/2.6.18-5-686/build/

Maintenant, la compilation du module devrait marcher...

Mon premier module à un but unique : s'inscrire dans la liste des modules chargés.

Le code source (helloworld1.c)


#include linux/init.h
#include linux/module.h
#include linux/kernel.h

/* Les sources contiennent une série de macros permettant de faire */
/* des déclarations diverses au sujets des modules, comme ici la */
/* licence.*/
MODULE_LICENSE("GPL");

static int hello_init(void) {
printk("<1>Bonjour monde\n");
return 0;
}

static void hello_exit(void) {
printk("<1>Au revoir\n");
}

module_init(hello_init);
module_exit(hello_exit);

Le seule chose que ce driver fait, c'est d'écrire un message en log système lorsqu'on
charge et lorsque on le décharge. Il est un peu prématuré de parler de driver pour ce
petit module car il n'est pas capable de faire grand chose. Il ne possède pas de
numéro majeur/mineur non plus.

La compilation

  • Le Makefile

obj-m := helloworld1.o

  • Un petit script pour lancer la compilation

make -C /usr/src/linux-headers-2.6.18-5-686 M=`pwd` modules


Bien entendu, ce script est à adapter en fonction de la version du noyau.

L'installation

par la commande insmod ./helloworld.ko

ensuite pour vérifier : lsmod

Module Size Used by
helloworld1 1408 0
ppdev 8676 0
lp 11012 0
button 6672 0
.....

puis on le désinstalle : rmmod helloworld1.ko

lsmod

Module Size Used by
ppdev 8676 0
lp 11012 0
button 6672 0
ac 5188 0
......

dans le log système on trouve :

Sep 18 20:56:58 localhost kernel: Bonjour monde
Sep 18 20:58:11 localhost kernel: Au revoir

En fait, tout cela est très simple, en tout cas plus simple que sur un système windows.

dimanche 9 septembre 2007

Linux et les E/S en pascal

Les programmes DOS que je souhaite porter sous linux sont écrit avec turbo-pascal. Donc voici la version pascal de l'article précédent :


program portout;

Uses ports;

function ioperm(from: Cardinal; num: Cardinal; turn_on: Integer): Integer; cdecl; external 'libc';

const
BASEPORT=$378; //lpt1

begin
// on demande l'accès des adresses BASEPORT à BASEPORT+3
if (ioperm(BASEPORT, 3, 1))<>0 then
Halt(1);

// Initialisation de tous les signaux de données (D0-D7) à l'état bas (0)
port[BASEPORT] := 0;

// on libère l'accès des adresses BASEPORT à BASEPORT+3
if (ioperm(BASEPORT, 3, 0))<>0 then
Halt(1);

end.


Le compilateur utilisé est freepascal. Donc un simple fpc portout.pas suffit à le compiler.

programmation des ports E/S sous linux

En cherchant à porter d'anciens programmes dos vers linux, j'ai été confronté à la problématique de l'accès aux ports d'entrées/sorties du processeur. A l'époque du dos c'était simple, puisque il n'existait aucune protection à ce niveau, mais avec des systèmes en mode protégé cela est moins évident. Il existe plusieurs possibilités avec linux. L'une d'entre elle passe par des api système qui sont ioperm, outb et inb.
ioperm a pour rôle de demander l'autorisation au système d'accéder aux entrées sorties. Lorsque les opérations d'E/S sont terminés on demande à ioperm de libérer les autorisations.
Si le système accorde l'accès au peut appeler des api comme outb ou inb. Voici un exemple minimaliste en c pour illustrer cette technique :


/*
* programme très simple permettant d'accéder au port parallèle
*/

#include
#include
#include

#define BASEPORT 0x378 /* lp1 */

int main()
{

/* on demande l'accès des adresses BASEPORT à BASEPORT+3*/
if (ioperm(BASEPORT, 3, 1)) {
perror("ioperm");
exit(1);
}

/* Initialisation de tous les signaux de données (D0-D7) à l'état bas (0) */

outb(0, BASEPORT);

/* on libère l'accès des adresses BASEPORT à BASEPORT+3*/
if (ioperm(BASEPORT, 3, 0)) {
perror("ioperm");
exit(1);
}

exit(0);
}

Installation de l'environnement MPLAB sous linux

Cet environnement est conçu par Microchip pour le développement d'application pour les micro-contrôleurs de la famille PIC. Ces circuits permettent d'embarquer et d'enfouir de petits systèmes informatiques.
L' IDE MPLAB est prévu à l'origine pour windows. Compte tenu qu'il s'agit d'une application windows classique, qui ne semble pas utiliser d'api très complexe, je me suis dit qu'elle devait assez bien fonctionner sous linux grâce à wine. L'installation de MPLAB s'est en effet déroulé sans problème, le setup installshield fonctionnant normalement. Par contre la mise en route de MPLAB s'est accompagné d'erreurs COM sur ce qui semble être une gestion de la localisation. Une fois les boites de dialogues passé, la première mise en route semble fonctionner tout à fait normalement.

Par contre mes tests se sont bornés à quelques opérations simple. Ne possédant pas de platine de développement, je n'ai pas pu valider la connection à ce genre de matériel.

vendredi 7 septembre 2007

créer un connecteur CAS

L'objectif ici est de permettre le développement d'application et d'extention autour de CAS, à l'aide
d'eclipse europa. CAS est un serveur d'authentification SSO développé à l'origine par l'université de Yale.

La première étape consiste à importer CAS dans eclipse. Pour cela, on va utiliser l'import du fichier war d'eclipse
pour créer un nouveau projet. Eclipse propose en cours d'import de laisser les fichiers jar du projet dans le
répertoire lib ou de les convertir en projet. Il faut les laisser en tant que lib.

Puisque il s'agit d'un fichier war, eclipse va l'importer en tant que projet web dynamique.

à partir de là, pour créer un nouveau connecteur il faut coder une classe java implémentant l'interface AuthenticationHandler. Cette implémentation est simple,
puisqu'il s'agit en gros de valider un couple login/mot de passe.

un exemple de code des deux méthodes peut être le suivant


public boolean authenticate(Credentials credentials)
throws AuthenticationException {

UsernamePasswordCredentials upCredentials = (UsernamePasswordCredentials) credentials;
String username = upCredentials.getUsername();
String password = upCredentials.getPassword();
......validation du login/mot de passe
}

public boolean supports(Credentials credentials) {
return credentials instanceof UsernamePasswordCredentials;
}



L'opération suivante consiste à déclarer le connecteur dans CAS. Ceci se fait dans le fichier deployerConfigContext.xml qui se trouve dans WEBINF. La lecture de ce fichier permet de facilement déclarer notre connecteur.

Mapping automatique d’objet vers une source de donnée

Cette technique s’adapte bien à des données de type code/libellé. L’objectif est d’écrire ce genre de chose :

Var

ObjetSource :TobjectSource ;
….
Showmessage(ObjetSource.T253) ; // affiche la valeur associé à T253
….
Showmessage(ObjetSource.T255) ; // affiche la valeur associé à T255

Bien sûr, si j’ajoute une valeur T569 à ma source, je veux avoir automatiquement la propriété T569 sur mon objet, sans changer son source.

Cela veut dire que l'on a plus besoin de modifier l'objet de type TobjectSource . Cela veut dire aussi qu'une instance de cette classe n'a pas de propriété est qu'elle est capable de les créer à la volée au moment de l'exécution. Si cette classe n'a pas de propriété, on a donc pas besoin de les déclarer. (Monsieur de Lapalisse, 1/4 avant sa mort était encore en vie.)

Je m'explique : Sur un objet classique Delphi lorsque l'on souhaite rajouter une propriété on déclare dans le source de la classe un champ de type property : read...write...; et ensuite on compile. Le compilateur de delphi écrit en dur dans la structure du programme les listes des propriétés des classes (voir TypInfo.pas). On pourrait, à l'exécution, rajouter une propriété toto sur une classe quelconque (en tout cas celles qui ont été compilées grace aux directives $M+, $M-) en bidouillant (copie + ajout d'un enregistrement dans le RTTI) en assembleur la zone des données qui définissent une classe...(une aspirine??).

Ici le but du jeux est de faire ceci facilement (sans assembleur). Ceci est possible à l'aide de l'interface IDispatch.

Cela revient à écrire ce genre de chose:

 z:= ObjetSource.T253;

au moment ou l'on cherche à évaluer ObjetSource.T253, il y a un bout de code de l'objet qui récupère la chaine 'T253’ et qui cherche la valeur correspondante dans la base et ensuite au moment de l'affectation renvoie la valeur qu'il a trouvé. Il va de soi que la propriété T253 n'a jamais été déclaré dans le source.( je radote, je sais)

Je vous laisse découvrir le code pour voir comment tout cela fonctionne. Il s'agit juste d'une ébauche pour valider le principe. La partie accès base de donnée n'est pas implémentée.

unit Unit3;
interface
uses ComObj, sysutils, classes;
type
 TobjectSource  = class(TInterfacedPersistent, IDispatch)
 private
   FValueName: string;
 protected
   function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
   function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
   function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
   function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
   function GetValueString(ValueName: string): string;
   procedure SetValueString(ValueName, Value: string);
 end;

implementation uses dialogs, ActiveX, variants;


function TobjectSource .GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;

begin
Result := 0;

end;


function TobjectSource .GetTypeInfoCount(out Count: Integer): HResult; begin
Result := 0;

end;


function TobjectSource .Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;

var
 zPutValue: OleVariant;
 zResult: OleVariant;
begin
 // on veut lire le paramètre
 if (Flags and DISPATCH_PROPERTYGET) = DISPATCH_PROPERTYGET then
 begin
   zResult := POleVariant(VarResult)^;
   // on peut faire un varType pour savoir si on a un string ou un integer à récupéré (ou autre chose)
   POleVariant(VarResult)^ := GetValueString(FValueName);
 end;
 // on veut écrire le paramètre
 if (Flags and DISPATCH_PROPERTYPUT) = DISPATCH_PROPERTYPUT then
 begin
   zPutValue := POleVariant(Params)^;
   // on peut faire un varType pour savoir si on a un string ou un integer à écrire (ou autre chose)
   SetValueString(FValueName, zPutValue);
 end;
 result := S_OK;

end;


function TobjectSource .GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; var
 P: POleStrList;

begin
 P := POleStrList(Names);
 FValueName := P^[0];
 result := S_OK;

end;


function TobjectSource .GetValueString(ValueName: string): string; begin
 // retourne la valeur correspondant au nom passé en paramètre
 Result:='On va dire que ceci est la chaine que lon a récupéré en base';

end;

procedure TobjectSource .SetValueString(ValueName, Value: string);
begin
 // affecte la valeur....
 showmessage('On va écrire en base:'+ValueName + '=' + Value);

end;