Created
April 12, 2019 13:37
-
-
Save tai/bc9349d82cd01ca813a6b16e7a0244ba to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8-unix -*- | |
| """ | |
| An attempt to rewrite ucdev.register.* using ctypes.Structure. | |
| Since ctypes.* and struct.* have been around for a long time, | |
| it first seem that an interface ucdev.register is trying to | |
| accomplish can somehow be done without reinventing the wheel | |
| as current version of ucdev does. It turned out it's not the case. | |
| Pros: | |
| - Re-use of ctypes.* class makes endian handling easier | |
| Cons: | |
| - You cannot define non-byte-aligned bitfield larger than 64bit (c_uint64). | |
| - Code is more complex to handle values held by ctypes.Structure class. | |
| Although some metaprogramming approaches like class/object marking | |
| with class decorator for automatic code generation seemed to work well. | |
| My conclusion is it doesn't worth spending more time trying to re-use | |
| ctypes.* classes for the purpose of ucdev.register.* class. | |
| I'll just put this code on gist and continue improving current | |
| ucdev.register code. | |
| """ | |
| import sys | |
| import os | |
| import struct | |
| import ctypes | |
| import inspect | |
| import bitstring | |
| import logging | |
| from bitstring import * | |
| from ctypes import * | |
| from struct import pack, unpack | |
| from IPython import embed | |
| log = logging.getLogger(__name__) | |
| logging.basicConfig(level=eval('logging.DEBUG')) | |
| ###################################################################### | |
| class RegisterLike(Structure): | |
| """Mix-in class to add helper APIs to ctypes.* structures""" | |
| @classmethod | |
| def from_int(ct_struct_t, val): | |
| bits = Bits(length=sizeof(ct_struct_t) * 8, uint=val).tobytes() | |
| return ct_struct_t.from_buffer(bytearray(bits)) | |
| @classmethod | |
| def from_bits(ct_struct_t, bits): | |
| return ct_struct_t.from_buffer(bytearray(bits)) | |
| @property | |
| def fields(self): | |
| return [f[0] for f in self._fields_ if f[0]] | |
| @property | |
| def value(self): | |
| def bits(self): | |
| for f in self._fields_: | |
| fname = f[0] | |
| ftype = f[1] | |
| fsize = f[2] if len(f) == 3 else (sizeof(ftype) * 8) | |
| yield Bits(length=fsize, uint=getattr(self, fname)) | |
| return sum(list(bits(self))) | |
| ###################################################################### | |
| def protect_object(obj): | |
| """Write-protect non-existing attribute of given object""" | |
| sub = type("Protected" + type(obj).__name__, (type(obj),), {}) | |
| fset = sub.__setattr__ | |
| def fset_wrap(self, key, val): | |
| if not hasattr(self, key): | |
| raise AttributeError("Access denied for key: %s" % key) | |
| return fset(self, key, val) | |
| sub.__setattr__ = fset_wrap | |
| obj.__class__ = sub | |
| return obj | |
| ###################################################################### | |
| def new_register_base(base=LittleEndianStructure): | |
| """Return anonymous class to mark/define registers""" | |
| class Register(int): | |
| """Base class for all registers""" | |
| def __new__(self, desc, addr=0x00, base=base, *args, **kw): | |
| # TODO: Create ctypes Struct class from desc/addr | |
| log.debug("Defining new structure with base=%s" % base) | |
| ct_struct_t = parse_desc(desc, base=base, *args, **kw) | |
| # dynamically generate class for this register configuration | |
| reg_t = type("c_reg", (self,), {}) | |
| # wrap structure class with register class | |
| obj = register(reg_t, addr)(ct_struct_t) | |
| return protect_object(obj) | |
| return Register | |
| ###################################################################### | |
| def register(myreg_t, addr=0x00): | |
| """Returns decorator that wraps ctypes class with myreg_t""" | |
| def wrap_as_register(ct_struct_t): | |
| """Decorator to wrap ctypes class with myreg_t""" | |
| log.debug("Wrapping with type: %s" % myreg_t) | |
| log.debug("Injecting RegisterLike APIs...") | |
| for k,v in RegisterLike.__dict__.items(): | |
| if not hasattr(ct_struct_t, k): | |
| log.debug("Injecting API: %s" % k) | |
| setattr(ct_struct_t, k, v) | |
| log.debug("Parsing field information...") | |
| # get total bitlength of defined register | |
| length = 0 | |
| fields = list(ct_struct_t._fields_) | |
| for field in fields: | |
| ftype = field[1] | |
| fsize = field[2] if len(field) == 3 else (sizeof(ftype) * 8) | |
| length += fsize | |
| log.debug("total bitlength: %d" % length) | |
| # add readonly attribute to my_ret_t subclass | |
| kw = {} | |
| offset = 0 | |
| fields.reverse() # start from lower end | |
| for field in fields: | |
| fname = field[0] | |
| ftype = field[1] | |
| fsize = field[2] if len(field) == 3 else (sizeof(ftype) * 8) | |
| fmask = Bits(length=length, uint=((1 << fsize) - 1) << offset) | |
| log.debug("adding readonly field: %s" % fname) | |
| def readonly(val): | |
| return property(lambda _: val) | |
| kw[fname] = readonly(fmask) | |
| offset += fsize | |
| fields.reverse() # reset order | |
| # dynamically generate class for register instance | |
| log.debug("wrapping %s with %s" % (ct_struct_t, myreg_t)) | |
| reg_t = type(ct_struct_t.__name__, (myreg_t, ), kw) | |
| reg_t.fields = property(lambda v: [f[0] for f in fields if f[0]]) | |
| reg_t.__call__ = ct_struct_t | |
| # create register instance with given address value | |
| log.debug("creating register object with value %d" % addr) | |
| obj = int.__new__(reg_t, addr) | |
| return obj | |
| return wrap_as_register | |
| def include(myreg_t): | |
| """Returns a class decorator to add register fields""" | |
| def add_register_field(mychip_t): | |
| """Decorator to add myreg_t registers to mychip_t class""" | |
| log.debug("Adding registers with type: %s" % myreg_t) | |
| for k, v in globals().items(): | |
| if isinstance(v, myreg_t) and v != myreg_t: | |
| log.debug("Adding register %s" % k) | |
| setattr(mychip_t, k, v()) | |
| return mychip_t | |
| return add_register_field | |
| ###################################################################### | |
| def parse_desc(desc, base=LittleEndianStructure, *args, **kw): | |
| """Returns new ctypes structure from given description""" | |
| # parse register description | |
| fields = [] | |
| for f in desc.split(): | |
| # expected: f in (":", "HOGE", "HOGE:123", ":123") | |
| pair = f.split(":") | |
| if len(pair) == 2: | |
| f_name, f_bitlen = pair[0], int(pair[1]) if pair[1] else 1 | |
| else: | |
| f_name, f_bitlen = pair[0], 1 | |
| # | |
| # BUG: | |
| # Due to ctypes limitation, I cannot handle non-byte-aligned | |
| # bitfield larger than 64bit. Might be better not to use | |
| # ctypes after all :-( | |
| # | |
| if f_bitlen % 8 == 0: | |
| fields.append((f_name, c_uint8 * (f_bitlen >> 3))) | |
| else: | |
| fields.append((f_name, c_uint64, f_bitlen)) | |
| ct_struct_t = type("ct_struct_t", (base, ), {}) | |
| ct_struct_t._fields_ = tuple(fields) | |
| return ct_struct_t | |
| ###################################################################### | |
| TDReg = new_register_base() | |
| AAA = TDReg(": FOO BAR:2 BAZ:4", 0x80) | |
| BBB = TDReg(": FOO BAR:2 BAZ:4 BUF:256", 0x80) | |
| @register(TDReg, 0x81) | |
| class SOME_CONFIG(LittleEndianStructure): | |
| _pack_ = 1 | |
| _fields_ = ( | |
| ('RATE', c_uint32, 24), | |
| ('BITS', c_uint32, 8), | |
| ) | |
| @include(TDReg) | |
| class TestDevice(object): | |
| def on_update(self, key, val): | |
| pass | |
| def main(): | |
| td = TestDevice() | |
| embed() | |
| if __name__ == '__main__' and '__file__' in globals(): | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment