crear driver

16
DESARROLLO DE DRIVERS CON DRIVERWORKS

Upload: tecalexmds

Post on 03-Oct-2015

216 views

Category:

Documents


0 download

DESCRIPTION

Como crear drivers

TRANSCRIPT

  • 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).

    o 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).

    o 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.

    o Read: para lectura.

    o Write: para escritura.

    o 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);

    o 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

  • 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_xxxIRP_MJ_xxx

    KDevice subclass member function

    CreateFile CREATE Create

    ReadFile READ Read

    WriteFile WRITE Write

    DeviceIoControl DEVICE_CONTROL DeviceControl

    CloseHandle CLOSE

    CLEANUP

    Close

    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 DESCRIPTION "" EXPORTS @1 @2 @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 . 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);