⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ethernet.tcl

📁 开放源码实时操作系统源码.
💻 TCL
📖 第 1 页 / 共 3 页
字号:
# {{{  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 + -