├── LICENSE ├── README.md ├── ecad └── ecad.go ├── ecee └── ecee.go ├── ecfr ├── datagram.go ├── eth.go ├── eth_test.go ├── frame.go ├── header.go └── marshalling.go ├── ecmd ├── commandframer.go ├── commandframer_test.go ├── ecmd.go ├── marshalling.go └── mux.go ├── ll └── udp │ ├── errormask_darwin.go │ ├── genericerrormask.go │ └── udp.go ├── raweni └── raweni.go └── sim ├── l2eeprom.go ├── l2slave.go ├── mem.go └── mmdevice.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Michael Meier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ecat is an ethercat library for Go. It consists of the following parts: 2 | 3 | ecfr provides framing of ethercat frames and datagrams. 4 | ecmd provides execution of ethercat commands on top of link layer drivers, 5 | sports a goroutine-safe command multiplexer and features a number of 6 | convenience feature for command retries in case of frame loss or mismatching 7 | working counters. 8 | ecee provides (read only) access to ESC EEPROMs. 9 | ecad contains a number of ESC register addresses. 10 | ll contains link layer drivers of which there currently only one, using UDP 11 | multicast. 12 | raweni provides very raw access to ESI files. it's a misnomer. 13 | sim contains rudimentary slave and bus simulation. 14 | -------------------------------------------------------------------------------- /ecad/ecad.go: -------------------------------------------------------------------------------- 1 | package ecad 2 | 3 | const ( 4 | Type = 0x0000 5 | Revision = 0x0001 6 | Build = 0x0002 7 | FMMUsSupported = 0x0004 8 | RAMSize = 0x0006 9 | PortDescriptor = 0x0007 10 | ESCFeaturesSupported = 0x0008 11 | 12 | ConfiguredStationAddress = 0x0010 13 | ConfiguredStationAlias = 0x0012 14 | 15 | ESCResetECAT = 0x0040 16 | 17 | DLControl = 0x0100 18 | DLStatus = 0x0110 19 | 20 | ALControl = 0x0120 21 | ALStatus = 0x0130 22 | ALStatusCode = 0x0134 23 | PDIControl = 0x0140 24 | 25 | ECATEventMask = 0x0200 26 | 27 | ESIEEPROMInterface = 0x0500 28 | EEPROMConfiguration = 0x0500 29 | EEPROMPDIAccessState = 0x0501 30 | EEPROMControlStatus = 0x0502 31 | EEPROMAddress = 0x0504 32 | EEPROMData = 0x0508 33 | 34 | FMMUBase = 0x0600 35 | 36 | SyncMangerBase = 0x0800 37 | SyncManagerChannelLen = 0x08 38 | SyncManagerPhysStartAddrOffset = 0x00 39 | SyncManagerLengthOffset = 0x02 40 | SyncManagerControlOffset = 0x04 41 | SyncManagerStatusOffset = 0x05 42 | SyncManagerActivateOffset = 0x06 43 | SyncManagerPDIControlOffset = 0x07 44 | ) 45 | -------------------------------------------------------------------------------- /ecee/ecee.go: -------------------------------------------------------------------------------- 1 | package ecee 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/distributed/ecat/ecad" 7 | "github.com/distributed/ecat/ecfr" 8 | "github.com/distributed/ecat/ecmd" 9 | "time" 10 | ) 11 | 12 | type blindEEPROM struct { 13 | addr ecfr.DatagramAddress 14 | commander ecmd.Commander 15 | readCommand ecfr.CommandType 16 | writeCommand ecfr.CommandType 17 | closed bool 18 | } 19 | 20 | type EEPROM interface { 21 | ReadWord(addr uint32) (word uint16, err error) 22 | WriteWord(addr uint32, word uint16) (err error) 23 | Close() error 24 | } 25 | 26 | func New(commander ecmd.Commander, addr ecfr.DatagramAddress) (EEPROM, error) { 27 | ee := &blindEEPROM{ 28 | addr: addr, 29 | commander: commander, 30 | } 31 | 32 | err := ee.waitForIdle(0) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return ee, nil 38 | } 39 | 40 | func (ee *blindEEPROM) waitForIdle(timeout time.Duration) error { 41 | if timeout == 0 { 42 | timeout = 250 * time.Millisecond 43 | } 44 | 45 | tot := time.Now().Add(timeout) 46 | 47 | for { 48 | addr := ee.addr 49 | addr.SetOffset(ecad.EEPROMControlStatus) 50 | rb, err := ecmd.ExecuteRead(ee.commander, addr, 2, 1) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | if rb[1]&0x80 == 0 { 56 | return nil 57 | } 58 | 59 | if time.Now().After(tot) { 60 | 61 | } 62 | } 63 | } 64 | 65 | func (ee *blindEEPROM) ReadWord(addr uint32) (word uint16, err error) { 66 | if ee.closed { 67 | err = errors.New("ecee eeprom is already closed") 68 | return 69 | } 70 | 71 | err = ee.waitForIdle(0) 72 | if err != nil { 73 | return 74 | } 75 | 76 | dgaddr := ee.addr 77 | 78 | // write EEPROM address to ESC 79 | dgaddr.SetOffset(ecad.EEPROMAddress) 80 | wb := make([]byte, 4) 81 | wb[0] = uint8(addr) 82 | wb[1] = uint8(addr >> 8) 83 | wb[2] = uint8(addr >> 16) 84 | wb[3] = uint8(addr >> 24) 85 | err = ecmd.ExecuteWrite(ee.commander, dgaddr, wb, 1) 86 | if err != nil { 87 | return 88 | } 89 | 90 | // write "read command" 91 | dgaddr.SetOffset(ecad.EEPROMControlStatus) 92 | wb = []byte{0x00, 0x01} // read command 93 | err = ecmd.ExecuteWrite(ee.commander, dgaddr, wb, 1) 94 | if err != nil { 95 | return 96 | } 97 | 98 | err = ee.waitForIdle(0) 99 | if err != nil { 100 | return 101 | } 102 | 103 | // check error bits 104 | dgaddr.SetOffset(ecad.EEPROMControlStatus) 105 | var rb []byte 106 | rb, err = ecmd.ExecuteRead(ee.commander, dgaddr, 2, 1) 107 | if err != nil { 108 | return 109 | } 110 | 111 | if rb[1]&0xE0 != 0x00 { 112 | err = fmt.Errorf("EEPROM status word bits indicate error, bytes are % x\n", rb) 113 | return 114 | } 115 | 116 | dgaddr.SetOffset(ecad.EEPROMData) 117 | rb, err = ecmd.ExecuteRead(ee.commander, dgaddr, 4, 1) 118 | if err != nil { 119 | return 120 | } 121 | 122 | //fmt.Printf("EEPROM read 4 bytes: % x\n", rb) 123 | 124 | word = uint16(rb[0]) | uint16(rb[1])<<8 125 | return 126 | } 127 | 128 | func (ee *blindEEPROM) WriteWord(addr uint32, word uint16) (err error) { 129 | if ee.closed { 130 | err = errors.New("ecee eeprom is already closed") 131 | return 132 | } 133 | 134 | err = ee.waitForIdle(0) 135 | if err != nil { 136 | return 137 | } 138 | 139 | dgaddr := ee.addr 140 | 141 | // write EEPROM address to ESC 142 | dgaddr.SetOffset(ecad.EEPROMAddress) 143 | wb := make([]byte, 4) 144 | wb[0] = uint8(addr) 145 | wb[1] = uint8(addr >> 8) 146 | wb[2] = uint8(addr >> 16) 147 | wb[3] = uint8(addr >> 24) 148 | err = ecmd.ExecuteWrite(ee.commander, dgaddr, wb, 1) 149 | if err != nil { 150 | return 151 | } 152 | 153 | // write data 154 | dgaddr.SetOffset(ecad.EEPROMData) 155 | wb = []byte{uint8(word), uint8(word >> 8)} 156 | err = ecmd.ExecuteWrite(ee.commander, dgaddr, wb, 1) 157 | if err != nil { 158 | return 159 | } 160 | 161 | // write "write command" 162 | dgaddr.SetOffset(ecad.EEPROMControlStatus) 163 | wb = []byte{0x01, 0x02} // write command 164 | err = ecmd.ExecuteWrite(ee.commander, dgaddr, wb, 1) 165 | if err != nil { 166 | return 167 | } 168 | 169 | err = ee.waitForIdle(0) 170 | if err != nil { 171 | return 172 | } 173 | 174 | // check error bits 175 | dgaddr.SetOffset(ecad.EEPROMControlStatus) 176 | var rb []byte 177 | rb, err = ecmd.ExecuteRead(ee.commander, dgaddr, 2, 1) 178 | if err != nil { 179 | return 180 | } 181 | 182 | if rb[1]&0xE0 != 0x00 { 183 | err = fmt.Errorf("EEPROM status word bits indicate error, bytes are % x\n", rb) 184 | return 185 | } 186 | 187 | dgaddr.SetOffset(ecad.EEPROMData) 188 | rb, err = ecmd.ExecuteRead(ee.commander, dgaddr, 4, 1) 189 | if err != nil { 190 | return 191 | } 192 | 193 | return 194 | } 195 | 196 | func (ee *blindEEPROM) Close() error { 197 | ee.closed = true 198 | return nil 199 | } 200 | -------------------------------------------------------------------------------- /ecfr/datagram.go: -------------------------------------------------------------------------------- 1 | package ecfr 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | type Datagram struct { 10 | DatagramHeader 11 | data []byte 12 | WorkingCounter uint16 13 | buffer []byte 14 | } 15 | 16 | func (dg *Datagram) Overlay(d []byte) (b []byte, err error) { 17 | //fmt.Printf("overlaying datagram over %s\n", spew.Sdump(d)) 18 | b, err = dg.DatagramHeader.Overlay(d) 19 | if err != nil { 20 | return 21 | } 22 | 23 | if len(b) < int(dg.DataLength()) { 24 | err = fmt.Errorf("overlaying ecat dgram: need %d bytes of data, have %d", dg.DataLength(), len(b)) 25 | return 26 | } 27 | 28 | dg.data = b[:dg.DataLength()] 29 | b = b[dg.DataLength():] 30 | 31 | if len(b) < 2 { 32 | err = fmt.Errorf("overlaying ecat dgram: need 2 bytes for working counter, got %d", len(b)) 33 | return 34 | } 35 | 36 | // guarded by condition above 37 | dg.WorkingCounter, b = getUint16(b) 38 | 39 | dg.buffer = d 40 | return 41 | } 42 | 43 | func (dg *Datagram) Commit() (d []byte, err error) { 44 | _, err = dg.DatagramHeader.Commit() 45 | if err != nil { 46 | 47 | } 48 | 49 | // the data is already committed 50 | 51 | wcoffs := datagramHeaderLength + len(dg.data) 52 | if len(dg.buffer) < (wcoffs + 2) { 53 | err = fmt.Errorf("cannot commit data: buffer too short, need %d bytes, have %d", wcoffs+2, len(dg.buffer)) 54 | return 55 | } 56 | 57 | putUint16(dg.buffer[wcoffs:wcoffs+2], dg.WorkingCounter) 58 | 59 | d = dg.buffer[:wcoffs+2] 60 | 61 | return 62 | } 63 | 64 | func (dg *Datagram) ByteLen() int { 65 | return DatagramOverheadLength + len(dg.Data()) 66 | } 67 | 68 | type DatagramHeader struct { 69 | Command CommandType 70 | Index uint8 71 | Addr32 uint32 72 | LenWord uint16 73 | Interrupt uint16 74 | buffer []byte 75 | } 76 | 77 | const ( 78 | datagramHeaderByteLen = 10 79 | ) 80 | 81 | func (dh *DatagramHeader) Overlay(d []byte) (b []byte, err error) { 82 | b = d 83 | if len(b) < datagramHeaderByteLen { 84 | err = fmt.Errorf("need %d bytes for dgram header, have %d", datagramHeaderByteLen, len(b)) 85 | return 86 | } 87 | 88 | var c8 uint8 89 | c8, b = getUint8(b) 90 | dh.Command = CommandType(c8) 91 | dh.Index, b = getUint8(b) 92 | dh.Addr32, b = getUint32(b) 93 | dh.LenWord, b = getUint16(b) 94 | dh.Interrupt, b = getUint16(b) 95 | 96 | dh.buffer = d 97 | 98 | return 99 | } 100 | 101 | func (dh *DatagramHeader) Commit() (d []byte, err error) { 102 | b := dh.buffer 103 | 104 | b = putUint8(b, uint8(dh.Command)) 105 | b = putUint8(b, dh.Index) 106 | b = putUint32(b, dh.Addr32) 107 | b = putUint16(b, dh.LenWord) 108 | b = putUint16(b, dh.Interrupt) 109 | 110 | d = dh.buffer[:len(b)] 111 | return 112 | } 113 | 114 | func (dh *DatagramHeader) SlaveAddr() uint16 { 115 | return uint16(dh.Addr32) 116 | } 117 | 118 | func (dh *DatagramHeader) OffsetAddr() uint16 { 119 | return uint16(dh.Addr32 >> 16) 120 | } 121 | 122 | func (dh *DatagramHeader) LogicalAddr() uint32 { 123 | return dh.Addr32 124 | } 125 | 126 | func (dh *DatagramHeader) DataLength() uint16 { 127 | return dh.LenWord & ((1 << 11) - 1) 128 | } 129 | 130 | func (dh *DatagramHeader) Roundtrip() bool { 131 | return (dh.LenWord & (1 << roundtripBit)) != 0 132 | } 133 | 134 | func (dh *DatagramHeader) Last() bool { 135 | return (dh.LenWord & (1 << lastindicatorBit)) == 0 136 | } 137 | 138 | func (dh *DatagramHeader) SetLast(last bool) { 139 | if last { 140 | dh.LenWord &^= (1 << lastindicatorBit) 141 | } else { 142 | dh.LenWord |= (1 << lastindicatorBit) 143 | } 144 | } 145 | 146 | func (dg *Datagram) Summary() string { 147 | buf := bytes.NewBuffer(nil) 148 | fmt.Fprintf(buf, "%-4s x%02x %04x %04x len %03x wc % 2d IRQ %04x ", dg.Command.String(), 149 | dg.Index, 150 | dg.SlaveAddr(), 151 | dg.OffsetAddr(), 152 | dg.DataLength(), 153 | dg.WorkingCounter, 154 | dg.Interrupt) 155 | 156 | cutoffstr := "" 157 | data := dg.Data() 158 | if len(data) > 8 { 159 | data = data[:8] 160 | cutoffstr = " ..." 161 | } 162 | 163 | fmt.Fprintf(buf, "> % x%s", data, cutoffstr) 164 | 165 | switch len(dg.Data()) { 166 | case 2: 167 | fmt.Fprintf(buf, " (%04x)", xgetUint16(dg.Data())) 168 | case 4: 169 | fmt.Fprintf(buf, " (%04x %04x | %08x)", xgetUint16(dg.Data()), xgetUint16(dg.Data()[2:]), xgetUint32(dg.Data())) 170 | } 171 | 172 | return buf.String() 173 | } 174 | 175 | func PointDatagramHeaderTo(d []byte) (dh DatagramHeader, err error) { 176 | if len(d) < datagramHeaderLength { 177 | err = fmt.Errorf("datagram header needs %d bytes, only have %d", datagramHeaderLength, len(d)) 178 | return 179 | } 180 | 181 | dh.buffer = d[0:datagramHeaderLength] 182 | return 183 | } 184 | 185 | func (dg *Datagram) Data() []byte { 186 | return dg.data 187 | } 188 | 189 | func (dg *Datagram) SetDataLen(ndl int) error { 190 | nl := DatagramOverheadLength + ndl 191 | if (cap(dg.buffer)) < nl { 192 | return fmt.Errorf("datagram with new size needs %d bytes of space, only %d in buffer", nl, cap(dg.buffer)) 193 | } 194 | 195 | if ndl > datagramMaxDataLength { 196 | return errors.New("new data length exceeds maximum datagram data length") 197 | } 198 | 199 | dg.data = dg.data[0:ndl] 200 | dg.buffer = dg.buffer[0:nl] 201 | 202 | dg.LenWord &^= datagramDataLengthMask 203 | dg.LenWord |= (uint16(ndl) & datagramDataLengthMask) 204 | 205 | return nil 206 | } 207 | 208 | func PointDatagramTo(d []byte) (dg Datagram, err error) { 209 | if len(d) < DatagramOverheadLength { 210 | err = errors.New("byte slice too short to be pointed to by a datagram") 211 | return 212 | } 213 | 214 | dg = Datagram{ 215 | buffer: d, 216 | data: d[datagramHeaderLength:datagramHeaderLength], 217 | } 218 | dg.DatagramHeader, err = PointDatagramHeaderTo(d) 219 | 220 | return 221 | } 222 | 223 | const ( 224 | roundtripBit = 14 225 | lastindicatorBit = 15 226 | 227 | datagramHeaderLength = 10 228 | datagramFooterLength = 2 229 | DatagramOverheadLength = datagramHeaderLength + datagramFooterLength 230 | 231 | datagramDataLengthMask = (1 << 12) - 1 232 | datagramMaxDataLength = datagramDataLengthMask 233 | ) 234 | 235 | type CommandType uint8 236 | 237 | func (ct CommandType) String() string { 238 | if cts, ok := commandTypeName[ct]; ok { 239 | return cts 240 | } 241 | return fmt.Sprintf("CommandType(%d)", uint(ct)) 242 | } 243 | 244 | type DatagramAddressType uint 245 | 246 | const ( 247 | UninitializedDatagramAddressType DatagramAddressType = 0 248 | Positional DatagramAddressType = 1 249 | Fixed DatagramAddressType = 2 250 | Broadcast DatagramAddressType = 3 251 | Logical DatagramAddressType = 4 252 | ) 253 | 254 | type DatagramAddress struct { 255 | addr uint32 256 | typ DatagramAddressType 257 | } 258 | 259 | func (d DatagramAddress) String() string { 260 | b := bytes.NewBuffer(nil) 261 | switch d.Type() { 262 | case Positional: 263 | b.WriteByte('p') 264 | case Fixed: 265 | b.WriteByte('f') 266 | case Broadcast: 267 | b.WriteByte('b') 268 | case Logical: 269 | b.WriteByte('l') 270 | default: 271 | b.WriteByte('U') 272 | } 273 | 274 | switch d.Type() { 275 | case Logical: 276 | fmt.Fprintf(b, "%08x", d.addr) 277 | default: 278 | fmt.Fprintf(b, "%04x.%04x", uint16(d.addr), uint16(d.addr>>16)) 279 | } 280 | 281 | return b.String() 282 | } 283 | 284 | func (d DatagramAddress) Addr32() uint32 { 285 | return d.addr 286 | } 287 | 288 | func (d DatagramAddress) Type() DatagramAddressType { 289 | return d.typ 290 | } 291 | 292 | func DatagramAddressFromCommand(addr32 uint32, ct CommandType) DatagramAddress { 293 | typ := UninitializedDatagramAddressType 294 | var ok bool 295 | if typ, ok = datagramAddressByOperation[ct]; ok { 296 | return DatagramAddress{addr32, typ} 297 | } 298 | return DatagramAddress{addr32, typ} 299 | } 300 | 301 | func (d DatagramAddress) Offset() uint16 { 302 | return uint16(d.Addr32() >> 16) 303 | } 304 | 305 | func (d *DatagramAddress) SetOffset(offs uint16) { 306 | d.addr &^= 0xffff0000 307 | d.addr |= uint32(offs) << 16 308 | } 309 | 310 | func (d DatagramAddress) PositionOrAddress() uint16 { 311 | return uint16(d.Addr32()) 312 | } 313 | 314 | func (d *DatagramAddress) IncrementSlaveAddr() { 315 | d.addr = (d.addr & 0xffff0000) | ((d.addr + 1) & 0x0000ffff) 316 | } 317 | 318 | func (d *DatagramAddress) IsPhysical() bool { 319 | switch d.Type() { 320 | case Positional: 321 | case Fixed: 322 | case Broadcast: 323 | default: 324 | return false 325 | } 326 | return true 327 | } 328 | 329 | func PositionalAddr(position int16, offset uint16) DatagramAddress { 330 | return DatagramAddress{uint32(uint16(position)) | uint32(offset)<<16, Positional} 331 | } 332 | 333 | func FixedAddr(stationaddr uint16, offset uint16) DatagramAddress { 334 | return DatagramAddress{uint32(stationaddr) | uint32(offset)<<16, Fixed} 335 | } 336 | 337 | var datagramAddressByOperation = map[CommandType]DatagramAddressType{ 338 | NOP: UninitializedDatagramAddressType, 339 | APRD: Positional, 340 | APWR: Positional, 341 | APRW: Positional, 342 | FPRD: Fixed, 343 | FPWR: Fixed, 344 | FPRW: Fixed, 345 | BRD: Broadcast, 346 | BWR: Broadcast, 347 | BRW: Broadcast, 348 | LRD: Logical, 349 | LWR: Logical, 350 | LRW: Logical, 351 | ARMW: Positional, 352 | FRMW: Positional, 353 | } 354 | 355 | const ( 356 | NOP CommandType = 0 357 | APRD CommandType = 1 358 | APWR CommandType = 2 359 | APRW CommandType = 3 360 | FPRD CommandType = 4 361 | FPWR CommandType = 5 362 | FPRW CommandType = 6 363 | BRD CommandType = 7 364 | BWR CommandType = 8 365 | BRW CommandType = 9 366 | LRD CommandType = 10 367 | LWR CommandType = 11 368 | LRW CommandType = 12 369 | ARMW CommandType = 13 370 | FRMW CommandType = 14 371 | ) 372 | 373 | var commandTypeName = map[CommandType]string{ 374 | NOP: "NOP", 375 | APRD: "APRD", 376 | APWR: "APWR", 377 | APRW: "APRW", 378 | FPRD: "FPRD", 379 | FPWR: "FPWR", 380 | FPRW: "FPRW", 381 | BRD: "BRD", 382 | BWR: "BWR", 383 | BRW: "BRW", 384 | LRD: "LRD", 385 | LWR: "LWR", 386 | LRW: "LRW", 387 | ARMW: "ARMW", 388 | FRMW: "FRMW", 389 | } 390 | 391 | func (ct CommandType) DoesRead() bool { 392 | switch ct { 393 | case APRD: 394 | case APRW: 395 | case FPRD: 396 | case FPRW: 397 | case BRD: 398 | case BRW: 399 | case LRD: 400 | case LRW: 401 | case ARMW: 402 | case FRMW: 403 | default: 404 | return false 405 | } 406 | return true 407 | } 408 | 409 | func (ct CommandType) DoesWrite() bool { 410 | switch ct { 411 | case APWR: 412 | case APRW: 413 | case FPWR: 414 | case FPRW: 415 | case BWR: 416 | case BRW: 417 | case LWR: 418 | case LRW: 419 | case ARMW: 420 | case FRMW: 421 | default: 422 | return false 423 | } 424 | return true 425 | } 426 | -------------------------------------------------------------------------------- /ecfr/eth.go: -------------------------------------------------------------------------------- 1 | package ecfr 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type ETHAddr [6]byte 9 | 10 | func (ea ETHAddr) String() string { 11 | return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", ea[0], ea[1], ea[2], ea[3], ea[4], ea[5]) 12 | } 13 | 14 | // bounds need to be checked already 15 | func sliceToETHADDR(s []byte) ETHAddr { 16 | var e ETHAddr 17 | e[0] = s[0] 18 | e[1] = s[1] 19 | e[2] = s[2] 20 | e[3] = s[3] 21 | e[4] = s[4] 22 | e[5] = s[5] 23 | return e 24 | } 25 | 26 | // payload len is len(ef.GetPayload()) 27 | // capacity for payload is: cap(ef.GetPayload()) - ef.GetFooterLen() 28 | type ETHFrame struct { 29 | Destination, Source ETHAddr 30 | Type uint16 31 | 32 | UseVlan bool 33 | VLANTCI uint16 34 | 35 | framebuf []byte 36 | } 37 | 38 | func OverlayETHFrame(fb []byte) (*ETHFrame, error) { 39 | if len(fb) == 0 { 40 | fb = make([]byte, max_framelen) 41 | } 42 | 43 | if len(fb) < min_framelen_with_fcs { 44 | return nil, fmt.Errorf( 45 | "NewETHFrame: buffer too small, need at least %d bytes", 46 | min_headerandpayload) 47 | } 48 | 49 | ef := ÐFrame{} 50 | ef.framebuf = fb 51 | 52 | // TODO: read this information at overlay time? 53 | 54 | // guarded by len(fb) < min_framelen_with_fcs 55 | ef.Destination = sliceToETHADDR(fb[offsetDestination:offsetSource]) 56 | ef.Source = sliceToETHADDR(fb[offsetSource:offsetVLANOrType]) 57 | ef.Type, _ = getUint16BE(fb[offsetVLANOrType:]) 58 | if ef.Type == etherTypeVLAN { 59 | ef.UseVlan = true 60 | ef.VLANTCI, _ = getUint16BE(fb[offsetVLANTCI:]) 61 | 62 | } 63 | return ef, nil 64 | } 65 | 66 | func (ef *ETHFrame) GetHeaderLen() int { 67 | vlanlen := 0 68 | if ef.UseVlan { 69 | vlanlen = 4 70 | } 71 | // dest, src, type, (vlan len) 72 | l := 6 + 6 + 2 + vlanlen 73 | 74 | return l 75 | } 76 | 77 | func (ef *ETHFrame) GetFooterLen() int { 78 | return fcs_len 79 | } 80 | 81 | // header contents will be undefined if you do not call WriteDown() before. 82 | func (ef *ETHFrame) GetFrameBuf() []byte { 83 | return ef.framebuf 84 | } 85 | 86 | func (ef *ETHFrame) GetPayload() []byte { 87 | return ef.framebuf[ef.GetHeaderLen() : len(ef.framebuf)-ef.GetFooterLen()] 88 | } 89 | 90 | func (ef *ETHFrame) SetPayloadLen(npl int) error { 91 | nl := npl + ef.GetHeaderLen() + ef.GetFooterLen() 92 | // TODO: check 93 | if nl < min_framelen_with_fcs { 94 | return fmt.Errorf( 95 | "SetPayloadLen: payload too small, need at least %d bytes", 96 | min_framelen_with_fcs-ef.GetHeaderLen()-ef.GetFooterLen()) 97 | } 98 | 99 | maxnl := max_framelen_novlan 100 | if ef.UseVlan { 101 | maxnl = max_framelen_vlan 102 | } 103 | 104 | if nl > maxnl { 105 | return fmt.Errorf( 106 | "SetPayloadLen: payload too big, maximum for this configuration is %d bytes", 107 | maxnl-ef.GetHeaderLen()) 108 | } 109 | 110 | if nl > cap(ef.framebuf) { 111 | return fmt.Errorf( 112 | "SetPayloadLen: payload of %d bytes too big for buffer, buffer can hold a %d bytes maximum", 113 | npl, 114 | cap(ef.framebuf)-ef.GetFooterLen()-ef.GetHeaderLen()) 115 | } 116 | 117 | ef.framebuf = ef.framebuf[0:nl] 118 | return nil 119 | } 120 | 121 | func (ef *ETHFrame) WriteDown() error { 122 | // bounds should already be checked 123 | // TODO: API clarification 124 | 125 | copy(ef.framebuf[0:6], ef.Destination[:]) 126 | copy(ef.framebuf[6:12], ef.Source[:]) 127 | 128 | pos := 12 129 | if ef.UseVlan { 130 | return errors.New("VLAN tags are not supported") 131 | } 132 | 133 | ef.framebuf[pos] = uint8(ef.Type >> 8) 134 | ef.framebuf[pos] = uint8(ef.Type) 135 | 136 | pos += 2 137 | 138 | // TODO: how to deal with the FCS? 139 | 140 | return nil 141 | } 142 | 143 | // TODO: +4 bytes of FCS for future compatibility? 144 | const ( 145 | min_framelen_with_fcs = 64 146 | fcs_len = 4 147 | min_headerandpayload = min_framelen_with_fcs - fcs_len 148 | 149 | // inclduing fcs 150 | max_framelen_novlan = 1522 151 | max_framelen_vlan = 1526 152 | max_framelen = max_framelen_vlan 153 | 154 | offsetDestination = 0 155 | offsetSource = 6 156 | offsetVLANOrType = 12 157 | offsetVLANTCI = 14 158 | 159 | etherTypeVLAN = 0x8100 160 | ) 161 | -------------------------------------------------------------------------------- /ecfr/eth_test.go: -------------------------------------------------------------------------------- 1 | package ecfr 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func makeEmptyFrameBuffer() []byte { 9 | return make([]byte, min_framelen_with_fcs) 10 | } 11 | 12 | func TestETHFramePayloadOps(t *testing.T) { 13 | { 14 | tlb := make([]byte, 20) 15 | _, err := OverlayETHFrame(tlb) 16 | if err == nil { 17 | t.Fatalf("OverlayETHFrame did not fail on buffer too small to contain ETH frame") 18 | } 19 | } 20 | 21 | buf := make([]byte, min_framelen_with_fcs) 22 | ef, err := OverlayETHFrame(buf) 23 | if err != nil { 24 | t.Fatalf("OverlayETHFrame should have worked, returned err %v", err) 25 | } 26 | 27 | { 28 | pl := ef.GetPayload() 29 | exppllen := len(buf) - 14 - 4 // avoid normal constant encoding 30 | if len(pl) != exppllen { 31 | t.Fatalf("for packet with frame buf size %d, expected %d payload bytes, got %d", len(buf), exppllen, len(pl)) 32 | } 33 | } 34 | 35 | { 36 | // maximum length payload 37 | // 4 bytes pl[len(pl):cap(pl)] should include FCS 38 | pl := ef.GetPayload() 39 | 40 | if cap(pl)-len(pl) != fcs_len { 41 | t.Fatalf("full size payload should have capacity for %d bytes of FCS, only have %d", fcs_len, cap(pl)-len(pl)) 42 | } 43 | 44 | // we got a maximally big payload, it should not be possible to make 45 | // it any bigger. 46 | err := ef.SetPayloadLen(len(pl) + 1) 47 | if err == nil { 48 | t.Fatalf("increasing the size of a maximally sized pl did not yield an error!") 49 | } 50 | 51 | // try to set a size too small to go without padding 52 | err = ef.SetPayloadLen(46 - 1) // avoid constants, again 53 | if err == nil { 54 | t.Fatalf("setting the payload too small did not yield an error!") 55 | } 56 | } 57 | } 58 | 59 | func TestETHFrameDecoding(t *testing.T) { 60 | hdrbytes := []byte{0xab, 0xcd, 0xef, 0x12, 0x23, 0x34, 0xde, 0xad, 0xbe, 0xef, 0xaa, 0x55, 0x88, 0xa2} 61 | fb := makeEmptyFrameBuffer() 62 | copy(fb, hdrbytes) 63 | ef, err := OverlayETHFrame(fb) 64 | if err != nil { 65 | t.Fatalf("overlaying should work on this header,failed with %v\n", err) 66 | } 67 | 68 | wantdest := ETHAddr{0xab, 0xcd, 0xef, 0x12, 0x23, 0x34} 69 | if !reflect.DeepEqual(ef.Destination, wantdest) { 70 | t.Fatalf("destination address does not match, want %v, got %v", wantdest, ef.Destination) 71 | } 72 | 73 | wantsrc := ETHAddr{0xde, 0xad, 0xbe, 0xef, 0xaa, 0x55} 74 | if !reflect.DeepEqual(ef.Source, wantsrc) { 75 | t.Fatalf("destination address does not match, want %v, got %v", wantsrc, ef.Source) 76 | } 77 | 78 | wantethtype := uint16(0x88a2) 79 | if ef.Type != wantethtype { 80 | t.Fatalf("want eth type %#04x, got %#04x", wantethtype, ef.Type) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ecfr/frame.go: -------------------------------------------------------------------------------- 1 | package ecfr 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | const ( 10 | FrameOverheadLen = 2 11 | ) 12 | 13 | type Frame struct { 14 | Header Header 15 | Datagrams []*Datagram 16 | buffer []byte 17 | } 18 | 19 | func (f *Frame) Overlay(d []byte) (b []byte, err error) { 20 | b, err = f.Header.Overlay(d) 21 | if err != nil { 22 | return 23 | } 24 | 25 | dgbl := f.Header.FrameLength() 26 | if int(dgbl) > len(b) { 27 | err = fmt.Errorf("frame expected %d bytes, only have %d", dgbl, len(b)) 28 | return 29 | } 30 | 31 | for { 32 | f.Datagrams = append(f.Datagrams, &Datagram{}) 33 | i := len(f.Datagrams) - 1 34 | 35 | b, err = f.Datagrams[i].Overlay(b) 36 | if err != nil { 37 | return 38 | } 39 | 40 | if f.Datagrams[i].Last() { 41 | break 42 | } 43 | } 44 | 45 | f.buffer = d 46 | 47 | return 48 | } 49 | 50 | func PointFrameTo(d []byte) (f Frame, err error) { 51 | if len(d) < FrameOverheadLen { 52 | err = errors.New("buffer too small to even contain frame header") 53 | return 54 | } 55 | 56 | d[0] = 0 57 | d[1] = 0 58 | _, err = f.Header.Overlay(d) 59 | if err != nil { 60 | return 61 | } 62 | 63 | f.buffer = d 64 | 65 | return 66 | } 67 | 68 | func (f *Frame) Commit() (d []byte, err error) { 69 | var incbuf []byte 70 | totlen := 0 71 | 72 | if len(f.Datagrams) == 0 { 73 | err = errors.New("ecat frame needs at least one datagram") 74 | return 75 | } 76 | 77 | clen := f.ByteLen() 78 | if clen > len(f.buffer) { 79 | err = fmt.Errorf("datagrams too long for frame, need %d, have %d", clen, len(f.buffer)) 80 | return 81 | } 82 | 83 | lenmask := uint16((1 << 12) - 1) 84 | f.Header.Word &^= lenmask 85 | f.Header.Word |= uint16(clen-2) & lenmask 86 | 87 | incbuf, err = f.Header.Commit() 88 | if err != nil { 89 | return 90 | } 91 | totlen += len(incbuf) 92 | 93 | for _, dgram := range f.Datagrams { 94 | incbuf, err = dgram.Commit() 95 | if err != nil { 96 | return 97 | } 98 | totlen += len(incbuf) 99 | } 100 | 101 | d = f.buffer[0:totlen] 102 | 103 | return 104 | } 105 | 106 | func (f *Frame) ByteLen() int { 107 | clen := FrameOverheadLen 108 | for _, dgram := range f.Datagrams { 109 | clen += dgram.ByteLen() 110 | } 111 | return clen 112 | } 113 | 114 | func (f *Frame) NewDatagram(datalen int) (*Datagram, error) { 115 | curlen := f.ByteLen() 116 | maxlen := len(f.buffer) 117 | curfree := maxlen - curlen 118 | //fmt.Printf("curlen %d, maxlen %d, curfree %d\n", curlen, maxlen, curfree) 119 | if datalen <= curfree { 120 | dgram, err := PointDatagramTo(f.buffer[curlen:]) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | err = dgram.SetDataLen(datalen) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | f.Datagrams = append(f.Datagrams, &dgram) 131 | 132 | return &dgram, nil 133 | } 134 | panic("datalen too high") 135 | return nil, errors.New("datalen too high") 136 | } 137 | 138 | func (f *Frame) MultilineSummary() string { 139 | b := bytes.NewBuffer(nil) 140 | fmt.Fprintf(b, "frame len %#03x\n", f.ByteLen()) 141 | for _, dgram := range f.Datagrams { 142 | b.WriteString(" ") 143 | b.WriteString(dgram.Summary()) 144 | b.WriteString("\n") 145 | } 146 | return b.String() 147 | } 148 | -------------------------------------------------------------------------------- /ecfr/header.go: -------------------------------------------------------------------------------- 1 | package ecfr 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type Header struct { 8 | Word uint16 9 | buffer []byte 10 | } 11 | 12 | func (h *Header) Overlay(b []byte) ([]byte, error) { 13 | if len(b) < 2 { 14 | return b, errors.New("not enough bytes for header") 15 | } 16 | 17 | h.buffer = b 18 | h.Word, b = getUint16(b) 19 | return b, nil 20 | } 21 | 22 | func (h *Header) FrameLength() uint16 { 23 | return h.Word & ((1 << 11) - 1) 24 | } 25 | 26 | // TODO: data type? 27 | func (h *Header) Type() uint8 { 28 | return uint8(h.Word>>12) & 0x0f 29 | } 30 | 31 | func (h *Header) SetType(t uint8) { 32 | h.Word &^= 0xf000 33 | h.Word |= uint16(t&0x0f) << 12 34 | } 35 | 36 | func (h *Header) Commit() (d []byte, err error) { 37 | putUint16(h.buffer, h.Word) 38 | d = h.buffer[:2] 39 | return 40 | } 41 | -------------------------------------------------------------------------------- /ecfr/marshalling.go: -------------------------------------------------------------------------------- 1 | package ecfr 2 | 3 | // the "native" byte ordering is the little endian encoding scheme 4 | // of ehthercat. big endian routines for the encoding used by ethernet 5 | // are provided below 6 | 7 | func getUint8(b []byte) (uint8, []byte) { 8 | return b[0], b[1:] 9 | } 10 | 11 | func getUint16(b []byte) (uint16, []byte) { 12 | return uint16(b[0]) | uint16(b[1])<<8, b[2:] 13 | } 14 | 15 | func getUint32(b []byte) (uint32, []byte) { 16 | v := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 17 | return v, b[4:] 18 | } 19 | 20 | func xgetUint8(b []byte) uint8 { 21 | return b[0] 22 | } 23 | 24 | func xgetUint16(b []byte) uint16 { 25 | return uint16(b[0]) | uint16(b[1])<<8 26 | } 27 | 28 | func xgetUint32(b []byte) uint32 { 29 | v := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 30 | return v 31 | } 32 | 33 | func putUint8(b []byte, v uint8) []byte { 34 | b[0] = v 35 | return b[1:] 36 | } 37 | 38 | func putUint16(b []byte, v uint16) []byte { 39 | b[0] = uint8(v) 40 | b[1] = uint8(v >> 8) 41 | return b[2:] 42 | } 43 | 44 | func putUint32(b []byte, v uint32) []byte { 45 | b[0] = uint8(v) 46 | b[1] = uint8(v >> 8) 47 | b[2] = uint8(v >> 16) 48 | b[3] = uint8(v >> 24) 49 | return b[4:] 50 | } 51 | 52 | func getUint8BE(b []byte) (uint8, []byte) { 53 | return b[0], b[1:] 54 | } 55 | 56 | func getUint16BE(b []byte) (uint16, []byte) { 57 | return uint16(b[0])<<8 | uint16(b[1]), b[2:] 58 | } 59 | 60 | func getUint32BE(b []byte) (uint32, []byte) { 61 | v := uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) 62 | return v, b[4:] 63 | } 64 | 65 | func putUint8BE(b []byte, v uint8) []byte { 66 | b[0] = v 67 | return b[1:] 68 | } 69 | 70 | func putUint16BE(b []byte, v uint16) []byte { 71 | b[0] = uint8(v >> 8) 72 | b[1] = uint8(v) 73 | return b[2:] 74 | } 75 | 76 | func putUint32BE(b []byte, v uint32) []byte { 77 | b[0] = uint8(v >> 24) 78 | b[1] = uint8(v >> 16) 79 | b[2] = uint8(v >> 8) 80 | b[3] = uint8(v) 81 | return b[4:] 82 | } 83 | -------------------------------------------------------------------------------- /ecmd/commandframer.go: -------------------------------------------------------------------------------- 1 | package ecmd 2 | 3 | import ( 4 | "errors" 5 | "github.com/distributed/ecat/ecfr" 6 | ) 7 | 8 | const ( 9 | CommandFramerMaxDatagramsLen = 1470 10 | ) 11 | 12 | type outgoingFrame struct { 13 | frame *ecfr.Frame 14 | cmds []*ExecutingCommand 15 | } 16 | 17 | type CommandFramer struct { 18 | currentIndex uint8 19 | 20 | frameOpen bool 21 | currentFrame *ecfr.Frame 22 | currentFrameLen uint16 23 | currentFrameOffset uint16 24 | currentDgram *ecfr.Datagram 25 | currentCmds []*ExecutingCommand 26 | 27 | frameQueue []outgoingFrame 28 | 29 | inFrameQueue []*ecfr.Frame 30 | 31 | framer Framer 32 | } 33 | 34 | func NewCommandFramer(framer Framer) *CommandFramer { 35 | return &CommandFramer{framer: framer} 36 | } 37 | 38 | func (cf *CommandFramer) New(datalen int) (*ExecutingCommand, error) { 39 | var err error 40 | 41 | dbgl := datalen + ecfr.DatagramOverheadLength 42 | if dbgl > CommandFramerMaxDatagramsLen { 43 | return nil, errors.New("datalen exceeds maximum datagram length") 44 | } 45 | 46 | if cf.frameOpen { 47 | if dbgl > int(cf.currentFrameLen-cf.currentFrameOffset) { 48 | cf.finishFrame() 49 | err = cf.newFrame() 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | } 55 | } else { 56 | err = cf.newFrame() 57 | if err != nil { 58 | return nil, err 59 | } 60 | } 61 | 62 | var dg *ecfr.Datagram 63 | //fmt.Printf("want NewDatagram datalen %d\n", datalen) 64 | dg, err = cf.currentFrame.NewDatagram(datalen) 65 | if err != nil { 66 | return nil, err 67 | } 68 | cf.currentDgram = dg 69 | 70 | cf.currentFrameOffset += uint16(dbgl) 71 | 72 | cmd := &ExecutingCommand{ 73 | DatagramOut: dg, 74 | } 75 | cf.currentCmds = append(cf.currentCmds, cmd) 76 | return cmd, nil 77 | } 78 | 79 | func (cf *CommandFramer) finishFrame() { 80 | if len(cf.currentFrame.Datagrams) > 0 { 81 | for i := 0; i < len(cf.currentFrame.Datagrams)-1; i++ { 82 | cf.currentFrame.Datagrams[i].SetLast(false) 83 | } 84 | cf.currentFrame.Datagrams[0].Index = cf.currentIndex 85 | cf.currentFrame.Datagrams[len(cf.currentFrame.Datagrams)-1].SetLast(true) 86 | cf.frameQueue = append(cf.frameQueue, outgoingFrame{cf.currentFrame, cf.currentCmds}) 87 | } 88 | 89 | cf.frameOpen = false 90 | cf.currentFrame = nil 91 | cf.currentFrameLen = 0 92 | cf.currentFrameOffset = 0xffff 93 | cf.currentCmds = nil 94 | cf.currentIndex++ 95 | } 96 | 97 | func (cf *CommandFramer) newFrame() error { 98 | // TODO: constant for ecat frame header len (2) 99 | 100 | var ( 101 | frame *ecfr.Frame 102 | err error 103 | ) 104 | 105 | /*buf := make([]byte, CommandFramerMaxDatagramsLen+2) 106 | frame, err = ecfr.PointFrameTo(buf) 107 | if err != nil { 108 | return err 109 | }*/ 110 | frame, err = cf.framer.New(CommandFramerMaxDatagramsLen) 111 | if err != nil { 112 | return err 113 | } 114 | 115 | cf.currentFrame = frame 116 | cf.currentDgram = nil 117 | cf.currentCmds = nil 118 | cf.frameOpen = true 119 | cf.currentFrameLen = CommandFramerMaxDatagramsLen 120 | cf.currentFrameOffset = 0 121 | return nil 122 | } 123 | 124 | func (cf *CommandFramer) Cycle() error { 125 | if cf.currentFrame != nil && len(cf.currentFrame.Datagrams) > 0 { 126 | cf.finishFrame() 127 | } 128 | 129 | /*for i, of := range cf.frameQueue { 130 | fr := of.frame 131 | frbuf, err := fr.Commit() 132 | if err != nil { 133 | return err 134 | } 135 | 136 | var f ecfr.Frame 137 | _, err = f.Overlay(frbuf) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | fmt.Printf("frameQueue entry %d len %d\n", i, len(frbuf)) 143 | for _, dgram := range f.Datagrams { 144 | fmt.Println(" ", dgram.Summary()) 145 | } 146 | fmt.Println() 147 | }*/ 148 | 149 | var err error 150 | cf.inFrameQueue, err = cf.framer.Cycle() 151 | if err != nil { 152 | return err 153 | } 154 | 155 | //for i, fr := range cf.inFrameQueue { 156 | /*frbuf, err := fr.Commit() 157 | if err != nil { 158 | return err 159 | } 160 | 161 | var f ecfr.Frame 162 | _, err = f.Overlay(frbuf) 163 | if err != nil { 164 | return err 165 | }*/ 166 | 167 | /*fmt.Printf("inFrameQueue entry %d len %d\n", i, fr.ByteLen()) 168 | for _, dgram := range fr.Datagrams { 169 | fmt.Println(" ", dgram.Summary()) 170 | } 171 | fmt.Println()*/ 172 | //} 173 | 174 | oi := 0 175 | for _, infr := range cf.inFrameQueue { 176 | if oi == len(cf.frameQueue) { 177 | // no more outgoing frames to scan 178 | break 179 | } 180 | 181 | for i := oi; i < len(cf.frameQueue); i++ { 182 | // is this outgoing frame a match for the incoming frame? 183 | ofr := cf.frameQueue[i].frame 184 | if infr.Header.FrameLength() != ofr.Header.FrameLength() { 185 | continue 186 | } 187 | 188 | if len(infr.Datagrams) == 0 || len(ofr.Datagrams) == 0 { 189 | continue 190 | } 191 | 192 | if len(infr.Datagrams) != len(ofr.Datagrams) { 193 | continue 194 | } 195 | 196 | if infr.Datagrams[0].Index != ofr.Datagrams[0].Index { 197 | continue 198 | } 199 | 200 | // TODO: more criteria 201 | for j, ocmd := range cf.frameQueue[i].cmds { 202 | odgram := ocmd.DatagramOut 203 | indgram := infr.Datagrams[j] 204 | 205 | if odgram.Command != indgram.Command { 206 | continue 207 | } 208 | 209 | if odgram.DataLength() != indgram.DataLength() { 210 | continue 211 | } 212 | 213 | ocmd.DatagramIn = indgram 214 | ocmd.Arrived = true 215 | ocmd.Overlayed = true 216 | ocmd.Error = nil 217 | } 218 | 219 | // update search start index 220 | oi = i 221 | } 222 | } 223 | 224 | cf.frameQueue = nil 225 | cf.inFrameQueue = nil 226 | 227 | return nil 228 | } 229 | 230 | func (cf *CommandFramer) Close() error { 231 | return nil 232 | } 233 | 234 | func (cf *CommandFramer) DebugMessage(m string) { 235 | if dm, ok := cf.framer.(debugMessager); ok { 236 | dm.DebugMessage(m) 237 | } 238 | } 239 | 240 | type Framer interface { 241 | New(maxdatalen int) (*ecfr.Frame, error) 242 | Cycle() ([]*ecfr.Frame, error) 243 | } 244 | 245 | type debugMessager interface { 246 | DebugMessage(string) 247 | } 248 | 249 | func printDebugMessage(p interface{}, m string) { 250 | if dm, ok := p.(debugMessager); ok { 251 | dm.DebugMessage(m) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /ecmd/commandframer_test.go: -------------------------------------------------------------------------------- 1 | package ecmd 2 | 3 | import ( 4 | "github.com/davecgh/go-spew/spew" 5 | "github.com/distributed/ecat/ecfr" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | type expectedCFScheduling struct { 11 | dgramslist [][]*ecfr.Datagram 12 | } 13 | 14 | func TestCommandFramerScheduling(t *testing.T) { 15 | type cfSchedulingPairs struct { 16 | lens []int 17 | expectedCFScheduling expectedCFScheduling 18 | } 19 | 20 | pairs := []cfSchedulingPairs{ 21 | cfSchedulingPairs{[]int{6}, expectedCFScheduling{ 22 | [][]*ecfr.Datagram{ 23 | []*ecfr.Datagram{makeLenDgram(6, 0, true)}}}}, 24 | cfSchedulingPairs{[]int{22, CommandFramerMaxDatagramsLen - ecfr.DatagramOverheadLength}, expectedCFScheduling{ 25 | [][]*ecfr.Datagram{ 26 | []*ecfr.Datagram{makeLenDgram(22, 0, true)}, 27 | []*ecfr.Datagram{makeLenDgram(CommandFramerMaxDatagramsLen-ecfr.DatagramOverheadLength, 1, true)}}}}, 28 | cfSchedulingPairs{[]int{128, 96}, expectedCFScheduling{ 29 | [][]*ecfr.Datagram{ 30 | []*ecfr.Datagram{makeLenDgram(128, 0, false), makeLenDgram(96, 0, true)}}}}, 31 | cfSchedulingPairs{[]int{140, 65, 1400}, expectedCFScheduling{ 32 | [][]*ecfr.Datagram{ 33 | []*ecfr.Datagram{makeLenDgram(140, 0, false), makeLenDgram(65, 0, true)}, 34 | []*ecfr.Datagram{makeLenDgram(1400, 1, true)}}}}, 35 | } 36 | 37 | for i, pair := range pairs { 38 | f := &oneshotFramer{} 39 | cf := NewCommandFramer(f) 40 | 41 | for _, l := range pair.lens { 42 | _, err := cf.New(l) 43 | if err != nil { 44 | t.Fatalf("case %d: did not expect New() to fail. err is %v", i, err) 45 | } 46 | } 47 | 48 | err := cf.Cycle() 49 | if err != nil { 50 | t.Fatalf("did not expect Cycle() to fail. err is %v", err) 51 | } 52 | 53 | dgramslist := pair.expectedCFScheduling.dgramslist 54 | if len(f.frames) != len(dgramslist) { 55 | t.Fatalf("case %d: expected %d frames, got %d", i, len(dgramslist), len(f.frames)) 56 | } 57 | 58 | for j, frame := range f.frames { 59 | dgrams := dgramslist[j] 60 | if len(frame.Datagrams) != len(dgrams) { 61 | t.Fatalf("case %d, frame %d: expected %d datagrams, got %d", i, j, len(dgrams), len(frame.Datagrams)) 62 | } 63 | 64 | for k, dgram := range frame.Datagrams { 65 | expdgram := dgrams[k] 66 | 67 | if !reflect.DeepEqual(expdgram, dgram) { 68 | spew.Dump(expdgram) 69 | spew.Dump(dgram) 70 | t.Fatalf("case %d, frame %d, dgram %d: expected %v, got %v\n", i, j, k, expdgram, dgram) 71 | } 72 | } 73 | } 74 | 75 | } 76 | } 77 | 78 | type oneshotFramer struct { 79 | frames []*ecfr.Frame 80 | cycled bool 81 | } 82 | 83 | func (f *oneshotFramer) New(maxdatalen int) (*ecfr.Frame, error) { 84 | b := make([]byte, maxdatalen+ecfr.FrameOverheadLen) 85 | var frame ecfr.Frame 86 | var err error 87 | frame, err = ecfr.PointFrameTo(b) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | f.frames = append(f.frames, &frame) 93 | return &frame, nil 94 | } 95 | 96 | func (f *oneshotFramer) Cycle() ([]*ecfr.Frame, error) { 97 | if !f.cycled { 98 | return f.frames, nil 99 | } 100 | panic("oneshotFramer was already cycled") 101 | } 102 | 103 | func makeLenDgram(plen int, index uint8, last bool) *ecfr.Datagram { 104 | ub := make([]byte, plen+ecfr.DatagramOverheadLength) 105 | dgram, err := ecfr.PointDatagramTo(ub) 106 | if err != nil { 107 | panic(err) 108 | } 109 | err = dgram.SetDataLen(plen) 110 | if err != nil { 111 | panic(err) 112 | } 113 | 114 | dgram.Index = index 115 | dgram.SetLast(last) 116 | 117 | return &dgram 118 | } 119 | -------------------------------------------------------------------------------- /ecmd/ecmd.go: -------------------------------------------------------------------------------- 1 | package ecmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/distributed/ecat/ecfr" 7 | "time" 8 | ) 9 | 10 | type Commander interface { 11 | New(datalen int) (*ExecutingCommand, error) 12 | Cycle() error 13 | Close() error 14 | } 15 | 16 | type ExecutingCommand struct { 17 | DatagramOut *ecfr.Datagram 18 | 19 | DatagramIn *ecfr.Datagram 20 | Arrived bool 21 | Overlayed bool 22 | Error error 23 | } 24 | 25 | var NoFrame = errors.New("frame did not arrive") 26 | var NoOverlay = errors.New("failed to overlay") 27 | 28 | type WorkingCounterError struct { 29 | Command ecfr.CommandType 30 | Addr32 uint32 31 | Want, Have uint16 32 | } 33 | 34 | func (e WorkingCounterError) Error() string { 35 | return fmt.Sprintf("working counter error, want %d, have %d on %v %#08x", e.Want, 36 | e.Have, 37 | e.Command, 38 | e.Addr32) 39 | } 40 | 41 | func ChooseDefaultError(cmd *ExecutingCommand) error { 42 | if !cmd.Arrived { 43 | return NoFrame 44 | } 45 | 46 | if !cmd.Overlayed { 47 | return NoOverlay 48 | } 49 | 50 | return cmd.Error 51 | } 52 | 53 | func IsNoFrame(err error) bool { 54 | return err == NoFrame 55 | } 56 | 57 | func IsWorkingCounterError(err error) bool { 58 | _, ok := err.(WorkingCounterError) 59 | return ok 60 | } 61 | 62 | func ChooseWorkingCounterError(ec *ExecutingCommand, expwc uint16) error { 63 | havewc := ec.DatagramIn.WorkingCounter 64 | if expwc != havewc { 65 | return WorkingCounterError{ 66 | ec.DatagramOut.Command, 67 | ec.DatagramOut.Addr32, 68 | expwc, havewc, 69 | } 70 | } 71 | 72 | return nil 73 | } 74 | 75 | const ( 76 | DefaultFramelossTries = 3 77 | ) 78 | 79 | type Options struct { 80 | FramelossTries int 81 | WCDeadline time.Time 82 | } 83 | 84 | func (o Options) getFramelossTries() int { 85 | if o.FramelossTries == 0 { 86 | return DefaultFramelossTries 87 | } 88 | return o.FramelossTries 89 | } 90 | func (o Options) getWCDeadline() time.Time { return o.WCDeadline } 91 | 92 | func ExecuteRead8(c Commander, addr ecfr.DatagramAddress, expwc uint16) (d uint8, err error) { 93 | return ExecuteRead8Options(c, addr, expwc, Options{}) 94 | } 95 | 96 | func ExecuteRead8Options(c Commander, addr ecfr.DatagramAddress, expwc uint16, opts Options) (d uint8, err error) { 97 | var ds []byte 98 | ds, err = ExecuteRead(c, addr, 1, expwc) 99 | if err != nil { 100 | return 101 | } 102 | d = xgetUint8(ds) 103 | return 104 | } 105 | 106 | func ExecuteRead16(c Commander, addr ecfr.DatagramAddress, expwc uint16) (d uint16, err error) { 107 | return ExecuteRead16Options(c, addr, expwc, Options{}) 108 | } 109 | 110 | func ExecuteRead16Options(c Commander, addr ecfr.DatagramAddress, expwc uint16, opt Options) (d uint16, err error) { 111 | var ds []byte 112 | ds, err = ExecuteRead(c, addr, 2, expwc) 113 | if err != nil { 114 | return 115 | } 116 | d = xgetUint16(ds) 117 | return 118 | } 119 | 120 | func ExecuteRead32(c Commander, addr ecfr.DatagramAddress, expwc uint16) (d uint32, err error) { 121 | return ExecuteRead32Options(c, addr, expwc, Options{}) 122 | } 123 | 124 | func ExecuteRead32Options(c Commander, addr ecfr.DatagramAddress, expwc uint16, opt Options) (d uint32, err error) { 125 | var ds []byte 126 | ds, err = ExecuteRead(c, addr, 4, expwc) 127 | if err != nil { 128 | return 129 | } 130 | d = xgetUint32(ds) 131 | return 132 | } 133 | 134 | func ExecuteRead(c Commander, addr ecfr.DatagramAddress, n int, expwc uint16) (d []byte, err error) { 135 | return ExecuteReadOptions(c, addr, n, expwc, Options{}) 136 | } 137 | 138 | func ExecuteReadOptions(c Commander, addr ecfr.DatagramAddress, n int, expwc uint16, opts Options) (d []byte, err error) { 139 | nFrameLoss := 0 140 | 141 | var ct ecfr.CommandType 142 | switch addr.Type() { 143 | case ecfr.Positional: 144 | ct = ecfr.APRD 145 | case ecfr.Fixed: 146 | ct = ecfr.FPRD 147 | case ecfr.Broadcast: 148 | ct = ecfr.BRD 149 | default: 150 | err = fmt.Errorf("ExecuteReadOptions: unsupported address type %v", addr.Type()) 151 | } 152 | 153 | for { 154 | var ec *ExecutingCommand 155 | ec, err = c.New(n) 156 | if err != nil { 157 | return 158 | } 159 | 160 | dgo := ec.DatagramOut 161 | dgo.Command = ct 162 | dgo.Addr32 = addr.Addr32() 163 | 164 | err = c.Cycle() 165 | if err != nil { 166 | return 167 | } 168 | 169 | err = ChooseDefaultError(ec) 170 | if err != nil { 171 | if IsNoFrame(err) { 172 | nFrameLoss++ 173 | if nFrameLoss < opts.getFramelossTries() { 174 | continue 175 | } 176 | } 177 | return 178 | } 179 | 180 | err = ChooseWorkingCounterError(ec, expwc) 181 | if err != nil { 182 | now := time.Now() 183 | if now.Before(opts.getWCDeadline()) { 184 | continue 185 | } 186 | } 187 | 188 | d = ec.DatagramIn.Data() 189 | return 190 | } 191 | 192 | panic("not reached") 193 | } 194 | 195 | func ExecuteWrite8(c Commander, addr ecfr.DatagramAddress, w uint8, expwc uint16) (err error) { 196 | return ExecuteWrite8Options(c, addr, w, expwc, Options{}) 197 | } 198 | 199 | func ExecuteWrite8Options(c Commander, addr ecfr.DatagramAddress, w uint8, expwc uint16, opts Options) (err error) { 200 | ws := make([]byte, 1) 201 | putUint8(ws, w) 202 | return ExecuteWriteOptions(c, addr, ws, expwc, opts) 203 | } 204 | 205 | func ExecuteWrite16(c Commander, addr ecfr.DatagramAddress, w uint16, expwc uint16) (err error) { 206 | return ExecuteWrite16Options(c, addr, w, expwc, Options{}) 207 | } 208 | 209 | func ExecuteWrite16Options(c Commander, addr ecfr.DatagramAddress, w uint16, expwc uint16, opts Options) (err error) { 210 | ws := make([]byte, 2) 211 | putUint16(ws, w) 212 | return ExecuteWriteOptions(c, addr, ws, expwc, opts) 213 | } 214 | 215 | func ExecuteWrite32(c Commander, addr ecfr.DatagramAddress, w uint32, expwc uint16) (err error) { 216 | return ExecuteWrite32Options(c, addr, w, expwc, Options{}) 217 | } 218 | 219 | func ExecuteWrite32Options(c Commander, addr ecfr.DatagramAddress, w uint32, expwc uint16, opts Options) (err error) { 220 | ws := make([]byte, 4) 221 | putUint32(ws, w) 222 | return ExecuteWriteOptions(c, addr, ws, expwc, opts) 223 | } 224 | 225 | func ExecuteWrite(c Commander, addr ecfr.DatagramAddress, w []byte, expwc uint16) (err error) { 226 | return ExecuteWriteOptions(c, addr, w, expwc, Options{}) 227 | } 228 | 229 | func ExecuteWriteOptions(c Commander, addr ecfr.DatagramAddress, w []byte, expwc uint16, opts Options) (err error) { 230 | nFrameLoss := 0 231 | 232 | var ct ecfr.CommandType 233 | switch addr.Type() { 234 | case ecfr.Positional: 235 | ct = ecfr.APWR 236 | case ecfr.Fixed: 237 | ct = ecfr.FPWR 238 | case ecfr.Broadcast: 239 | ct = ecfr.BWR 240 | default: 241 | err = fmt.Errorf("ExecuteWriteOptions: unsupported address type %v", addr.Type()) 242 | } 243 | 244 | for { 245 | var ec *ExecutingCommand 246 | ec, err = c.New(len(w)) 247 | if err != nil { 248 | return 249 | } 250 | 251 | dgo := ec.DatagramOut 252 | copy(dgo.Data(), w) 253 | 254 | dgo.Command = ct 255 | dgo.Addr32 = addr.Addr32() 256 | 257 | err = c.Cycle() 258 | if err != nil { 259 | return 260 | } 261 | 262 | err = ChooseDefaultError(ec) 263 | if err != nil { 264 | if IsNoFrame(err) { 265 | nFrameLoss++ 266 | if nFrameLoss < opts.getFramelossTries() { 267 | continue 268 | } 269 | } 270 | return 271 | } 272 | 273 | err = ChooseWorkingCounterError(ec, expwc) 274 | if err != nil { 275 | now := time.Now() 276 | if now.Before(opts.getWCDeadline()) { 277 | continue 278 | } 279 | } 280 | 281 | return 282 | } 283 | 284 | } 285 | -------------------------------------------------------------------------------- /ecmd/marshalling.go: -------------------------------------------------------------------------------- 1 | package ecmd 2 | 3 | // the "native" byte ordering is the little endian encoding scheme 4 | // of ehthercat. big endian routines for the encoding used by ethernet 5 | // are provided below 6 | 7 | func getUint8(b []byte) (uint8, []byte) { 8 | return b[0], b[1:] 9 | } 10 | 11 | func getUint16(b []byte) (uint16, []byte) { 12 | return uint16(b[0]) | uint16(b[1])<<8, b[2:] 13 | } 14 | 15 | func getUint32(b []byte) (uint32, []byte) { 16 | v := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 17 | return v, b[4:] 18 | } 19 | 20 | func xgetUint8(b []byte) uint8 { 21 | return b[0] 22 | } 23 | 24 | func xgetUint16(b []byte) uint16 { 25 | return uint16(b[0]) | uint16(b[1])<<8 26 | } 27 | 28 | func xgetUint32(b []byte) uint32 { 29 | v := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 30 | return v 31 | } 32 | 33 | func putUint8(b []byte, v uint8) []byte { 34 | b[0] = v 35 | return b[1:] 36 | } 37 | 38 | func putUint16(b []byte, v uint16) []byte { 39 | b[0] = uint8(v) 40 | b[1] = uint8(v >> 8) 41 | return b[2:] 42 | } 43 | 44 | func putUint32(b []byte, v uint32) []byte { 45 | b[0] = uint8(v) 46 | b[1] = uint8(v >> 8) 47 | b[2] = uint8(v >> 16) 48 | b[3] = uint8(v >> 24) 49 | return b[4:] 50 | } 51 | -------------------------------------------------------------------------------- /ecmd/mux.go: -------------------------------------------------------------------------------- 1 | package ecmd 2 | 3 | import ( 4 | "errors" 5 | "launchpad.net/tomb" 6 | ) 7 | 8 | type Multiplexer struct { 9 | muxlinked bool 10 | c Commander 11 | mux *Multiplexer 12 | 13 | reqchan chan interface{} 14 | tomb tomb.Tomb 15 | 16 | chans []*muxChanControlBlock 17 | //cyclingChans []cyclingChan 18 | 19 | cyclepending bool 20 | cycleRespChan chan error 21 | } 22 | 23 | func NewMultiplexer(c Commander) (m *Multiplexer, err error) { 24 | m = &Multiplexer{ 25 | c: c, 26 | reqchan: make(chan interface{}), 27 | } 28 | 29 | go m.loop() 30 | 31 | return 32 | } 33 | 34 | func (m *Multiplexer) loop() { 35 | defer m.tomb.Done() 36 | 37 | down: 38 | for { 39 | if m.cyclepending { 40 | allcycling := true 41 | for _, cb := range m.chans { 42 | if cb.commandsOpen && !cb.cycling { 43 | allcycling = false 44 | break 45 | } 46 | } 47 | //log.Printf("allcycling %v\n", allcycling) 48 | 49 | if allcycling { 50 | //log.Printf("mux calling underlying Cycle\n") 51 | err := m.c.Cycle() 52 | //log.Printf("underlying Cycle err %v\n", err) 53 | 54 | nnot := 0 55 | for _, cb := range m.chans { 56 | if cb.cycling { 57 | cb.cyclingChan.responseChan <- err 58 | nnot++ 59 | } 60 | cb.cycling = false 61 | cb.commandsOpen = false 62 | } 63 | //log.Printf("notified %d channels\n", nnot) 64 | 65 | m.cyclepending = false 66 | m.cycleRespChan <- err 67 | m.cycleRespChan = nil 68 | } 69 | } 70 | 71 | select { 72 | case req := <-m.reqchan: 73 | switch req := req.(type) { 74 | case muxChanNew: 75 | ec, err := m.c.New(req.datalen) 76 | req.responseChan <- muxChanNewResponse{ec, err} 77 | m.getCB(req.muxChannel).commandsOpen = true 78 | 79 | case muxChanCycle: 80 | // wait for mux controlled cycle 81 | //m.cyclingChans = append(m.cyclingChans, cyclingChan{req.muxChannel, req.responseChan}) 82 | //log.Printf("mux chan is cycling") 83 | cb := m.getCB(req.muxChannel) 84 | if cb.cycling { 85 | req.responseChan <- errors.New("there already is a concurrent Cycle() pending on this mux channel") 86 | } 87 | 88 | cb.cycling = true 89 | cb.cyclingChan = cyclingChan{req.muxChannel, req.responseChan} 90 | 91 | case muxCycle: 92 | // mux controlled cycle 93 | //log.Printf("mux make cycle pending\n") 94 | if m.cycleRespChan != nil { 95 | req.responseChan <- errors.New("there already is a concurrent Cycle() on this multiplexer") 96 | } 97 | m.cyclepending = true 98 | m.cycleRespChan = req.responseChan 99 | 100 | case openCommander: 101 | c := &muxChannel{ 102 | mux: m, 103 | newResponseChan: make(chan muxChanNewResponse), 104 | errResponseChan: make(chan error), 105 | } 106 | 107 | m.chans = append(m.chans, &muxChanControlBlock{muxChannel: c}) 108 | 109 | req.responseChan <- openCommanderResponse{c, nil} 110 | } 111 | case <-m.tomb.Dying(): 112 | break down 113 | } 114 | } 115 | } 116 | 117 | func (m *Multiplexer) getCB(mc *muxChannel) *muxChanControlBlock { 118 | for _, cb := range m.chans { 119 | if cb.muxChannel == mc { 120 | return cb 121 | } 122 | } 123 | panic("missing mux chan control block") 124 | } 125 | 126 | func (m *Multiplexer) OpenCommander() (Commander, error) { 127 | req := openCommander{make(chan openCommanderResponse)} 128 | m.reqchan <- req 129 | resp := <-req.responseChan 130 | return resp.Commander, resp.err 131 | } 132 | 133 | func (c *Multiplexer) Cycle() error { 134 | req := muxCycle{make(chan error)} 135 | c.reqchan <- req 136 | return <-req.responseChan 137 | } 138 | 139 | type muxChanControlBlock struct { 140 | *muxChannel 141 | cyclingChan cyclingChan 142 | commandsOpen bool 143 | cycling bool 144 | } 145 | 146 | // cycle bound channel 147 | type muxChannel struct { 148 | mux *Multiplexer 149 | newResponseChan chan muxChanNewResponse 150 | errResponseChan chan error 151 | } 152 | 153 | func (mc *muxChannel) New(datalen int) (*ExecutingCommand, error) { 154 | mc.mux.reqchan <- muxChanNew{mc, datalen, mc.newResponseChan} 155 | resp := <-mc.newResponseChan 156 | return resp.ExecutingCommand, resp.error 157 | } 158 | 159 | func (mc *muxChannel) Cycle() error { 160 | mc.mux.reqchan <- muxChanCycle{mc, mc.errResponseChan} 161 | return <-mc.errResponseChan 162 | } 163 | 164 | func (mc *muxChannel) Close() error { 165 | return errors.New("nimpl") 166 | } 167 | 168 | func (mc *muxChannel) DebugMessage(m string) { 169 | printDebugMessage(mc.mux.c, m) 170 | } 171 | 172 | type muxChanNew struct { 173 | *muxChannel 174 | datalen int 175 | responseChan chan muxChanNewResponse 176 | } 177 | 178 | type muxChanNewResponse struct { 179 | *ExecutingCommand 180 | error 181 | } 182 | 183 | type muxChanCycle struct { 184 | *muxChannel 185 | responseChan chan error 186 | } 187 | 188 | type muxCycle struct { 189 | responseChan chan error 190 | } 191 | 192 | type openCommander struct { 193 | responseChan chan openCommanderResponse 194 | } 195 | 196 | type openCommanderResponse struct { 197 | Commander Commander 198 | err error 199 | } 200 | 201 | type cyclingChan struct { 202 | *muxChannel 203 | responseChan chan error 204 | } 205 | -------------------------------------------------------------------------------- /ll/udp/errormask_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package udp 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | ) 9 | 10 | // on darwin we filter out "can't assign requested address" errors. these happen 11 | // when there is no link or when the interface has not yet been properly 12 | // configured with IP addresses. 13 | 14 | func errorMask(err error) error { 15 | if oe, ok := err.(*net.OpError); ok { 16 | if se, ok := oe.Err.(syscall.Errno); ok { 17 | if se == syscall.EADDRNOTAVAIL { 18 | return nil 19 | } 20 | } 21 | } 22 | return err 23 | } 24 | -------------------------------------------------------------------------------- /ll/udp/genericerrormask.go: -------------------------------------------------------------------------------- 1 | // +build !darwin 2 | 3 | package udp 4 | 5 | func errorMask(err error) error { 6 | return err 7 | } 8 | -------------------------------------------------------------------------------- /ll/udp/udp.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "golang.org/x/net/ipv4" 5 | "github.com/distributed/ecat/ecfr" 6 | "net" 7 | "time" 8 | ) 9 | 10 | const ( 11 | EthercatUDPPort = 0x88a4 12 | ) 13 | 14 | const ( 15 | udpReceiveBuflen = 1500 16 | maxDatagramsLen = 1470 17 | ) 18 | 19 | type UDPFramer struct { 20 | oframes []*ecfr.Frame 21 | 22 | sock *net.UDPConn 23 | mcsock *ipv4.PacketConn 24 | group net.IP 25 | iface *net.Interface 26 | laddr *net.UDPAddr 27 | groupaddr *net.UDPAddr 28 | cycletime time.Duration 29 | 30 | cycnum int 31 | } 32 | 33 | func NewUDPFramer(iface *net.Interface, group net.IP, cycletime time.Duration) (f *UDPFramer, err error) { 34 | f = &UDPFramer{} 35 | f.group = group 36 | f.iface = iface 37 | f.cycletime = cycletime 38 | 39 | f.laddr = &net.UDPAddr{net.IPv4(0, 0, 0, 0), EthercatUDPPort, ""} 40 | f.groupaddr = &net.UDPAddr{f.group, EthercatUDPPort, ""} 41 | 42 | f.sock, err = net.ListenUDP("udp4", f.laddr) 43 | if err != nil { 44 | return 45 | } 46 | 47 | f.mcsock = ipv4.NewPacketConn(f.sock) 48 | 49 | err = f.mcsock.SetMulticastInterface(f.iface) 50 | if err != nil { 51 | return 52 | } 53 | 54 | err = f.mcsock.JoinGroup(iface, &net.UDPAddr{IP: group}) 55 | if err != nil { 56 | return 57 | } 58 | 59 | err = f.mcsock.SetMulticastLoopback(false) 60 | if err != nil { 61 | return 62 | } 63 | 64 | return 65 | } 66 | 67 | func (f *UDPFramer) New(maxdatalen int) (fr *ecfr.Frame, err error) { 68 | var vframe ecfr.Frame 69 | buf := make([]byte, maxDatagramsLen+ecfr.FrameOverheadLen) 70 | vframe, err = ecfr.PointFrameTo(buf) 71 | if err != nil { 72 | return 73 | } 74 | 75 | vframe.Header.SetType(1) 76 | 77 | fr = &vframe 78 | f.oframes = append(f.oframes, fr) 79 | return 80 | } 81 | 82 | func (f *UDPFramer) Cycle() (iframes []*ecfr.Frame, err error) { 83 | defer func() { 84 | f.cycnum++ 85 | f.oframes = nil 86 | }() 87 | // TODO: send/receive concurrently to be independent of queue depth? 88 | 89 | // TODO: write deadline? 90 | var obytes []byte 91 | for _, oframe := range f.oframes { 92 | obytes, err = oframe.Commit() 93 | if err != nil { 94 | return 95 | } 96 | 97 | _, err = f.sock.WriteTo(obytes, f.groupaddr) 98 | if err != nil { 99 | err = errorMask(err) 100 | return 101 | } 102 | //fmt.Printf("cycnum %d out %s", f.cycnum, oframe.MultilineSummary()) 103 | } 104 | 105 | err = f.sock.SetReadDeadline(time.Now().Add(f.cycletime)) 106 | if err != nil { 107 | return 108 | } 109 | 110 | stretchcnt := 0 111 | rbuf := make([]byte, udpReceiveBuflen) 112 | for { 113 | var n int 114 | n, _, err = f.sock.ReadFromUDP(rbuf) 115 | if isTimeout(err) { 116 | if stretchcnt < 10 && len(iframes) < len(f.oframes) { 117 | //fmt.Printf("================================= activating cycle stretching %d =======\n", stretchcnt) 118 | stretchcnt++ 119 | f.sock.SetReadDeadline(time.Now().Add(1 * f.cycletime)) 120 | continue 121 | } 122 | err = nil 123 | break 124 | } 125 | if err != nil { 126 | return 127 | } 128 | 129 | var fr ecfr.Frame 130 | _, err = fr.Overlay(rbuf[0:n]) 131 | if err != nil { 132 | // discard malformed frames 133 | continue 134 | } 135 | 136 | //fmt.Printf("cycnum %d in %s", f.cycnum, fr.MultilineSummary()) 137 | iframes = append(iframes, &fr) 138 | rbuf = make([]byte, udpReceiveBuflen) 139 | } 140 | 141 | return 142 | } 143 | 144 | func (f *UDPFramer) Close() error { 145 | if f.mcsock != nil { 146 | f.mcsock.Close() 147 | } 148 | if f.sock != nil { 149 | return f.Close() 150 | } 151 | return nil 152 | } 153 | 154 | func (f *UDPFramer) DebugMessage(m string) { 155 | addr := *f.groupaddr 156 | addr.Port = 1024 157 | 158 | // the error is intentionally ingored 159 | f.sock.WriteTo([]byte(m), &addr) 160 | } 161 | 162 | type timeouter interface { 163 | Timeout() bool 164 | } 165 | 166 | func isTimeout(err error) bool { 167 | if t, ok := err.(timeouter); ok { 168 | return t.Timeout() 169 | } 170 | return false 171 | } 172 | -------------------------------------------------------------------------------- /raweni/raweni.go: -------------------------------------------------------------------------------- 1 | package raweni 2 | 3 | import ( 4 | "github.com/rogpeppe/go-charset/charset" 5 | "encoding/xml" 6 | "github.com/davecgh/go-spew/spew" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | import _ "github.com/rogpeppe/go-charset/data" 14 | 15 | func ReadEtherCATInfoFromFile(filename string) (eci EtherCATInfo, err error) { 16 | var r io.Reader 17 | r, err = os.Open(filename) 18 | if err != nil { 19 | return 20 | } 21 | 22 | return ReadEtherCATInfo(r) 23 | } 24 | 25 | func ReadEtherCATInfo(r io.Reader) (eci EtherCATInfo, err error) { 26 | dec := xml.NewDecoder(r) 27 | dec.CharsetReader = charset.NewReader 28 | 29 | err = dec.Decode(&eci) 30 | if err != nil { 31 | spew.Dump(err) 32 | return 33 | } 34 | 35 | return 36 | } 37 | 38 | type EtherCATInfo struct { 39 | Vendor Vendor 40 | Descriptions Descriptions 41 | } 42 | 43 | type Vendor struct { 44 | Id uint32 45 | Name string 46 | } 47 | 48 | type Descriptions struct { 49 | Groups []Group `xml:"Groups>Group"` 50 | Devices []Device `xml:"Devices>Device"` 51 | } 52 | 53 | type Group struct { 54 | Type string 55 | Names []GroupName `xml:"Name"` 56 | } 57 | 58 | type GroupName struct { 59 | LcIdentifiedName 60 | } 61 | 62 | type LcIdentifiedName struct { 63 | String string `xml:",chardata"` 64 | LcId uint `xml:",attr"` 65 | } 66 | 67 | type Device struct { 68 | Type DeviceType 69 | Names []LcIdentifiedName `xml:"Name"` 70 | Sms []Sm `xml:"Sm"` 71 | Eeprom Eeprom 72 | } 73 | 74 | type DeviceType struct { 75 | Name string `xml:",chardata"` 76 | ProductCodeRaw string `xml:"ProductCode,attr"` 77 | RevisionNoRaw string `xml:"RevisionNo,attr"` 78 | } 79 | 80 | func (d DeviceType) ProductCode() uint32 { 81 | return uint32(bh2i(d.ProductCodeRaw)) 82 | } 83 | 84 | func (d DeviceType) RevisionNo() uint32 { 85 | return uint32(bh2i(d.RevisionNoRaw)) 86 | } 87 | 88 | type Sm struct { 89 | Name string `xml:",chardata"` 90 | MinSize, MaxSize, DefaultSize uint `xml:",attr"` 91 | StartAddressRaw string `xml:"StartAddress,attr"` 92 | ControlByteRaw string `xml:"ControlByte,attr"` 93 | } 94 | 95 | func (s Sm) StartAddress() uint16 { 96 | return uint16(bh2i(s.StartAddressRaw)) 97 | } 98 | 99 | func (s Sm) ControlByte() uint8 { 100 | return uint8(bh2i(s.ControlByteRaw)) 101 | } 102 | 103 | // beckhoff hex string to integer, 0 on failure 104 | func bh2i(s string) uint64 { 105 | var ( 106 | n uint64 107 | err error 108 | ) 109 | 110 | if strings.HasPrefix(s, "#x") { 111 | // as s has 2 byte prefix, indexing is OK 112 | n, err = strconv.ParseUint(s[2:], 16, 64) 113 | } else { 114 | n, err = strconv.ParseUint(s, 10, 64) 115 | } 116 | 117 | if err != nil { 118 | return 0 119 | } 120 | 121 | return n 122 | } 123 | 124 | type Eeprom struct { 125 | ByteSize uint 126 | ConfigDataRaw string `xml:"ConfigData"` 127 | } 128 | -------------------------------------------------------------------------------- /sim/l2eeprom.go: -------------------------------------------------------------------------------- 1 | package sim 2 | 3 | type L2EEPROM struct { 4 | Array [8 * 1024]uint16 5 | 6 | Addr uint32 7 | DataScratch [8]byte // already in wire encoding 8 | 9 | PDIControl bool 10 | WriteEnable bool 11 | ChecksumError bool 12 | EENotLoaded bool 13 | MissingAcknowledge bool 14 | ErrorWriteEnable bool 15 | Busy bool 16 | } 17 | 18 | func NewL2EEPROM() *L2EEPROM { 19 | ee := &L2EEPROM{} 20 | 21 | for i := 0; i < len(ee.Array); i++ { 22 | ee.Array[i] = 0xee00 + uint16(i) 23 | } 24 | 25 | return ee 26 | } 27 | 28 | func (ee *L2EEPROM) Reg() *L2EEPROMRegisterSet { 29 | return &L2EEPROMRegisterSet{ee} 30 | } 31 | 32 | type L2EEPROMRegisterSet struct{ *L2EEPROM } 33 | 34 | func (ee *L2EEPROMRegisterSet) Read(offs uint16, dp *uint8) bool { 35 | switch offs { 36 | case 0: 37 | if ee.PDIControl { 38 | *dp = 0x01 39 | } else { 40 | *dp = 0x00 41 | } 42 | case 1: 43 | *dp = 0x00 44 | case 2: 45 | if ee.WriteEnable { 46 | *dp |= 0x01 47 | } 48 | *dp |= 0xc0 // 2 address bytes, support 8 bytes 49 | case 3: 50 | // lower 3 bits are command 51 | if ee.ChecksumError { 52 | *dp |= 1 << (11 - 8) 53 | } 54 | if ee.EENotLoaded { 55 | *dp |= 1 << (12 - 8) 56 | } 57 | if ee.MissingAcknowledge { 58 | *dp |= 1 << (13 - 8) 59 | } 60 | if ee.ErrorWriteEnable { 61 | *dp |= 1 << (14 - 8) 62 | } 63 | if ee.Busy { 64 | *dp |= 1 << (15 - 8) 65 | } 66 | case 4: 67 | *dp = uint8(ee.Addr) 68 | case 5: 69 | *dp = uint8(ee.Addr >> 8) 70 | case 6: 71 | *dp = uint8(ee.Addr >> 16) 72 | case 7: 73 | *dp = uint8(ee.Addr >> 24) 74 | default: 75 | if offs > 16 { 76 | panic("invalid use of ee reg area, read past end") 77 | } 78 | if offs >= 8 && offs < 16 { 79 | *dp = ee.DataScratch[offs-8] 80 | } 81 | } 82 | 83 | return true 84 | } 85 | 86 | func (ee *L2EEPROMRegisterSet) WriteInteract(offs uint16) bool { 87 | if offs == 2 || offs == 3 { 88 | return !ee.Busy 89 | } 90 | return true 91 | } 92 | 93 | func (ee L2EEPROMRegisterSet) Latch(shadow []byte, shadowWriteMask []bool) { 94 | for offs := 0; offs < len(shadow); offs++ { 95 | switch { 96 | case offs == 0: 97 | if shadowWriteMask[0] { 98 | if shadow[0]&0x01 != 0 { 99 | ee.PDIControl = true 100 | } else { 101 | ee.PDIControl = false 102 | } 103 | } 104 | case offs == 1: 105 | // pdi access state. we don't even fake that 106 | case offs == 2: 107 | if shadowWriteMask[2] { 108 | if shadow[2]&0x01 != 0 { 109 | ee.WriteEnable = true 110 | } else { 111 | ee.WriteEnable = false 112 | } 113 | } 114 | case offs == 3: 115 | if shadowWriteMask[3] { 116 | switch shadow[3] & 0x03 { 117 | case 0x00: 118 | ee.ChecksumError = false 119 | ee.EENotLoaded = false 120 | ee.MissingAcknowledge = false 121 | ee.ErrorWriteEnable = false 122 | case 0x01: 123 | // TODO: busy time/cycles 124 | ee.Busy = false 125 | ee.readIntoScratch() 126 | default: 127 | // write/reload not supported 128 | } 129 | } 130 | case offs == 4: 131 | if shadowWriteMask[4] { 132 | ee.Addr &^= 0xff 133 | ee.Addr |= uint32(shadow[4]) << 0 134 | } 135 | case offs == 5: 136 | if shadowWriteMask[5] { 137 | ee.Addr &^= 0xff00 138 | ee.Addr |= uint32(shadow[5]) << 8 139 | } 140 | case offs == 6: 141 | if shadowWriteMask[6] { 142 | ee.Addr &^= 0xff0000 143 | ee.Addr |= uint32(shadow[6]) << 16 144 | } 145 | case offs == 7: 146 | if shadowWriteMask[7] { 147 | ee.Addr &^= 0xff000000 148 | ee.Addr |= uint32(shadow[7]) << 32 149 | } 150 | 151 | case offs >= 8 && offs < 16: 152 | if shadowWriteMask[offs] { 153 | ee.DataScratch[offs-8] = shadow[offs] 154 | } 155 | } 156 | } 157 | } 158 | 159 | func (ee *L2EEPROM) readIntoScratch() { 160 | for i := 0; i < 4; i++ { 161 | w16 := ee.Array[(int(ee.Addr)+i)%len(ee.Array)] 162 | ee.DataScratch[i*2] = uint8(w16) 163 | ee.DataScratch[i*2+1] = uint8(w16 >> 8) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /sim/l2slave.go: -------------------------------------------------------------------------------- 1 | package sim 2 | 3 | import ( 4 | "github.com/distributed/ecat/ecad" 5 | "github.com/distributed/ecat/ecfr" 6 | ) 7 | 8 | const ( 9 | regAreaLength = 0x1000 10 | ) 11 | 12 | type FrameProcessor interface { 13 | ProcessFrame(*ecfr.Frame) *ecfr.Frame 14 | } 15 | 16 | type L2Slave struct { 17 | BackingMemory [1 << 16]byte 18 | 19 | registerShadow [regAreaLength]byte 20 | registerShadowWriteMask [regAreaLength]bool 21 | 22 | regMappings []MMapping 23 | 24 | ALStatusControl *ALStatusControl 25 | EEPROM *L2EEPROM 26 | } 27 | 28 | func NewL2Slave() *L2Slave { 29 | s := &L2Slave{} 30 | 31 | // ET1100 signature 32 | copy(s.BackingMemory[:0x10], []byte{0x11, 0x00, 0x02, 0x00, 0x08, 0x08, 0x08, 0x0b, 0xfc}) 33 | 34 | s.ALStatusControl = NewALStatusControl() 35 | s.regMappings = append(s.regMappings, DevMapping{ecad.ALControl, 0x02, s.ALStatusControl.ControlReg()}) 36 | s.regMappings = append(s.regMappings, DevMapping{ecad.ALStatus, 0x06, s.ALStatusControl.StatusReg()}) 37 | 38 | s.EEPROM = NewL2EEPROM() 39 | s.regMappings = append(s.regMappings, DevMapping{ecad.ESIEEPROMInterface, 0x10, s.EEPROM.Reg()}) 40 | 41 | return s 42 | } 43 | 44 | // returns true if interaction happened 45 | func (s L2Slave) llread8p(addr uint16, dp *uint8) bool { 46 | if addr < regAreaLength { 47 | // register access 48 | m := s.addrToMapping(addr) 49 | if m != nil { 50 | return m.Device().Read(addr-m.Start(), dp) 51 | } 52 | } 53 | 54 | *dp = s.BackingMemory[addr] 55 | return true 56 | } 57 | 58 | // returns true if interaction happened. 59 | func (s *L2Slave) llwrite8(addr uint16, d uint8) bool { 60 | if addr < regAreaLength { 61 | s.registerShadow[addr] = d 62 | s.registerShadowWriteMask[addr] = true 63 | 64 | // TODO: need to consult regs if writing is OK 65 | m := s.addrToMapping(addr) 66 | if m != nil { 67 | return m.Device().WriteInteract(addr - m.Start()) 68 | } 69 | } 70 | 71 | // no support for sync managers so far 72 | s.BackingMemory[addr] = d 73 | return true 74 | } 75 | 76 | func (s *L2Slave) addrToMapping(addr uint16) MMapping { 77 | for _, m := range s.regMappings { 78 | if addr >= m.Start() && addr < (m.Start()+m.Length()) { 79 | return m 80 | } 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func (s *L2Slave) ProcessFrame(infr *ecfr.Frame) (ofr *ecfr.Frame) { 87 | ofr = infr 88 | 89 | for _, dg := range infr.Datagrams { 90 | // TODO: should ecfr.Frame contain a DatagramAddress instead of Addr32? 91 | if s.isPhysicalAddr(dg.Command, dg.Addr32) { 92 | dga := ecfr.DatagramAddressFromCommand(dg.Addr32, dg.Command) 93 | physaddressed := s.isPhysicallyAdressed(dga) 94 | dga.IncrementSlaveAddr() 95 | dg.Addr32 = dga.Addr32() 96 | if !physaddressed { 97 | continue 98 | } 99 | 100 | readUnmasked := true 101 | if dg.Command.DoesRead() { 102 | physbase := dga.Offset() 103 | for i := uint16(0); i < dg.DataLength(); i++ { 104 | //di := dg.Data()[i] 105 | readUnmasked = s.llread8p(physbase+i, &(dg.Data()[i])) && readUnmasked 106 | //do := dg.Data()[i] 107 | //fmt.Printf("llread8p di %02x -> do %02x @ %p\n", di, do, &(dg.Data()[i])) 108 | } 109 | } 110 | 111 | writeUnmasked := true 112 | if dg.Command.DoesWrite() { 113 | physbase := dga.Offset() 114 | for i := uint16(0); i < dg.DataLength(); i++ { 115 | writeUnmasked = s.llwrite8(physbase+i, dg.Data()[i]) && writeUnmasked 116 | } 117 | } 118 | 119 | // working counter update logic 120 | if dg.Command.DoesRead() && dg.Command.DoesWrite() { 121 | // TODO: RW/ARMW update logic 122 | } else if dg.Command.DoesRead() { 123 | if readUnmasked { 124 | dg.WorkingCounter++ 125 | } 126 | } else if dg.Command.DoesWrite() { 127 | if writeUnmasked { 128 | dg.WorkingCounter++ 129 | } 130 | } 131 | } 132 | // no support for logical addresses 133 | } 134 | 135 | // latch register shadow into registers 136 | s.latchRegs() 137 | // frame is processed 138 | 139 | return 140 | } 141 | 142 | func (s *L2Slave) latchRegs() { 143 | for _, m := range s.regMappings { 144 | start := m.Start() 145 | end := start + m.Length() 146 | m.Device().Latch(s.registerShadow[start:end], 147 | s.registerShadowWriteMask[start:end]) 148 | } 149 | } 150 | 151 | func (s *L2Slave) isPhysicalAddr(ct ecfr.CommandType, addr32 uint32) bool { 152 | dga := ecfr.DatagramAddressFromCommand(addr32, ct) 153 | return dga.IsPhysical() 154 | } 155 | 156 | func (s *L2Slave) isPhysicallyAdressed(addr ecfr.DatagramAddress) bool { 157 | if addr.Type() == ecfr.Broadcast { 158 | return true 159 | } 160 | 161 | if addr.Type() == ecfr.Positional { 162 | return addr.PositionOrAddress() == 0 163 | } 164 | 165 | if addr.Type() == ecfr.Fixed { 166 | // TODO: station address reg 167 | return false 168 | } 169 | 170 | return false 171 | } 172 | 173 | func NewALStatusControl() *ALStatusControl { 174 | return &ALStatusControl{Store: 0x0011} 175 | } 176 | 177 | type ALStatusControl struct { 178 | Store uint16 179 | } 180 | 181 | func (a *ALStatusControl) IsECATWritable() bool { 182 | return true 183 | } 184 | 185 | func (a *ALStatusControl) InError() bool { 186 | return (a.Store & 0x10) != 0 187 | } 188 | 189 | func (a *ALStatusControl) SetError(seterr bool) { 190 | if seterr { 191 | a.Store |= 0x10 192 | } else { 193 | a.Store &^= 0x10 194 | } 195 | } 196 | 197 | type ALControl struct{ *ALStatusControl } 198 | 199 | func (sc *ALStatusControl) ControlReg() ALControl { return ALControl{sc} } 200 | 201 | func (c ALControl) Read(offs uint16, dp *uint8) bool { 202 | switch offs { 203 | case 0: 204 | *dp = uint8(c.Store) 205 | case 1: 206 | *dp = uint8(c.Store >> 8) 207 | default: 208 | panic("invalid mapping for ALControl exceeds possible length") 209 | } 210 | 211 | return true 212 | } 213 | 214 | func (c ALControl) WriteInteract(offs uint16) bool { 215 | return c.IsECATWritable() 216 | } 217 | 218 | func (c ALControl) Latch(shadow []byte, shadowWriteMask []bool) { 219 | if shadowWriteMask[0] { 220 | if (c.InError() && (shadow[0]&0x10) != 0) || !c.InError() { 221 | c.Store &^= 0x1f 222 | c.Store |= uint16(shadow[0] & 0x0f) 223 | } 224 | } 225 | } 226 | 227 | type ALStatus struct{ *ALStatusControl } 228 | 229 | func (sc *ALStatusControl) StatusReg() ALStatus { return ALStatus{sc} } 230 | 231 | func (s ALStatus) Read(offs uint16, dp *uint8) bool { 232 | //fmt.Printf("AL Status Read offs %d, dp %p\n", offs, dp) 233 | switch offs { 234 | case 0: 235 | *dp = uint8(s.Store) 236 | //fmt.Printf("read 0, *dp %#02x\n", *dp) 237 | case 1: 238 | *dp = uint8(s.Store >> 8) 239 | //fmt.Printf("read 1, AL Store %04x\n", s.Store) 240 | default: 241 | *dp = 0x00 242 | } 243 | return true 244 | } 245 | 246 | func (s ALStatus) WriteInteract(offs uint16) bool { 247 | return false 248 | } 249 | 250 | func (s ALStatus) Latch(shadow []byte, shadowWriteMask []bool) {} 251 | -------------------------------------------------------------------------------- /sim/mem.go: -------------------------------------------------------------------------------- 1 | package sim 2 | 3 | import ( 4 | "github.com/distributed/ecat/ecfr" 5 | ) 6 | 7 | const ( 8 | maxDatagramsLen = 1470 9 | ) 10 | 11 | type L2Bus struct { 12 | oframes []*ecfr.Frame 13 | 14 | Slaves []FrameProcessor 15 | } 16 | 17 | func (b *L2Bus) New(maxdatalen int) (fr *ecfr.Frame, err error) { 18 | var vframe ecfr.Frame 19 | buf := make([]byte, maxDatagramsLen+ecfr.FrameOverheadLen) 20 | vframe, err = ecfr.PointFrameTo(buf) 21 | if err != nil { 22 | return 23 | } 24 | 25 | vframe.Header.SetType(1) 26 | 27 | fr = &vframe 28 | b.oframes = append(b.oframes, fr) 29 | return 30 | } 31 | 32 | func (b *L2Bus) Cycle() (iframes []*ecfr.Frame, err error) { 33 | defer func() { 34 | b.oframes = nil 35 | }() 36 | 37 | for i, oframe := range b.oframes { 38 | var obytes []byte 39 | 40 | obytes, err = oframe.Commit() 41 | if err != nil { 42 | return 43 | } 44 | 45 | _ = i 46 | //fmt.Printf("oframe #%d: %s", i, oframe.MultilineSummary()) 47 | 48 | coframe := new(ecfr.Frame) 49 | cbytes := make([]byte, len(obytes)) 50 | copy(cbytes, obytes) 51 | _, err = coframe.Overlay(cbytes) 52 | if err != nil { 53 | return 54 | } 55 | 56 | for _, slave := range b.Slaves { 57 | coframe = slave.ProcessFrame(coframe) 58 | if coframe == nil { 59 | break 60 | } 61 | } 62 | 63 | if coframe != nil { 64 | iframes = append(iframes, coframe) 65 | } 66 | } 67 | 68 | for i, iframe := range iframes { 69 | _, _ = i, iframe 70 | //fmt.Printf("iframe #%d: %s", i, iframe.MultilineSummary()) 71 | } 72 | 73 | return 74 | } 75 | 76 | func (b *L2Bus) Close() error { return nil } 77 | -------------------------------------------------------------------------------- /sim/mmdevice.go: -------------------------------------------------------------------------------- 1 | package sim 2 | 3 | type MMDevice interface { 4 | Read(offs uint16, dp *uint8) bool 5 | WriteInteract(offs uint16) bool 6 | Latch(shadow []byte, shadowWriteMask []bool) 7 | } 8 | 9 | type MMapping interface { 10 | Start() uint16 11 | Length() uint16 12 | Device() MMDevice 13 | } 14 | 15 | type DevMapping struct { 16 | StartAddr uint16 17 | LengthField uint16 18 | DeviceField MMDevice 19 | } 20 | 21 | func (d DevMapping) Start() uint16 { return d.StartAddr } 22 | func (d DevMapping) Length() uint16 { return d.LengthField } 23 | func (d DevMapping) Device() MMDevice { return d.DeviceField } 24 | --------------------------------------------------------------------------------