G A C E T A   D E   L I N U X
...haciendo a Linux un poco más divertido!

Escribiendo un driver de dispositivo de red - Parte 1
Por Bhaskaran
Traducción al español por Jesus Marcos
el día 19 de Agosto 2003, para La Gaceta de Linux

Introducción

Este artículo ayudará al lector a comprender y desarrollar un driver de red para una tarjeta ethernet en Linux. Como observación, el desarrollo del driver se realizó en C y como un módulo, así que asumo que sus lectores están famirializados con el entorno de C y Linux. El documento pretende mostrar únicamente algunos puntos esenciales en la construcción de un driver para una tarjeta de red. (Para mejoras y profesionales por favor, diríjanse a la lista de código Linux).

Redes Linux y tarjetas PCI

Es evidente que el soporte para red es inherente al kernel de Linux. Se podría ver también a Linux como uno de los más seguros sistemas operativos de red disponibles en el mercado. Internamente el kernel de Linux implementa la pila del protocolo TCP/IP. Es posible dividir el código de red en partes - una que implemente los protocolos actuales (el directorio /usr/linux/net/ipv4) y otra que implemente drivers de dispositivo para varios hardware de red (/usr/src/linux/drivers/net ).

El código del kernel para TCP/IP está escrito de forma que es muy simple agregar drivers para muchos tipos de canales de comunicación reales (o virtuales) sin preocuparse demasiado sobre el funcionamiento de la red ni del código de la capa de transporte. Exactamente requiere un módulo estándar, conectando la tarjeta hardware a la interfaz del software actual. El hardware consiste en una tarjeta Ethernet en lugar de una LAN o un módem en internet.

Hoy en día  hay disponibles muchas tarjetas de red en el mercado, una de ellas es la tarjeta PCI ethernet RTL8139. Las tarjetas RTL8139 son un tipo de dispositivos 'plug and play', conectadas a la cpu a través del esquema del bus PCI. PCI son las siglas de Interconexión de Componentes Periféricos, es un set completo de especificaciones definiendo cómo diferentes partes de una computadora interactúan con otras. La arquitectura PCI se diseñó como reemplazo del anterior estándar ISA debido a sus prometedoras características como la velocidad de transferencia de datos, naturaleza independiente, simplificación al añadir y eliminar un dispositivo, etc.

Bases del trabajo en red

Puede configurar su PC para red a través del comando netconfig. Éste configura las direcciones de comunicación (dirección IP dada como cuatro octetos), máscara de red, gateway, nombre del servidor primario, etc a través de un proceso automatizado. Una vez hecho esto, la máquina Linux escucha mensajes a la dirección IP asignada.

Otra manera importante es detectando manualmente y configurando una tarjeta de red, para lo cual el comando ifconfig es usado. Una salida típica del comando ifconfig sin ningún argumento se muestra abajo (puede variar de sistema a sistema, dependiendo de la configuración).


eth0      Link encap:Ethernet  HWaddr 00:80:48:12:FE:B2
	  RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100 
          RX bytes:0 (0.0 b)  TX bytes:600 (600.0 b)
          Interrupt:11 Base address:0x7000 

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:4 errors:0 dropped:0 overruns:0 frame:0
          TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:336 (336.0 b)  TX bytes:336 (336.0 b)

Esta salida nos muestra que tengo ejecutándose una interfaz para eth0 y lo, que corresponden a una tarjeta ethernet y a una interfaz loopback respectivamente. eth0 es el nombre por defecto asignado a la verdadera interfaz hardware para la tarjeta de red realtek 8139. El listado también nos informa sobre sus direcciones de hardware (HWaddr), internet (inet addr), Broadcast (Bcast), Máscara(Mask) con alguna otra información estadística sobre la transferencia de datos, incluyendo la unidad de datos máxima que puede ser transferida (MTU), número de paquetes recibidos (RX), número de paquetes transferidos (TX), colisiones, etc. El comando ifconfig también puede ser empleado para levantar la interfaz si ésta no ha sido detectada en el momento del arranque. También puede ser asociado con una dirección IP como se muestra a continuación.


	ifconfig eht0 192.9.200.1 up  
Activa la tarjeta ethernet para escuchar a la dirección IP 192.9.200.1, un cliente de clase C. Al mismo tiempo ifconfig también puede ser usado para desactivar una interfaz activada. Esto se muestra a continuación.

	ifconfig eth0 down
Lo mismo es aplicable a la interfaz loopback. Éstas también son posibles.

	ifconfig lo 192.9.200.1 up
	ifconfig lo down
'ifconfig' soporta muchas opciones que pueden ser descubiertas consultando las páginas man.

Otro comando que necesita referencia es netstat. Muestra conexiones de red, tablas de enrutamiento, estadísticas de la interfaz, conexiones de máscara y miembros multicast. Una lista exhaustiva de opciones puede ser encontrada en las páginas man.

Iterfaz del Kernel

El Kernel, como siempre, provee concisas pero eficientes estructuras de datos y funciones para realizar una programación elegante, comprensible incluso para un programador regular, y la interfaz suministrada es completamente independiente de la suit del protocolo de alto nivel. Para un rápido repaso de las estructuras de datos del kernel, funciones, interacciones entre el driver y la capa superior de la pila del protocolo, primero intentamos desarrollar un driver independiente del hardware. Una vez que tenemos un concepto general podemos profundizar en la plataforma real.

Cuando un módulo es cargado en la memoria del kernel, éste solicita los recursos necesarios para su funcionamiento como puertos I/O, IRQ etc. De forma similar cuando un driver de red se registra, éste inserta una estructura de datos para cada nueva interfaz detectada en una lista global de dispositivos de red.

Cada estructura está definida por un item struct net_device. la declaración del dispositivo rtl8139 puede hacerse como sigue


	struct net_device rtl8139 = {init: rtl8139_init};

La estructura struct net_device está definida en el fichero include linux/net_device.h. El código anterior inicializa sólo un campo simple 'init' que lleva las funciones de inicialización. Cuando registramos un dispositivo el kernel llama a esta función init, que inicializa el hardware y rellena el ítem struct net_device. La estructura struct net_device es enorme y maneja todas las funciones relacionadas con las operaciones del hardware. Let us look upon some revelent ones.

name : El primer campo que necesita explicación es el campo 'name', que posee el nombre de la interfaz (la cadena de texto identificando la interfaz). Obviamente es la cadena "rtl8139" en nuestro caso.

int (*open) (struct net_device *dev) : Este método abre la interfaz cuando ifconfig la activa. El método abierto debe registrar cualquier recurso del sistema que necesite.

int (*stop) (struct net_device *dev) : Este método cierra o detiene la interfaz (como cuando es tirado por ifconfig).

int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev) : Este método inicia la transmisión a través del dispositivo 'dev'. La información está contenida en la estructura del buffer del socket skb. La estructura skb es definida posteriormente.

struct net_device * (*get_status) (struct net_device *dev): Cuando una aplicación necesita obtener estadísticas para la interfaz, este método es llamado. Esto ocurre, por ejemplo, cuando ifconfig o netstat -i se están ejecutando.

void *priv: El desarrollador del driver posee este puntero y puede usarlo libremente. La utilidad de este miembro se verá en una fase posterior. Existen muchos más métodos a explicar, pero antes de esto vamos a ver una demostración de código trabajando de un driver de prueba construido a partir de la discusión anterior. Este código hará las interacciones entre estos elementos tan claras como el agua.

Código 1

	
#define MODULE
#define __KERNEL__

#include < linux/module.h >
#include < linux/config.h >

#include < linux/netdevice.h >

int rtl8139_open (struct net_device *dev)
{
printk("rtl8139_open llamada\n");
netif_start_queue (dev);
return 0;
}

int rtl8139_release (struct net_device *dev)
{
printk ("rtl8139_release llamada\n");
netif_stop_queue(dev);
return 0;
}

static int rtl8139_xmit (struct sk_buff *skb,
struct net_device *dev)
{
printk ("funcion de prueba xmit llamada....\n");
dev_kfree_skb(skb);
return 0;
}

int rtl8139_init (struct net_device *dev)
{
dev->open = rtl8139_open;
dev->stop = rtl8139_release;
dev->hard_start_xmit = rtl8139_xmit;
printk ("dispositivo 8139 inicializado\n");
return 0;
}

struct net_device rtl8139 = {init: rtl8139_init};

int rtl8139_init_module (void)
{
int result;

strcpy (rtl8139.name, "rtl8139");
if ((result = register_netdev (&rtl8139))) {
printk ("rtl8139: Error %d inicializando la tarjeta rtl8139",result);
return result;
}
return 0;
}

void rtl8139_cleanup (void)
{
printk ("<0> liberando el modulo\n");
unregister_netdev (&rtl8139);
return;
}

module_init (rtl8139_init_module);
module_exit (rtl8139_cleanup);
Este típico módulo define su punto de entrada en la función rtl8139_init_module. El método define un net_device, llamándolo "rtl8139" y registra este dispositivo en el kernel. Otra función importante, rtl8139_init, inserta las funciones de prueba rtl8139_open, rtl8139_stop, rtl8139_xmit en la structura net_device. Aunque son funciones de prueba, realizan una pequeña tarea cuando la interfaz rtl8139 es activada. Cuando la función rtl8139_open es llamada, esta rutina anuncia la disposición del driver a aceptar datos llamando a la función netif_start_queue. De forma similar ésto se detiene llamando a netif_stop_queue.

Vamos a compilar el programa de arriba y a juguetear con él. Una invocación a 'cc' desde la línea de comandos como la de abajo es suficiente para compilar nuestro fichero rtl8139.c

[root@localhost modules]# cc -I/usr/src/linux-2.4/include/ -Wall -c rtl8139.c

Comprobamos nuestro driver de red de prueba. La siguiente salida se obtuvo en mi sistema. Podemos usar lsmod para comprobar la existencia de módulos cargados. Una salida de lsmod también es mostrada.

(Nota: Debe ser superusuario para insertar o eliminar un módulo.)


[root@localhost modules]# insmod rtl8139.o 
Warning: loading test.o will taint the kernel: no license
  See http://www.tux.org/lkml/#export-tainted for information about tainted modules
Module test loaded, with warnings

[root@localhost modules]# lsmod
Module                  Size  Used by    Tainted: P  
rtl8139                 2336   0  (unused)
mousedev                5492   1  (autoclean)
input                   5856   0  (autoclean) [mousedev]
i810                   67300   6 
agpgart                47776   7  (autoclean)
autofs                 13268   0  (autoclean) (unused)

[root@localhost modules]# ifconfig rtl8139 192.9.200.1 up
[root@localhost modules]# ifconfig

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:4 errors:0 dropped:0 overruns:0 frame:0
          TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:336 (336.0 b)  TX bytes:336 (336.0 b)

rtl8139   Link encap:AMPR NET/ROM  HWaddr   
          inet addr:192.9.200.1  Mask:255.255.255.0
          UP RUNNING  MTU:0  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 b)  TX bytes:600 (600.0 b)
Ahora que ha sido famirializado con la escritura de un driver de prueba, vamos a continuar con una interfaz real para la rtl8139.

La tarjeta PCI y su inicialización

Aunque la interfaz de red haya sido construida sin embargo todavía no nos es posible probar e inicializar la tarjeta. Esto sólo es posible hasta que comprobemos la disponibilidad de una interfaz y un dispositivo PCI. Así que se hace necesario que echemos un vistazo a la PCI y las funciones PCI disponibles.

Como he descito anteriormente, el hardware PCI es un completo protocolo que determina la forma en la que cada componente interacciona con el otro. Cada dispositivo PCI es definido por un número de bus, un número de dispositivo y un número de función. La especificación PCI permite al sistema soportar unos 256 buses, teniendo cada bus capacidad para soportar 32 dispositivos multiboard.

El firmware del PC inicializa el hardware PCI en el arranque del sistema, mapeando cada región del dispositivo I/O a una dirección diferente, que es accesible desde el espacio de configuración PCI, que consiste en 256 bytes para cada dispositivo. Tres de los registros PCI identifican un dispositivo: vendorID, deviceID, class. En ocasiones Subsystem vendorID y Subsystem deviceID son también empleados. Vamos a verlos en detalle.

  • El vendorID es un registro de 16 bits que identifica a un fabricante de hardware. Por ejemplo todos los dispositivos Intel tienen como ID de vendedor 0x8086.
  • El deviceID es otro registro de 16 bits, seleccionado por el fabricante. Este ID está emparejado con el ID de vendedor para identificar unívocamente el dispositivo.
  • Todos los dispositivos periféricos pertenecen a una clase. El registro de clase tiene un valor de 16 bits cuyo byte más significativo define el grupo (de dispositivos). Ej.: ethernet pertenece a la clase network.
  • Subsystem vendorID y Subsystem deviceID son campos que pueden ser usados para una completa identificación del dispositivo.

    Una lista completa de dispositivos PCI en una máquina linux puede ser vista mediante el comando lspci.

    Basado en la información de arriba podemos realizar la detección de la rtl8139 que podría hacerse en la propia función rtl8139_init, una versión modificada se mostrará como sigue

    Código 2

    
    
    #include < linux/pci.h >
    
    static int rtl8139_probe (struct net_device *dev, struct pci_dev *pdev)
    {
    	int ret;
    	unsigned char pci_rev;
    
    	if (! pci_present ()) {
    		printk ("No pci device present\n");
    		return -ENODEV;
    	}
    	else  printk ("<0> pci device were found\n");
    	
    	pdev = pci_find_device (PCI_VENDOR_ID_REALTEK, 
    			PCI_DEVICE_ID_REALTEK_8139, pdev);
    	
    	if (pdev)  printk ("probed for rtl 8139 \n");
    	else       printk ("Rtl8193 card not present\n");
    	
    	pci_read_config_byte (pdev, PCI_REVISION_ID, &pci_rev);
    	
    	if (ret = pci_enable_device (pdev)) {
    		printk ("Error enabling the device\n");
    		return ret;
    	}
    	
    	if (pdev->irq < 2) {
    		printk ("Invalid irq number\n");
    		ret = -EIO;
    	}
    	else {
    		printk ("Irq Obtained is %d",pdev->irq); 
    		dev->irq = pdev->irq;
    	}
    	return 0;
    }
    
    int rtl8139_init (struct net_device *dev)
    {
    	int ret;
    	struct pci_dev *pdev = NULL;
    	
    	if ((ret = rtl8139_probe (dev, pdev)) != 0)
    		return ret;
    	
    	dev->open = rtl8139_open;
    	dev->stop = rtl8139_release;
    	dev->hard_start_xmit = rtl8139_xmit;
    	printk ("My device initialized\n");
    	return 0;
    }
    

    Como puede ver una función de prueba es llamada a través de la función rtl8139_init. Un análisis detallado de las funciones de prueba muestra que han sido pasados punteros del tipo struct net_device y struct pci_dev. La estructura struct pci_dev soporta la interfaz pci y la otra estructura soporta la interfaz de red.

    La función pci_present comprueba si se encuentra disponible soporte pci válido. Devuelve el valor '0' en caso de haberlo. A partir de entonces un examen de la RTL8139 se inicia a través de la función pci_find_device. Ésta acepta el vendor_ID, device_ID y la estructura 'pdev' como argumentos. En un retorno sin errores, por ejemplo cuando la RTL8139 está presente, envía la estructura pdev rellena. Las constantes PCI_VENDOR_ID_REALTEK, PCI_DEVICE_ID_REALTEK_8139 definen el vendorID y device_ID de la tarjeta realtek. Éstas se encuentran en linux/pci.h.

    pci_read_config_byte/word/dword son funciones que leen, respectivamente, direcciones de memoria de tamaño byte/word/dword del espacio de trabajo. Una llamada a la función pci_enable para habilitar el dispositivo pci para la rtl8139, que también ayuda en el registro de su número de interrupción en la interfaz. Por esta razón si todo va bien y libre de errores, tu rtl_8139 ha sido detectada y le ha sido asignado un número de interrupción.

    En la siguiente sección veremos cómo detectar las direcciones hardware de la rtl8139 y comenzar la comunicación.

     

    [BIO] El autor acaba de completar la licenciatura técnica del Colegio de Ingenieros Guvernamental de Thrissur.


    Copyright © 2003, Bhaskaran. Licencia de Copia http://www.linuxgazette.com/copying.html
    Publicado en la edición 93 de Linux Gazette, Agosto 2003