#
# Copyright 2017 Ettus Research (National Instruments)
#
# 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 3 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, see .
#
"""
N310 implementation module
"""
from __future__ import print_function
import os
import copy
import shutil
from six import iteritems, itervalues
from builtins import object
from .base import PeriphManagerBase
from ..net import get_iface_addrs
from ..net import byte_to_mac
from ..net import get_mac_addr
from ..mpmtypes import SID
from usrp_mpm.rpc_server import no_rpc
from usrp_mpm import net
from usrp_mpm import dtoverlay
from ..sysfs_gpio import SysFSGPIO
from ..ethtable import EthDispatcherTable
from ..liberiotable import LiberioDispatcherTable
from .. import libpyusrp_periphs as lib
N3XX_DEFAULT_EXT_CLOCK_FREQ = 10e6
N3XX_DEFAULT_CLOCK_SOURCE = 'external'
N3XX_DEFAULT_TIME_SOURCE = 'internal'
N3XX_DEFAULT_ENABLE_GPS = True
N3XX_DEFAULT_ENABLE_FPGPIO = True
###############################################################################
# Additional peripheral controllers specific to Magnesium
###############################################################################
class TCA6424(object):
"""
Abstraction layer for the port/gpio expander
pins_list is an array of different version of TCA6424 pins map.
First element of this array corresponding to revC, second is revD etc...
"""
pins_list = [
(
'PWREN-CLK-MGT156MHz',
'NETCLK-CE', #revC name: 'PWREN-CLK-WB-CDCM',
'NETCLK-RESETn', #revC name: 'WB-CDCM-RESETn',
'NETCLK-PR0', #revC name: 'WB-CDCM-PR0',
'NETCLK-PR1', #revC name: 'WB-CDCM-PR1',
'NETCLK-OD0', #revC name: 'WB-CDCM-OD0',
'NETCLK-OD1', #revC name: 'WB-CDCM-OD1',
'NETCLK-OD2', #revC name: 'WB-CDCM-OD2',
'PWREN-CLK-MAINREF',
'CLK-MAINSEL-25MHz', #revC name: 'CLK-MAINREF-SEL1',
'CLK-MAINSEL-EX_B', #revC name: 'CLK-MAINREF-SEL0',
'12',
'CLK-MAINSEL-GPS', #revC name: '13',
'FPGA-GPIO-EN',
'PWREN-CLK-WB-20MHz',
'PWREN-CLK-WB-25MHz',
'GPS-PHASELOCK',
'GPS-nINITSURV',
'GPS-nRESET',
'GPS-WARMUP',
'GPS-SURVEY',
'GPS-LOCKOK',
'GPS-ALARM',
'PWREN-GPS',
),
(
'NETCLK-PR1',
'NETCLK-PR0',
'NETCLK-CE',
'NETCLK-RESETn',
'NETCLK-OD2',
'NETCLK-OD1',
'NETCLK-OD0',
'PWREN-CLK-MGT156MHz',
'PWREN-CLK-MAINREF',
'CLK-MAINSEL-25MHz',
'CLK-MAINSEL-EX_B',
'12',
'CLK-MAINSEL-GPS',
'FPGA-GPIO-EN',
'PWREN-CLK-WB-20MHz',
'PWREN-CLK-WB-25MHz',
'GPS-PHASELOCK',
'GPS-nINITSURV',
'GPS-nRESET',
'GPS-WARMUP',
'GPS-SURVEY',
'GPS-LOCKOK',
'GPS-ALARM',
'PWREN-GPS',
)]
def __init__(self, rev):
# Default state: Turn on GPS power, take GPS out of reset or
# init-survey, turn on 156.25 MHz clock
# min Support from revC or rev = 2
if rev == 2:
self.pins = self.pins_list[0]
else:
self.pins = self.pins_list[1]
default_val = 0x860101 if rev == 2 else 0x860780
self._gpios = SysFSGPIO('tca6424', 0xFFF7FF, 0x86F7FF, default_val)
def set(self, name, value=None):
"""
Assert a pin by name
"""
assert name in self.pins
self._gpios.set(self.pins.index(name), value=value)
def reset(self, name):
"""
Deassert a pin by name
"""
self.set(name, value=0)
def get(self, name):
"""
Read back a pin by name
"""
assert name in self.pins
return self._gpios.get(self.pins.index(name))
class FP_GPIO(object):
"""
Abstraction layer for the front panel GPIO
"""
EMIO_BASE = 54
FP_GPIO_OFFSET = 32 # Bit offset within the ps_gpio_* pins
def __init__(self, ddr):
self._gpiosize = 12
self._offset = self.FP_GPIO_OFFSET + self.EMIO_BASE
self.usemask = 0xFFF
self.ddr = ddr
self._gpios = SysFSGPIO(
'zynq_gpio',
self.usemask< 5
assert int(xport_info.get('src_port')) > 0
sender_addr = xport_info['src_ipv4']
sender_port = int(xport_info['src_port'])
self.log.trace("Incoming connection is coming from %s:%d",
sender_addr, sender_port)
mac_addr = get_mac_addr(sender_addr)
if mac_addr is None:
raise RuntimeError(
"Could not find MAC address for IP address {}".format(
sender_addr))
self.log.trace("Incoming connection is coming from %s",
mac_addr)
eth_iface = net.ip_addr_to_iface(xport_info['ipv4'], self._chdr_ifaces)
xbar_port = self.xbar_port_map[eth_iface]
self.log.trace("Using Ethernet interface %s, crossbar port %d",
eth_iface, xbar_port)
my_xbar = lib.xbar.xbar.make("/dev/crossbar0") # TODO don't hardcode
my_xbar.set_route(sid.src_addr, xbar_port)
self._eth_dispatchers[eth_iface].set_route(
sid.reversed(), sender_addr, sender_port)
self.log.trace("UDP transport successfully committed!")
return True
class XportMgrLiberio(object):
"""
Transport manager for UDP connections
"""
# udev label for the UIO device that controls the DMA engine
liberio_label = 'liberio'
def __init__(self, log):
self.log = log
self._dma_dispatcher = LiberioDispatcherTable(self.liberio_label)
self._data_chan_ctr = 0
self._max_chan = 10 # TODO get this number from somewhere
def init(self, args):
"""
Call this when the user calls 'init' on the periph manager
"""
pass
def deinit(self):
" Clean up after a session terminates "
self._data_chan_ctr = 0
def request_xport(
self,
sid,
xport_type,
):
"""
Return liberio xport info
"""
assert xport_type in ('CTRL', 'ASYNC_MSG', 'TX_DATA', 'RX_DATA')
if xport_type == 'CTRL':
chan = 0
elif xport_type == 'ASYNC_MSG':
chan = 1
else:
chan = 2 + self._data_chan_ctr
self._data_chan_ctr += 1
xport_info = {
'type': 'liberio',
'send_sid': str(sid),
'muxed': str(xport_type in ('CTRL', 'ASYNC_MSG')),
'dma_chan': str(chan),
'tx_dev': "/dev/tx-dma{}".format(chan),
'rx_dev': "/dev/rx-dma{}".format(chan),
}
self.log.trace("Liberio: Chan: {} TX Device: {} RX Device: {}".format(
chan, xport_info['tx_dev'], xport_info['rx_dev']))
self.log.trace("Liberio channel is muxed: %s",
"Yes" if xport_info['muxed'] else "No")
return [xport_info]
def commit_xport(self, sid, xport_info):
" Commit liberio transport "
chan = int(xport_info['dma_chan'])
my_xbar = lib.xbar.xbar.make("/dev/crossbar0") # TODO
my_xbar.set_route(sid.src_addr, 2) # TODO
self._dma_dispatcher.set_route(sid.reversed(), chan)
self.log.trace("Liberio transport successfully committed!")
return True
###############################################################################
# Main Class
###############################################################################
class n310(PeriphManagerBase):
"""
Holds N310 specific attributes and methods
"""
#########################################################################
# Overridables
#
# See PeriphManagerBase for documentation on these fields
#########################################################################
pids = [0x4242,]
mboard_eeprom_addr = "e0005000.i2c"
mboard_eeprom_max_len = 256
mboard_info = {"type": "n3xx",
"product": "n310"
}
mboard_max_rev = 3 # 3 == RevD
mboard_sensor_callback_map = {
'ref_locked': 'get_ref_lock_sensor',
'gps_locked': 'get_gps_lock_sensor',
}
dboard_eeprom_addr = "e0004000.i2c"
dboard_eeprom_max_len = 64
# We're on a Zynq target, so the following two come from the Zynq standard
# device tree overlay (tree/arch/arm/boot/dts/zynq-7000.dtsi)
dboard_spimaster_addrs = ["e0006000.spi", "e0007000.spi"]
chdr_interfaces = ['eth1', 'eth2']
# N310-specific settings
# Path to N310 FPGA bin file
# This file will always contain the current image, regardless of SFP type,
# dboard, etc. The host is responsible for providing a compatible image
# for the N310's current setup.
# Override the list of updateable components
updateable_components = {
'fpga': {
'callback': "update_fpga",
'path': '/lib/firmware/n3xx.bin',
'reset': True,
},
'dts': {
'callback': "update_dts",
'path': '/lib/firmware/n3xx.dts',
'reset': False,
},
}
@staticmethod
def list_required_dt_overlays(eeprom_md, device_args):
"""
Lists device tree overlays that need to be applied before this class can
be used. List of strings.
Are applied in order.
eeprom_md -- Dictionary of info read out from the mboard EEPROM
device_args -- Arbitrary dictionary of info, typically user-defined
"""
return ['n3xx']
def __init__(self, args):
super(n310, self).__init__(args)
self._device_initialized = False
self._ext_clock_freq = None
self._clock_source = None
self._time_source = None
try:
self._init_peripherals(args)
self._device_initialized = True
except Exception as ex:
self.log.error("Failed to initialize motherboard: %s", str(ex))
def _init_peripherals(self, args):
"""
Turn on all peripherals. This may throw an error on failure, so make
sure to catch it.
"""
# Init peripherals
self.log.trace("Initializing TCA6424 port expander controls...")
self._gpios = TCA6424(int(self.mboard_info['rev']))
self.log.trace("Enabling power of MGT156MHZ clk")
self._gpios.set("PWREN-CLK-MGT156MHz")
self.enable_1G_ref_clock()
self.enable_gps(
enable=bool(
args.default_args.get('enable_gps', N3XX_DEFAULT_ENABLE_GPS)
)
)
self.enable_fp_gpio(
enable=bool(
args.default_args.get(
'enable_fp_gpio',
N3XX_DEFAULT_ENABLE_FPGPIO
)
)
)
# Init clocking
self.enable_ref_clock(enable=True)
self._ext_clock_freq = None
self._clock_source = None
self._time_source = None
self._init_ref_clock_and_time(args.default_args)
# Init CHDR transports
self._xport_mgrs = {
'udp': XportMgrUDP(self.chdr_interfaces, self.log),
'liberio': XportMgrLiberio(self.log),
}
# Init complete.
self.log.info("mboard info: {}".format(self.mboard_info))
def _init_ref_clock_and_time(self, default_args):
"""
Initialize clock and time sources. After this function returns, the
reference signals going to the FPGA are valid.
"""
self._ext_clock_freq = float(
default_args.get('ext_clock_freq', N3XX_DEFAULT_EXT_CLOCK_FREQ)
)
if len(self.dboards) == 0:
self.log.warning(
"No dboards found, skipping setting clock and time source " \
"configuration."
)
self._clock_source = N3XX_DEFAULT_CLOCK_SOURCE
self._time_source = N3XX_DEFAULT_TIME_SOURCE
else:
self.set_clock_source(
default_args.get('clock_source', N3XX_DEFAULT_CLOCK_SOURCE)
)
self.set_time_source(
default_args.get('time_source', N3XX_DEFAULT_TIME_SOURCE)
)
def init(self, args):
"""
Calls init() on the parent class, and then programs the Ethernet
dispatchers accordingly.
"""
result = super(n310, self).init(args)
for xport_mgr in itervalues(self._xport_mgrs):
xport_mgr.init(args)
return result
def deinit(self):
"""
Clean up after a UHD session terminates.
"""
super(n310, self).deinit()
for xport_mgr in itervalues(self._xport_mgrs):
xport_mgr.deinit()
def tear_down(self):
"""
Tear down all members that need to be specially handled before
deconstruction.
For N310, this means the overlay.
"""
active_overlays = self.list_active_overlays()
self.log.trace("N310 has active device tree overlays: {}".format(
active_overlays
))
for overlay in active_overlays:
dtoverlay.rm_overlay(overlay)
###########################################################################
# Transport API
###########################################################################
def request_xport(
self,
dst_address,
suggested_src_address,
xport_type
):
"""
See PeriphManagerBase.request_xport() for docs.
"""
# For now, we always accept the suggestion if available, or fail
src_address = suggested_src_address
if src_address not in self._available_endpoints:
raise RuntimeError("no more sids yo")
sid = SID(src_address << 16 | dst_address)
self.log.debug(
"request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \
"operating on SID: %s",
dst_address, suggested_src_address, str(xport_type), str(sid))
# FIXME token!
assert self.mboard_info['rpc_connection'] in ('remote', 'local')
if self.mboard_info['rpc_connection'] == 'remote':
return self._xport_mgrs['udp'].request_xport(
sid,
xport_type,
)
elif self.mboard_info['rpc_connection'] == 'local':
return self._xport_mgrs['liberio'].request_xport(
sid,
xport_type,
)
def commit_xport(self, xport_info):
"""
See PeriphManagerBase.commit_xport() for docs.
Reminder: All connections are incoming, i.e. "send" or "TX" means
remote device to local device, and "receive" or "RX" means this local
device to remote device. "Remote device" can be, for example, a UHD
session.
"""
## Go, go, go
assert self.mboard_info['rpc_connection'] in ('remote', 'local')
sid = SID(xport_info['send_sid'])
self._available_endpoints.remove(sid.src_ep)
self.log.debug("Committing transport for SID %s, xport info: %s",
str(sid), str(xport_info))
if self.mboard_info['rpc_connection'] == 'remote':
return self._xport_mgrs['udp'].commit_xport(sid, xport_info)
elif self.mboard_info['rpc_connection'] == 'local':
return self._xport_mgrs['liberio'].commit_xport(sid, xport_info)
def get_clock_sources(self):
" Lists all available clock sources. "
self.log.trace("Listing available clock sources...")
return ('external', 'internal', 'gpsdo')
def get_clock_source(self):
" Returns the currently selected clock source "
return self._clock_source
def set_clock_source(self, *args):
"""
Switch reference clock.
Throws if clock_source is not a valid value.
"""
clock_source = args[0]
assert clock_source in self.get_clock_sources()
self.log.trace("Setting clock source to `{}'".format(clock_source))
if clock_source == self.get_clock_source():
self.log.trace("Nothing to do -- clock source already set.")
return
if clock_source == 'internal':
self._gpios.set("CLK-MAINSEL-EX_B")
self._gpios.set("CLK-MAINSEL-25MHz")
self._gpios.reset("CLK-MAINSEL-GPS")
elif clock_source == 'gpsdo':
self._gpios.set("CLK-MAINSEL-EX_B")
self._gpios.reset("CLK-MAINSEL-25MHz")
self._gpios.set("CLK-MAINSEL-GPS")
else: # external
self._gpios.reset("CLK-MAINSEL-EX_B")
self._gpios.reset("CLK-MAINSEL-GPS")
# SKY13350 needs to be in known state
self._gpios.set("CLK-MAINSEL-25MHz")
self._clock_source = clock_source
ref_clk_freq = self.get_ref_clock_freq()
self.log.info("Reference clock frequency is: {} MHz".format(
ref_clk_freq/1e6
))
for slot, dboard in enumerate(self.dboards):
if hasattr(dboard, 'update_ref_clock_freq'):
self.log.trace(
"Updating reference clock on dboard `{}' to {} MHz...".format(
slot, ref_clk_freq/1e6
)
)
dboard.update_ref_clock_freq(ref_clk_freq)
def set_ref_clock_freq(self, freq):
"""
Tell our USRP what the frequency of the external reference clock is.
Will throw if it's not a valid value.
"""
assert freq in (10e6, 20e6, 25e6)
self.log.debug("We've been told the external reference clock " \
"frequency is {} MHz.".format(freq/1e6))
if self._ext_clk_freq == freq:
self.log.trace("New external reference clock frequency assignment matches "\
"previous assignment. Ignoring update command.")
return
self._ext_clock_freq = freq
if self.get_clock_source() == 'external':
for slot, dboard in enumerate(self.dboards):
if hasattr(dboard, 'update_ref_clock_freq'):
self.log.trace(
"Updating reference clock on dboard `{}' to {} MHz...".format(
slot, freq/1e6
)
)
dboard.update_ref_clock_freq(freq)
def get_ref_clock_freq(self):
" Returns the currently active reference clock frequency"
return {
'internal': 25e6,
'external': self._ext_clock_freq,
'gpsdo': 20e6,
}[self._clock_source]
def get_time_sources(self):
" Returns list of valid time sources "
return ['internal', 'external']
def get_time_source(self):
" Return the currently selected time source "
return self._time_source
def set_time_source(self, time_source):
" Set a time source "
assert time_source in self.get_time_sources()
self.log.trace("Setting time source to `{}'".format(time_source))
# FIXME use uio
if time_source == 'internal':
os.system('devmem2 0x4001000C w 0x1')
elif time_source == 'external':
os.system('devmem2 0x4001000C w 0x0')
else:
assert False
def enable_gps(self, enable):
"""
Turn power to the GPS off or on.
"""
self.log.trace("{} power to GPS".format(
"Enabling" if enable else "Disabling"
))
self._gpios.set("PWREN-GPS", int(bool(enable)))
def enable_fp_gpio(self, enable):
"""
Turn power to the front panel GPIO off or on.
"""
self.log.trace("{} power to front-panel GPIO".format(
"Enabling" if enable else "Disabling"
))
self._gpios.set("FPGA-GPIO-EN", int(bool(enable)))
def enable_ref_clock(self, enable):
"""
Enables the ref clock voltage (+3.3-MAINREF). Without setting this to
True, *no* ref clock works.
"""
self.log.trace("{} power to reference clocks".format(
"Enabling" if enable else "Disabling"
))
self._gpios.set("PWREN-CLK-MAINREF", int(bool(enable)))
def enable_1G_ref_clock(self):
"""
Enables 125 MHz refclock for 1G interface.
"""
self.log.trace("Enable 125 MHz Clock for 1G SFP interface.")
self._gpios.set("NETCLK-CE")
self._gpios.set("NETCLK-RESETn", 0)
self._gpios.set("NETCLK-PR0", 1)
self._gpios.set("NETCLK-PR1", 1)
self._gpios.set("NETCLK-OD0", 1)
self._gpios.set("NETCLK-OD1", 1)
self._gpios.set("NETCLK-OD2", 0)
self._gpios.set("PWREN-CLK-WB-25MHz", 1)
self.log.trace("Finished configuring NETCLK CDCM.")
self._gpios.set("NETCLK-RESETn", 1)
###########################################################################
# Sensors
###########################################################################
def get_ref_lock_sensor(self):
"""
The N310 has no ref lock sensor, but because the ref lock is
historically considered a motherboard-level sensor, we will return the
combined lock status of all daughterboards. If no dboard is connected,
or none has a ref lock sensor, we simply return True.
"""
self.log.trace(
"Querying ref lock status from %d dboards.",
len(self.dboards)
)
lock_status = all([
not hasattr(db, 'get_ref_lock') or db.get_ref_lock()
for db in self.dboards
])
return {
'name': 'ref_locked',
'type': 'BOOLEAN',
'unit': 'locked' if lock_status else 'unlocked',
'value': str(lock_status).lower(),
}
def get_gps_lock_sensor(self):
"""
Get lock status of GPS as a sensor dict
"""
self.log.trace("Reading status GPS lock pin from port expander")
gps_locked = bool(self._gpios.get("GPS-LOCKOK"))
return {
'name': 'gps_lock',
'type': 'BOOLEAN',
'unit': 'locked' if gps_locked else 'unlocked',
'value': str(gps_locked).lower(),
}
###########################################################################
# EEPROMs
###########################################################################
def get_mb_eeprom(self):
"""
Return a dictionary with EEPROM contents.
All key/value pairs are string -> string.
We don't actually return the EEPROM contents, instead, we return the
mboard info again. This filters the EEPROM contents to what we think
the user wants to know/see.
"""
return self.mboard_info
def set_mb_eeprom(self, eeprom_vals):
"""
See PeriphManagerBase.set_mb_eeprom() for docs.
"""
self.log.warn("Called set_mb_eeprom(), but not implemented!")
raise NotImplementedError
def get_db_eeprom(self, dboard_idx):
"""
See PeriphManagerBase.get_db_eeprom() for docs.
"""
try:
dboard = self.dboards[dboard_idx]
except KeyError:
error_msg = "Attempted to access invalid dboard index `{}' " \
"in get_db_eeprom()!".format(dboard_idx)
self.log.error(error_msg)
raise RuntimeError(error_msg)
db_eeprom_data = copy.copy(dboard.device_info)
if hasattr(dboard, 'get_user_eeprom_data') and \
callable(dboard.get_user_eeprom_data):
for blob_id, blob in iteritems(dboard.get_user_eeprom_data()):
if blob_id in db_eeprom_data:
self.log.warn("EEPROM user data contains invalid blob ID " \
"%s", blob_id)
else:
db_eeprom_data[blob_id] = blob
return db_eeprom_data
def set_db_eeprom(self, dboard_idx, eeprom_data):
"""
Write new EEPROM contents with eeprom_map.
Arguments:
dboard_idx -- Slot index of dboard
eeprom_data -- Dictionary of EEPROM data to be written. It's up to the
specific device implementation on how to handle it.
"""
try:
dboard = self.dboards[dboard_idx]
except KeyError:
error_msg = "Attempted to access invalid dboard index `{}' " \
"in set_db_eeprom()!".format(dboard_idx)
self.log.error(error_msg)
raise RuntimeError(error_msg)
if not hasattr(dboard, 'set_user_eeprom_data') or \
not callable(dboard.set_user_eeprom_data):
error_msg = "Dboard has no set_user_eeprom_data() method!"
self.log.error(error_msg)
raise RuntimeError(error_msg)
safe_db_eeprom_user_data = {}
for blob_id, blob in iteritems(eeprom_data):
if blob_id in dboard.device_info:
error_msg = "Trying to overwrite read-only EEPROM " \
"entry `{}'!".format(blob_id)
self.log.error(error_msg)
raise RuntimeError(error_msg)
if not isinstance(blob, str) and not isinstance(blob, bytes):
error_msg = "Blob data for ID `{}' is not a " \
"string!".format(blob_id)
self.log.error(error_msg)
raise RuntimeError(error_msg)
assert isinstance(blob, str)
safe_db_eeprom_user_data[blob_id] = blob.encode('ascii')
dboard.set_user_eeprom_data(safe_db_eeprom_user_data)
@no_rpc
def update_fpga(self, filepath, metadata):
"""
Update the FPGA image in the filesystem and reload the overlay
:param filepath: path to new FPGA image
:param metadata: Dictionary of strings containing metadata
"""
self.log.trace("Updating FPGA with image at {}"
.format(filepath))
_, file_extension = os.path.splitext(filepath)
# Cut off the period from the file extension
file_extension = file_extension[1:].lower()
binfile_path = self.updateable_components['fpga']['path']
if file_extension == "bit":
self.log.trace("Converting bit to bin file and writing to {}"
.format(binfile_path))
from usrp_mpm.fpga_bit_to_bin import fpga_bit_to_bin
fpga_bit_to_bin(filepath, binfile_path, flip=True)
elif file_extension == "bin":
self.log.trace("Copying bin file to {}"
.format(binfile_path))
shutil.copy(filepath, binfile_path)
else:
self.log.error("Invalid FPGA bitfile: {}"
.format(filepath))
raise RuntimeError("Invalid N310 FPGA bitfile")
# TODO: Implement reload procedure
return True
@no_rpc
def update_dts(self, filepath, metadata):
"""
Update the DTS image in the filesystem
:param filepath: path to new DTS image
:param metadata: Dictionary of strings containing metadata
"""
dtsfile_path = self.updateable_components['dts']['path']
self.log.trace("Updating DTS with image at {} to {}"
.format(filepath, dtsfile_path)
)
shutil.copy(filepath, dtsfile_path)
# TODO: Compile the new dts file into a usable dtbo
return True