Ir al contenido



Foto

Leer y escribir puertos hardware directamente en modo usuario


  • Por favor identifícate para responder
13 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.943 mensajes
  • LocationMadrid - España

Escrito 02 abril 2009 - 02:27

Posiblemente este post debiera ir en el subforo de Trucos y Consejos, pero al estar escrito en C y para animar esta sección, lo voy a publicar aquí­.

En Windows XP no se puede acceder a los puertos de forma natural con las funciones C inport, inportb, outpor y outportb; o con asm desde el modo usuario:



cpp
  1. __asm{   
  2.   mov AX,3    // salida utilizando los pines 2 y 3   
  3.   mov DX,0x378 // puerto paralelo 378h   
  4.   OUT DX,AX   
  5. } 



Para solventar el problema existen drivers y alguna utilidad encerrada en una dll.

Voy a colocar un código que funciona sin librerí­as ni drivers. El único requisito es que seamos Administradores del sistema. Para instalar un driver también necesitamos privilegios especiales, aunque no para usarlo después.

El truco está en usar una API poco conocida y que se usa para los debuggers, ZwSystemDebugControl que permite cierta comunicación entre el usuario y el Kernel.

Defino dos funciones para escribir y leer puertos, en este caso se leen y escriben BYTES. Las funciones se llaman, por similitud con las clásicas del C, InPortB y OutPortB.

Expongo un programa funcional de consola para ilustrar el tema, en este caso activaremos el Beep utilizando los puertos 42, 43, y 61. Puede utilizarse para leer o escribir el puerto paralelo (0x378), pero todo el mundo tiene altavoz del sistema y no todos disponen de dispositivos en el puerto paralelo tipo circuitos de diodos para comprobar los resultados.

Se debe preparar la aplicación para que enlace la librerí­a advapi32.lib


cpp
  1. //---------------------------------------------------------------------------
  2. #pragma hdrstop
  3.  
  4. //---------------------------------------------------------------------------
  5. #pragma argsused
  6.  
  7. //------------------------------------------------------------------------
  8. #include <windows.h>
  9. #include <stdio.h>
  10.  
  11. //#pragma comment(lib, "advapi32")
  12. //#pragma link "advapi32.lib"
  13.  
  14. #define NTAPI  __stdcall
  15.  
  16. typedef int NTSTATUS;
  17.  
  18. typedef enum _SYSDBG_COMMAND
  19. {
  20.   SysDbgSysReadIoSpace = 14,
  21.   SysDbgSysWriteIoSpace = 15
  22. }SYSDBG_COMMAND, *PSYSDBG_COMMAND;
  23.  
  24. typedef NTSTATUS (NTAPI * PZwSystemDebugControl) (
  25.     SYSDBG_COMMAND ControlCode,
  26.     PVOID InputBuffer,
  27.     ULONG InputBufferLength,
  28.     PVOID OutputBuffer,
  29.     ULONG OutputBufferLength,
  30.     PULONG ReturnLength
  31.     );
  32.  
  33. PZwSystemDebugControl ZwSystemDebugControl = NULL;
  34.  
  35. typedef struct _IO_STRUCT
  36. {
  37.     DWORD IoAddr;      // IN: Aligned to NumBYTEs,I/O address
  38.     DWORD Reserved1;    // Never accessed by the kernel
  39.     PVOID pBuffer;      // IN (write) or OUT (read): Ptr to buffer
  40.     DWORD NumBYTEs;    // IN: # BYTEs to read/write. Only use 1, 2, or 4.
  41.     DWORD Reserved4;    // Must be 1
  42.     DWORD Reserved5;    // Must be 0
  43.     DWORD Reserved6;    // Must be 1
  44.     DWORD Reserved7;    // Never accessed by the kernel
  45. }
  46. IO_STRUCT, *PIO_STRUCT;
  47.  
  48. BOOL EnablePrivilege (PCSTR name)
  49. {
  50.     HANDLE hToken;
  51.     TOKEN_PRIVILEGES priv = {1, {0, 0, SE_PRIVILEGE_ENABLED}};
  52.  
  53.     LookupPrivilegeValue(0, name, &priv.Privileges[0].Luid);
  54.     OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
  55.     AdjustTokenPrivileges (hToken, FALSE, &priv, sizeof priv, 0, 0);
  56.     CloseHandle (hToken);
  57.  
  58.     return (GetLastError() == ERROR_SUCCESS);
  59. }
  60.  
  61. BOOL Inicializa()
  62. {
  63.     HMODULE hNtdll;
  64.     hNtdll = LoadLibrary ("ntdll.dll");
  65.  
  66.     if(EnablePrivilege(SE_DEBUG_NAME)==FALSE){
  67.       printf("Usted no tiene suficientes privilegios\n");
  68.       return FALSE;
  69.     }
  70.  
  71.     if(hNtdll){
  72.       ZwSystemDebugControl = (PZwSystemDebugControl)GetProcAddress(hNtdll, "ZwSystemDebugControl");
  73.       return TRUE;
  74.     }
  75.     return FALSE;
  76. }
  77.  
  78. BYTE InPortB(int Port)
  79. {
  80.     BYTE Value;
  81.     IO_STRUCT io;
  82.  
  83.     io.IoAddr = Port;
  84.     io.Reserved1 = 0;
  85.     io.pBuffer = (PVOID)(PULONG)&Value;
  86.     io.NumBYTEs = sizeof(BYTE);
  87.     io.Reserved4 = 1;
  88.     io.Reserved5 = 0;
  89.     io.Reserved6 = 1;
  90.     io.Reserved7 = 0;
  91.  
  92.     ZwSystemDebugControl(SysDbgSysReadIoSpace, &io, sizeof(io), NULL, 0, NULL);
  93.     return Value;
  94. }
  95.  
  96. void OutPortB(int Port, BYTE Value)
  97. {
  98.     IO_STRUCT io;
  99.    
  100.     io.IoAddr = Port;
  101.     io.Reserved1 = 0;
  102.     io.pBuffer = (PVOID)(PULONG)&Value;
  103.     io.NumBYTEs = sizeof(BYTE);
  104.     io.Reserved4 = 1;
  105.     io.Reserved5 = 0;
  106.     io.Reserved6 = 1;
  107.     io.Reserved7 = 0;
  108.    
  109.     ZwSystemDebugControl(SysDbgSysWriteIoSpace, &io, sizeof(io), NULL, 0, NULL);
  110. };
  111.  
  112. void BeepOn (int Freq)
  113. {
  114.     BYTE b;
  115.  
  116.     if((Freq >= 20) && (Freq <= 20000)){
  117.         Freq = 1193181 / Freq;
  118.         b = InPortB(0x61);
  119.         if((b & 3) == 0){
  120.           OutPortB (0x61, (BYTE) (b | 3));
  121.           OutPortB (0x43, 0xb6);
  122.         }
  123.         OutPortB (0x42, (BYTE)Freq);
  124.         OutPortB (0x42, (BYTE)(Freq >> 8));
  125.     }
  126. }
  127.  
  128. void BeepOff (void)
  129. {
  130.     BYTE b;
  131.  
  132.     b = (InPortB(0x61) & 0xfc);
  133.     OutPortB(0x61, b);
  134. }
  135.  
  136. // Sólo funciona en Windows XP y superiores
  137. // Se debe ser Administrador del Sistema
  138. int main(void)
  139. {
  140.     if(Inicializa()==FALSE){
  141.       printf("Error al inicializar los puertos\n");
  142.       Sleep (2000);
  143.       return 0;
  144.     }
  145.  
  146.     // Aquí­ podemos utilizar las funciones InPortB y OutPortB
  147.  
  148.     // Activamos el altavoz del sistema durante 300ms con una frecuencia
  149.     // de 1000Hz
  150.     BeepOn(1000);  // frecuencia de 1000Hz
  151.     Sleep (300);
  152.     BeepOff();
  153.  
  154.     return 0;
  155. }



Para el puerto paralelo, pondrí­amos li siguiente, basado en el anterior código:


cpp
  1. Inicializa();
  2. BYTE B = InPortB(0x378);  // Lee del puerto paralelo
  3. OutPortB(0x378, 3);      // Escribe en el puerto paralelo el valor 3


Saludos.
  • 0

#2 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.111 mensajes
  • LocationMéxico

Escrito 02 abril 2009 - 02:57

Muy interesante amigo escafandra,

Gracias por animar esta sección :D

Salud OS

PD. me tome el atrevimiento de modificar el tí­tulo porque pienso que quisiste decir MODO en lugar de MUDO :)
  • 0

#3 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.943 mensajes
  • LocationMadrid - España

Escrito 02 abril 2009 - 03:21

Me alegro de que te parezca interesante, egostar, la verdad es que "estás a todas".

me tome el atrevimiento de modificar el tí­tulo porque pienso que quisiste decir MODO en lugar de MUDO :)

¿Si? bueno, a veces uno comete gazapos  :-#. Si, era "modo usuario" lo que querí­a decir. :)

Saludos.
  • 0

#4 seoane

seoane

    Advanced Member

  • Administrador
  • 1.254 mensajes
  • LocationEspaña

Escrito 02 abril 2009 - 03:36

Muy interesante  (y)
  • 0

#5 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.943 mensajes
  • LocationMadrid - España

Escrito 02 abril 2009 - 03:48

Muy interesante  (y)


Muchas gracias, seoane, por tu apreciación. Siempre es estimulante interesar a los maestros. :)

Saludos.
  • 0

#6 cHackAll

cHackAll

    Advanced Member

  • Administrador
  • 598 mensajes

Escrito 02 abril 2009 - 06:22

Lo dicho, buen aporte 8-|
  • 0

#7 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.943 mensajes
  • LocationMadrid - España

Escrito 03 abril 2009 - 01:53

Lo dicho, buen aporte 8-|

Me agrada que te parezca interesante. La opinión de los maestros es la mas valorada.

Saludos.  :)
  • 0

#8 WillyP

WillyP

    Member

  • Miembro Platino
  • PipPip
  • 22 mensajes

Escrito 04 abril 2009 - 04:17

Hola escafandra, muy interesante. Direccionando el puerto serie en vez del paralelo funcionará ?. Es sólo para XP o en Vista también? 
  • 0

#9 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.943 mensajes
  • LocationMadrid - España

Escrito 04 abril 2009 - 06:07

Hola escafandra, muy interesante. Direccionando el puerto serie en vez del paralelo funcionará ?. Es sólo para XP o en Vista también? 


Gracias WillyP. No puedo probarlo en Vista, pero es de suponer que la API ZwSystemDebugControl se mantenga en Vista y siguientes S.O. Está diseñada para los debuggers como el mismí­simo WinDbg, el debuger en modo Kernel de Microsoft. Al encapsular cada vez mas al Kernel, se hace necesario de un modo de comunicación para estos programas, que por otro lado son necesarios. Bien es verdad que la documentación es mí­nima.

Como comentaba al principio del post, para probar al 100% el funcionamiento de este código con el puerto paralelo, nos hace falta circuiterí­a. Ese es el motivo por el que usé el altavoz del sistema. Serí­a interesante que alguien que disponga de los elementos necesarios lo pruebe.

Las funciones leen y escriben un Byte, pero pueden adaptarse para tamaños mas largos. El puerto paralelo dispone de salida de un Byte, pines del 2 al 9, por lo que debe funcionar sin problemas tal cual está. En este sitio y aquí­ podemos encontrar información sobre el puerto paralelo y la circuiterí­a necesaria.

Saludos.
  • 0

#10 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 04 abril 2009 - 06:30

Hola muchachos, interesante el tema.

Tengo una pregunta al respecto:



asm
  1. OutPort(0x378, 3);      // Escribe en el puerto paralelo el valor 3



Esto escribe un byte en el puerto.La pregunta es se puede escribir solo uno de sus bits sin afectar los demas?

Un Saludo.

#11 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.943 mensajes
  • LocationMadrid - España

Escrito 04 abril 2009 - 06:59



asm
  1. OutPort(0x378, 3);      // Escribe en el puerto paralelo el valor 3



Esto escribe un byte en el puerto.La pregunta es se puede escribir solo uno de sus bits sin afectar los demas?

Hola Jose Fco, claro que se puede, solo tienes que decidir cual. En C puedes usar unas estructuras llamadas campos de bits, pero tambien puedes usar operadores lógicos binarios para determinar los bites en juego, como lo harí­as en asm.

De hecho si haces:

cpp
  1. OutPortB(0x378, 2); // 0000 0010b


Estas pasando un solo bit al puerto paralelo con lo que si tuvieses un led conectado al pin 3 (recuerda que enpiezan en el 2), se encenderí­a.

Saludos.

PD. He corregido un error en el nombre de la función en el primer post, poní­a InPort y OutPort, debe ser InPortB y OutPortB, la "B" significa que se lee o escribe un Byte. Esto es así­ porque como ya comenté puede adaptarse a otros tamaños de dato, y nos reservamos el nombre....

Por si quieres probar en C, ten en cuenta que es las mayúsculas y minúsculas no dan lo mismo, como en delphi o en asm.   
  • 0

#12 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 04 abril 2009 - 07:28

Ok pero seguimos escribiendo un byte.Solo que los demas estaran a cero y el segundo bit a uno.El problema es que si tenia otro led encendido en otro pin de este puerto pues se apagaria.O entendi mal el asunto?

Un Saludo.

#13 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.943 mensajes
  • LocationMadrid - España

Escrito 04 abril 2009 - 07:55

No, no lo has entendido mal. Solo que si tienes un led encendido es porque antes lo hiciste, ¿No?.

Supón que en un primer momento enciendes el led del pin 4 que corresponde al binario 0000 0100b. Supón que ahora quieres encender también el led del pin 6 que serí­a el 0001 0000b.Una simple operación or binaria nos darí­a el 0001 0100b que corresponde al 20 decimal o 14 hexadecimal.

Pues escribirí­amos:


cpp
  1. BYTE A = 0x10;  // 0001 0000b
  2. BYTE B = 0x04;  // 0000 0100b
  3. BYTE C = A | B; // C == 0x14 ó 0001 0100b
  4. OutPortB(0x378, C);



Con los operadores lógicos pones a uno o cero los bits que quieras y luego los escribes :)

Saludos.

  • 0

#14 Guest_Jose Fco_*

Guest_Jose Fco_*
  • Visitante

Escrito 04 abril 2009 - 08:20

Gracias amigo, la cosa seria tener en cuenta el estatus de los restantes para no afectarlos.Entonces parece que el registro del puerto paralelo en las PCs es de 8 bits y no se puede acceder a uno solo de ellos en forma directa.Cosa que no pasa con los micros que si nos permite escribir o leer uno solo de ellos sin tener que afectar los demas. ;)

Un Saludo Maestro y gracias por su tiempo. (y)