Skip to content

Instantly share code, notes, and snippets.

@tai
Created April 12, 2019 13:37
Show Gist options
  • Select an option

  • Save tai/bc9349d82cd01ca813a6b16e7a0244ba to your computer and use it in GitHub Desktop.

Select an option

Save tai/bc9349d82cd01ca813a6b16e7a0244ba to your computer and use it in GitHub Desktop.
#!/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