📄 ethernet.tcl
字号:
# {{{ Banner # ============================================================================# # ethernet.tcl# # Ethernet support for the eCos synthetic target I/O auxiliary# # ============================================================================# ####COPYRIGHTBEGIN##### # ----------------------------------------------------------------------------# Copyright (C) 2002 Bart Veer# # This file is part of the eCos host tools.# # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version.# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details.# # You should have received a copy of the GNU General Public License along with# this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.# ----------------------------------------------------------------------------# # ####COPYRIGHTEND##### ============================================================================# #####DESCRIPTIONBEGIN##### # Author(s): bartv# Contact(s): bartv# Date: 2002/08/07# Version: 0.01# Description:# Implementation of the ethernet device. This script should only ever# be run from inside the ecosynth auxiliary.# # ####DESCRIPTIONEND##### ============================================================================# }}}# Overview.## Linux provides a number of different ways of performing low-level# ethernet I/O from user space, including accessing an otherwise# unused ethernet card via a PF_PACKET socket, and the tap facility.# The necessary functionality is not readily accessible from Tcl,# and performing this low-level I/O generally requires special# privileges. Therefore the actual I/O happens in a C program# rawether, installed suid root,## The synthetic ethernet package supports up to four ethernet devices,# eth0 to eth3. The target definition file maps these onto the# underlying I/O facility. Instantiation requires spawning a rawether# process with appropriate arguments, and then waiting for a message# from that process indicating whether or not the instantiation# succeeded. That message includes the MAC address. A file event# handler is installed to handle data detected by raw ether.## eCos can send a number of requests: transmit a packet, start the# interface (possibly in promiscuous mode), stop the interface,# or get the various parameters such as the MAC address. All those# requests can just be passed on to the rawether process. Incoming# ethernet packets are slightly more complicated: rawether will# immediately pass these up to this Tcl script, which will buffer# the packets until they are requested by eCos; in addition an# interrupt will be raised.namespace eval ethernet { # The protocol between eCos and this script. variable SYNTH_ETH_TX 0x01 variable SYNTH_ETH_RX 0x02 variable SYNTH_ETH_START 0x03 variable SYNTH_ETH_STOP 0x04 variable SYNTH_ETH_GETPARAMS 0x05 variable SYNTH_ETH_MULTIALL 0x06 # This array holds all the interesting data for all the # interfaces, indexed by the instance id. It is also useful # to keep track of the instance id's associated with ethernet # devices. array set data [list] set ids [list] # One-off initialization, for example loading images. If this fails # then all attempts at instantiation will fail as well. variable init_ok 1 variable install_dir $synth::device_install_dir variable rawether_executable [file join $ethernet::install_dir "rawether"] if { ![file exists $rawether_executable] } { synth::report_error "Ethernet device, rawether executable has not been installed in $ethernet::install_dir.\n" set init_ok 0 } elseif { ![file executable $rawether_executable] } { synth::report_error "Ethernet device, installed program $rawether_executable is not executable.\n" set init_ok 0 } if { $synth::flag_gui } { foreach _image [list "netrecord.xbm"] { variable image_[file rootname $_image] if { ! [synth::load_image "ethernet::image_[file rootname $_image]" [file join $ethernet::install_dir $_image]] } { set init_ok 0 } } unset _image } # Maximum number of packets that should be buffered per interface. # This can be changed in the target definition variable max_buffered_packets 16 if { [synth::tdf_has_option "ethernet" "max_buffer"] } { set ethernet::max_buffered_packets [synth::tdf_get_option "ethernet" "max_buffer"] if { ![string is integer -strict $ethernet::max_buffered_packets] } { synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \ Entry max_buffer should be a simple integer, not $ethernet::max_buffered_packets\n" set init_ok 0 } } # Define hooks for tx and rx packets synth::hook_define "ethernet_tx" synth::hook_define "ethernet_rx" # Get a list of known ethernet devices proc devices_get_list { } { set result [list] foreach id $ids { lappend result $::ethernet::data($id,name) } return $result } # ---------------------------------------------------------------------------- proc instantiate { id name data } { if { ! $ethernet::init_ok } { synth::report_warning "Cannot instantiate ethernet device $name, initialization failed.\n" return "" } # id is a small number that uniquely identifies this device. It will # be used as an array index. # name is something like eth0 or eth1 # There should be no device-specific data # The hard work is done by an auxiliary process which needs to be # spawned off. It requires some additional information to map the # eCos device name on to a suitable Linux network device such # as tap0. That information has to come from the config file. if { ![synth::tdf_has_option "ethernet" $name] } { synth::report_error "Cannot instantiate ethernet device $name\n \ No entry in target definition file $synth::target_definition\n" return "" } set use [synth::tdf_get_option "ethernet" $name] # Do some validation here, before the rawether process is started. # Typical entries would look like # eth0 real eth1 # eth1 ethertap [[tap-device] [MAC] [persistent]] set junk "" set optional "" set mac "" if { [regexp -- {^\s*real\s*[a-zA-z0-9_]+$} $use] } { # Real ethernet. } elseif { [regexp -- {^\s*ethertap\s*(.*)$} $use junk optional ] } { if { "" != $optional } { if { ! [regexp -- {^tap[0-9]+\s*(.*)$} $optional junk mac ] } { synth::report_error "Cannot instantiate ethernet device $name\n \ Invalid entry \"$use\" in target definition file $synth::target_definition\n \ Should be \"ethertap \[<tap-device> \[<MAC address>\]\] [persistent]\"\n" return "" } if { "" != $mac } { if { ! [regexp -- {^\s*([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\s*} $mac ] } { synth::report_error "Cannot instantiate ethernet device $name\n \ Invalid entry \"$use\" in target definition file $synth::target_definition\n \ MAC address should be of the form xx:xx:xx:xx:xx:xx, all hexadecimal digits.\n" return "" } } } } else { synth::report_error "Cannot instantiate ethernet device $name\n \ Invalid entry \"$use\" in target definition file $synth::target_definition\n \ Should be \"real <Linux ethernet device>\" or \"ethertap \[<tap-device> \[<MAC address>\]\]\"\n" return "" } # Now spawn the rawether process. Its stdin and stdout are # pipes connected to ecosynth. Its stderr is redirected to # the current tty to avoid confusion between incoming ethernet # packets and diagnostics. if { [catch { set rawether [open "|$ethernet::rawether_executable $use 2>/dev/tty" w+] } message ] } { synth::report_error "Failed to spawn rawether process for device $name\n $message" return "" } # No translation on this pipe please. fconfigure $rawether -translation binary -encoding binary -buffering none # Now wait for the rawether device to initialize. It should send back a single # byte, '0' for failure or '1' for success. Failure is followed by a text # message which should be reported. Success is followed by a six-byte MAC # address. set reply [read $rawether 1] if { "" == $reply } { synth::report_error "rawether process for device $name exited unexpectedly.\n" catch { close $rawether } return "" } if { "1" != $reply } { set message [read $rawether 1024] synth::report_error "rawether process was unable to initialize eCos device $name ($use)\n $message" catch { close $rawether } return "" } set reply [read $rawether 7] if { [string length $reply] != 7 } { synth::report_error "rawether process for eCos device $name ($use) failed to provide the initialization response.\n" catch { close $rawether } return "" } set mac [string range $reply 0 5] set multi [string index $reply 6] # Finally allocate an interrupt vector set vector [synth::interrupt_allocate $name] if { -1 == $vector } { # No more interrupts left. An error will have been reported already. catch { close $rawether } return "" } # The device is up and running. Fill in the array entries lappend ethernet::ids $id set ethernet::data($id,alive) 1 set ethernet::data($id,name) $name set ethernet::data($id,rawether) $rawether set ethernet::data($id,packets) [list] set ethernet::data($id,packet_count) 0 set ethernet::data($id,up) 0 set ethernet::data($id,interrupt_vector) $vector set ethernet::data($id,MAC) $mac set ethernet::data($id,multi) $multi # Set up the event handler to handle incoming packets. There should # not be any until the interface is brought up fileevent $rawether readable [list ethernet::handle_packet $name $id $rawether] # Finally return the request handler. The eCos device driver will # automatically get back an ack. return ethernet::handle_request } # ---------------------------------------------------------------------------- # eCos has sent a request to a device instance. Most of these requests should # just be forwarded to rawether. Some care has to be taken to preserve # packet boundaries and avoid confusion. It is also necessary to worry # about the rawether process exiting unexpectedly, which may cause # puts operations to raise an error (subject to buffering). # # Note: it might actually be more efficient to always send a header plus # 1514 bytes of data, reducing the number of system calls at the cost of # some extra data copying, but with at least two process switches per # ethernet transfer efficiency is not going to be particularly good # anyway. proc send_rawether { id packet } { if { $ethernet::data($id,alive) } { set chan $ethernet::data($id,rawether) if { [catch { puts -nonewline $chan $packet } ] } { set ethernet::data($id,alive) 0 # No further action is needed here, instead the read handler # will detect EOF and report abnormal termination. } } } proc handle_request { id reqcode arg1 arg2 reqdata reqlen reply_len } { if { $reqcode == $ethernet::SYNTH_ETH_TX } { # Transmit a single packet. To preserve packet boundaries # this involves a four-byte header containing opcode and # size, followed by the data itself. set header [binary format "ccs" $reqcode 0 [string length $reqdata]] ethernet::send_rawether $id $header ethernet::send_rawether $id $reqdata if { $ethernet::logging_enabled } { ethernet::log_packet $ethernet::data($id,name) "tx" $reqdata } synth::hook_call "ethernet_tx" $ethernet::data($id,name) $reqdata } elseif { $reqcode == $ethernet::SYNTH_ETH_RX } { # Return a single packet to eCos, plus a count of the number # of remaining packets. All packets are buffered here, not # in rawether. if { $ethernet::data($id,packet_count) == 0 } { synth::send_reply 0 0 "" } else { incr ethernet::data($id,packet_count) -1 set packet [lindex $ethernet::data($id,packets) 0] set ethernet::data($id,packets) [lrange $ethernet::data($id,packets) 1 end] synth::send_reply $ethernet::data($id,packet_count) [string length $packet] $packet if { $ethernet::logging_enabled } { ethernet::log_packet $ethernet::data($id,name) "rx" $packet } synth::hook_call "ethernet_rx" $ethernet::data($id,name) $packet } } elseif { $reqcode == $ethernet::SYNTH_ETH_START } { # Start the interface in either normal or promiscuous # mode, depending on arg1. No reply is expected. Also # mark the interface as up so that any packets transmitted # by rawether will not be discarded set ethernet::data($id,up) 1 set header [binary format "ccs" $reqcode $arg1 0] ethernet::send_rawether $id $header } elseif { $reqcode == $ethernet::SYNTH_ETH_STOP } { # Stop the interface. All pending packets should be # discarded and no new packets should be accepted. # No reply is expected so just pass this on to rawether set ethernet::data($id,up) 0 set ethernet::data($id,packets) [list] set ethernet::data($id,packet_count) 0 set header [binary format "ccs" $reqcode 0 0] ethernet::send_rawether $id $header } elseif { $reqcode == $ethernet::SYNTH_ETH_GETPARAMS } { # Retrieve the interrupt number, the MAC address, # and the multicast flag for this interface. eCos should be # expecting back 6 bytes of data for the MAC, plus an # extra byte for the multi flag, and the interrupt # number as the return code. This is all known locally. set reply "$ethernet::data($id,MAC)$ethernet::data($id,multi)" synth::send_reply $ethernet::data($id,interrupt_vector) 7 $reply } elseif { $reqcode == $ethernet::SYNTH_ETH_MULTIALL } { set header [binary format "ccs" $reqcode $arg1 0] ethernet::send_rawether $id $header } else { synth::report_error "Received unexpected request $reqcode for ethernet device" } } # ---------------------------------------------------------------------------- # Incoming data. # # The rawether process continually reads packets from the low-level device # and tries to forward them on to this script, where they will be received # by an event handler. The packet consists of a four-byte header containing # the size, followed by the ethernet data itself. This ensures that # packet boundaries are preserved. Incoming packets are buffered inside # the auxiliary until eCos sends an RX request, and an interrupt is # generated. # # If eCos stops accepting data or if it cannot process the ethernet packets # quickly enough then the auxiliary could end up buffering an unbounded # amount of data. That is a bad idea, so there is an upper bound on the # number of buffered packets. Any excess packets get dropped. # # Error conditions or EOF indicate that rawether has terminated. This # should not happen during normal operation. rawether should only exit # because of an ecos_exit hook when the channel gets closed, and the # event handler gets removed first. # # Incoming packets are logged when they are received by eCos, not when # they are received from the rawether device. That gives a somewhat more # accurate view of what is happening inside eCos - a packet stuck in # a fifo has little impact. proc _handle_packet_error { msg id } { append msg " No further I/O will happen on this interface.\n" synth::report_warning $msg set ethernet::data($id,alive) 0 fileevent $ethernet::data($id,rawether) readable "" catch { close $ethernet::data($id,rawether) } } proc handle_packet { name id chan } { set header [read $chan 4] if { 4 != [string length $header] } { ethernet::_handle_packet_error "rawether process for $name has terminated unexpectedly.\n" $id return } binary scan $header "ccs" code arg1 len if { $ethernet::SYNTH_ETH_RX != $code } { set msg "protocol mismatch from rawether process for $name\n" append msg " Function code $code not recognised.\n" ethernet::_handle_packet_error $msg $id return } if { ($len < 14) || ($len > 1514) } { set msg "protocol mismatch from rawether process for $name\n" append msg " Invalid transfer length $len\n" ethernet::_handle_packet_error $msg $id return } set data [read $chan $len] if { $len != [string length $data] } { set msg "protocol mismatch from rawether process for $name\n"
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -