-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
coredevice/comm: split protocol to allow reuse for Ethernet
1 parent
44304a3
commit cb2596b
Showing
3 changed files
with
249 additions
and
196 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import struct | ||
import zlib | ||
import logging | ||
from enum import Enum | ||
from fractions import Fraction | ||
|
||
from artiq.language import units | ||
from artiq.coredevice.runtime import Environment | ||
from artiq.coredevice import runtime_exceptions | ||
from artiq.language import core as core_language | ||
from artiq.coredevice.rpc_wrapper import RPCWrapper | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class _H2DMsgType(Enum): | ||
LINK_MESSAGE = 1 | ||
|
||
REQUEST_IDENT = 2 | ||
SWITCH_CLOCK = 3 | ||
|
||
LOAD_OBJECT = 4 | ||
RUN_KERNEL = 5 | ||
|
||
|
||
class _D2HMsgType(Enum): | ||
MESSAGE_UNRECOGNIZED = 1 | ||
LOG = 2 | ||
|
||
IDENT = 3 | ||
CLOCK_SWITCH_COMPLETED = 4 | ||
CLOCK_SWITCH_FAILED = 5 | ||
|
||
OBJECT_LOADED = 6 | ||
OBJECT_INCORRECT_LENGTH = 7 | ||
OBJECT_CRC_FAILED = 8 | ||
OBJECT_UNRECOGNIZED = 9 | ||
|
||
KERNEL_FINISHED = 10 | ||
KERNEL_STARTUP_FAILED = 11 | ||
KERNEL_EXCEPTION = 12 | ||
|
||
RPC_REQUEST = 13 | ||
|
||
|
||
class UnsupportedDevice(Exception): | ||
pass | ||
|
||
|
||
class CommGeneric: | ||
# methods for derived classes to implement | ||
def open(self): | ||
"""Opens the communication channel. | ||
Must do nothing if already opened.""" | ||
raise NotImplementedError | ||
|
||
def close(self): | ||
"""Closes the communication channel. | ||
Must do nothing if already closed.""" | ||
raise NotImplementedError | ||
|
||
def read(self, length): | ||
"""Reads exactly length bytes from the communication channel. | ||
The channel is assumed to be opened.""" | ||
raise NotImplementedError | ||
|
||
def write(self, data): | ||
"""Writes exactly length bytes to the communication channel. | ||
The channel is assumed to be opened.""" | ||
raise NotImplementedError | ||
# | ||
|
||
def _read(self, length): | ||
self.open() | ||
return self.read(length) | ||
|
||
def _write(self, data): | ||
self.open() | ||
self.write(data) | ||
|
||
def _get_device_msg(self): | ||
while True: | ||
(reply, ) = struct.unpack("B", self._read(1)) | ||
msg = _D2HMsgType(reply) | ||
if msg == _D2HMsgType.LOG: | ||
(length, ) = struct.unpack(">h", self._read(2)) | ||
log_message = "" | ||
for i in range(length): | ||
(c, ) = struct.unpack("B", self._read(1)) | ||
log_message += chr(c) | ||
logger.info("DEVICE LOG: %s", log_message) | ||
else: | ||
logger.debug("message received: %r", msg) | ||
return msg | ||
|
||
def get_runtime_env(self): | ||
self._write(struct.pack(">lb", 0x5a5a5a5a, | ||
_H2DMsgType.REQUEST_IDENT.value)) | ||
msg = self._get_device_msg() | ||
if msg != _D2HMsgType.IDENT: | ||
raise IOError("Incorrect reply from device: {}".format(msg)) | ||
(reply, ) = struct.unpack("B", self._read(1)) | ||
runtime_id = chr(reply) | ||
for i in range(3): | ||
(reply, ) = struct.unpack("B", self._read(1)) | ||
runtime_id += chr(reply) | ||
if runtime_id != "AROR": | ||
raise UnsupportedDevice("Unsupported runtime ID: {}" | ||
.format(runtime_id)) | ||
ref_freq_i, ref_freq_fn, ref_freq_fd = struct.unpack( | ||
">lBB", self._read(6)) | ||
ref_freq = (ref_freq_i + Fraction(ref_freq_fn, ref_freq_fd))*units.Hz | ||
ref_period = 1/ref_freq | ||
logger.debug("environment ref_period: %s", ref_period) | ||
return Environment(ref_period) | ||
|
||
def switch_clock(self, external): | ||
self._write(struct.pack( | ||
">lbb", 0x5a5a5a5a, _H2DMsgType.SWITCH_CLOCK.value, | ||
int(external))) | ||
msg = self._get_device_msg() | ||
if msg != _D2HMsgType.CLOCK_SWITCH_COMPLETED: | ||
raise IOError("Incorrect reply from device: {}".format(msg)) | ||
|
||
def load(self, kcode): | ||
self._write(struct.pack( | ||
">lblL", | ||
0x5a5a5a5a, _H2DMsgType.LOAD_OBJECT.value, | ||
len(kcode), zlib.crc32(kcode))) | ||
self._write(kcode) | ||
msg = self._get_device_msg() | ||
if msg != _D2HMsgType.OBJECT_LOADED: | ||
raise IOError("Incorrect reply from device: "+str(msg)) | ||
|
||
def run(self, kname): | ||
self._write(struct.pack( | ||
">lbl", 0x5a5a5a5a, _H2DMsgType.RUN_KERNEL.value, len(kname))) | ||
for c in kname: | ||
self._write(struct.pack(">B", ord(c))) | ||
logger.debug("running kernel: %s", kname) | ||
|
||
|
||
|
||
def _receive_rpc_values(self): | ||
r = [] | ||
while True: | ||
type_tag = chr(struct.unpack(">B", self._read(1))[0]) | ||
if type_tag == "\x00": | ||
return r | ||
if type_tag == "n": | ||
r.append(None) | ||
if type_tag == "b": | ||
r.append(bool(struct.unpack(">B", self._read(1))[0])) | ||
if type_tag == "i": | ||
r.append(struct.unpack(">l", self._read(4))[0]) | ||
if type_tag == "I": | ||
r.append(struct.unpack(">q", self._read(8))[0]) | ||
if type_tag == "f": | ||
r.append(struct.unpack(">d", self._read(8))[0]) | ||
if type_tag == "F": | ||
n, d = struct.unpack(">qq", self._read(16)) | ||
r.append(Fraction(n, d)) | ||
if type_tag == "l": | ||
r.append(self._receive_rpc_values()) | ||
|
||
def _serve_rpc(self, rpc_wrapper, rpc_map, user_exception_map): | ||
rpc_num = struct.unpack(">h", self._read(2))[0] | ||
args = self._receive_rpc_values() | ||
logger.debug("rpc service: %d %r", rpc_num, args) | ||
eid, r = rpc_wrapper.run_rpc( | ||
user_exception_map, rpc_map[rpc_num], args) | ||
self._write(struct.pack(">ll", eid, r)) | ||
logger.debug("rpc service: %d %r == %r", rpc_num, args, r) | ||
|
||
def _serve_exception(self, rpc_wrapper, user_exception_map): | ||
eid, p0, p1, p2 = struct.unpack(">lqqq", self._read(4+3*8)) | ||
rpc_wrapper.filter_rpc_exception(eid) | ||
if eid < core_language.first_user_eid: | ||
exception = runtime_exceptions.exception_map[eid] | ||
raise exception(self.core, p0, p1, p2) | ||
else: | ||
exception = user_exception_map[eid] | ||
raise exception | ||
|
||
def serve(self, rpc_map, user_exception_map): | ||
rpc_wrapper = RPCWrapper() | ||
while True: | ||
msg = self._get_device_msg() | ||
if msg == _D2HMsgType.RPC_REQUEST: | ||
self._serve_rpc(rpc_wrapper, rpc_map, user_exception_map) | ||
elif msg == _D2HMsgType.KERNEL_EXCEPTION: | ||
self._serve_exception(rpc_wrapper, user_exception_map) | ||
elif msg == _D2HMsgType.KERNEL_FINISHED: | ||
return | ||
else: | ||
raise IOError("Incorrect request from device: "+str(msg)) | ||
|
||
def send_link_message(self, data): | ||
self._write(struct.pack( | ||
">lb", 0x5a5a5a5a, _H2DMsgType.LINK_MESSAGE.value)) | ||
self._write(data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters