diff --git a/ebpfcat/__init__.py b/ebpfcat/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1a0d66d69d155d5ddd284bbd3cdeca1261523d13 100644 --- a/ebpfcat/__init__.py +++ b/ebpfcat/__init__.py @@ -0,0 +1,17 @@ +# ebpfcat, A Python-based EBPF generator and EtherCAT master +# Copyright (C) 2021 Martin Teichmann <martin.teichmann@gmail.com> +# +# 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + diff --git a/ebpfcat/bpf.py b/ebpfcat/bpf.py index bea41006debee46a4d3c1bc4a60e168b55c1dfd4..b9985af20f10f491f688f6f0100043d6e650f89f 100644 --- a/ebpfcat/bpf.py +++ b/ebpfcat/bpf.py @@ -152,10 +152,9 @@ def prog_test_run(fd, data_in, data_out, ctx_in, ctx_out, ctx_in = create_string_buffer(ctx_in, len(ctx_in)) data_out = create_string_buffer(data_out) ctx_out = create_string_buffer(ctx_out) - ret, (_, retval, _, _, _, _, _, duration, _, _, _, _) = bpf( - 10, "IIIIQQIIIIQQ20x", fd, 0, len(data_in), len(data_out), - addrof(data_in), addrof(data_out), repeat, 0, 0, 0, 0, 0) - #len(ctx_in), len(ctx_out), addrof(ctx_in), addrof(ctx_out)) + ret, (_, retval, _, _, _, _, _, duration) = bpf( + 10, "IIIIQQII20x", fd, 0, len(data_in), len(data_out), + addrof(data_in), addrof(data_out), repeat, 0) return ret, retval, duration, data_out.value, ctx_out.value if __name__ == "__main__": diff --git a/ebpfcat/chem.py b/ebpfcat/chem.py deleted file mode 100644 index 4ac4f3e4261087bf1e5a0051f2a65fc4ba1373c6..0000000000000000000000000000000000000000 --- a/ebpfcat/chem.py +++ /dev/null @@ -1,101 +0,0 @@ -# ebpfcat, A Python-based EBPF generator and EtherCAT master -# Copyright (C) 2021 Martin Teichmann <martin.teichmann@gmail.com> -# -# 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., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from asyncio import gather, sleep, ensure_future -from struct import unpack, pack -from .terminals import EL7041, EL5042, Skip -from .devices import Motor, Counter, AnalogInput -from .ebpfcat import FastEtherCat, FastSyncGroup, SyncGroup - -con = Skip() -tm1 = EL7041() -te12 = EL5042() -tm2 = EL7041() - -ec = FastEtherCat("enp0s31f6", [con, tm1, te12, tm2]) - - -async def monitor(ec): - while True: - print("M", ec.ebpf.count, ec.ebpf.allcount, await tin.get_state()) - await sleep(0.1) - - -async def main(): - await ec.connect() - await ec.scan_bus() - #ensure_future(monitor(ec)) - - print("S", await te12.set_state(2)) - for i in [1, 2, 3] + list(range(0x11, 0x19)): - print(f"P{i:x}", await te12.sdo_read(0x8018, i)) - await te12.sdo_write(b"\0", 0x8018, 0x2) # statusbits - await te12.sdo_write(b"\0", 0x8018, 0x15) # multi - #await te12.sdo_write(b"\x1a", 0x8018, 0x16) # singleturn - await te12.sdo_write(b" ", 0x8018, 0x16) # singleturn - - await tm2.set_state(2) - await tm2.sdo_write(pack("<H", 2500), 0x8010, 0x1) # current - await tm2.sdo_write(pack("<H", 24000), 0x8010, 0x3) # voltage - - for i in [1, 2, 3]: - print(f"S{i:x}", await te12.sdo_read(0xB018, i)) - print(f"Pos", await te12.sdo_read(0x6010, 0x11)) - print(f"V", await te12.sdo_read(0xA018, 0x5)) - print(f"M", await te12.sdo_read(0x10F3, 0x6)) - print("State", await te12.get_state()) - - print("S", await tm2.set_state(2)) - for i in [1,2,3,4,5,6,7,9,0x10,0x11]: - print(f"M{i:x}", unpack("H", await tm2.sdo_read(0x8010, i))[0]) - - - m1 = Motor() - m1.velocity = tm2.velocity - m1.encoder = te12.channel2.position - m1.enable = tm2.enable - - aie = AnalogInput(te12.channel2.status) - aim = AnalogInput(tm2.status) - - fsg = SyncGroup(ec, [m1, aie, aim]) - - m1.max_velocity = 10000 - m1.proportional = 23 - m1.target = 185525000 - - print("do start") - fsg.start() - print("did start") - - print(f"P0", unpack("I", await tm2.sdo_read(0x6010, 0x14))[0]) - await sleep(0.1) - m1.set_enable = True - for i in range(20): - print(f"M {m1.current_position:12} {m1.velocity:5} {aie.value:4x} {aim.value:4x}") - await sleep(0.2) - m1.set_enable = False - m1.max_velocity = 0 - for i in range(20): - print(f"S {m1.current_position:12} {aie.value:4x} {aim.value:4x}") - await sleep(0.02) - print(f"P1", unpack("I", await tm2.sdo_read(0x6010, 0x14))[0]) - -if __name__ == "__main__": - from asyncio import get_event_loop - loop = get_event_loop() - loop.run_until_complete(main()) diff --git a/ebpfcat/ebpf_test.py b/ebpfcat/ebpf_test.py index a15f8d0c37f9694912b0447bc5d2fd3073e45c46..6cea964a6aaf658a350f50a49024724411db091b 100644 --- a/ebpfcat/ebpf_test.py +++ b/ebpfcat/ebpf_test.py @@ -372,7 +372,6 @@ class Tests(TestCase): e.r2 = 5 with cond.Else(): e.r3 = 7 - self.maxDiff = None self.assertEqual(e.opcodes, [ Instruction(opcode=O.JLE, dst=2, src=0, off=2, imm=3), Instruction(opcode=O.JLE, dst=3, src=0, off=1, imm=2), @@ -394,7 +393,6 @@ class Tests(TestCase): with cond.Else(): e.r3 = 7 e.r4 = 3 - self.maxDiff = None self.assertEqual(e.opcodes, [ Instruction(opcode=O.JGT, dst=2, src=0, off=1, imm=3), Instruction(opcode=O.JLE, dst=3, src=0, off=1, imm=2), @@ -588,7 +586,6 @@ class Tests(TestCase): e.r8 = e.r1 def test_binary_alloc(self): - self.maxDiff = None e = EBPF() e.r3 = e.r1 - (2 * e.r10) e.mH[e.r10 - 10] = 2 * e.r3 @@ -696,7 +693,7 @@ class KernelTests(TestCase): e.a += 7 e.exit() - fd = e.load() + fd, _ = e.load(log_level=1) prog_test_run(fd, 1000, 1000, 0, 0, 1) e.a *= 2 prog_test_run(fd, 1000, 1000, 0, 0, 1) @@ -757,12 +754,14 @@ class KernelTests(TestCase): self.assertEqual(s2.bw, 69) def test_minimal(self): - class Global(XDP): - map = HashMap() - a = map.globalVar() + class Local(EBPF): + a = LocalVar('I') - e = Global(license="GPL") - e.a += 1 + e = Local(ProgType.XDP, "GPL") + e.a = 7 + e.a += 3 + e.mI[e.r1] += e.r1 + e.a -= 3 e.exit() print(e.opcodes) print(e.load(log_level=1)[1]) diff --git a/ebpfcat/ebpfcat.py b/ebpfcat/ebpfcat.py index 488fe2bee0ec9ef745dd429621feb2fd1d7c0019..ba228b64aa665cfe3e33698b64e0cd845a87f1ec 100644 --- a/ebpfcat/ebpfcat.py +++ b/ebpfcat/ebpfcat.py @@ -276,7 +276,10 @@ class EtherXDP(XDP): with exceed.Else(): self.exit(XDPExitCode.TX) self.r2 = self.get_fd(self.programs) + for i, o in enumerate(self.opcodes): + print(i, o) self.call(FuncId.tail_call) + self.exit(XDPExitCode.PASS) @@ -319,6 +322,7 @@ class FastEtherCat(SimpleEtherCat): if ((counts[i] ^ lastcounts[i]) & 0xffff == 0 or (counts[i] >> 24) > 3): self.send_packet(sg.assembled) + print("sent", i) lastcounts[i] = counts[i] await sleep(0.001) @@ -392,6 +396,11 @@ class FastSyncGroup(SyncGroupBase, XDP): p.pB[pos + Packet.ETHERNET_HEADER] = cmd.value for dev in self.devices: dev.program() + for o in self.opcodes: + if o is not None: + print(o, hex(o.opcode.value)) + else: + print("JMP") self.exit(XDPExitCode.TX) def start(self): diff --git a/ebpfcat/ethercat.py b/ebpfcat/ethercat.py index 6677da0ec405e4951b62cbbeca2295778b90c273..261ee20cb9b9c3ec84728a821d616376c1566b9b 100644 --- a/ebpfcat/ethercat.py +++ b/ebpfcat/ethercat.py @@ -400,6 +400,8 @@ class Terminal: elif mode == 6: self.mbx_out_off = offset self.mbx_out_sz = size + s = await self.read(0x800, data=0x80) + print(absolute, " ".join(f"{c:02x} {'|' if i % 8 == 7 else ''}" for i, c in enumerate(s))) def parse_pdos(self): def parse_pdo(s): @@ -602,7 +604,7 @@ class Terminal: index, subindex, data) type, data = await self.mbx_recv() if type is not MBXType.COE: - raise RuntimeError(f"expected CoE, got {type}") + raise RuntimeError(f"expected CoE, got {type}, {data} {odata} {index:x} {subindex}") coecmd, sdocmd, idx, subidx = unpack("<HBHB", data[:6]) if idx != index or subindex != subidx: raise RuntimeError(f"requested index {index}, got {idx}") @@ -679,9 +681,9 @@ class Terminal: if dataType == 0: continue assert i == oe.valueInfo - try: + if dataType < 2048: oe.dataType = ECDataType(dataType) - except: + else: oe.dataType = dataType oe.name = data[8:].decode("utf8") od.entries[i] = oe @@ -689,38 +691,51 @@ class Terminal: async def main(): - from .ebpfcat import install_ebpf from .bpf import lookup_elem - ec = await EtherCat("eth0") - map_fd = await install_ebpf("eth0") - tin = Terminal(ec) - tout = Terminal(ec) - tdigi = Terminal(ec) + ec = EtherCat("eth0") + await ec.connect() + #map_fd = await install_ebpf2() + tin = Terminal() + tin.ec = ec + tout = Terminal() + tout.ec = ec + tdigi = Terminal() + tdigi.ec = ec await gather( - tin.initialize(-1, 5), - tout.initialize(-2, 6), + tin.initialize(-4, 19), + tout.initialize(-2, 55), tdigi.initialize(0, 22), ) print("tin") - await tin.to_operational(), + #await tin.to_operational() + await tin.set_state(2) print("tout") - await tout.to_operational(), - odlist = await tin.read_ODlist() - for o in odlist: + await tout.to_operational() + print("reading odlist") + odlist2, odlist = await gather(tin.read_ODlist(), tout.read_ODlist()) + #oe = odlist[0x7001][1] + #await oe.write(1) + for o in odlist.values(): print(hex(o.index), o.name, o.maxSub) for i, p in o.entries.items(): print(" ", i, p.name, "|", p.dataType, p.bitLength, p.objectAccess) #sdo = await tin.sdo_read(o.index, i) - sdo = await p.read() - print(" ", sdo) - print("tdigi") - print("bla", lookup_elem(map_fd, b"AAAA", 4)) - await tdigi.to_operational(), - - print(tout.eeprom[10]) - print(await tout.write(0x1100, "HHHH", 10000, 20000, 30000, 40000)) - print(await tin.read(0x1180, "HHHHHHHH")) + try: + sdo = await p.read() + if isinstance(sdo, int): + t = hex(sdo) + else: + t = "" + print(" ", sdo, t) + except RuntimeError as e: + print(" E", e) + print("set sdo") + oe = odlist[0x8010][7] + print("=", await oe.read()) + await oe.write(1) + print("=", await oe.read()) + print(tdigi.eeprom[10]) if __name__ == "__main__": loop = get_event_loop() diff --git a/ebpfcat/ethercat_test.py b/ebpfcat/ethercat_test.py index 9921fd565ad7749e668f7b8ec64d400c16f7bbdd..f95f0c701ae51b3d64986a7ea01fc95bac6f1dfa 100644 --- a/ebpfcat/ethercat_test.py +++ b/ebpfcat/ethercat_test.py @@ -18,7 +18,7 @@ from asyncio import CancelledError, Future, get_event_loop, sleep, gather from unittest import TestCase, main -from .devices import AnalogInput, AnalogOutput +from .devices import AnalogInput, AnalogOutput, Motor from .terminals import EL4104, EL3164, EK1814 from .ethercat import ECCmd from .ebpfcat import ( @@ -237,5 +237,84 @@ class Tests(TestCase): Instruction(opcode=O.MOV+O.LONG, dst=0, src=0, off=0, imm=3), Instruction(opcode=O.EXIT, dst=0, src=0, off=0, imm=0)]) + + def tet_two(self): + ti1 = EL3164() + ti2 = EL3164() + ti1.pdo_in_sz = ti2.pdo_in_sz = 4 + ti1.pdo_in_off = ti2.pdo_in_off = 0xABCD + ti1.position = 0x77 + ti2.position = 0x99 + ti1.pdo_out_sz = ti2.pdo_out_sz = None + ti1.pdo_out_off = ti2.pdo_out_off = None + ec = MockEtherCat(self) + ti1.ec = ti2.ec = ec + ai1 = AnalogInput(ti1.ch1_value) + ai2 = AnalogInput(ti2.ch1_value) + ai3 = AnalogInput(ti1.ch1_attrs) + SyncGroup.packet_index = 1000 + sg = SyncGroup(ec, [ai1, ai2, ai3]) + self.task = sg.start() + ec.expected = [ + (ECCmd.FPRD, 0x99, 304, "H2xH"), # get state + (ECCmd.FPRD, 0x77, 304, "H2xH"), # get state + bytes.fromhex("2d10" # EtherCAT Header, length & type + "0000e8030000008000000000" # ID datagram + "04007700cdab04800000000000000000" # in datagram + "050077002143030000000000000000"), # out datagram + 1000, # == 0x3e8, see ID datagram + bytes.fromhex("2d10" # EtherCAT Header, length & type + "0000e8030000008000000000" # ID datagram + "04007700cdab04800000123456780000" # in datagram + "050077002143030000000000000000"), # out datagram + 1000, + ] + ec.results = [ + (8, 0), # return state 8, no error + (8, 0), # return state 8, no error + bytes.fromhex("2d10" # EtherCAT Header, length & type + "0000e8030000008000000000" # ID datagram + "04007700cdab04800000123456780000" # in datagram + "050077002143030000000000000000"), # out datagram + ] + self.future = Future() + with self.assertRaises(CancelledError): + get_event_loop().run_until_complete( + gather(self.future, self.task)) + self.assertEqual(ai.value, 0x7856) + self.task.cancel() + with self.assertRaises(CancelledError): + get_event_loop().run_until_complete(self.task) + + def test_motor(self): + class T(EBPFTerminal): + v = PacketDesc((0, 2), "H") + e = PacketDesc((1, 0), "H") + q = PacketDesc((0, 0), 0) + t = T() + t.pdo_in_sz = 2 + t.pdo_in_off = 0x1234 + t.pdo_out_sz = 4 + t.pdo_out_off = 0x5678 + t.position = 7 + m = Motor() + m.velocity = t.v + m.encoder = t.e + m.low_switch = m.high_switch = t.q + me = MockEtherCat(self) + me.expected = [ + bytes.fromhex("2c10" + "000033000000008000000000" + "0400070034120280000000000000" + "05000700785604000000000000000000")] + sg = FastSyncGroup(me, [m]) + sg.start() + #sg.program() + #sg.opcodes = sg.opcodes[:-1] + print(sg.load(log_level=1)[1]) + self.maxDiff = None + self.assertEqual(sg.opcodes, []) + + if __name__ == "__main__": main() diff --git a/ebpfcat/testsystem.py b/ebpfcat/testsystem.py deleted file mode 100644 index be7aab353c242c9490ca4e78112f6bf93f633da4..0000000000000000000000000000000000000000 --- a/ebpfcat/testsystem.py +++ /dev/null @@ -1,63 +0,0 @@ -# ebpfcat, A Python-based EBPF generator and EtherCAT master -# Copyright (C) 2021 Martin Teichmann <martin.teichmann@gmail.com> -# -# 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., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from asyncio import gather, sleep, ensure_future -from .terminals import EL3164, EL4104, EK1814 -from .devices import AnalogInput, AnalogOutput, DigitalInput, DigitalOutput -from .ebpfcat import FastEtherCat, FastSyncGroup, SyncGroup - -tdigi = EK1814() -tout = EL4104() -tin = EL3164() - -ec = FastEtherCat("eth0", [tdigi, tin, tout]) - - -async def monitor(ec): - while True: - print("M", ec.ebpf.count, ec.ebpf.allcount, await tin.get_state()) - await sleep(0.1) - - -async def main(): - await ec.connect() - await ec.scan_bus() - #ensure_future(monitor(ec)) - - ai = AnalogInput(tin.ch1_value) - ao = AnalogOutput(tout.ch1_value) - di = DigitalInput(tdigi.ch1) - do = DigitalOutput(tdigi.ch8) - #fsg = FastSyncGroup(ec, [ai, ao]) - fsg = SyncGroup(ec, [ai, ao, do, di]) - - fsg.start() - ao.value = 0 - do.value = False - - for i in range(100): - await sleep(0.1) - #fsg.properties.read() - ao.value = 300 * i - do.value = (i % 7) in (0, 1, 2, 5) - #fsg.properties.write() - print(i, ai.value, ao.value, di.value, await tout.get_state()) - -if __name__ == "__main__": - from asyncio import get_event_loop - loop = get_event_loop() - loop.run_until_complete(main()) diff --git a/setup.cfg b/setup.cfg index 97986f0de36f3446b8586290dd35c58a68af2c5e..a77669319d761a69002241d5e251c069daffc991 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = ebpfcat -version = 0.0.1 +version = 0.1.0 author = Martin Teichmann author_email = martin.teichmann@xfel.eu description = An EtherCAT master written in Python using EBPF and XDP