Você está na página 1de 16

DESARROLLO DE DRIVERS CON

DRIVERWORKS

El asistente de DriverWorks
Para iniciar Visual C++ con soporte para DDK utilizaremos: Programas->Compuware
DriverStudio->Tools->DDK Build Settings(SetDDKGo) ->launch program.
A continuacin se explican los pasos seguidos para generar el driver de la FPGA; algunos
de los pasos son especficos para PCI, y otros son comunes a todos los drivers que se desarrollen
con DriverWorks.

PASO 1
Escogemos ruta y nombre para el driver.

PASO 2
En nuestro caso el tipo de driver a utilizar es WDM Driver; puesto que el driver que se
desea conseguir es el ms genrico posible en Windows.

PASO 3
WDM Function Driver: nuestro driver va a controlar un dispositivo hardware directamente,
no a travs de otro driver.

PASO 4
Bus PCI. Rellenamos los datos del fabricante y dispositivo. En nuestro caso:

PCI Vendor ID: E159

PCI Device ID: 0002

PCI Subsystem ID: 00010080

PCI Revision ID: 00

PASO 5
Establecemos los nombres para la clase que se va a generar, y el nombre de fichero.

PASO 6
Dependiendo de lo que se haya implementado en la placa. Las opciones marcadas por
defecto crean funciones de lectura y escritura genricas. Quitaremos read y write, puesto que el
dispositivo que tenemos para realizar ejemplo se puede leer y escribir en diferentes direcciones,

por lo que las funciones de lectura y escritura genricas no tienen mucho sentido.

PASO 7
Si utilizamos la primera opcin, las peticiones que se hagan al driver se atendern
directamente, en lugar de encolarlas. En caso de elegir encolar las peticiones en el cdigo
generado se encontrarn las sentencias necesarias para ir pasando de una peticin a la siguiente
en la cola, lo cual corresponde a las siguientes opciones.

PASO 8
Parmetros que se guardarn en el registro de windows. El que viene por defecto se puede
dar por bueno.

PASO 9
En este paso es donde definiremos los recursos que utilizar nuestro dispositivo.

Pestaa Resources
Aadiremos tantos IO Ports como direcciones base tengamos contempladas para
nuestro dispositivo; en nuestro caso aadiremos un IO Port con la direccin base 0 (los
dispositivos WDM son plug and play, y la direccin real se asignar; aqu se pone el ndice
de la direccin base a utilizar.).

Pestaa Interface
Lo normal es utilizar el interfaz WDM, definiendo el class GUID, usndose el que
define el propio asistente. El interfaz de enlace se utiliza cuando se hace un driver ms
genrico, para sistemas operativo que no utilizan el WDM para crear drivers

Pestaa Buffers
Siempre he utilizado Direct, puesto que nuestra placa cuando se desea leer o
escribir en un registro se desea hacerlo en un instante determinado; es decir, la lectura y la
escritura son directas. Slo se utilizara BUFFER si deseamos crear una pequea reserva
de memoria para realizar a travs e ella toda la transferencia

Pestaa Power
Aqu escogeremos si el driver manejar el estado de energa de la placa, es decir;
qu hacer al iniciarla, qu hacer si se va a suspender, qu hacer al recuperarse, etc.

PASO 10
Aqu aadiremos todas las funciones que se utilizarn para acceder a la placa; en este

paso conviene pensar bien todas las operaciones que se desean aadir, aunque se incluyan
algunas que despus no se utilicen, puesto que despus en el cdigo es muy fcil cometer errores
al aadir una nueva funcin (ver apartado Cmo aadir una nueva funcin de control desde el
cdigo fuente?).
Al aadir desde el asistente una nueva IOCTL, se nos pedirn los siguientes parmetros:

El nombre de la funcin.

El ordinal de la funcin (normalmente usaremos el mismo que se nos da).

El mtodo. En los tres modos que se muestran a continuacin, viene implementado parte
del acceso a los parmetros pasados y al lugar donde dejar el resultado; lo nico que
habr que hacer es leer lo que nos interese, y proporcionar los resultados que nos
interese. Los modos son:
o

Buffered (Se utilizarn unos buffers destinados a estas operaciones).

In_Direct Out_Direct (No utilizan buffers, el mapeo de memoria se hace de forma


diferente; de todos modos el acceso a los parmetros y la forma de devolver
resultados ya viene implementada o al menos explicada en el cdigo generado por
el asistente).

Neither (En este modo, y bajo el punto de vista de DriverWorks se utilizar un tercer
Buffer; de todos modos tambin debe venir implementado parte del acceso).

El tipo de acceso:
o

Any: cualquier tipo de acceso.

Read: para lectura.

Write: para escritura.

Read_Write: para lectura/escritura (en realidad no conozco muy bien la diferencia


de este tipo con el Any).

PASO 11
Normalmente en esta pestaa se pide que genere una aplicacin de prueba, que haga una
seal en el punto de entrada del driver, que genere cdigo para depuracin (Trace Code), se
cambia el smbolo que se utilizar para etiquetar las reas de memoria que el driver reserve, de
manera que haya algo que se entienda mejor (tener en cuenta en este punto que la etiqueta que
se ponga aqu debe ir escrita al revs). Se debe quitar la opcin de la configuracin de 64 bits,
puesto que no tenemos de momento necesidad de drivers para un ordenador de 64bits de ancho
de palabra de datos. Tras este ltimo paso hacemos click en Finish y se nos generar el cdigo
fuente segn todas las opciones antes proporcionadas.

Conviene salvar/guardar el contenido de la pantalla final de informacin de los pasos que


hemos realizado para disear el driver.

Cmo aadir una nueva funcin de control desde el


cdigo fuente?
Lo explicar con un ejemplo. Supongamos que nuestro driver se llama DRIVER1. Abrimos
el fichero DRIVER1ioctl.h y le aadimos una lnea del tipo:
#define DRIVER1_IOCTL_nombre_del_ioctl_nuevo CTL_CODE
(FILE_DEVICE_UNKNOWN, ordinal, mtodo de acceso, acceso permitido)

En ordinal pondremos el siguiente a los ya definidos en el fichero.


En mtodo de acceso pondremos:

METHOD_BUFFERED (se utilizarn unos buffers destinados a estas operaciones).

METHOD_IN_DIRECT METHOD_OUT_DIRECT (no utilizan buffers, el mapeo de


memoria se hace de forma diferente; de todos modos el acceso a los parmetros y la
forma de devolver resultados ya viene implementada o al menos explicada en el cdigo
generado por el asistente).

METHOD_NEITHER (en este modo, y bajo el punto de vista de DriverWorks se utilizar un


tercer Buffer; de todos modos tambin debe venir implementado parte del acceso).
En acceso permitido pondremos:

FILE_ANY_ACCESS: cualquier tipo de acceso.

FILE_READ_ACCESS: para lectura.

FILE_WRITE_ACCESS: para escritura.

FILE_READ_WRITE_ACCESS: para lectura/escritura.


Abrimos el fichero DRIVER1Device.h y DRIVER1Device.cpp: en el primero aadimos una

funcin en la seccin public cuyo prototipo debe ser del tipo:


NTSTATUS DRIVER1_IOCTL_nombre_del_ioctl_nuevo_Handler (KIrp I);

En el segundo fichero implementamos dicha funcin:


NTSTATUS DRIVER1_IOCTL_nombre_del_ioctl_nuevo_Handler (KIrp I)
{
NTSTATUS status=STATUS_SUCCESS;
//Codigo de la funcin.
I.Information() = 0; //En el campo Information() debemos poner el
nmero de bytes ledos o escritos, segn la operacin.
return status;
}

Yo normalmente utilizo el METHOD_BUFFERED, de forma que los parmetros de

entrada/salida de la funcin los tenemos en un buffer que est en I.IoctlBuffer(); para obtener los
de entrada simplemente definiremos un puntero y haremos un cast a I.IoctlBuffer para asignarlo a
este puntero, por ejemplo:
ULONG *inbuffer=(ULONG *)I.IoctlBuffer();

Para el buffer de salida deberemos utilizar tambin el I.IoctlBuffer, por lo que lo es


recomendable recoger los parmetros de entrada en variables locales, para luego rellenar el buffer
con los parmetros de salida. En I.Information() deberemos colocar la cantidad de datos que se
devuelven (nmero de bytes o numero de palabras). Siempre que se devuelva un STATUS que
signifique error, deberemos poner I.Information() a cero. Hasta el momento siempre hemos
utilizado registros para acceder al dispositivo; por lo que tendremos una variable en el driver (si le
hemos asignado el recurso en el paso 9 del asistente); dicha variable tiene que ser del tipo
KIoRange, y podremos encontrarla en el fichero .h de nuestro driver. El proceso normal para
realizar una entrada/salida con este tipo de variables, dentro del driver, es el siguiente:

Comprobamos si la variable est en un estado vlido, comprobando el resultado de


variable_iorange->IsValid()

En caso afirmativo realizamos la operacin de entrada/salida. Para llevar a cabo dicha


entrada salida utilizaremos las siguientes funciones:
o

Operaciones con bytes:

variable_iorange->inb(ULONG ByteOffset): devuelve un byte leido en la


direccin ByteOffset a partir de la direccin base.

variable_iorange->inb(ULONG ByteOffset, PUCHAR Buffer, ULONG Count):


lee y vuelca en el buffer apuntado por Buffer tantos bytes como indique
Count; leidos a partir del ByteOffset+Direccin Base.

variable_iorange->outb(ULONG ByteOffset, UCHAR Data): escribe Data en


la direccin indicada por ByteOffset+Direccin Base.

variable_iorange->outb(ULONG

ByteOffset,

PUCHAR

Buffer,

ULONG

Count): escribe en la direccin ByteOffset+Direccin Base los bytes


almacenados en el buffer apuntado por Buffer; escribe Count bytes de los
almacenados en Buffer.
o

Operaciones con palabras (mismo funcionamiento que las anteriores pero lee datos
de 16bits):

USHORT inw(ULONG ByteOffset);

VOID variable_iorange->inw(ULONG ByteOffset, PUSHORT Buffer, ULONG


Count);

VOID outw(ULONG ByteOffset, USHORT Data);

VOID outw(ULONG ByteOffset, PUSHORT Buffer, ULONG Count);

Operaciones con long (mismo funcionamiento que la anterior pero lee datos de
32bits):

ULONG ind(ULONG ByteOffset);

VOID ind(ULONG ByteOffset, PULONG Buffer, ULONG Count);

VOID outd(ULONG ByteOffset, ULONG Data);

VOID outd(ULONG ByteOffset, PULONG Buffer, ULONG Count);

Una vez hecho esto buscamos la funcin DeviceControl(KIrp I); cuyo cuerpo tendr una
sentencia switch, en la que aadimos un nuevo caso llamado como nuestro IOCTL, y dentro del
cual llamamos a la funcin definida anteriormente.
NOTA: para depurar el funcionamiento interno de cualquier parte del driver podemos
mostrar mensajes del sistema utilizando una instruccin del tipo t<<Mensaje a mostrar<<EOL;
para ver estos mensajes utilizaremos la herramienta DriverMonitor que viene en Compuware
DriverStudio; esta herramienta suele estar en: Programas->Compuware DriverStudio->Tools>DriverMonitor.

Cmo llamar a las funciones del driver desde una


aplicacin.
Tenemos dos formas de llamar al driver desde una aplicacin:
1. Creando un enlace simblico al dispositivo.
2. Exportando un interface. sta es la opcin ms comn puesto que para drivers WDM se
recomienda, y debido a que preserva las credenciales de seguridad del identificador del
dispositivo, garantizndose la creacin de una forma de acceder al dispositivo nica e
independiente del lenguaje utilizado.

Abriendo un manejador de un dispositivo que exporta un interface.


DriverWorks nos proporciona dos clases que nos ayudan en esta tarea; CdeviceInterface y
CdeviceInterfaceClass.
CdeviceInterfaceClass encapsula informacin acerca de todas las interfaces de dispositivo para una
clase de dispositivo en particular.
Una aplicacin puede usar una instancia de CdeviceInterfaceClass para obtener una o ms
instancias de CdeviceInterface; esta ltima abstrae una sola interfaz de dispositivo. Su funcin
DevicePath() devuelve un puntero a un path que puede pasarse a la funcin CreateFile para abrir el
dispositivo.
Abajo se muestra un ejemplo que aparece en el driver desarrollado (es una de las funciones que el
propio DriverWorks crea para la aplicacin de testeo, y que he aprovechado en la DLL):
HANDLE OpenByInterface(
GUID* pClassGuid, // points to the GUID that identifies the
interface class
DWORD instance, // specifies which instance of the
enumerated devices to open
PDWORD pError // address of variable to receive error
status
)
{
HANDLE hDev;
CDeviceInterfaceClass DevClass(pClassGuid, pError);
if(*pError != ERROR_SUCCESS)
return INVALID_HANDLE_VALUE;
CDeviceInterface DevInterface(&DevClass, instance, pError);
if(*pError != ERROR_SUCCESS)
return INVALID_HANDLE_VALUE;
hDev = CreateFile(DevInterface.DevicePath(),
GENERIC_READ | GENERIC_WRITE,

FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if(hDev == INVALID_HANDLE_VALUE)
*pError = GetLastError();
return hDev;
}

Realizando operaciones de E/S en el dispositivo.


Una vez que la aplicacin tiene un descriptor de dispositivo vlido se puede utilizar
llamadas al API Win32 para generar peticiones al dispositivo. La tabla muestra la correspondencia
entre la llamada de WinAPI y el driver.
Win32 API

DRIVER_FUNCTION_xxx KDevice subclass member function


IRP_MJ_xxx

CreateFile

CREATE

Create

ReadFile

READ

Read

WriteFile

WRITE

Write

DeviceIoControl DEVICE_CONTROL

DeviceControl

CloseHandle

CLOSE

Close

CLEANUP

CleanUp

Si el dispositivo no soporta alguna de las funciones, la llamada al API causa un error de


funcin no vlida.
Ejemplo de llamada completa a un dispositivo, para leer un byte; se inicializa el dispositivo
y luego se lee:
ULONG bufInput[IOCTL_INBUF_SIZE]; // Input to device
CHAR bufOutput[IOCTL_OUTBUF_SIZE]; // Output from device
ULONG nOutput; // Count written to bufOutput
DWORD Error;
hDevice = OpenByInterface( &ClassGuid, 0, &Error);
if (hDevice == INVALID_HANDLE_VALUE)
{
Exit(1);
}
bufInput[0]=wPortAddr;

if (!DeviceIoControl(hDevice,
IOCTL_READ_BYTE,
bufInput,
IOCTL_INBUF_SIZE,
bufOutput,
IOCTL_OUTBUF_SIZE,
&nOutput,
NULL)
)
{
Exit(1);
}
CloseIfOpen();
return bufOutput[0];

Cmo crear una DLL en Visual C++.


(Obtenido del tutorial de National Instruments)
1. Crear un proyecto DLL.

Abrimos un nuevo proyecto en Visual C++, del tipo Win32 Dinamic-Link Library;
escogeremos del tipo de proyecto A simple DLL project. Esto crea un proyecto de
DLL con un fichero de cdigo fuente que tiene el mismo nombre que el proyecto.
Tambin genera un fichero stdafx.cpp; que es necesario, pero normalmente no
necesitars editarlo.

2. Editar el cdigo fuente.

Cada DLL debe tener una funcin llamada DllMain, que ser el punto de entrada a
la DLL; a no ser que se necesite una inicializacin compleja, o la DLL sea para un
nico propsito, bastar con la funcin creada al crear el proyecto. En caso
contrario, complete la funcin.

A continuacin inserte las funciones que considere necesarias en la DLL.

En este punto puedes compilar y enlazar la DLL, pero no se exportar ninguna


funcin, as que no ser de mucha utilidad.

3. Exportar smbolos.

Para podder acceder a las funciones de la DLL desde una aplicacin es necesario
indicar al compilador que exporte los smbolos deseados.

Para evitar que Visual C++ compile las funciones a modo C++, declararemos todas
las funciones a exportar como extern C en la declaracin de la funcin.
extern C int suma(int x, int y);

Para exportar la funcin hay dos maneras. La primera y ms simple es utilizar la


directiva __declspec(dllexport) en el prototipo de la funcin que se desee exportar.
Se deber de incluir la directiva tanto en la declaracin de la funcin como en la
definicin.
extern C __declspec(dllexport) int suma(int x, int y);
...
extern C __declspec(dllexport) int suma(int x, int y)
{
...
}

La segunda manera es utilizar un fichero .def para declarar qu funciones deben


ser exportadas. Dicho fichero es un fichero de texto que contiene informacin que
utiliza el enlazador para decidir qu exportar. Tiene el siguiente formato:

LIBRARY <Name to use inside DLL>


DESCRIPTION "<Description>"
EXPORTS
<First export> @1
<Second export>@2
<Third export> @3
...

4. Especificando la convencin de llamada a funciones.

Tipo C: utilizamos la directiva __cdecl en la declaracin y en la definicin de la


funcin:
extern C __declspec(dllexport) int __cdecl suma(int x, int y);
...
extern C __declspec(dllexport) int __cdecl suma(int x, int y)
{
...
}

Estndar: utilizamos la directiva __stdcall en la declaracin y en la definicin de la


funcin.
extern C __declspec(dllexport) int __stdcall suma(int x,

int y);
...
extern C __declspec(dllexport) int __stdcall suma(int x,
int y)
{
...
}

5. Compilando y enlazando la DLL

Una vez que tenemos escrito el cdigo, las funciones a exportar declaradas como
tales, y establecida la convencin de llamada a funciones, ests listo para construir
la DLL. Ve al men Build->Build <tu proyecto>. Debera compilar y enlazar la
DLL.

ANDRES_PIO_DIO.DLL: Funciones disponibles.


1. DriverInit
Inicializa el driver poniendo las variables dentro de la dll al valor necesario.
unsigned int __stdcall PIODIO_DriverInit(void);

2. PIODIO_InputByte
Recoge un byte de entrada del dispositivo. Parmetros:

wPortAddr: offset del registro del que se leer.

Devuelve: el byte ledo.


unsigned char __stdcall PIODIO_InputByte(unsigned long wPortAddr);

3. PIODIO_OutputByte
Escribe un byte en el dispositivo. Parmetros:

wPortAddr: offset del registro en el que se escribir.

bOutputValue: byte a escribir.

void __stdcall PIODIO_OutputByte(unsigned long wPortAddr, unsigned


short int bOutputValue);

4. PIODIO_OutputMatrixDLL
Escribe una matriz en el dispositivo. Parmetros:

wPortAddr: offset del registro en el que se escribir la matriz.

wIndexAddr: offset del registro donde se escribe el ndice que recorre la matriz.

width: valor del ancho de la matriz.

height: valor del alto de la matriz.

bOutputValue: puntero a los datos.

NOTA: por compatibilidad con LabView la matriz debe ser bidimensional, pero slo
utilizaremos la primera fila, se considerarn datos tiles a partir de la 4 posicin.
void __stdcall PIODIO_OutputMatrixDLL(unsigned long
wPortAddr,unsigned long wIndexAddr, unsigned long width, unsigned long

height, short int **bOutputValue);

5. PIODIO_InputMatrixDLL
Lee una matriz del dispositivo. Parmetros:

wPortAddr: offset del registro del que se leer la matriz.

wIndexAddr: offset del registro donde se escribe el ndice que recorre la matriz.

width: valor del ancho de la matriz.

height: valor del alto de la matriz.

bInputValue: aqu es donde se recogern los datos.

NOTA: por compatibilidad con LabView la matriz debe ser bidimensional, pero slo
utilizaremos la primera fila, se considerarn datos tiles a partir de la 4 posicin.
void __stdcall PIODIO_InputMatrixDLL( unsigned long wPortAddr,
unsigned long wIndexAddr, unsigned long width, unsigned long height,
short int **bInputValue);

6. PIODIO_Output1DArray
Pasa un array de 1dimension al dispositivo. Parmetros:

wPortAddr: offset del registro en el que se escribir el array.

wIndexAddr: offset del registro donde se escribe el ndice que recorre el array.

length: longitud del array.

bOutputValue: array de los bytes a escribir.

void __stdcall PIODIO_Output1DArray(unsigned long wPortAddr, unsigned


long wIndexAddr, unsigned long lenght, unsigned char *bOutputValue);

7. PIODIO_Input1DArray
Lee un array de 1dimension del dispositivo. Parmetros:

wPortAddr: offset del registro del que se leer el array.

wIndexAddr: offset del registro donde se escribe el ndice que recorre el array.

length: longitud del array.

bInputValue: array donde se depositan los bytes ledos.

void __stdcall PIODIO_Input1DArray(unsigned long wPortAddr, unsigned


long wIndexAddr, unsigned long length, unsigned char *bInputValue);

Você também pode gostar