📄 ethdrv.sgml
字号:
<!-- {{{ Banner --><!-- =============================================================== --><!-- --><!-- ethdrv.sgml --><!-- --><!-- eCos generic ethernet driver documentation --><!-- --><!-- =============================================================== --><!-- ####COPYRIGHTBEGIN#### --><!-- --><!-- =============================================================== --><!-- Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. --><!-- This material may be distributed only subject to the terms --><!-- and conditions set forth in the Open Publication License, v1.0 --><!-- or later (the latest version is presently available at --><!-- http://www.opencontent.org/openpub/) --><!-- Distribution of the work or derivative of the work in any --><!-- standard (paper) book form is prohibited unless prior --><!-- permission obtained from the copyright holder --><!-- =============================================================== --><!-- --> <!-- ####COPYRIGHTEND#### --><!-- =============================================================== --><!-- #####DESCRIPTIONBEGIN#### --><!-- --><!-- ####DESCRIPTIONEND#### --><!-- =============================================================== --><!-- }}} --><part id="io-eth-drv-generic"><title>Ethernet Device Drivers</title><chapter id="io-eth-drv-generic1"><title>Generic Ethernet Device Driver</title><sect1 id="io-eth-drv-api"><title>Generic Ethernet API</title><para>This section provides a simple description of how to write a low-level,hardware dependent ethernet driver.</para><para>There is a high-level driver (which is only code — with no state ofits own) that is part of the stack. There will be one or more low-leveldrivers tied to the actual network hardware. Each of these driverscontains one or more driver instances. The intent is that thelow-level drivers know nothing of the details of the stack that will beusing them. Thus, the same driver can be used by the<productname>eCos</productname>supported<acronym>TCP/IP</acronym>stack,<productname>RedBoot</productname>,or any other, with no changes.</para><para>A driver instance is contained within a<type>struct eth_drv_sc</type>:<programlisting>struct eth_hwr_funs { // Initialize hardware (including startup) void (*start)(struct eth_drv_sc *sc, unsigned char *enaddr, int flags); // Shut down hardware void (*stop)(struct eth_drv_sc *sc); // Device control (ioctl pass-thru) int (*control)(struct eth_drv_sc *sc, unsigned long key, void *data, int data_length); // Query - can a packet be sent? int (*can_send)(struct eth_drv_sc *sc); // Send a packet of data void (*send)(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, int total_len, unsigned long key); // Receive [unload] a packet of data void (*recv)(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len); // Deliver data to/from device from/to stack memory space // (moves lots of memcpy()s out of DSRs into thread) void (*deliver)(struct eth_drv_sc *sc); // Poll for interrupts/device service void (*poll)(struct eth_drv_sc *sc); // Get interrupt information from hardware driver int (*int_vector)(struct eth_drv_sc *sc); // Logical driver interface struct eth_drv_funs *eth_drv, *eth_drv_old;};struct eth_drv_sc { struct eth_hwr_funs *funs; void *driver_private; const char *dev_name; int state; struct arpcom sc_arpcom; /* ethernet common */};</programlisting></para><note><para>If you have two instances of the same hardware, you only need one<type>struct eth_hwr_funs</type> shared between them.</para></note><para>There is another structure which is used to communicate with the rest ofthe stack:<programlisting>struct eth_drv_funs { // Logical driver - initialization void (*init)(struct eth_drv_sc *sc, unsigned char *enaddr); // Logical driver - incoming packet notifier void (*recv)(struct eth_drv_sc *sc, int total_len); // Logical driver - outgoing packet notifier void (*tx_done)(struct eth_drv_sc *sc, CYG_ADDRESS key, int status);};</programlisting>Your driver does <emphasis>not</emphasis> create an instance of thisstructure. It is provided for driver code to use in the<type>eth_drv</type> member of the function record.Its usage is described below in <xref linkend=io-eth-drv-upper-api></para><para>One more function completes the API with which your driver communicateswith the rest of the stack:<programlisting>extern void eth_drv_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data);</programlisting></para><para>This function is designed so that it can be registered as the DSR for yourinterrupt handler. It will awaken the“Network Delivery Thread”to call your deliver routine. See <xref linkend=io-eth-drv-api-deliver>.</para><para>You create an instance of <type>struct eth_drv_sc</type>using the<function>ETH_DRV_SC()</function>macro whichsets up the structure, including the prototypes for the functions, etc.By doing things this way, if the internal design of the ethernet driverschanges (e.g. we need to add a new low-level implementation function),existing drivers will no longer compile until updated. This is muchbetter than to have all of the definitions in the low-level driversthemselves and have them be (quietly) broken if the interfaces change.</para><para>The “magic”which gets the drivers started (and indeed, linked) issimilar to what is used for the I/O subsystem.This is done using the<function>NETDEVTAB_ENTRY()</function>macro, which defines an initialization functionand the basic data structures for the low-level driver.</para><para><programlisting> typedef struct cyg_netdevtab_entry { const char *name; bool (*init)(struct cyg_netdevtab_entry *tab); void *device_instance; unsigned long status; } cyg_netdevtab_entry_t;</programlisting>The <varname>device_instance</varname>entry here would point to the <type>struct eth_drv_sc</type>entry previously defined. This allows the network driversetup to work with any class of driver, not just ethernet drivers. Inthe future, there will surely be serial <acronym>PPP</acronym>drivers, etc. These willuse the <function>NETDEVTAB_ENTRY()</function>setup to create the basic driver, but they willmost likely be built on top of other high-level device driver layers.</para><para>To instantiate itself, and connect it to the system,a hardware driver will have a template(boilerplate) which looks something like this:<programlisting>#include <cyg/infra/cyg_type.h>#include <cyg/hal/hal_arch.h>#include <cyg/infra/diag.h>#include <cyg/hal/drv_api.h>#include <cyg/io/eth/netdev.h>#include <cyg/io/eth/eth_drv.h>ETH_DRV_SC(<replaceable>DRV</replaceable>_sc, 0, // No driver specific data needed "eth0", // Name for this interface <replaceable>HRDWR</replaceable>_start, <replaceable>HRDWR</replaceable>_stop, <replaceable>HRDWR</replaceable>_control, <replaceable>HRDWR</replaceable>_can_send <replaceable>HRDWR</replaceable>_send, <replaceable>HRDWR</replaceable>_recv, <replaceable>HRDWR</replaceable>_deliver, <replaceable>HRDWR</replaceable>_poll, <replaceable>HRDWR</replaceable>_int_vector);NETDEVTAB_ENTRY(<replaceable>DRV</replaceable>_netdev, "<replaceable>DRV</replaceable>", <replaceable>DRV_HRDWR</replaceable>_init, &<replaceable>DRV</replaceable>_sc);</programlisting></para><para>This, along with the referenced functions, completely define the driver.</para><note><para>If one needed the same low-level driver to handlemultiple similar hardware interfaces, you would need multiple invocationsof the<function>ETH_DRV_SC()</function>/<function>NETDEVTAB_ENTRY()</function>macros. You would add a pointerto some instance specific data, e.g. containing base addresses, interruptnumbers, etc, where the<programlisting> 0, // No driver specific data</programlisting>is currently.</para></note></sect1><sect1 id="io-eth-drv-api-funcs"><title>Review of the functions</title><para>Now a brief review of the functions. This discussion will use genericnames for the functions — your driver should use hardware-specificnames to maintain uniqueness against any other drivers.</para><sect2 id="io-eth-drv-api-init"><title>Init function</title><para><programlisting>static bool <replaceable>DRV_HDWR</replaceable>_init(struct cyg_netdevtab_entry *tab)</programlisting>This function is called as part of system initialization. Its primaryfunction is to decide if the hardware (as indicated via<type>tab->device_instance</type>)is working and if the interface needs to be madeavailable in the system. If this is the case, this function needs tofinish with a call to the ethernet driver function:<programlisting> struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance; <replaceable>....initialization code....</replaceable> // Initialize upper level driver (sc->funs->eth_drv->init)( sc, unsigned char *enaddr );</programlisting>where <parameter>enaddr</parameter>is a pointer to the ethernet station address for this unit, to informthe stack of this device's readiness and availability.</para><note><para>The ethernet station address(<acronym>ESA</acronym>)is supposed to be aworld-unique, 48 bit address for this particular ethernet interface.Typically it is provided by the board/hardware manufacturer in ROM.</para><para>In many packages it is possible for the<acronym>ESA</acronym>to be set from RedBoot,(perhaps from 'fconfig' data), hard-coded from<acronym>CDL</acronym>, or from an <acronym>EPROM</acronym>.A driver should choose a run-time specified<acronym>ESA</acronym>(e.g. from RedBoot)preferentially, otherwise (in order) it should use a <acronym>CDL</acronym> specified<acronym>ESA</acronym>if one has been set, otherwise an <acronym>EPROM</acronym> set<acronym>ESA</acronym>, or otherwisefail. See the <filename>cl/cs8900a</filename>ethernet driver for an example.</para></note></sect2><sect2 id="io-eth-drv-api-start"><title>Start function</title><para><programlisting>static void<replaceable>HRDWR</replaceable>_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags)</programlisting>This function is called, perhaps much later than system initializationtime, when the system (an application) is ready for the interface tobecome active. The purpose of this function is to set up the hardwareinterface to start accepting packets from the network and be able tosend packets out. The receiver hardware should not be enabled prior tothis call.</para><note><para>This function will be called whenever theup/down state of the logical interface changes, e.g. when the IP addresschanges, or when promiscuous mode is selected by means of an<function>ioctl()</function> call in the application.This may occur more than once, so this function needs tobe prepared for that case.</para></note><note><para>In future, the <parameter>flags</parameter>field (currently unused) may be used to tell thefunction how to start up, e.g. whether interrupts will be used,alternate means of selecting promiscuous mode etc.</para></note></sect2><sect2 id="io-eth-drv-api-stop"><title>Stop function</title><para><programlisting>static void <replaceable>HRDWR</replaceable>_stop(struct eth_drv_sc *sc)</programlisting>This function is the inverse of “start.”It should shut down the hardware, disable the receiver, and keep it frominteracting with the physical network.</para></sect2><sect2 id="io-eth-drv-api-control"><title>Control function</title><para><programlisting>static int<replaceable>HRDWR</replaceable>_control( struct eth_drv_sc *sc, unsigned long key, void *data, int len)</programlisting>This function is used to perform low-level “control”operations on theinterface. These operations would typically be initiated via<function>ioctl()</function> calls in the BSDstack, and would be anything that might require the hardware setup tochange (i.e. cannot be performed totally by theplatform-independent layers).</para><para>The <parameter>key</parameter> parameter selects the operation, and the<parameter>data</parameter> and <parameter>len</parameter> params point describe,as required, some data for the operation in question.</para><variablelist><title>Available Operations:</title><varlistentry><term>ETH_DRV_SET_MAC_ADDRESS</term><listitem><para>This operation sets the ethernet station address (ESA or MAC) for thedevice. Normally this address is kept in non-volatile memory and isunique in the world. This function must at least set the interface touse the new address. It may also update the NVM as appropriate.</para></listitem></varlistentry><varlistentry><term>ETH_DRV_GET_IF_STATS_UD</term><term>ETH_DRV_GET_IF_STATS</term><listitem><para>These acquire a set of statistical counters from the interface, and writethe information into the memory pointed to by <parameter>data</parameter>.The “UD” variant explicitly instructs the driver to acquireup-to-date values. This is a separate option because doing so may takesome time, depending on the hardware.</para><para>The definition of the data structure is in<filename>cyg/io/eth/eth_drv_stats.h</filename>.</para><para>This call is typically made by SNMP, see <xref linkend=net-snmp-ecos-port>.</para></listitem></varlistentry><varlistentry><term>ETH_DRV_SET_MC_LIST</term><listitem><para>This entry instructs the device to set up multicast packet filteringto receive only packets addressed to the multicast ESAs in the list pointedto by <parameter>data</parameter>.</para><para>The format of the data is a 32-bit count of the ESAs in the list, followedby packed bytes which are the ESAs themselves, thus:<programlisting>#define ETH_DRV_MAX_MC 8struct eth_drv_mc_list { int len; unsigned char addrs[ETH_DRV_MAX_MC][ETHER_ADDR_LEN];};</programlisting></para></listitem></varlistentry><varlistentry><term>ETH_DRV_SET_MC_ALL</term><listitem><para>This entry instructs the device to receive all multicast packets, anddelete any explicit filtering which had been set up.</para></listitem></varlistentry>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -