Python Netlink library

This commit creates the Netlink shared library for Python. In this
release, only Netlink DPLL interface has been implemented to meet the
requirements for communicating with DPLL modules standalone and
integrated in NICs.

TEST PLAN:
PASS: Compile the code and run unit tests
PASS: Build deb packages for python wheel and std distribution

Story: 2011345
Task: 51672

Change-Id: I7fe9a0ff36aa3f74c8453dc684e2e547f32ef989
Signed-off-by: Reynaldo P Gomes <reynaldo.patronegomes@windriver.com>
This commit is contained in:
reynaldopgomes 2025-01-24 07:56:34 -03:00 committed by Reynaldo P Gomes
parent 94fec17864
commit 77732293d6
45 changed files with 8190 additions and 1 deletions

View File

@ -378,6 +378,9 @@ python3-setuptools
#python3.9
python3.9
#pynetlink
pynetlink
#pyzmq
python3-zmq

View File

@ -95,6 +95,7 @@ ostree/mttyexec
ostree/ostree
ostree/ostree-upgrade-mgr
python/dh-python
python/pynetlink
python/python-nss
python/python3-nsenter
python/python3-setuptools

1
debian_stable_wheels.inc Normal file
View File

@ -0,0 +1 @@
pynetlink-wheel

View File

@ -0,0 +1,5 @@
pynetlink (1.0.0-0) unstable; urgency=medium
* New upstream release.
-- Reynaldo P Gomes <reynaldo.patronegomes@windriver.com> Thu, 16 Jan 2025 10:00:00 -0300

View File

@ -0,0 +1,33 @@
Source: pynetlink
Section: python
Priority: optional
Maintainer: Starlingx Developers <StarlingX-discuss@lists.StarlingX.io>
Build-Depends: debhelper-compat (= 13),
dh-python,
python3-dev,
python3-jsonschema,
python3-setuptools,
python3-yaml,
python3-wheel
Standards-Version: 4.5.1
Homepage: https://www.starlingx.io
Rules-Requires-Root: no
Package: pynetlink
Architecture: any
Depends: ${python3:Depends},
python3-jsonschema,
python3-yaml
Description: Python Netlink library for StarlingX
This package provides the Python library to interact with the Linux Kernel
using the Netlink protocol.
Package: pynetlink-wheel
Architecture: any
Depends: ${python3:Depends},
python3-jsonschema,
python3-yaml,
python3-wheel
Description: Python Netlink library for StarlingX - wheel
This package provides the Python library to interact with the Linux Kernel
using the Netlink protocol.

View File

@ -0,0 +1,42 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: pynetlink
Source: https://opendev.org/starlingx/integ/python/netlink-python
Files: *
Copyright: 2025 Wind River Systems, Inc
Others (See individual files for more details)
License: Apache-2
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
https://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian-based systems the full text of the Apache version 2.0 license
can be found in `/usr/share/common-licenses/Apache-2.0'.
# If you want to use GPL v2 or later for the /debian/* files use
# the following clauses, or change it to suit. Delete these two lines
Files: debian/*
Copyright: 2025 Wind River Systems, Inc
License: Apache-2
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
https://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian-based systems the full text of the Apache version 2.0 license
can be found in `/usr/share/common-licenses/Apache-2.0'.

View File

@ -0,0 +1 @@
usr/lib/python*/dist-packages/*

View File

@ -0,0 +1,23 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
# Uncomment to force running of the unit tests
#export DEB_BUILD_OPTIONS=""
# Disable python byte compiling
export PYTHONDONTWRITEBYTECODE=1
%:
dh $@ --with python3 --buildsystem=pybuild
override_dh_install:
python3 setup.py bdist_wheel -d debian/pynetlink-wheel/usr/share/python-wheels
dh_install
override_dh_auto_test:
dh_auto_test -- --system=custom --test-args="{interpreter} -m pynetlink.tests.run"
override_dh_python3:
dh_python3 --shebang=/usr/bin/python3

View File

@ -0,0 +1 @@
3.0 (quilt)

View File

@ -0,0 +1,7 @@
---
debname: pynetlink
debver: 1.0.0
src_path: src
revision:
dist: $STX_DIST
PKG_GITREVCOUNT: true

6
python/pynetlink/src/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
*.egg-info/
__pycache__/
dist/
build/
libs/
.tox/

View File

@ -0,0 +1,16 @@
If you would like to contribute to the development of StarlingX,
you must follow the steps in this page:
https://docs.starlingx.io/contributor/
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
https://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed in Launchpad:
https://bugs.launchpad.net/starlingx

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,2 @@
include pynetlink/specs/*.yaml
include pynetlink/schemas/*.yaml

View File

@ -0,0 +1,101 @@
pynetlink - Netlink Python library
===================================
This library acts as an adapter for the Netlink library (YNL) provided with the Linux Kernel. More details about
Netlink protocol and YNL library can be found at: https://docs.kernel.org/userspace-api/netlink/.
Notes
=====
The required files from YNL (source code, schema and specification files) are being imported into the project from Linux repository.
This allow to use a newer version of Linux source code with DPLL support in Netlink. The path of these files in Linux repository is
described in Folder Structure section.
NOTE: After migrating to Linux 6.12 or higher, the required files can be extracted directly from the linux-source
package in the Debian distribution during the build process, making it unnecessary to keep a copy of these files in the project.
Features / Restrictions
=======================
* Only DPLL specification is implemented. Additional Netlink specifications can be implemented as needed.
You can find the list of supported specifications here: https://docs.kernel.org/networking/netlink_spec/index.html.
* Generic Netlink schema is used by default.
Schema file defines how to translate data sent and received from Netlink into Python compatible data types.
* Netlink allows read and write operations and also receives notifications of changes.
* All pynetlink instances shares a single YNL instance, avoiding loading and parsing spec/schema YAML files for each
new instance created. To handle concurrent operations scenario, there is an option to enable dedicated YNL instances.
* Requires CAP_NET_ADMIN privilege.
Folder Structure
================
+ base: contains the YNL library files (imported from Linux repository: tools/net/ynl/lib);
+ common: classes and functions shared by different classes of the project;
+ schemas: YAML schema files used by YNL library (imported from Linux repository: Documentation/netlink);
+ specs: YAML specification files used by YNL library (imported from Linux repository: Documentation/netlink/specs);
+ tests: contains the unit test cases;
+ dpll: contains the classes for DPLL implementation (use separate folders for each specification);
How to use
==========
Create a shared instance for access the DPLL:
dpll = NetlinkDPLL()
If you need a dedicated instance:
dpll = NetlinkDPLL(True)
The library has several methods to communicate directly to Netlink to get information about DPLL devices/pins. The
get_all_pins() method obtains information of the devices and pins and correlate the data into a DPLLPins instance
while get_all_devices() method returns only device part of the information into a DPLLDevices instance.
try:
# Obtain all DPLL devices in the system
devices = dpll.get_all_devices()
# Print the lock status of all devices
for device in devices:
print(f'Device Id: {device.dev_id} - Lock status: {device.lock_status}')
...
except NetlinkException:
...
(or)
try:
# Obtain all DPLL pins in the system
pins = dpll.get_all_pins()
# Print a list of pins with receive input signal
print( f'Pins: {pins.filter_by_pin_direction(PinDirection.INPUT)}')
...
except NetlinkException:
...
The DPLLDevices and DPLLPins classes offer a set of filters that can be combined to obtain the desired information.
# Get the devices with status locked and holdover acquired
devices = devices.filter_by_device_lock_status(LockStatus.LOCKED_AND_HOLDOVER)
...
# Obtain the available GNSS pins in the system
gnss = pins.filter_by_pin_type(PinType.GNSS)
.filter_by_pin_states([PinState.CONNECTED, PinState.SELECTABLE])
...
Also they allow to use operators to get the difference/intersection/union/etc between different instances.
# Get the pins that have been changed since the last read.
pins_diff = pins_updated - pins_previous
...
Other samples of how to use can be found in the test cases.

View File

@ -0,0 +1,39 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
# Classes
from .common import NetlinkException
from .dpll import NetlinkDPLL
from .dpll import DpllDevice
from .dpll import DpllDevices
from .dpll import DpllPin
from .dpll import DpllPins
# Enumerations
from .dpll import DeviceMode
from .dpll import DeviceType
from .dpll import PinType
from .dpll import PinState
from .dpll import PinDirection
from .dpll import LockStatus
from .dpll import LockStatusError
__all__ = [
'NetlinkException',
'NetlinkDPLL',
'DpllDevice',
'DpllDevices',
'DpllPin',
'DpllPins',
'DeviceMode',
'DeviceType',
'PinType',
'PinState',
'PinDirection',
'LockStatus',
'LockStatusError']

View File

@ -0,0 +1,4 @@
pynetlink - Netlink Python library
===================================
This folder contains the Python YNL libraries.

View File

@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \
SpecFamily, SpecOperation
from .ynl import YnlFamily, Netlink, NlError
__all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet",
"SpecFamily", "SpecOperation", "YnlFamily", "Netlink", "NlError"]

View File

@ -0,0 +1,614 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
import collections
import importlib
import os
import yaml
# To be loaded dynamically as needed
jsonschema = None
class SpecElement:
"""Netlink spec element.
Abstract element of the Netlink spec. Implements the dictionary interface
for access to the raw spec. Supports iterative resolution of dependencies
across elements and class inheritance levels. The elements of the spec
may refer to each other, and although loops should be very rare, having
to maintain correct ordering of instantiation is painful, so the resolve()
method should be used to perform parts of init which require access to
other parts of the spec.
Attributes:
yaml raw spec as loaded from the spec file
family back reference to the full family
name name of the entity as listed in the spec (optional)
ident_name name which can be safely used as identifier in code (optional)
"""
def __init__(self, family, yaml):
self.yaml = yaml
self.family = family
if 'name' in self.yaml:
self.name = self.yaml['name']
self.ident_name = self.name.replace('-', '_')
self._super_resolved = False
family.add_unresolved(self)
def __getitem__(self, key):
return self.yaml[key]
def __contains__(self, key):
return key in self.yaml
def get(self, key, default=None):
return self.yaml.get(key, default)
def resolve_up(self, up):
if not self._super_resolved:
up.resolve()
self._super_resolved = True
def resolve(self):
pass
class SpecEnumEntry(SpecElement):
""" Entry within an enum declared in the Netlink spec.
Attributes:
doc documentation string
enum_set back reference to the enum
value numerical value of this enum (use accessors in most situations!)
Methods:
raw_value raw value, i.e. the id in the enum, unlike user value which is a mask for flags
user_value user value, same as raw value for enums, for flags it's the mask
"""
def __init__(self, enum_set, yaml, prev, value_start):
if isinstance(yaml, str):
yaml = {'name': yaml}
super().__init__(enum_set.family, yaml)
self.doc = yaml.get('doc', '')
self.enum_set = enum_set
if 'value' in yaml:
self.value = yaml['value']
elif prev:
self.value = prev.value + 1
else:
self.value = value_start
def has_doc(self):
return bool(self.doc)
def raw_value(self):
return self.value
def user_value(self, as_flags=None):
if self.enum_set['type'] == 'flags' or as_flags:
return 1 << self.value
else:
return self.value
class SpecEnumSet(SpecElement):
""" Enum type
Represents an enumeration (list of numerical constants)
as declared in the "definitions" section of the spec.
Attributes:
type enum or flags
entries entries by name
entries_by_val entries by value
Methods:
get_mask for flags compute the mask of all defined values
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.type = yaml['type']
prev_entry = None
value_start = self.yaml.get('value-start', 0)
self.entries = dict()
self.entries_by_val = dict()
for entry in self.yaml['entries']:
e = self.new_entry(entry, prev_entry, value_start)
self.entries[e.name] = e
self.entries_by_val[e.raw_value()] = e
prev_entry = e
def new_entry(self, entry, prev_entry, value_start):
return SpecEnumEntry(self, entry, prev_entry, value_start)
def has_doc(self):
if 'doc' in self.yaml:
return True
return self.has_entry_doc()
def has_entry_doc(self):
for entry in self.entries.values():
if entry.has_doc():
return True
return False
def get_mask(self, as_flags=None):
mask = 0
for e in self.entries.values():
mask += e.user_value(as_flags)
return mask
class SpecAttr(SpecElement):
""" Single Netlink attribute type
Represents a single attribute type within an attr space.
Attributes:
type string, attribute type
value numerical ID when serialized
attr_set Attribute Set containing this attr
is_multi bool, attr may repeat multiple times
struct_name string, name of struct definition
sub_type string, name of sub type
len integer, optional byte length of binary types
display_hint string, hint to help choose format specifier
when displaying the value
sub_message string, name of sub message type
selector string, name of attribute used to select
sub-message type
is_auto_scalar bool, attr is a variable-size scalar
"""
def __init__(self, family, attr_set, yaml, value):
super().__init__(family, yaml)
self.type = yaml['type']
self.value = value
self.attr_set = attr_set
self.is_multi = yaml.get('multi-attr', False)
self.struct_name = yaml.get('struct')
self.sub_type = yaml.get('sub-type')
self.byte_order = yaml.get('byte-order')
self.len = yaml.get('len')
self.display_hint = yaml.get('display-hint')
self.sub_message = yaml.get('sub-message')
self.selector = yaml.get('selector')
self.is_auto_scalar = self.type == "sint" or self.type == "uint"
class SpecAttrSet(SpecElement):
""" Netlink Attribute Set class.
Represents a ID space of attributes within Netlink.
Note that unlike other elements, which expose contents of the raw spec
via the dictionary interface Attribute Set exposes attributes by name.
Attributes:
attrs ordered dict of all attributes (indexed by name)
attrs_by_val ordered dict of all attributes (indexed by value)
subset_of parent set if this is a subset, otherwise None
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.subset_of = self.yaml.get('subset-of', None)
self.attrs = collections.OrderedDict()
self.attrs_by_val = collections.OrderedDict()
if self.subset_of is None:
val = 1
for elem in self.yaml['attributes']:
if 'value' in elem:
val = elem['value']
attr = self.new_attr(elem, val)
self.attrs[attr.name] = attr
self.attrs_by_val[attr.value] = attr
val += 1
else:
real_set = family.attr_sets[self.subset_of]
for elem in self.yaml['attributes']:
attr = real_set[elem['name']]
self.attrs[attr.name] = attr
self.attrs_by_val[attr.value] = attr
def new_attr(self, elem, value):
return SpecAttr(self.family, self, elem, value)
def __getitem__(self, key):
return self.attrs[key]
def __contains__(self, key):
return key in self.attrs
def __iter__(self):
yield from self.attrs
def items(self):
return self.attrs.items()
class SpecStructMember(SpecElement):
"""Struct member attribute
Represents a single struct member attribute.
Attributes:
type string, type of the member attribute
byte_order string or None for native byte order
enum string, name of the enum definition
len integer, optional byte length of binary types
display_hint string, hint to help choose format specifier
when displaying the value
struct string, name of nested struct type
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.type = yaml['type']
self.byte_order = yaml.get('byte-order')
self.enum = yaml.get('enum')
self.len = yaml.get('len')
self.display_hint = yaml.get('display-hint')
self.struct = yaml.get('struct')
class SpecStruct(SpecElement):
"""Netlink struct type
Represents a C struct definition.
Attributes:
members ordered list of struct members
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.members = []
for member in yaml.get('members', []):
self.members.append(self.new_member(family, member))
def new_member(self, family, elem):
return SpecStructMember(family, elem)
def __iter__(self):
yield from self.members
def items(self):
return self.members.items()
class SpecSubMessage(SpecElement):
""" Netlink sub-message definition
Represents a set of sub-message formats for polymorphic nlattrs
that contain type-specific sub messages.
Attributes:
name string, name of sub-message definition
formats dict of sub-message formats indexed by match value
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.formats = collections.OrderedDict()
for elem in self.yaml['formats']:
format = self.new_format(family, elem)
self.formats[format.value] = format
def new_format(self, family, format):
return SpecSubMessageFormat(family, format)
class SpecSubMessageFormat(SpecElement):
""" Netlink sub-message format definition
Represents a single format for a sub-message.
Attributes:
value attribute value to match against type selector
fixed_header string, name of fixed header, or None
attr_set string, name of attribute set, or None
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.value = yaml.get('value')
self.fixed_header = yaml.get('fixed-header')
self.attr_set = yaml.get('attribute-set')
class SpecOperation(SpecElement):
"""Netlink Operation
Information about a single Netlink operation.
Attributes:
value numerical ID when serialized, None if req/rsp values differ
req_value numerical ID when serialized, user -> kernel
rsp_value numerical ID when serialized, user <- kernel
modes supported operation modes (do, dump, event etc.)
is_call bool, whether the operation is a call
is_async bool, whether the operation is a notification
is_resv bool, whether the operation does not exist (it's just a reserved ID)
attr_set attribute set name
fixed_header string, optional name of fixed header struct
yaml raw spec as loaded from the spec file
"""
def __init__(self, family, yaml, req_value, rsp_value):
super().__init__(family, yaml)
self.value = req_value if req_value == rsp_value else None
self.req_value = req_value
self.rsp_value = rsp_value
self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'}
self.is_call = 'do' in yaml or 'dump' in yaml
self.is_async = 'notify' in yaml or 'event' in yaml
self.is_resv = not self.is_async and not self.is_call
self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)
# Added by resolve:
self.attr_set = None
delattr(self, "attr_set")
def resolve(self):
self.resolve_up(super())
if 'attribute-set' in self.yaml:
attr_set_name = self.yaml['attribute-set']
elif 'notify' in self.yaml:
msg = self.family.msgs[self.yaml['notify']]
attr_set_name = msg['attribute-set']
elif self.is_resv:
attr_set_name = ''
else:
raise Exception(f"Can't resolve attribute set for op '{self.name}'")
if attr_set_name:
self.attr_set = self.family.attr_sets[attr_set_name]
class SpecMcastGroup(SpecElement):
"""Netlink Multicast Group
Information about a multicast group.
Value is only used for classic netlink families that use the
netlink-raw schema. Genetlink families use dynamic ID allocation
where the ids of multicast groups get resolved at runtime. Value
will be None for genetlink families.
Attributes:
name name of the mulitcast group
value integer id of this multicast group for netlink-raw or None
yaml raw spec as loaded from the spec file
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.value = self.yaml.get('value')
class SpecFamily(SpecElement):
""" Netlink Family Spec class.
Netlink family information loaded from a spec (e.g. in YAML).
Takes care of unfolding implicit information which can be skipped
in the spec itself for brevity.
The class can be used like a dictionary to access the raw spec
elements but that's usually a bad idea.
Attributes:
proto protocol type (e.g. genetlink)
msg_id_model enum-model for operations (unified, directional etc.)
license spec license (loaded from an SPDX tag on the spec)
attr_sets dict of attribute sets
msgs dict of all messages (index by name)
sub_msgs dict of all sub messages (index by name)
ops dict of all valid requests / responses
ntfs dict of all async events
consts dict of all constants/enums
fixed_header string, optional name of family default fixed header struct
mcast_groups dict of all multicast groups (index by name)
kernel_family dict of kernel family attributes
"""
def __init__(self, spec_path, schema_path=None, exclude_ops=None):
with open(spec_path, "r") as stream:
prefix = '# SPDX-License-Identifier: '
first = stream.readline().strip()
if not first.startswith(prefix):
raise Exception('SPDX license tag required in the spec')
self.license = first[len(prefix):]
stream.seek(0)
spec = yaml.safe_load(stream)
self._resolution_list = []
super().__init__(self, spec)
self._exclude_ops = exclude_ops if exclude_ops else []
self.proto = self.yaml.get('protocol', 'genetlink')
self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')
if schema_path is None:
schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'
if schema_path:
global jsonschema
with open(schema_path, "r") as stream:
schema = yaml.safe_load(stream)
if jsonschema is None:
jsonschema = importlib.import_module("jsonschema")
jsonschema.validate(self.yaml, schema)
self.attr_sets = collections.OrderedDict()
self.sub_msgs = collections.OrderedDict()
self.msgs = collections.OrderedDict()
self.req_by_value = collections.OrderedDict()
self.rsp_by_value = collections.OrderedDict()
self.ops = collections.OrderedDict()
self.ntfs = collections.OrderedDict()
self.consts = collections.OrderedDict()
self.mcast_groups = collections.OrderedDict()
self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {}))
last_exception = None
while len(self._resolution_list) > 0:
resolved = []
unresolved = self._resolution_list
self._resolution_list = []
for elem in unresolved:
try:
elem.resolve()
except (KeyError, AttributeError) as e:
self._resolution_list.append(elem)
last_exception = e
continue
resolved.append(elem)
if len(resolved) == 0:
raise last_exception
def new_enum(self, elem):
return SpecEnumSet(self, elem)
def new_attr_set(self, elem):
return SpecAttrSet(self, elem)
def new_struct(self, elem):
return SpecStruct(self, elem)
def new_sub_message(self, elem):
return SpecSubMessage(self, elem);
def new_operation(self, elem, req_val, rsp_val):
return SpecOperation(self, elem, req_val, rsp_val)
def new_mcast_group(self, elem):
return SpecMcastGroup(self, elem)
def add_unresolved(self, elem):
self._resolution_list.append(elem)
def _dictify_ops_unified(self):
self.fixed_header = self.yaml['operations'].get('fixed-header')
val = 1
for elem in self.yaml['operations']['list']:
if 'value' in elem:
val = elem['value']
op = self.new_operation(elem, val, val)
val += 1
self.msgs[op.name] = op
def _dictify_ops_directional(self):
self.fixed_header = self.yaml['operations'].get('fixed-header')
req_val = rsp_val = 1
for elem in self.yaml['operations']['list']:
if 'notify' in elem or 'event' in elem:
if 'value' in elem:
rsp_val = elem['value']
req_val_next = req_val
rsp_val_next = rsp_val + 1
req_val = None
elif 'do' in elem or 'dump' in elem:
mode = elem['do'] if 'do' in elem else elem['dump']
v = mode.get('request', {}).get('value', None)
if v:
req_val = v
v = mode.get('reply', {}).get('value', None)
if v:
rsp_val = v
rsp_inc = 1 if 'reply' in mode else 0
req_val_next = req_val + 1
rsp_val_next = rsp_val + rsp_inc
else:
raise Exception("Can't parse directional ops")
if req_val == req_val_next:
req_val = None
if rsp_val == rsp_val_next:
rsp_val = None
skip = False
for exclude in self._exclude_ops:
skip |= bool(exclude.match(elem['name']))
if not skip:
op = self.new_operation(elem, req_val, rsp_val)
req_val = req_val_next
rsp_val = rsp_val_next
self.msgs[op.name] = op
def find_operation(self, name):
"""
For a given operation name, find and return operation spec.
"""
for op in self.yaml['operations']['list']:
if name == op['name']:
return op
return None
def resolve(self):
self.resolve_up(super())
definitions = self.yaml.get('definitions', [])
for elem in definitions:
if elem['type'] == 'enum' or elem['type'] == 'flags':
self.consts[elem['name']] = self.new_enum(elem)
elif elem['type'] == 'struct':
self.consts[elem['name']] = self.new_struct(elem)
else:
self.consts[elem['name']] = elem
for elem in self.yaml['attribute-sets']:
attr_set = self.new_attr_set(elem)
self.attr_sets[elem['name']] = attr_set
for elem in self.yaml.get('sub-messages', []):
sub_message = self.new_sub_message(elem)
self.sub_msgs[sub_message.name] = sub_message
if self.msg_id_model == 'unified':
self._dictify_ops_unified()
elif self.msg_id_model == 'directional':
self._dictify_ops_directional()
for op in self.msgs.values():
if op.req_value is not None:
self.req_by_value[op.req_value] = op
if op.rsp_value is not None:
self.rsp_by_value[op.rsp_value] = op
if not op.is_async and 'attribute-set' in op:
self.ops[op.name] = op
elif op.is_async:
self.ntfs[op.name] = op
mcgs = self.yaml.get('mcast-groups')
if mcgs:
for elem in mcgs['list']:
mcg = self.new_mcast_group(elem)
self.mcast_groups[elem['name']] = mcg

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
from .netlink import NetlinkException
from .netlink import NetlinkFactory
__all__ = ['NetlinkException', 'NetlinkFactory']

View File

@ -0,0 +1,52 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module contains common classes for Netlink library"""
import os
from importlib.resources import files
from ..base import NlError
from ..base import YnlFamily
class NetlinkException(Exception):
def __init__(self, e: NlError):
self.os_code = e.error
self.nl_msg = e.nl_msg.extack
self.nl_code = e.nl_msg.error
def __str__(self):
if(self.os_code != 0):
return f'OS error: {os.strerror(self.os_code)} ({self.os_code})'
else:
return f'Netlink error: {self.nl_msg} ({self.nl_code})'
class NetlinkFactory:
@staticmethod
def get_instance(yaml_spec_file: str,
yaml_schema_file: str = 'genetlink.yaml') -> YnlFamily:
"""Returns a new instance for access the netlink protocol.
yaml_spec_file: Name of the YAML file containing the access specifications
for the netlink interface.
yaml_schema_file: Name of the YAML file containing the schema definitions
to translate the values of the netlink protocol.
"""
return YnlFamily(
files('pynetlink').joinpath('specs', yaml_spec_file),
files('pynetlink').joinpath('schemas', yaml_schema_file))
@staticmethod
def get_dpll_instance() -> YnlFamily:
"""Returns a new instance for interact with DPLL devices using netlink protocol."""
return NetlinkFactory.get_instance('dpll.yaml')

View File

@ -0,0 +1,46 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
# Classes
from .dpll import NetlinkDPLL
from .dpll import DPLLCommands
from .devices import DpllDevice
from .devices import DpllDevices
from .pins import DpllPin
from .pins import DpllPins
# DPLL Fields
from .constants import DeviceFields
from .constants import PinFields
from .constants import PinParentFields
# Enumerations
from .constants import DeviceMode
from .constants import DeviceType
from .constants import PinType
from .constants import PinState
from .constants import PinDirection
from .constants import LockStatus
from .constants import LockStatusError
__all__ = [
'NetlinkDPLL',
'DPLLCommands',
'DpllDevice',
'DpllDevices',
'DpllPin',
'DpllPins',
'DeviceFields',
'PinFields',
'PinParentFields',
'DeviceMode',
'DeviceType',
'PinType',
'PinState',
'PinDirection',
'LockStatus',
'LockStatusError']

View File

@ -0,0 +1,144 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module has the constants and enumerators used by other dpll modules."""
from enum import Enum
from enum import unique
class DeviceFields:
"""List of possible field names returned in device structure."""
__slots__ = ()
CLOCK_ID = 'clock-id'
CLOCK_QUALITY = 'clock-quality-level'
ID = 'id'
MODE = 'mode'
MODE_SUPPORTED = 'mode-supported'
MODULE_NAME = 'module-name'
PAD = 'pad'
TYPE = 'type'
LOCK_STATUS = 'lock-status'
LOCK_STATUS_ERROR = 'lock-status-error'
class PinFields:
"""List of possible field names returned in pin structure."""
__slots__ = ()
BOARD_LABEL = 'board-label'
CAPABILITIES = 'capabilities'
CLOCK_ID = 'clock-id'
DIRECTION = 'direction'
FREQUENCY = 'frequency'
FREQUENCY_SUPPORTED = 'frequency-supported'
FREQUENCY_MAX = 'frequency-max'
FREQUENCY_MIN = 'frequency-min'
ID = 'id'
MODULE_NAME = 'module-name'
PACKAGE_LABEL = 'package-label'
PANEL_LABEL = 'panel-label'
PARENT_ID = 'parent-id'
PARENT_DEVICE = 'parent-device'
PARENT_PIN = 'parent-pin'
PHASE_ADJUST = 'phase-adjust'
PHASE_ADJUST_MAX = 'phase-adjust-max'
PHASE_ADJUST_MIN = 'phase-adjust-min'
PHASE_OFFSET = 'phase-offset'
PRIORITY = 'prio'
STATE = 'state'
TYPE = 'type'
class PinParentFields:
"""List of possible field names returned in the nested structure 'parent-device'."""
__slots__ = ()
DIRECTION = 'direction'
PARENT_ID = 'parent-id'
PHASE_OFFSET = 'phase-offset'
PRIORITY = 'prio'
STATE = 'state'
class DpllEnum(Enum):
def __repr__(self):
return self.value
@unique
class DeviceType(DpllEnum):
"""Enumeration for dev_type field."""
UNDEFINED = 'undefined' # undefined
EEC = 'eec' # Ethernet Equipment Clock
PPS = 'pps' # Pulse-Per-Second signal
@unique
class DeviceMode(DpllEnum):
"""Enumeration for dev_mode and dev_mode_supported fields."""
UNDEFINED = 'undefined' # undefined
MANUAL = 'manual' # input can be only selected by sending a request to dpll
AUTO = 'automatic' # highest prio input pin auto selected by dpll
@unique
class LockStatus(DpllEnum):
"""Enumeration for lock_status field."""
UNDEFINED = 'undefined' # dpll lock status not set
HOLDOVER = 'holdover' # dpll is in holdover state
LOCKED = 'locked' # dpll is locked to a valid signal, but no holdover available
LOCKED_AND_HOLDOVER = 'locked-ho-acq' # dpll is locked and holdover acquired
UNLOCKED = 'unlocked' # dpll was not yet locked to any valid input
@unique
class LockStatusError(DpllEnum):
"""Enumeration for lock_status_error field."""
# the FFO (Fractional Frequency Offset) between the RX and TX symbol rate on the media got too high.
FFO_TOO_HIGH = 'fractional-frequency-offset-too-high'
MEDIA_DOWN = 'media-down' # dpll device lock status was changed because of associated media got down
NONE = 'none' # pll device lock status was changed without any error
UNDEFINED = 'undefined' # dpll device lock status was changed due to undefined error
@unique
class PinType(DpllEnum):
"""Enumeration for pin_type field."""
UNDEFINED = 'undefined' # undefined
EXT = 'ext' # external input
GNSS = 'gnss' # GNSS recovered clock
MUX = 'mux' # Aggregates another layer of selectable pins
OCXO = 'int-oscillator' # device internal oscillator
SYNCE = 'synce-eth-port' # ethernet port PHYs recovered clock
@unique
class PinState(DpllEnum):
"""Enumeration for pin_state field."""
UNDEFINED = 'undefined' # undefined
CONNECTED = 'connected' # pin connected, active input of phase locked loop
DISCONNECTED = 'disconnected' # pin disconnected, not considered as a valid input
SELECTABLE = 'selectable' # pin enabled for automatic input selection
@unique
class PinDirection(DpllEnum):
"""Enumeration for pin_direction field."""
UNDEFINED = 'undefined' # undefined
INPUT = 'input' # pin used as a input of a signal
OUTPUT = 'output' # pin used to output the signal

View File

@ -0,0 +1,171 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module contains classes related to DPLL devices."""
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import field
from typing import Union
from .constants import DeviceFields
from .constants import DeviceMode
from .constants import DeviceType
from .constants import LockStatus
from .constants import LockStatusError
@dataclass(frozen=True)
class DpllDevice:
dev_id: int
dev_clock_id: int = field(hash=False)
dev_clock_quality_level: str = field(hash=False, default=None)
dev_mode: DeviceMode = field(hash=False, default=DeviceMode.UNDEFINED)
dev_mode_supported: list[DeviceMode] = field(hash=False, default_factory=list)
dev_module_name: str = field(hash=False, default=None)
dev_pad: str = field(hash=False, default=None)
dev_type: DeviceType = field(hash=False, default=DeviceType.UNDEFINED)
lock_status: LockStatus = field(hash=False, default=LockStatus.UNDEFINED)
lock_status_error: LockStatusError = field(hash=False, default=LockStatusError.UNDEFINED)
@classmethod
def loadDevice(cls, device: dict) -> DpllDevice:
"""Parse DPLL device data returned from Netlink and creates a DPLLDevice class.
The fields not specified in the list will be set to a default value.
device: DPLL device data in raw format.
"""
if device is None:
raise ValueError('Device parameter must be informed.')
return cls(
dev_id=int(device[DeviceFields.ID]),
dev_clock_id=int(device[DeviceFields.CLOCK_ID]),
dev_clock_quality_level=device.get(DeviceFields.CLOCK_QUALITY),
dev_mode=DeviceMode(device.get(DeviceFields.MODE, DeviceMode.UNDEFINED)),
dev_mode_supported=[DeviceMode(x) for x in device.get(DeviceFields.MODE_SUPPORTED, DeviceMode.UNDEFINED)]
if DeviceFields.MODE_SUPPORTED in device else list(),
dev_module_name=str(device[DeviceFields.MODULE_NAME])
if DeviceFields.MODULE_NAME in device else None,
dev_pad=str(device[DeviceFields.PAD]) if DeviceFields.PAD in device else None,
dev_type=DeviceType(device.get(DeviceFields.TYPE, DeviceType.UNDEFINED)),
lock_status=LockStatus(device.get(DeviceFields.LOCK_STATUS, LockStatus.UNDEFINED)),
lock_status_error=LockStatusError(
device.get(DeviceFields.LOCK_STATUS_ERROR, LockStatusError.UNDEFINED)),
)
class DpllDevices(set):
@classmethod
def loadDevices(cls, devices: list[dict]) -> DpllDevices:
"""Parses a list of DPLL devices returned from Netlink and creates the DPLLDevices class.
The fields not specified will be set to the default value.
devices: list of DPLL devices in raw format.
"""
instance = cls()
for device in devices:
instance.add(DpllDevice.loadDevice(device))
return instance
def filter_by_device_clock_id(self, clock_id: int) -> DpllDevices:
"""Returns a new instance containing only items with the given clock id.
clock_id: clock identifier to be filtered.
"""
return self.__class__(filter(lambda x: x.dev_clock_id == clock_id, self))
def filter_by_device_clock_ids(self, clock_ids: list[int]) -> DpllDevices:
"""Returns a new instance containing only items with the given clocks.
clock_ids: list of the clock identifier to be filtered.
"""
return self.__class__(filter(lambda x: x.dev_clock_id in clock_ids, self))
def filter_by_device_id(self, device_id: int) -> Union[DpllDevice, None]:
"""Searches for the device identifier and returns a DpllPin instance.
If not found, returns None.
device_id: device id to be searched
"""
return next((filter(lambda x: x.dev_id == device_id, self)), None)
def filter_by_device_type(self, device_type: DeviceType) -> DpllDevices:
"""Returns a new instance containing only items with the given device type.
device_type: device type enumeration to be filtered.
"""
return self.__class__(filter(lambda x: x.dev_type == device_type, self))
def filter_by_device_types(self, device_types: list[DeviceType]) -> DpllDevices:
"""Returns a new instance containing only items with the given devices types.
device_types: list with the device type enumerations to be filtered.
"""
return self.__class__(filter(lambda x: x.dev_type in device_types, self))
def filter_by_device_mode(self, device_mode: DeviceMode) -> DpllDevices:
"""Returns a new instance containing only items with the given device mode.
device_type: device type enumeration to be filtered.
"""
return self.__class__(filter(lambda x: x.dev_mode == device_mode, self))
def filter_by_device_mode_supported(self, mode_supported: DeviceMode) -> DpllDevices:
"""Returns a new instance containing only items with the given device mode.
device_type: device type enumeration to be filtered.
"""
return self.__class__(filter(lambda x: mode_supported in x.dev_mode_supported, self))
def filter_by_device_lock_status(self, lock_status: LockStatus) -> DpllDevices:
"""Returns a new instance containing only items with the given device lock status.
lock_status: lock status to be filtered.
"""
return self.__class__(filter(lambda x: x.lock_status == lock_status, self))
def filter_by_device_lock_statuses(self, lock_statuses: list[LockStatus]) -> DpllDevices:
"""Returns a new instance containing only items with the given lock statuses.
lock_statuses: list with the lock status enumerations to be filtered.
"""
return self.__class__(filter(lambda x: x.lock_status in lock_statuses, self))
def filter_by_device_lock_status_error(self, lock_status_error: LockStatusError) -> DpllDevices:
"""Returns a new instance containing only items with the given device lock status error.
lock_status_error: error status to be filtered.
"""
return self.__class__(filter(lambda x: x.lock_status_error == lock_status_error, self))
def filter_by_device_lock_status_errors(self, lock_status_errors: list[LockStatusError]) -> DpllDevices:
"""Returns a new instance containing only items with the given lock status errors.
lock_status_errors: list with the error status enumerations to be filtered.
"""
return self.__class__(filter(lambda x: x.lock_status_error in lock_status_errors, self))

View File

@ -0,0 +1,179 @@
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module defines and implements the DPLL interface for netlink"""
from __future__ import annotations
from ..base import NlError
from ..common import NetlinkException
from ..common import NetlinkFactory
from .constants import DeviceFields
from .constants import PinFields
from .devices import DpllDevice
from .devices import DpllDevices
from .pins import DpllPins
class DPLLCommands:
"""List of Netlink operation for DPLL."""
__slots__ = ()
DEVICE_GET = 'device-get'
PIN_GET = 'pin-get'
class NetlinkDPLL:
_ynl = NetlinkFactory.get_dpll_instance()
def __init__(self, multi_instance: bool = False):
"""Get a DPLL instance.
By default, a single YNL instance is shared by all DPLL instances, avoiding
loading and parsing spec/schema YAML files on each new instance. If simultaneous
access is required, you can create dedicated instances by setting the multi_instance
option to True.
multi_instance: if true creates a dedicated instance.
"""
if multi_instance:
self._ynl = NetlinkFactory.get_dpll_instance()
def _get_device_by_id(self, dev_id: int) -> dict:
"""Returns a dict containing the details of the device.
The returned dict has all the fields returned by Netlink.
Raises NetlinkException class in case of error.
dev_id: Netlink device identifier.
"""
try:
dev_info = self._ynl.do(
DPLLCommands.DEVICE_GET,
{DeviceFields.ID: dev_id})
return dev_info
except NlError as e:
raise NetlinkException(e)
def get_device_by_id(self, dev_id: int) -> DpllDevice:
"""Returns a DpllDevice instance with the device information.
Raises NetlinkException class in case of error.
dev_id: Netlink device identifier.
"""
return DpllDevice.loadDevice(self._get_device_by_id(dev_id))
def get_devices_by_clock_id(self, clock_id: int) -> DpllDevices:
"""Returns a list of devices with their settings for a specific clock device.
Raises NetlinkException class in case of error.
clock_id: Clock device identifier.
"""
return self.get_all_devices().filter_by_device_clock_id(clock_id)
def _get_all_devices(self) -> list[dict]:
"""Obtain all DPLL devices in raw format.
Returns a list of devices available in the system in raw format (keeps
the same fields and structure returned by Netlink).
Raises NetlinkException class in case of error.
"""
try:
dev_info_list = self._ynl.dump(DPLLCommands.DEVICE_GET, {})
return dev_info_list
except NlError as e:
raise NetlinkException(e)
def get_all_devices(self) -> DpllDevices:
"""Obtain all DPLL devices.
Loads and parses all DPLL information found in the system returned by get_all_devices
method and creates a DpllDevices instance. No pins information is included.
See DPllDevices.load() method.
Raises NetlinkException class in case of error.
"""
return DpllDevices.loadDevices(self._get_all_devices())
def _get_pin_by_id(self, pin_id: int) -> dict:
"""Returns a dict containing the details of the DPLL device pin.
Keeps the same format and fields returned by Netlink.
Raises NetlinkException class in case of error.
pin_id: Device pin id.
"""
try:
pin_info = self._ynl.do(
DPLLCommands.PIN_GET, {PinFields.ID: pin_id})
return pin_info
except NlError as e:
raise NetlinkException(e)
def get_pins_by_id(self, pin_id: int) -> DpllPins:
"""Returns a DpllPins instance with all pins of the DPLL device.
Raises NetlinkException class in case of error.
pin_id: Device pin id.
"""
return DpllPins.loadPins(self._get_all_devices(), [self._get_pin_by_id(pin_id)])
def get_pins_by_clock_id(self, clock_id: int) -> DpllPins:
"""Returns a list of all pins and their settings for the given clock device.
Raises NetlinkException class in case of error.
clock_id: Clock device identifier.
"""
return self.get_all_pins().filter_by_device_clock_id(clock_id)
def _get_all_pins(self) -> list[dict]:
"""Obtain all DPLL pins in raw format.
Returns a list of all pins from all devices available in the system in raw
format (keeps the same fields and structure returned by Netlink).
Raises NetlinkException class in case of error.
"""
try:
pins_info = self._ynl.dump(DPLLCommands.PIN_GET, {})
return pins_info
except NlError as e:
raise NetlinkException(e)
def get_all_pins(self) -> DpllPins:
"""Obtain all DPLL pins.
Loads and parses all DPLL information found in the system returned by
get_all_devices and get_all_pins methods and creates a DpllPins instance.
See DPllPins.load() method.
Raises NetlinkException class in case of error.
"""
return DpllPins.loadPins(self._get_all_devices(), self._get_all_pins())

View File

@ -0,0 +1,265 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module contains classes related to DPLL device pins."""
from __future__ import annotations
from dataclasses import asdict
from dataclasses import dataclass
from dataclasses import field
from typing import Union
from .constants import PinDirection
from .constants import PinFields
from .constants import PinParentFields
from .constants import PinState
from .constants import PinType
from .devices import DpllDevice
from .devices import DpllDevices
@dataclass(frozen=True, init=False)
class DpllPin(DpllDevice):
pin_id: int
pin_board_label: str = field(hash=False)
pin_panel_label: str = field(hash=False)
pin_package_label: str = field(hash=False)
pin_type: PinType = field(hash=False)
pin_state: PinState = field(hash=False)
pin_priority: int = field(hash=False)
pin_phase_offset: int = field(hash=False)
pin_direction: PinDirection = field(hash=False)
def __init__(self, **kwargs):
# Python 3.9 doesn't support kw_only option of dataclasses.
# The init method has been overwritten to allow having fields with default values in
# the parent class (DpllDevice) and having other mandatory fields in child class (DpllPin)
super().__init__(**{k: v for k, v in kwargs.items() if k in DpllDevice.__annotations__})
super().__setattr__('pin_id', kwargs['pin_id'])
super().__setattr__('pin_board_label', kwargs.get('pin_board_label', None))
super().__setattr__('pin_panel_label', kwargs.get('pin_panel_label', None))
super().__setattr__('pin_package_label', kwargs.get('pin_package_label', None))
super().__setattr__('pin_type', kwargs.get('pin_type', PinType.UNDEFINED))
super().__setattr__('pin_state', kwargs.get('pin_state', PinState.UNDEFINED))
super().__setattr__('pin_priority', kwargs.get('pin_priority', None))
super().__setattr__('pin_phase_offset', kwargs.get('pin_phase_offset', None))
super().__setattr__('pin_direction', kwargs.get('pin_direction', PinDirection.UNDEFINED))
@classmethod
def loadPin(cls, device: DpllDevice, pin: dict) -> DpllPin:
"""Correlates DPLL device and pin data returned from Netlink and creates a DpllPin class.
The fields not specified in the list will be set to a default value.
device: DPLL device data in raw format.
pin: DPLL device pin data in raw format.
"""
if device is None:
raise ValueError('Device parameter must be informed.')
elif pin is None:
raise ValueError('Pin parameter must be informed.')
if PinFields.PARENT_ID in pin:
return DpllPin(
**asdict(device),
pin_id=int(pin[PinFields.ID]),
pin_board_label=str(pin[PinFields.BOARD_LABEL]) if PinFields.BOARD_LABEL in pin else None,
pin_panel_label=str(pin[PinFields.PANEL_LABEL]) if PinFields.PANEL_LABEL in pin else None,
pin_package_label=str(pin[PinFields.PACKAGE_LABEL]) if PinFields.PACKAGE_LABEL in pin else None,
pin_type=PinType(pin[PinFields.TYPE]),
pin_state=PinState(pin.get(PinFields.STATE, PinState.UNDEFINED)),
pin_priority=pin.get(PinFields.PRIORITY),
pin_phase_offset=pin.get(PinFields.PHASE_OFFSET),
pin_direction=PinDirection(pin.get(PinFields.DIRECTION,
PinDirection.UNDEFINED)),
)
elif PinFields.PARENT_DEVICE in pin:
parent_pin = next((x for x in pin[PinFields.PARENT_DEVICE]
if x.get(PinParentFields.PARENT_ID) == device.dev_id), None)
if parent_pin is None:
raise ValueError(f'No pin parent reference found for device id {device.dev_id}')
return DpllPin(
**asdict(device),
pin_id=int(pin[PinFields.ID]),
pin_board_label=str(pin[PinFields.BOARD_LABEL]) if PinFields.BOARD_LABEL in pin else None,
pin_panel_label=str(pin[PinFields.PANEL_LABEL]) if PinFields.PANEL_LABEL in pin else None,
pin_package_label=str(pin[PinFields.PACKAGE_LABEL]) if PinFields.PACKAGE_LABEL in pin else None,
pin_type=PinType(pin[PinFields.TYPE]),
pin_state=PinState(parent_pin.get(PinParentFields.STATE,
PinState.UNDEFINED)),
pin_priority=parent_pin.get(PinParentFields.PRIORITY),
pin_phase_offset=parent_pin.get(PinParentFields.PHASE_OFFSET),
pin_direction=PinDirection(parent_pin.get(PinParentFields.DIRECTION,
PinDirection.UNDEFINED)),
)
else:
raise ValueError(f'No pin reference found for device id {device.dev_id}')
class DpllPins(DpllDevices):
@classmethod
def loadPins(cls, devices: list[dict], pins: list[dict]) -> DpllPins:
"""Correlates DPLL device and pins data and creates the DpllPins class.
Both parameters are expect in the format returned by Netlink. The fields not specified
will be set to the default value.
devices: list of DPLL devices in raw format.
pins: list of DPLL pins in raw format.
"""
instance = cls()
dpll_devices = DpllDevices.loadDevices(devices)
for pin in pins:
# Processes only pins without parent pin fields
if PinFields.PARENT_PIN in pin:
continue
if PinFields.PARENT_DEVICE in pin:
for pin_parent in pin[PinFields.PARENT_DEVICE]:
# Get the device reference
device = dpll_devices.filter_by_device_id(
pin_parent.get(PinParentFields.PARENT_ID))
instance.add(DpllPin.loadPin(device, pin))
else:
# Get the device reference
device = dpll_devices.filter_by_device_id(
pin.get(PinFields.PARENT_ID))
instance.add(DpllPin.loadPin(device, pin))
return instance
def filter_by_pin_id(self, pin_id: int) -> Union[DpllPin, None]:
"""Searches for the pin identifier and returns a DpllPin instance.
If not found, returns None.
pin_id: pin id to be filtered
"""
return next((x for x in self if x.pin_id == pin_id), None)
def filter_by_pin_direction(self, direction: PinDirection) -> DpllPins:
"""Returns a new instance containing only items with the given direction value.
direction: direction enumeration to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_direction == direction, self))
def filter_by_pin_directions(self, directions: list[PinDirection]) -> DpllPins:
"""Returns a new instance containing only items with the given direction value.
directions: list with the direction enumerations to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_direction in directions, self))
def filter_by_pin_board_label(self, board_label: str) -> DpllPins:
"""Returns a new instance containing only items with the given board label.
board_label: label value to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_board_label == board_label, self))
def filter_by_pin_board_labels(self, board_labels: list[str]) -> DpllPins:
"""Returns a new instance containing only items with the given board labels.
board_labels: list of the labels to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_board_label in board_labels, self))
def filter_by_pin_panel_label(self, panel_label: str) -> DpllPins:
"""Returns a new instance containing only items with the given panel label.
panel_label: label value to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_panel_label == panel_label, self))
def filter_by_pin_panel_labels(self, panel_labels: list[str]) -> DpllPins:
"""Returns a new instance containing only items with the given panel labels.
panel_labels: list of the labels to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_panel_label in panel_labels, self))
def filter_by_pin_package_label(self, package_label: str) -> DpllPins:
"""Returns a new instance containing only items with the given package label.
package_label: label value to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_package_label == package_label, self))
def filter_by_pin_package_labels(self, package_labels: list[str]) -> DpllPins:
"""Returns a new instance containing only items with the given package labels.
package_labels: list of the labels to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_package_label in package_labels, self))
def filter_by_pin_state(self, pin_state: PinState) -> DpllPins:
"""Returns a new instance containing only items with the given pin state.
pin_state: state enumeration to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_state == pin_state, self))
def filter_by_pin_states(self, pin_states: list[PinState]) -> DpllPins:
"""Returns a new instance containing only items with the given pin states.
pin_states: list with the state enumerations to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_state in pin_states, self))
def filter_by_pin_type(self, pin_type: PinType) -> DpllPins:
"""Returns a new instance containing only items with the given pin type.
pin_type: type enumeration to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_type == pin_type, self))
def filter_by_pin_types(self, pin_types: list[PinType]) -> DpllPins:
"""Returns a new instance containing only items with the given pin types.
pin_types: list with the types enumeration to be filtered.
"""
return self.__class__(filter(lambda x: x.pin_type in pin_types, self))
def order_by_pin_priority(self, reverse: bool = False) -> list[DpllPin]:
"""Returns a list of sorted DPllPins.
Sorts using the priority of DPLL pins in ascending order and returns a
list of sorted DpllPin.
reverse: sort in descending order.
"""
return sorted(self,
key=lambda x: (x.pin_priority is None, x.pin_priority),
reverse=reverse)

View File

@ -0,0 +1,4 @@
pynetlink - Netlink Python library
===================================
This folder contains the Schema files used by the YNL library to communicate with the Netlink protocol.

View File

@ -0,0 +1,460 @@
# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
%YAML 1.2
---
$id: http://kernel.org/schemas/netlink/genetlink-legacy.yaml#
$schema: https://json-schema.org/draft-07/schema
# Common defines
$defs:
uint:
type: integer
minimum: 0
len-or-define:
type: [ string, integer ]
pattern: ^[0-9A-Za-z_-]+( - 1)?$
minimum: 0
len-or-limit:
# literal int or limit based on fixed-width type e.g. u8-min, u16-max, etc.
type: [ string, integer ]
pattern: ^[su](8|16|32|64)-(min|max)$
minimum: 0
# Schema for specs
title: Protocol
description: Specification of a genetlink protocol
type: object
required: [ name, doc, attribute-sets, operations ]
additionalProperties: False
properties:
name:
description: Name of the genetlink family.
type: string
doc:
type: string
protocol:
description: Schema compatibility level. Default is "genetlink".
enum: [ genetlink, genetlink-c, genetlink-legacy ] # Trim
uapi-header:
description: Path to the uAPI header, default is linux/${family-name}.h
type: string
# Start genetlink-c
c-family-name:
description: Name of the define for the family name.
type: string
c-version-name:
description: Name of the define for the version of the family.
type: string
max-by-define:
description: Makes the number of attributes and commands be specified by a define, not an enum value.
type: boolean
cmd-max-name:
description: Name of the define for the last operation in the list.
type: string
cmd-cnt-name:
description: The explicit name for constant holding the count of operations (last operation + 1).
type: string
# End genetlink-c
# Start genetlink-legacy
kernel-policy:
description: |
Defines if the input policy in the kernel is global, per-operation, or split per operation type.
Default is split.
enum: [ split, per-op, global ]
version:
description: Generic Netlink family version. Default is 1.
type: integer
minimum: 1
# End genetlink-legacy
definitions:
description: List of type and constant definitions (enums, flags, defines).
type: array
items:
type: object
required: [ type, name ]
additionalProperties: False
properties:
name:
type: string
header:
description: For C-compatible languages, header which already defines this value.
type: string
type:
enum: [ const, enum, flags, struct ] # Trim
doc:
type: string
# For const
value:
description: For const - the value.
type: [ string, integer ]
# For enum and flags
value-start:
description: For enum or flags the literal initializer for the first value.
type: [ string, integer ]
entries:
description: For enum or flags array of values.
type: array
items:
oneOf:
- type: string
- type: object
required: [ name ]
additionalProperties: False
properties:
name:
type: string
value:
type: integer
doc:
type: string
render-max:
description: Render the max members for this enum.
type: boolean
# Start genetlink-c
enum-name:
description: Name for enum, if empty no name will be used.
type: [ string, "null" ]
name-prefix:
description: For enum the prefix of the values, optional.
type: string
# End genetlink-c
# Start genetlink-legacy
members:
description: List of struct members. Only scalars and strings members allowed.
type: array
items:
type: object
required: [ name, type ]
additionalProperties: False
properties:
name:
type: string
type:
description: The netlink attribute type
enum: [ u8, u16, u32, u64, s8, s16, s32, s64, string, binary ]
len:
$ref: '#/$defs/len-or-define'
byte-order:
enum: [ little-endian, big-endian ]
doc:
description: Documentation for the struct member attribute.
type: string
enum:
description: Name of the enum type used for the attribute.
type: string
display-hint: &display-hint
description: |
Optional format indicator that is intended only for choosing
the right formatting mechanism when displaying values of this
type.
enum: [ hex, mac, fddi, ipv4, ipv6, uuid ]
# End genetlink-legacy
attribute-sets:
description: Definition of attribute spaces for this family.
type: array
items:
description: Definition of a single attribute space.
type: object
required: [ name, attributes ]
additionalProperties: False
properties:
name:
description: |
Name used when referring to this space in other definitions, not used outside of the spec.
type: string
name-prefix:
description: |
Prefix for the C enum name of the attributes. Default family[name]-set[name]-a-
type: string
enum-name:
description: |
Name for the enum type of the attribute, if empty no name will be used.
type: [ string, "null" ]
doc:
description: Documentation of the space.
type: string
subset-of:
description: |
Name of another space which this is a logical part of. Sub-spaces can be used to define
a limited group of attributes which are used in a nest.
type: string
# Start genetlink-c
attr-cnt-name:
description: The explicit name for constant holding the count of attributes (last attr + 1).
type: string
attr-max-name:
description: The explicit name for last member of attribute enum.
type: string
# End genetlink-c
attributes:
description: List of attributes in the space.
type: array
items:
type: object
required: [ name ]
additionalProperties: False
properties:
name:
type: string
type: &attr-type
description: The netlink attribute type
enum: [ unused, pad, flag, binary, bitfield32,
uint, sint, u8, u16, u32, u64, s32, s64,
string, nest, indexed-array, nest-type-value ]
doc:
description: Documentation of the attribute.
type: string
value:
description: Value for the enum item representing this attribute in the uAPI.
$ref: '#/$defs/uint'
type-value:
description: Name of the value extracted from the type of a nest-type-value attribute.
type: array
items:
type: string
byte-order:
enum: [ little-endian, big-endian ]
multi-attr:
type: boolean
nested-attributes:
description: Name of the space (sub-space) used inside the attribute.
type: string
enum:
description: Name of the enum type used for the attribute.
type: string
enum-as-flags:
description: |
Treat the enum as flags. In most cases enum is either used as flags or as values.
Sometimes, however, both forms are necessary, in which case header contains the enum
form while specific attributes may request to convert the values into a bitfield.
type: boolean
checks:
description: Kernel input validation.
type: object
additionalProperties: False
properties:
flags-mask:
description: Name of the flags constant on which to base mask (unsigned scalar types only).
type: string
min:
description: Min value for an integer attribute.
$ref: '#/$defs/len-or-limit'
max:
description: Max value for an integer attribute.
$ref: '#/$defs/len-or-limit'
min-len:
description: Min length for a binary attribute.
$ref: '#/$defs/len-or-define'
max-len:
description: Max length for a string or a binary attribute.
$ref: '#/$defs/len-or-define'
exact-len:
description: Exact length for a string or a binary attribute.
$ref: '#/$defs/len-or-define'
unterminated-ok:
description: |
For string attributes, do not check whether attribute
contains the terminating null character.
type: boolean
sub-type: *attr-type
display-hint: *display-hint
# Start genetlink-c
name-prefix:
type: string
# End genetlink-c
# Start genetlink-legacy
struct:
description: Name of the struct type used for the attribute.
type: string
# End genetlink-legacy
# Make sure name-prefix does not appear in subsets (subsets inherit naming)
dependencies:
name-prefix:
not:
required: [ subset-of ]
subset-of:
not:
required: [ name-prefix ]
# type property is only required if not in subset definition
if:
properties:
subset-of:
not:
type: string
then:
properties:
attributes:
items:
required: [ type ]
operations:
description: Operations supported by the protocol.
type: object
required: [ list ]
additionalProperties: False
properties:
enum-model:
description: |
The model of assigning values to the operations.
"unified" is the recommended model where all message types belong
to a single enum.
"directional" has the messages sent to the kernel and from the kernel
enumerated separately.
enum: [ unified, directional ] # Trim
name-prefix:
description: |
Prefix for the C enum name of the command. The name is formed by concatenating
the prefix with the upper case name of the command, with dashes replaced by underscores.
type: string
enum-name:
description: |
Name for the enum type with commands, if empty no name will be used.
type: [ string, "null" ]
async-prefix:
description: Same as name-prefix but used to render notifications and events to separate enum.
type: string
async-enum:
description: |
Name for the enum type with commands, if empty no name will be used.
type: [ string, "null" ]
# Start genetlink-legacy
fixed-header: &fixed-header
description: |
Name of the structure defining the optional fixed-length protocol
header. This header is placed in a message after the netlink and
genetlink headers and before any attributes.
type: string
# End genetlink-legacy
list:
description: List of commands
type: array
items:
type: object
additionalProperties: False
required: [ name, doc ]
properties:
name:
description: Name of the operation, also defining its C enum value in uAPI.
type: string
doc:
description: Documentation for the command.
type: string
value:
description: Value for the enum in the uAPI.
$ref: '#/$defs/uint'
attribute-set:
description: |
Attribute space from which attributes directly in the requests and replies
to this command are defined.
type: string
flags: &cmd_flags
description: Command flags.
type: array
items:
enum: [ admin-perm, uns-admin-perm ]
dont-validate:
description: Kernel attribute validation flags.
type: array
items:
enum: [ strict, dump, dump-strict ]
config-cond:
description: |
Name of the kernel config option gating the presence of
the operation, without the 'CONFIG_' prefix.
type: string
# Start genetlink-legacy
fixed-header: *fixed-header
# End genetlink-legacy
do: &subop-type
description: Main command handler.
type: object
additionalProperties: False
properties:
request: &subop-attr-list
description: Definition of the request message for a given command.
type: object
additionalProperties: False
properties:
attributes:
description: |
Names of attributes from the attribute-set (not full attribute
definitions, just names).
type: array
items:
type: string
# Start genetlink-legacy
value:
description: |
ID of this message if value for request and response differ,
i.e. requests and responses have different message enums.
$ref: '#/$defs/uint'
# End genetlink-legacy
reply: *subop-attr-list
pre:
description: Hook for a function to run before the main callback (pre_doit or start).
type: string
post:
description: Hook for a function to run after the main callback (post_doit or done).
type: string
dump: *subop-type
notify:
description: Name of the command sharing the reply type with this notification.
type: string
event:
type: object
additionalProperties: False
properties:
attributes:
description: Explicit list of the attributes for the notification.
type: array
items:
type: string
mcgrp:
description: Name of the multicast group generating given notification.
type: string
mcast-groups:
description: List of multicast groups.
type: object
required: [ list ]
additionalProperties: False
properties:
list:
description: List of groups.
type: array
items:
type: object
required: [ name ]
additionalProperties: False
properties:
name:
description: |
The name for the group, used to form the define and the value of the define.
type: string
# Start genetlink-c
c-define-name:
description: Override for the name of the define in C uAPI.
type: string
# End genetlink-c
flags: *cmd_flags
kernel-family:
description: Additional global attributes used for kernel C code generation.
type: object
additionalProperties: False
properties:
headers:
description: |
List of extra headers which should be included in the source
of the generated code.
type: array
items:
type: string
sock-priv:
description: |
Literal name of the type which is used within the kernel
to store the socket state. The type / structure is internal
to the kernel, and is not defined in the spec.
type: string

View File

@ -0,0 +1,349 @@
# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
%YAML 1.2
---
$id: http://kernel.org/schemas/netlink/genetlink-legacy.yaml#
$schema: https://json-schema.org/draft-07/schema
# Common defines
$defs:
uint:
type: integer
minimum: 0
len-or-define:
type: [ string, integer ]
pattern: ^[0-9A-Za-z_-]+( - 1)?$
minimum: 0
len-or-limit:
# literal int or limit based on fixed-width type e.g. u8-min, u16-max, etc.
type: [ string, integer ]
pattern: ^[su](8|16|32|64)-(min|max)$
minimum: 0
# Schema for specs
title: Protocol
description: Specification of a genetlink protocol
type: object
required: [ name, doc, attribute-sets, operations ]
additionalProperties: False
properties:
name:
description: Name of the genetlink family.
type: string
doc:
type: string
protocol:
description: Schema compatibility level. Default is "genetlink".
enum: [ genetlink ]
uapi-header:
description: Path to the uAPI header, default is linux/${family-name}.h
type: string
definitions:
description: List of type and constant definitions (enums, flags, defines).
type: array
items:
type: object
required: [ type, name ]
additionalProperties: False
properties:
name:
type: string
header:
description: For C-compatible languages, header which already defines this value.
type: string
type:
enum: [ const, enum, flags ]
doc:
type: string
# For const
value:
description: For const - the value.
type: [ string, integer ]
# For enum and flags
value-start:
description: For enum or flags the literal initializer for the first value.
type: [ string, integer ]
entries:
description: For enum or flags array of values.
type: array
items:
oneOf:
- type: string
- type: object
required: [ name ]
additionalProperties: False
properties:
name:
type: string
value:
type: integer
doc:
type: string
render-max:
description: Render the max members for this enum.
type: boolean
attribute-sets:
description: Definition of attribute spaces for this family.
type: array
items:
description: Definition of a single attribute space.
type: object
required: [ name, attributes ]
additionalProperties: False
properties:
name:
description: |
Name used when referring to this space in other definitions, not used outside of the spec.
type: string
name-prefix:
description: |
Prefix for the C enum name of the attributes. Default family[name]-set[name]-a-
type: string
enum-name:
description: Name for the enum type of the attribute.
type: string
doc:
description: Documentation of the space.
type: string
subset-of:
description: |
Name of another space which this is a logical part of. Sub-spaces can be used to define
a limited group of attributes which are used in a nest.
type: string
attributes:
description: List of attributes in the space.
type: array
items:
type: object
required: [ name ]
additionalProperties: False
properties:
name:
type: string
type: &attr-type
enum: [ unused, pad, flag, binary,
uint, sint, u8, u16, u32, u64, s32, s64,
string, nest, indexed-array, nest-type-value ]
doc:
description: Documentation of the attribute.
type: string
value:
description: Value for the enum item representing this attribute in the uAPI.
$ref: '#/$defs/uint'
type-value:
description: Name of the value extracted from the type of a nest-type-value attribute.
type: array
items:
type: string
byte-order:
enum: [ little-endian, big-endian ]
multi-attr:
type: boolean
nested-attributes:
description: Name of the space (sub-space) used inside the attribute.
type: string
enum:
description: Name of the enum type used for the attribute.
type: string
enum-as-flags:
description: |
Treat the enum as flags. In most cases enum is either used as flags or as values.
Sometimes, however, both forms are necessary, in which case header contains the enum
form while specific attributes may request to convert the values into a bitfield.
type: boolean
checks:
description: Kernel input validation.
type: object
additionalProperties: False
properties:
flags-mask:
description: Name of the flags constant on which to base mask (unsigned scalar types only).
type: string
min:
description: Min value for an integer attribute.
$ref: '#/$defs/len-or-limit'
max:
description: Max value for an integer attribute.
$ref: '#/$defs/len-or-limit'
min-len:
description: Min length for a binary attribute.
$ref: '#/$defs/len-or-define'
max-len:
description: Max length for a string or a binary attribute.
$ref: '#/$defs/len-or-define'
exact-len:
description: Exact length for a string or a binary attribute.
$ref: '#/$defs/len-or-define'
sub-type: *attr-type
display-hint: &display-hint
description: |
Optional format indicator that is intended only for choosing
the right formatting mechanism when displaying values of this
type.
enum: [ hex, mac, fddi, ipv4, ipv6, uuid ]
# Make sure name-prefix does not appear in subsets (subsets inherit naming)
dependencies:
name-prefix:
not:
required: [ subset-of ]
subset-of:
not:
required: [ name-prefix ]
# type property is only required if not in subset definition
if:
properties:
subset-of:
not:
type: string
then:
properties:
attributes:
items:
required: [ type ]
operations:
description: Operations supported by the protocol.
type: object
required: [ list ]
additionalProperties: False
properties:
enum-model:
description: |
The model of assigning values to the operations.
"unified" is the recommended model where all message types belong
to a single enum.
"directional" has the messages sent to the kernel and from the kernel
enumerated separately.
enum: [ unified ]
name-prefix:
description: |
Prefix for the C enum name of the command. The name is formed by concatenating
the prefix with the upper case name of the command, with dashes replaced by underscores.
type: string
enum-name:
description: Name for the enum type with commands.
type: string
async-prefix:
description: Same as name-prefix but used to render notifications and events to separate enum.
type: string
async-enum:
description: Name for the enum type with notifications/events.
type: string
list:
description: List of commands
type: array
items:
type: object
additionalProperties: False
required: [ name, doc ]
properties:
name:
description: Name of the operation, also defining its C enum value in uAPI.
type: string
doc:
description: Documentation for the command.
type: string
value:
description: Value for the enum in the uAPI.
$ref: '#/$defs/uint'
attribute-set:
description: |
Attribute space from which attributes directly in the requests and replies
to this command are defined.
type: string
flags: &cmd_flags
description: Command flags.
type: array
items:
enum: [ admin-perm ]
dont-validate:
description: Kernel attribute validation flags.
type: array
items:
enum: [ strict, dump, dump-strict ]
config-cond:
description: |
Name of the kernel config option gating the presence of
the operation, without the 'CONFIG_' prefix.
type: string
do: &subop-type
description: Main command handler.
type: object
additionalProperties: False
properties:
request: &subop-attr-list
description: Definition of the request message for a given command.
type: object
additionalProperties: False
properties:
attributes:
description: |
Names of attributes from the attribute-set (not full attribute
definitions, just names).
type: array
items:
type: string
reply: *subop-attr-list
pre:
description: Hook for a function to run before the main callback (pre_doit or start).
type: string
post:
description: Hook for a function to run after the main callback (post_doit or done).
type: string
dump: *subop-type
notify:
description: Name of the command sharing the reply type with this notification.
type: string
event:
type: object
additionalProperties: False
properties:
attributes:
description: Explicit list of the attributes for the notification.
type: array
items:
type: string
mcgrp:
description: Name of the multicast group generating given notification.
type: string
mcast-groups:
description: List of multicast groups.
type: object
required: [ list ]
additionalProperties: False
properties:
list:
description: List of groups.
type: array
items:
type: object
required: [ name ]
additionalProperties: False
properties:
name:
description: |
The name for the group, used to form the define and the value of the define.
type: string
flags: *cmd_flags
kernel-family:
description: Additional global attributes used for kernel C code generation.
type: object
additionalProperties: False
properties:
headers:
description: |
List of extra headers which should be included in the source
of the generated code.
type: array
items:
type: string
sock-priv:
description: |
Literal name of the type which is used within the kernel
to store the socket state. The type / structure is internal
to the kernel, and is not defined in the spec.
type: string

View File

@ -0,0 +1,508 @@
# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
%YAML 1.2
---
$id: http://kernel.org/schemas/netlink/netlink-raw.yaml#
$schema: https://json-schema.org/draft-07/schema
# Common defines
$defs:
uint:
type: integer
minimum: 0
len-or-define:
type: [ string, integer ]
pattern: ^[0-9A-Za-z_-]+( - 1)?$
minimum: 0
# Schema for specs
title: Protocol
description: Specification of a raw netlink protocol
type: object
required: [ name, doc, attribute-sets, operations ]
additionalProperties: False
properties:
name:
description: Name of the netlink family.
type: string
doc:
type: string
protocol:
description: Schema compatibility level.
enum: [ netlink-raw ] # Trim
# Start netlink-raw
protonum:
description: Protocol number to use for netlink-raw
type: integer
# End netlink-raw
uapi-header:
description: Path to the uAPI header, default is linux/${family-name}.h
type: string
# Start genetlink-c
c-family-name:
description: Name of the define for the family name.
type: string
c-version-name:
description: Name of the define for the version of the family.
type: string
max-by-define:
description: Makes the number of attributes and commands be specified by a define, not an enum value.
type: boolean
cmd-max-name:
description: Name of the define for the last operation in the list.
type: string
cmd-cnt-name:
description: The explicit name for constant holding the count of operations (last operation + 1).
type: string
# End genetlink-c
# Start genetlink-legacy
kernel-policy:
description: |
Defines if the input policy in the kernel is global, per-operation, or split per operation type.
Default is split.
enum: [ split, per-op, global ]
# End genetlink-legacy
definitions:
description: List of type and constant definitions (enums, flags, defines).
type: array
items:
type: object
required: [ type, name ]
additionalProperties: False
properties:
name:
type: string
header:
description: For C-compatible languages, header which already defines this value.
type: string
type:
enum: [ const, enum, flags, struct ] # Trim
doc:
type: string
# For const
value:
description: For const - the value.
type: [ string, integer ]
# For enum and flags
value-start:
description: For enum or flags the literal initializer for the first value.
type: [ string, integer ]
entries:
description: For enum or flags array of values.
type: array
items:
oneOf:
- type: string
- type: object
required: [ name ]
additionalProperties: False
properties:
name:
type: string
value:
type: integer
doc:
type: string
render-max:
description: Render the max members for this enum.
type: boolean
# Start genetlink-c
enum-name:
description: Name for enum, if empty no name will be used.
type: [ string, "null" ]
name-prefix:
description: For enum the prefix of the values, optional.
type: string
# End genetlink-c
# Start genetlink-legacy
members:
description: List of struct members. Only scalars and strings members allowed.
type: array
items:
type: object
required: [ name, type ]
additionalProperties: False
properties:
name:
type: string
type:
description: |
The netlink attribute type. Members of type 'binary' or 'pad'
must also have the 'len' property set.
enum: [ u8, u16, u32, u64, s8, s16, s32, s64, string, binary, pad ]
len:
$ref: '#/$defs/len-or-define'
byte-order:
enum: [ little-endian, big-endian ]
doc:
description: Documentation for the struct member attribute.
type: string
enum:
description: Name of the enum type used for the attribute.
type: string
enum-as-flags:
description: |
Treat the enum as flags. In most cases enum is either used as flags or as values.
Sometimes, however, both forms are necessary, in which case header contains the enum
form while specific attributes may request to convert the values into a bitfield.
type: boolean
display-hint: &display-hint
description: |
Optional format indicator that is intended only for choosing
the right formatting mechanism when displaying values of this
type.
enum: [ hex, mac, fddi, ipv4, ipv6, uuid ]
struct:
description: Name of the nested struct type.
type: string
if:
properties:
type:
const: pad
then:
required: [ len ]
if:
properties:
type:
const: binary
then:
oneOf:
- required: [ len ]
- required: [ struct ]
# End genetlink-legacy
attribute-sets:
description: Definition of attribute spaces for this family.
type: array
items:
description: Definition of a single attribute space.
type: object
required: [ name, attributes ]
additionalProperties: False
properties:
name:
description: |
Name used when referring to this space in other definitions, not used outside of the spec.
type: string
name-prefix:
description: |
Prefix for the C enum name of the attributes. Default family[name]-set[name]-a-
type: string
enum-name:
description: |
Name for the enum type of the attribute, if empty no name will be used.
type: [ string, "null" ]
doc:
description: Documentation of the space.
type: string
subset-of:
description: |
Name of another space which this is a logical part of. Sub-spaces can be used to define
a limited group of attributes which are used in a nest.
type: string
# Start genetlink-c
attr-cnt-name:
description: The explicit name for constant holding the count of attributes (last attr + 1).
type: string
attr-max-name:
description: The explicit name for last member of attribute enum.
type: string
# End genetlink-c
attributes:
description: List of attributes in the space.
type: array
items:
type: object
required: [ name ]
additionalProperties: False
properties:
name:
type: string
type: &attr-type
description: The netlink attribute type
enum: [ unused, pad, flag, binary, bitfield32,
u8, u16, u32, u64, s8, s16, s32, s64,
string, nest, indexed-array, nest-type-value,
sub-message ]
doc:
description: Documentation of the attribute.
type: string
value:
description: Value for the enum item representing this attribute in the uAPI.
$ref: '#/$defs/uint'
type-value:
description: Name of the value extracted from the type of a nest-type-value attribute.
type: array
items:
type: string
byte-order:
enum: [ little-endian, big-endian ]
multi-attr:
type: boolean
nested-attributes:
description: Name of the space (sub-space) used inside the attribute.
type: string
enum:
description: Name of the enum type used for the attribute.
type: string
enum-as-flags:
description: |
Treat the enum as flags. In most cases enum is either used as flags or as values.
Sometimes, however, both forms are necessary, in which case header contains the enum
form while specific attributes may request to convert the values into a bitfield.
type: boolean
checks:
description: Kernel input validation.
type: object
additionalProperties: False
properties:
flags-mask:
description: Name of the flags constant on which to base mask (unsigned scalar types only).
type: string
min:
description: Min value for an integer attribute.
type: integer
min-len:
description: Min length for a binary attribute.
$ref: '#/$defs/len-or-define'
max-len:
description: Max length for a string or a binary attribute.
$ref: '#/$defs/len-or-define'
exact-len:
description: Exact length for a string or a binary attribute.
$ref: '#/$defs/len-or-define'
unterminated-ok:
description: |
For string attributes, do not check whether attribute
contains the terminating null character.
type: boolean
sub-type: *attr-type
display-hint: *display-hint
# Start genetlink-c
name-prefix:
type: string
# End genetlink-c
# Start genetlink-legacy
struct:
description: Name of the struct type used for the attribute.
type: string
# End genetlink-legacy
# Start netlink-raw
sub-message:
description: |
Name of the sub-message definition to use for the attribute.
type: string
selector:
description: |
Name of the attribute to use for dynamic selection of sub-message
format specifier.
type: string
# End netlink-raw
# Make sure name-prefix does not appear in subsets (subsets inherit naming)
dependencies:
name-prefix:
not:
required: [ subset-of ]
subset-of:
not:
required: [ name-prefix ]
# type property is only required if not in subset definition
if:
properties:
subset-of:
not:
type: string
then:
properties:
attributes:
items:
required: [ type ]
# Start netlink-raw
sub-messages:
description: Definition of sub message attributes
type: array
items:
type: object
additionalProperties: False
required: [ name, formats ]
properties:
name:
description: Name of the sub-message definition
type: string
formats:
description: Dynamically selected format specifiers
type: array
items:
type: object
additionalProperties: False
required: [ value ]
properties:
value:
description: |
Value to match for dynamic selection of sub-message format
specifier.
type: string
fixed-header:
description: |
Name of the struct definition to use as the fixed header
for the sub message.
type: string
attribute-set:
description: |
Name of the attribute space from which to resolve attributes
in the sub message.
type: string
# End netlink-raw
operations:
description: Operations supported by the protocol.
type: object
required: [ list ]
additionalProperties: False
properties:
enum-model:
description: |
The model of assigning values to the operations.
"unified" is the recommended model where all message types belong
to a single enum.
"directional" has the messages sent to the kernel and from the kernel
enumerated separately.
enum: [ unified, directional ] # Trim
name-prefix:
description: |
Prefix for the C enum name of the command. The name is formed by concatenating
the prefix with the upper case name of the command, with dashes replaced by underscores.
type: string
enum-name:
description: |
Name for the enum type with commands, if empty no name will be used.
type: [ string, "null" ]
async-prefix:
description: Same as name-prefix but used to render notifications and events to separate enum.
type: string
async-enum:
description: |
Name for the enum type with commands, if empty no name will be used.
type: [ string, "null" ]
# Start genetlink-legacy
fixed-header: &fixed-header
description: |
Name of the structure defining the optional fixed-length protocol
header. This header is placed in a message after the netlink and
genetlink headers and before any attributes.
type: string
# End genetlink-legacy
list:
description: List of commands
type: array
items:
type: object
additionalProperties: False
required: [ name, doc ]
properties:
name:
description: Name of the operation, also defining its C enum value in uAPI.
type: string
doc:
description: Documentation for the command.
type: string
value:
description: Value for the enum in the uAPI.
$ref: '#/$defs/uint'
attribute-set:
description: |
Attribute space from which attributes directly in the requests and replies
to this command are defined.
type: string
flags: &cmd_flags
description: Command flags.
type: array
items:
enum: [ admin-perm ]
dont-validate:
description: Kernel attribute validation flags.
type: array
items:
enum: [ strict, dump ]
# Start genetlink-legacy
fixed-header: *fixed-header
# End genetlink-legacy
do: &subop-type
description: Main command handler.
type: object
additionalProperties: False
properties:
request: &subop-attr-list
description: Definition of the request message for a given command.
type: object
additionalProperties: False
properties:
attributes:
description: |
Names of attributes from the attribute-set (not full attribute
definitions, just names).
type: array
items:
type: string
# Start genetlink-legacy
value:
description: |
ID of this message if value for request and response differ,
i.e. requests and responses have different message enums.
$ref: '#/$defs/uint'
# End genetlink-legacy
reply: *subop-attr-list
pre:
description: Hook for a function to run before the main callback (pre_doit or start).
type: string
post:
description: Hook for a function to run after the main callback (post_doit or done).
type: string
dump: *subop-type
notify:
description: Name of the command sharing the reply type with this notification.
type: string
event:
type: object
additionalProperties: False
properties:
attributes:
description: Explicit list of the attributes for the notification.
type: array
items:
type: string
mcgrp:
description: Name of the multicast group generating given notification.
type: string
mcast-groups:
description: List of multicast groups.
type: object
required: [ list ]
additionalProperties: False
properties:
list:
description: List of groups.
type: array
items:
type: object
required: [ name ]
additionalProperties: False
properties:
name:
description: |
The name for the group, used to form the define and the value of the define.
type: string
# Start genetlink-c
c-define-name:
description: Override for the name of the define in C uAPI.
type: string
# End genetlink-c
flags: *cmd_flags
# Start netlink-raw
value:
description: Value of the netlink multicast group in the uAPI.
type: integer
# End netlink-raw

View File

@ -0,0 +1,4 @@
pynetlink - Netlink Python library
===================================
This folder contains the Specification files used by the YNL library to communicate with the Netlink protocol.

View File

@ -0,0 +1,623 @@
# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
name: dpll
doc: DPLL subsystem.
definitions:
-
type: enum
name: mode
doc: |
working modes a dpll can support, differentiates if and how dpll selects
one of its inputs to syntonize with it, valid values for DPLL_A_MODE
attribute
entries:
-
name: manual
doc: input can be only selected by sending a request to dpll
value: 1
-
name: automatic
doc: highest prio input pin auto selected by dpll
render-max: true
-
type: enum
name: lock-status
doc: |
provides information of dpll device lock status, valid values for
DPLL_A_LOCK_STATUS attribute
entries:
-
name: unlocked
doc: |
dpll was not yet locked to any valid input (or forced by setting
DPLL_A_MODE to DPLL_MODE_DETACHED)
value: 1
-
name: locked
doc: |
dpll is locked to a valid signal, but no holdover available
-
name: locked-ho-acq
doc: |
dpll is locked and holdover acquired
-
name: holdover
doc: |
dpll is in holdover state - lost a valid lock or was forced
by disconnecting all the pins (latter possible only
when dpll lock-state was already DPLL_LOCK_STATUS_LOCKED_HO_ACQ,
if dpll lock-state was not DPLL_LOCK_STATUS_LOCKED_HO_ACQ, the
dpll's lock-state shall remain DPLL_LOCK_STATUS_UNLOCKED)
render-max: true
-
type: enum
name: lock-status-error
doc: |
if previous status change was done due to a failure, this provides
information of dpll device lock status error.
Valid values for DPLL_A_LOCK_STATUS_ERROR attribute
entries:
-
name: none
doc: |
dpll device lock status was changed without any error
value: 1
-
name: undefined
doc: |
dpll device lock status was changed due to undefined error.
Driver fills this value up in case it is not able
to obtain suitable exact error type.
-
name: media-down
doc: |
dpll device lock status was changed because of associated
media got down.
This may happen for example if dpll device was previously
locked on an input pin of type PIN_TYPE_SYNCE_ETH_PORT.
-
name: fractional-frequency-offset-too-high
doc: |
the FFO (Fractional Frequency Offset) between the RX and TX
symbol rate on the media got too high.
This may happen for example if dpll device was previously
locked on an input pin of type PIN_TYPE_SYNCE_ETH_PORT.
render-max: true
-
type: enum
name: clock-quality-level
doc: |
level of quality of a clock device. This mainly applies when
the dpll lock-status is DPLL_LOCK_STATUS_HOLDOVER.
The current list is defined according to the table 11-7 contained
in ITU-T G.8264/Y.1364 document. One may extend this list freely
by other ITU-T defined clock qualities, or different ones defined
by another standardization body (for those, please use
different prefix).
entries:
-
name: itu-opt1-prc
value: 1
-
name: itu-opt1-ssu-a
-
name: itu-opt1-ssu-b
-
name: itu-opt1-eec1
-
name: itu-opt1-prtc
-
name: itu-opt1-eprtc
-
name: itu-opt1-eeec
-
name: itu-opt1-eprc
render-max: true
-
type: const
name: temp-divider
value: 1000
doc: |
temperature divider allowing userspace to calculate the
temperature as float with three digit decimal precision.
Value of (DPLL_A_TEMP / DPLL_TEMP_DIVIDER) is integer part of
temperature value.
Value of (DPLL_A_TEMP % DPLL_TEMP_DIVIDER) is fractional part of
temperature value.
-
type: enum
name: type
doc: type of dpll, valid values for DPLL_A_TYPE attribute
entries:
-
name: pps
doc: dpll produces Pulse-Per-Second signal
value: 1
-
name: eec
doc: dpll drives the Ethernet Equipment Clock
render-max: true
-
type: enum
name: pin-type
doc: |
defines possible types of a pin, valid values for DPLL_A_PIN_TYPE
attribute
entries:
-
name: mux
doc: aggregates another layer of selectable pins
value: 1
-
name: ext
doc: external input
-
name: synce-eth-port
doc: ethernet port PHY's recovered clock
-
name: int-oscillator
doc: device internal oscillator
-
name: gnss
doc: GNSS recovered clock
render-max: true
-
type: enum
name: pin-direction
doc: |
defines possible direction of a pin, valid values for
DPLL_A_PIN_DIRECTION attribute
entries:
-
name: input
doc: pin used as a input of a signal
value: 1
-
name: output
doc: pin used to output the signal
render-max: true
-
type: const
name: pin-frequency-1-hz
value: 1
-
type: const
name: pin-frequency-10-khz
value: 10000
-
type: const
name: pin-frequency-77_5-khz
value: 77500
-
type: const
name: pin-frequency-10-mhz
value: 10000000
-
type: enum
name: pin-state
doc: |
defines possible states of a pin, valid values for
DPLL_A_PIN_STATE attribute
entries:
-
name: connected
doc: pin connected, active input of phase locked loop
value: 1
-
name: disconnected
doc: pin disconnected, not considered as a valid input
-
name: selectable
doc: pin enabled for automatic input selection
render-max: true
-
type: flags
name: pin-capabilities
doc: |
defines possible capabilities of a pin, valid flags on
DPLL_A_PIN_CAPABILITIES attribute
entries:
-
name: direction-can-change
doc: pin direction can be changed
-
name: priority-can-change
doc: pin priority can be changed
-
name: state-can-change
doc: pin state can be changed
-
type: const
name: phase-offset-divider
value: 1000
doc: |
phase offset divider allows userspace to calculate a value of
measured signal phase difference between a pin and dpll device
as a fractional value with three digit decimal precision.
Value of (DPLL_A_PHASE_OFFSET / DPLL_PHASE_OFFSET_DIVIDER) is an
integer part of a measured phase offset value.
Value of (DPLL_A_PHASE_OFFSET % DPLL_PHASE_OFFSET_DIVIDER) is a
fractional part of a measured phase offset value.
attribute-sets:
-
name: dpll
enum-name: dpll_a
attributes:
-
name: id
type: u32
-
name: module-name
type: string
-
name: pad
type: pad
-
name: clock-id
type: u64
-
name: mode
type: u32
enum: mode
-
name: mode-supported
type: u32
enum: mode
multi-attr: true
-
name: lock-status
type: u32
enum: lock-status
-
name: temp
type: s32
-
name: type
type: u32
enum: type
-
name: lock-status-error
type: u32
enum: lock-status-error
-
name: clock-quality-level
type: u32
enum: clock-quality-level
multi-attr: true
doc: |
Level of quality of a clock device. This mainly applies when
the dpll lock-status is DPLL_LOCK_STATUS_HOLDOVER. This could
be put to message multiple times to indicate possible parallel
quality levels (e.g. one specified by ITU option 1 and another
one specified by option 2).
-
name: pin
enum-name: dpll_a_pin
attributes:
-
name: id
type: u32
-
name: parent-id
type: u32
-
name: module-name
type: string
-
name: pad
type: pad
-
name: clock-id
type: u64
-
name: board-label
type: string
-
name: panel-label
type: string
-
name: package-label
type: string
-
name: type
type: u32
enum: pin-type
-
name: direction
type: u32
enum: pin-direction
-
name: frequency
type: u64
-
name: frequency-supported
type: nest
multi-attr: true
nested-attributes: frequency-range
-
name: frequency-min
type: u64
-
name: frequency-max
type: u64
-
name: prio
type: u32
-
name: state
type: u32
enum: pin-state
-
name: capabilities
type: u32
enum: pin-capabilities
-
name: parent-device
type: nest
multi-attr: true
nested-attributes: pin-parent-device
-
name: parent-pin
type: nest
multi-attr: true
nested-attributes: pin-parent-pin
-
name: phase-adjust-min
type: s32
-
name: phase-adjust-max
type: s32
-
name: phase-adjust
type: s32
-
name: phase-offset
type: s64
-
name: fractional-frequency-offset
type: sint
doc: |
The FFO (Fractional Frequency Offset) between the RX and TX
symbol rate on the media associated with the pin:
(rx_frequency-tx_frequency)/rx_frequency
Value is in PPM (parts per million).
This may be implemented for example for pin of type
PIN_TYPE_SYNCE_ETH_PORT.
-
name: esync-frequency
type: u64
doc: |
Frequency of Embedded SYNC signal. If provided, the pin is configured
with a SYNC signal embedded into its base clock frequency.
-
name: esync-frequency-supported
type: nest
multi-attr: true
nested-attributes: frequency-range
doc: |
If provided a pin is capable of embedding a SYNC signal (within given
range) into its base frequency signal.
-
name: esync-pulse
type: u32
doc: |
A ratio of high to low state of a SYNC signal pulse embedded
into base clock frequency. Value is in percents.
-
name: pin-parent-device
subset-of: pin
attributes:
-
name: parent-id
-
name: direction
-
name: prio
-
name: state
-
name: phase-offset
-
name: pin-parent-pin
subset-of: pin
attributes:
-
name: parent-id
-
name: state
-
name: frequency-range
subset-of: pin
attributes:
-
name: frequency-min
-
name: frequency-max
operations:
enum-name: dpll_cmd
list:
-
name: device-id-get
doc: |
Get id of dpll device that matches given attributes
attribute-set: dpll
flags: [ admin-perm ]
do:
pre: dpll-lock-doit
post: dpll-unlock-doit
request:
attributes:
- module-name
- clock-id
- type
reply:
attributes:
- id
-
name: device-get
doc: |
Get list of DPLL devices (dump) or attributes of a single dpll device
attribute-set: dpll
flags: [ admin-perm ]
do:
pre: dpll-pre-doit
post: dpll-post-doit
request:
attributes:
- id
reply: &dev-attrs
attributes:
- id
- module-name
- mode
- mode-supported
- lock-status
- lock-status-error
- temp
- clock-id
- type
dump:
reply: *dev-attrs
-
name: device-set
doc: Set attributes for a DPLL device
attribute-set: dpll
flags: [ admin-perm ]
do:
pre: dpll-pre-doit
post: dpll-post-doit
request:
attributes:
- id
-
name: device-create-ntf
doc: Notification about device appearing
notify: device-get
mcgrp: monitor
-
name: device-delete-ntf
doc: Notification about device disappearing
notify: device-get
mcgrp: monitor
-
name: device-change-ntf
doc: Notification about device configuration being changed
notify: device-get
mcgrp: monitor
-
name: pin-id-get
doc: |
Get id of a pin that matches given attributes
attribute-set: pin
flags: [ admin-perm ]
do:
pre: dpll-lock-doit
post: dpll-unlock-doit
request:
attributes:
- module-name
- clock-id
- board-label
- panel-label
- package-label
- type
reply:
attributes:
- id
-
name: pin-get
doc: |
Get list of pins and its attributes.
- dump request without any attributes given - list all the pins in the
system
- dump request with target dpll - list all the pins registered with
a given dpll device
- do request with target dpll and target pin - single pin attributes
attribute-set: pin
flags: [ admin-perm ]
do:
pre: dpll-pin-pre-doit
post: dpll-pin-post-doit
request:
attributes:
- id
reply: &pin-attrs
attributes:
- id
- board-label
- panel-label
- package-label
- type
- frequency
- frequency-supported
- capabilities
- parent-device
- parent-pin
- phase-adjust-min
- phase-adjust-max
- phase-adjust
- fractional-frequency-offset
- esync-frequency
- esync-frequency-supported
- esync-pulse
dump:
request:
attributes:
- id
reply: *pin-attrs
-
name: pin-set
doc: Set attributes of a target pin
attribute-set: pin
flags: [ admin-perm ]
do:
pre: dpll-pin-pre-doit
post: dpll-pin-post-doit
request:
attributes:
- id
- frequency
- direction
- prio
- state
- parent-device
- parent-pin
- phase-adjust
- esync-frequency
-
name: pin-create-ntf
doc: Notification about pin appearing
notify: pin-get
mcgrp: monitor
-
name: pin-delete-ntf
doc: Notification about pin disappearing
notify: pin-get
mcgrp: monitor
-
name: pin-change-ntf
doc: Notification about pin configuration being changed
notify: pin-get
mcgrp: monitor
mcast-groups:
list:
-
name: monitor

View File

@ -0,0 +1,7 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#

View File

@ -0,0 +1,451 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module tests the DpllDevice and DpllDevices classes"""
import json
import os
from unittest import TestCase
from ..dpll import DpllDevices
from ..dpll import DpllDevice
from ..dpll import DeviceFields
from ..dpll import DeviceType
from ..dpll import DeviceMode
from ..dpll import LockStatus
from ..dpll import LockStatusError
class DpllDeviceTestCase(TestCase):
def test_create_new_device_instance_using_all_fields(self):
dev_id = 1
dev_clock_id = 111111122222222
dev_clock_quality_level = 'itu-opt1-prc'
dev_mode = DeviceMode.AUTO
dev_mode_supported = [DeviceMode.AUTO]
dev_module_name = 'module'
dev_pad = 'pad'
dev_type = DeviceType.PPS
lock_status = LockStatus.UNLOCKED
lock_status_error = LockStatusError.FFO_TOO_HIGH
device = DpllDevice(
dev_id=dev_id,
dev_clock_id=dev_clock_id,
dev_clock_quality_level=dev_clock_quality_level,
dev_mode=dev_mode,
dev_mode_supported=dev_mode_supported,
dev_module_name=dev_module_name,
dev_pad=dev_pad,
dev_type=dev_type,
lock_status=lock_status,
lock_status_error=lock_status_error,
)
self.assertEqual(
device.dev_id,
dev_id,
f'Device id does not match: {device.dev_id} != {dev_id}')
self.assertEqual(
device.dev_clock_id,
dev_clock_id,
f'Device clock id does not match: {device.dev_clock_id} != {dev_clock_id}')
self.assertEqual(
device.dev_clock_quality_level,
dev_clock_quality_level,
f'Device clock quality level does not match: {device.dev_clock_quality_level} != {dev_clock_quality_level}')
self.assertEqual(
device.dev_mode,
dev_mode,
f'Device mode does not match: {device.dev_mode} != {dev_mode}')
self.assertListEqual(
device.dev_mode_supported,
dev_mode_supported,
f'Device mode supported does not match: {device.dev_mode_supported} != {dev_mode_supported}')
self.assertEqual(
device.dev_module_name,
dev_module_name,
f'Device module name does not match: {device.dev_module_name} != {dev_module_name}')
self.assertEqual(
device.dev_pad,
dev_pad,
f'Device pad does not match: {device.dev_pad} != {dev_pad}')
self.assertEqual(
device.dev_type,
dev_type,
f'Device type does not match: {device.dev_type} != {dev_type}')
self.assertEqual(
device.lock_status,
lock_status,
f'Device lock status does not match: {device.lock_status} != {lock_status}')
self.assertEqual(
device.lock_status_error,
lock_status_error,
f'Device lock status error does not match: {device.lock_status_error} != {lock_status_error}')
def test_create_new_device_instance_using_only_required_fields(self):
dev_id = 199
dev_clock_id = 9999999999999999999999999999
device = DpllDevice(dev_id=dev_id, dev_clock_id=dev_clock_id)
self.assertEqual(
device.dev_id,
dev_id,
f'Device id does not match: {device.dev_id} != {dev_id}')
self.assertEqual(
device.dev_clock_id,
dev_clock_id,
f'Device clock id does not match: {device.dev_clock_id} != {dev_clock_id}')
self.assertIsNone(
device.dev_clock_quality_level,
f'Device clock quality level is not none: {device.dev_clock_quality_level}')
self.assertEqual(
device.dev_mode,
DeviceMode.UNDEFINED,
f'Device mode is not {DeviceMode.UNDEFINED}: {device.dev_mode}')
self.assertListEqual(
device.dev_mode_supported,
list(),
f'Device mode supported is not empty: {device.dev_mode_supported}')
self.assertIsNone(
device.dev_module_name,
f'Device module name is not none: {device.dev_module_name}')
self.assertIsNone(
device.dev_pad,
f'Device pad is not none: {device.dev_pad}')
self.assertEqual(
device.dev_type,
DeviceType.UNDEFINED,
f'Device type is not {DeviceType.UNDEFINED}: {device.dev_pad}')
self.assertEqual(
device.lock_status,
LockStatus.UNDEFINED,
f'Device lock status is not {LockStatus.UNDEFINED}: {device.dev_pad}')
self.assertEqual(
device.lock_status_error,
LockStatusError.UNDEFINED,
f'Device lock status error is not {LockStatusError.UNDEFINED}: {device.lock_status_error}')
def test_load_device_raises_exception_when_device_is_none(self):
with self.assertRaises(ValueError) as ctx:
DpllDevice.loadDevice(None)
self.assertIsInstance(ctx.exception, ValueError)
self.assertEqual('Device parameter must be informed.', str(ctx.exception))
def test_load_device_from_dict_with_all_fields_filled(self):
device_dict = {
"id": 1,
"clock-id": 13007330308713495000,
"clock-quality-level": "itu-opt1-eec1",
"mode": "automatic",
"mode-supported": ["automatic", "manual"],
"module-name": "module1",
"pad": "pad-value",
"type": "eec",
"lock-status": "unlocked",
"lock-status-error": "none",
}
device = DpllDevice.loadDevice(device_dict)
self.assertEqual(
device.dev_id,
device_dict[DeviceFields.ID],
f'Device id does not match: {device.dev_id} != {device_dict[DeviceFields.ID]}')
self.assertEqual(
device.dev_clock_id,
device_dict[DeviceFields.CLOCK_ID],
f'Device clock id does not match: {device.dev_clock_id} != {device_dict[DeviceFields.CLOCK_ID]}')
self.assertEqual(
device.dev_clock_quality_level,
device_dict[DeviceFields.CLOCK_QUALITY],
f'Device clock quality level does not match: {device.dev_clock_quality_level} != \
{device_dict[DeviceFields.CLOCK_QUALITY]}')
self.assertEqual(
device.dev_mode,
DeviceMode(device_dict[DeviceFields.MODE]),
f'Device mode does not match: {device.dev_mode} != {device_dict[DeviceFields.MODE]}')
self.assertListEqual(
device.dev_mode_supported,
[DeviceMode(x) for x in device_dict[DeviceFields.MODE_SUPPORTED]],
f'Device mode supported does not match: {device.dev_mode_supported} != \
{device_dict[DeviceFields.MODE_SUPPORTED]}')
self.assertEqual(
device.dev_module_name,
device_dict[DeviceFields.MODULE_NAME],
f'Device module name does not match: {device.dev_module_name} != {device_dict[DeviceFields.MODULE_NAME]}')
self.assertEqual(
device.dev_pad,
device_dict[DeviceFields.PAD],
f'Device pad does not match: {device.dev_pad} != {device_dict[DeviceFields.PAD]}')
self.assertEqual(
device.dev_type,
DeviceType(device_dict[DeviceFields.TYPE]),
f'Device type does not match: {device.dev_type} != {device_dict[DeviceFields.TYPE]}')
self.assertEqual(
device.lock_status,
LockStatus(device_dict[DeviceFields.LOCK_STATUS]),
f'Device lock status does not match: {device.lock_status} != {device_dict[DeviceFields.LOCK_STATUS]}')
self.assertEqual(
device.lock_status_error,
LockStatusError(device_dict[DeviceFields.LOCK_STATUS_ERROR]),
f'Device lock status error does not match: {device.lock_status_error} != \
{device_dict[DeviceFields.LOCK_STATUS_ERROR]}')
def test_load_device_from_dict_with_only_required_fields(self):
device_dict = {
"id": 2,
"clock-id": 12345678901234567889,
}
device = DpllDevice.loadDevice(device_dict)
self.assertEqual(
device.dev_id,
device_dict[DeviceFields.ID],
f'Device id does not match: {device.dev_id} != {device_dict[DeviceFields.ID]}')
self.assertEqual(
device.dev_clock_id,
device_dict[DeviceFields.CLOCK_ID],
f'Device clock id does not match: {device.dev_clock_id} != {device_dict[DeviceFields.CLOCK_ID]}')
self.assertIsNone(
device.dev_clock_quality_level,
f'Device clock quality level is not none: {device.dev_clock_quality_level}')
self.assertEqual(
device.dev_mode,
DeviceMode.UNDEFINED,
f'Device mode is not {DeviceMode.UNDEFINED}: {device.dev_mode}')
self.assertListEqual(
device.dev_mode_supported,
list(),
f'Device mode supported is not empty: {device.dev_mode_supported}')
self.assertIsNone(
device.dev_module_name,
f'Device module name is not none: {device.dev_module_name}')
self.assertIsNone(
device.dev_pad,
f'Device pad is not none: {device.dev_pad}')
self.assertEqual(
device.dev_type,
DeviceType.UNDEFINED,
f'Device type is not {DeviceType.UNDEFINED}: {device.dev_pad}')
self.assertEqual(
device.lock_status,
LockStatus.UNDEFINED,
f'Device lock status is not {LockStatus.UNDEFINED}: {device.dev_pad}')
self.assertEqual(
device.lock_status_error,
LockStatusError.UNDEFINED,
f'Device lock status error is not {LockStatusError.UNDEFINED}: {device.lock_status_error}')
class DpllDevicesTestCase(TestCase):
@classmethod
def setUpClass(cls):
device_json = os.path.dirname(__file__) + os.sep + 'dump_get_device.json'
with open(device_json, 'r', encoding='utf-8') as f_device:
cls.dump_get_devices = json.load(f_device)
cls.devices = DpllDevices.loadDevices(cls.dump_get_devices)
def test_load_devices_from_list_of_dicts(self):
self.assertIsInstance(self.devices, DpllDevices)
self.assertEqual(4, len(self.devices),
f'Expected 4 devices. Got {len(self.devices)}')
# ID
def test_filter_device_by_existing_id(self):
dev_id = 1
device = self.devices.filter_by_device_id(dev_id)
self.assertEqual(dev_id, device.dev_id, f'Device should have identifier {dev_id}')
def test_filter_device_by_not_existing_id(self):
device = self.devices.filter_by_device_id(99999)
self.assertIsNone(device, 'Should be none. Got {device}')
# Clock id
def test_filter_device_by_existing_clock_id(self):
clock_id = 13007330308713495000
filtered_devices = self.devices.filter_by_device_clock_id(clock_id)
self.assertTrue(len(filtered_devices) == 2,
f'Expected 2 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertEqual(clock_id, device.dev_clock_id,
f'Should have clock identifier {clock_id}')
def test_filter_device_by_not_existing_clock_id(self):
filtered_devices = self.devices.filter_by_device_clock_id(0)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
def test_filter_device_by_existing_clock_ids(self):
clock_ids = [13007330308713495000, 5799633565437375000]
filtered_devices = self.devices.filter_by_device_clock_ids(clock_ids)
self.assertTrue(len(filtered_devices) == 4,
f'Expected 4 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertIn(device.dev_clock_id, clock_ids,
f'Contains invalid clock identifier {device.dev_clock_id}')
def test_filter_device_by_not_existing_clock_ids(self):
invalid_clock_ids = [0, 9999999999999999999999]
filtered_devices = self.devices.filter_by_device_clock_ids(invalid_clock_ids)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
# Type
def test_filter_device_by_existing_type(self):
dev_type = DeviceType.EEC
filtered_devices = self.devices.filter_by_device_type(dev_type)
self.assertTrue(len(filtered_devices) == 2,
f'Expected 2 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertEqual(dev_type, device.dev_type,
f'Should have lock status {type}')
def test_filter_device_by_not_existing_type(self):
filtered_devices = self.devices.filter_by_device_type(DeviceType.UNDEFINED)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
def test_filter_device_by_existing_types(self):
types = [DeviceType.EEC, DeviceType.PPS]
filtered_devices = self.devices.filter_by_device_types(types)
self.assertTrue(len(filtered_devices) == 4,
f'Expected 4 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertIn(device.dev_type, types,
f'Contains invalid lock status {device.dev_type}')
def test_filter_device_by_not_existing_types(self):
invalid_types = [DeviceType.UNDEFINED]
filtered_devices = self.devices.filter_by_device_types(invalid_types)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
# Mode
def test_filter_device_by_existing_mode(self):
mode = DeviceMode.AUTO
filtered_devices = self.devices.filter_by_device_mode(mode)
self.assertTrue(len(filtered_devices) == 4,
f'Expected 4 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertEqual(mode, device.dev_mode,
f'Should have mode {mode}')
def test_filter_device_by_not_existing_mode(self):
filtered_devices = self.devices.filter_by_device_mode(DeviceMode.MANUAL)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
# Supported Mode
def test_filter_device_by_existing_mode_supported(self):
mode = DeviceMode.MANUAL
filtered_devices = self.devices.filter_by_device_mode_supported(mode)
self.assertTrue(len(filtered_devices) == 2,
f'Expected 2 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertIn(mode, device.dev_mode_supported,
f'Should have mode {mode}')
def test_filter_device_by_not_existing_mode_supported(self):
filtered_devices = self.devices.filter_by_device_mode_supported(DeviceMode.UNDEFINED)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
# Lock status
def test_filter_device_by_existing_lock_status(self):
status = LockStatus.LOCKED_AND_HOLDOVER
filtered_devices = self.devices.filter_by_device_lock_status(status)
self.assertTrue(len(filtered_devices) == 2,
f'Expected 2 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertEqual(status, device.lock_status,
f'Should have lock status {status}')
def test_filter_device_by_not_existing_lock_status(self):
filtered_devices = self.devices.filter_by_device_lock_status(LockStatus.HOLDOVER)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
def test_filter_device_by_existing_lock_statuses(self):
statuses = [LockStatus.LOCKED_AND_HOLDOVER, LockStatus.UNLOCKED]
filtered_devices = self.devices.filter_by_device_lock_statuses(statuses)
self.assertTrue(len(filtered_devices) == 4,
f'Expected 4 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertIn(device.lock_status, statuses,
f'Contains invalid lock status {device.lock_status}')
def test_filter_device_by_not_existing_lock_statuses(self):
invalid_statuses = [LockStatus.HOLDOVER, LockStatus.LOCKED]
filtered_devices = self.devices.filter_by_device_lock_statuses(invalid_statuses)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
# Lock status error
def test_filter_device_by_existing_lock_status_error(self):
status = LockStatusError.NONE
filtered_devices = self.devices.filter_by_device_lock_status_error(status)
self.assertTrue(len(filtered_devices) == 2,
f'Expected 2 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertEqual(status, device.lock_status_error,
f'Should have lock status error {status}')
def test_filter_device_by_not_existing_lock_status_error(self):
status = LockStatusError.MEDIA_DOWN
filtered_devices = self.devices.filter_by_device_lock_status_error(status)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')
def test_filter_device_by_existing_lock_status_errors(self):
statuses = [LockStatusError.NONE, LockStatusError.UNDEFINED]
filtered_devices = self.devices.filter_by_device_lock_status_errors(statuses)
self.assertTrue(len(filtered_devices) == 4,
f'Expected 4 devices. Got {len(filtered_devices)}')
for device in filtered_devices:
self.assertIn(device.lock_status_error, statuses,
f'Contains invalid lock status error {device.lock_status_error}')
def test_filter_device_by_not_existing_lock_status_errors(self):
invalid_statuses = [LockStatusError.MEDIA_DOWN, LockStatusError.FFO_TOO_HIGH]
filtered_devices = self.devices.filter_by_device_lock_statuses(invalid_statuses)
self.assertTrue(len(filtered_devices) == 0,
f'Should be empty. Got {len(filtered_devices)}')

View File

@ -0,0 +1,901 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module tests the DpllPin and DpllPins classes"""
import json
import os
from unittest import TestCase
from ..dpll import DpllPins
from ..dpll import DpllPin
from ..dpll import PinFields
from ..dpll import PinParentFields
from ..dpll import PinDirection
from ..dpll import PinState
from ..dpll import PinType
from ..dpll import DpllDevice
from ..dpll import DeviceMode
from ..dpll import DeviceType
from ..dpll import LockStatus
from ..dpll import LockStatusError
class DpllPinTestCase(TestCase):
def assert_device_fields(self, pin: DpllPin, device: DpllDevice):
self.assertEqual(
pin.dev_id,
device.dev_id,
f'Device id does not match: {pin.dev_id} != {device.dev_id}')
self.assertEqual(
pin.dev_clock_id,
device.dev_clock_id,
f'Device clock id does not match: {pin.dev_clock_id} != {device.dev_clock_id}')
self.assertEqual(
pin.dev_mode,
device.dev_mode,
f'Device mode does not match: {pin.dev_mode} != {device.dev_mode}')
self.assertListEqual(
pin.dev_mode_supported,
device.dev_mode_supported,
f'Device mode supported does not match: {pin.dev_mode_supported} != {device.dev_mode_supported}')
self.assertEqual(
pin.dev_module_name,
device.dev_module_name,
f'Device module name does not match: {pin.dev_module_name} != {device.dev_module_name}')
self.assertEqual(
pin.dev_pad,
device.dev_pad,
f'Device pad does not match: {pin.dev_pad} != {device.dev_pad}')
self.assertEqual(
pin.dev_type,
device.dev_type,
f'Device type does not match: {pin.dev_type} != {device.dev_type}')
self.assertEqual(
pin.lock_status,
device.lock_status,
f'Device lock status does not match: {pin.lock_status} != {device.lock_status}')
self.assertEqual(
pin.lock_status_error,
device.lock_status_error,
f'Device lock status error does not match: {pin.lock_status_error} != {device.lock_status_error}')
def test_create_new_pin_instance_using_all_fields(self):
dev_id = 1
dev_clock_id = 'clock-id'
dev_clock_quality_level = 'itu-opt1-prc'
dev_mode = DeviceMode.AUTO
dev_mode_supported = [DeviceMode.AUTO]
dev_module_name = 'module'
dev_pad = 'pad'
dev_type = DeviceType.PPS
pin_id = 101
pin_board_label = 'board-label'
pin_panel_label = 'panel-label'
pin_package_label = 'package-label'
pin_type = PinType.EXT
pin_state = PinState.DISCONNECTED
pin_priority = 2
pin_phase_offset = 10101010
pin_direction = PinDirection.OUTPUT
lock_status = LockStatus.UNLOCKED
lock_status_error = LockStatusError.FFO_TOO_HIGH
pin = DpllPin(
dev_id=dev_id,
dev_clock_id=dev_clock_id,
dev_clock_quality_level=dev_clock_quality_level,
dev_mode=dev_mode,
dev_mode_supported=dev_mode_supported,
dev_module_name=dev_module_name,
dev_pad=dev_pad,
dev_type=dev_type,
pin_id=pin_id,
pin_board_label=pin_board_label,
pin_panel_label=pin_panel_label,
pin_package_label=pin_package_label,
pin_type=pin_type,
pin_state=pin_state,
pin_priority=pin_priority,
pin_phase_offset=pin_phase_offset,
pin_direction=pin_direction,
lock_status=lock_status,
lock_status_error=lock_status_error,
)
self.assertEqual(
pin.dev_id,
dev_id,
f'Device id does not match: {pin.dev_id} != {dev_id}')
self.assertEqual(
pin.dev_clock_id,
dev_clock_id,
f'Device clock id does not match: {pin.dev_clock_id} != {dev_clock_id}')
self.assertEqual(
pin.dev_clock_quality_level,
dev_clock_quality_level,
f'Device clock quality level does not match: {pin.dev_clock_quality_level} != {dev_clock_quality_level}')
self.assertEqual(
pin.dev_mode,
dev_mode,
f'Device mode does not match: {pin.dev_mode} != {dev_mode}')
self.assertListEqual(
pin.dev_mode_supported,
dev_mode_supported,
f'Device mode supported does not match: {pin.dev_mode_supported} != {dev_mode_supported}')
self.assertEqual(
pin.dev_module_name,
dev_module_name,
f'Device module name does not match: {pin.dev_module_name} != {dev_module_name}')
self.assertEqual(
pin.dev_pad,
dev_pad,
f'Device pad does not match: {pin.dev_pad} != {dev_pad}')
self.assertEqual(
pin.dev_type,
dev_type,
f'Device type does not match: {pin.dev_type} != {dev_type}')
self.assertEqual(
pin.pin_id,
pin_id,
f'Pin id does not match: {pin.pin_id} != {pin_id}')
self.assertEqual(
pin.pin_board_label,
pin_board_label,
f'Pin board label does not match: {pin.pin_board_label} != {pin_board_label}')
self.assertEqual(
pin.pin_panel_label,
pin_panel_label,
f'Pin panel label does not match: {pin.pin_panel_label} != {pin_panel_label}')
self.assertEqual(
pin.pin_package_label,
pin_package_label,
f'Pin package label does not match: {pin.pin_package_label} != {pin_package_label}')
self.assertEqual(
pin.pin_type,
pin_type,
f'Pin type does not match: {pin.pin_type} != {pin_type}')
self.assertEqual(
pin.pin_state,
pin_state,
f'Pin state does not match: {pin.pin_state} != {pin_state}')
self.assertEqual(
pin.pin_priority,
pin_priority,
f'Pin priority does not match: {pin.pin_priority} != {pin_priority}')
self.assertEqual(
pin.pin_phase_offset,
pin_phase_offset,
f'Pin phase offset does not match: {pin.pin_phase_offset} != {pin_phase_offset}')
self.assertEqual(
pin.pin_direction,
pin_direction,
f'Pin direction does not match: {pin.pin_direction} != {pin_direction}')
self.assertEqual(
pin.lock_status,
lock_status,
f'Device lock status does not match: {pin.lock_status} != {lock_status}')
self.assertEqual(
pin.lock_status_error,
lock_status_error,
f'Device lock status error does not match: {pin.lock_status_error} != {lock_status_error}')
def test_create_new_pin_instance_using_only_required_fields(self):
dev_id = 4
dev_clock_id = 'clock-id/4'
pin_id = 44
pin_type = PinType.OCXO
pin = DpllPin(
dev_id=dev_id,
dev_clock_id=dev_clock_id,
pin_id=pin_id,
pin_type=pin_type,
)
self.assertEqual(
pin.dev_id,
dev_id,
f'Device id does not match: {pin.dev_id} != {dev_id}')
self.assertEqual(
pin.dev_clock_id,
dev_clock_id,
f'Device clock id does not match: {pin.dev_clock_id} != {dev_clock_id}')
self.assertIsNone(
pin.dev_clock_quality_level,
f'Device clock quality level is not none: {pin.dev_clock_quality_level}')
self.assertEqual(
pin.dev_mode,
DeviceMode.UNDEFINED,
f'Device mode is not {DeviceMode.UNDEFINED}: {pin.dev_mode}')
self.assertListEqual(
pin.dev_mode_supported,
list(),
f'Device mode supported is not empty: {pin.dev_mode_supported}')
self.assertIsNone(
pin.dev_module_name,
f'Device module name is not none: {pin.dev_module_name}')
self.assertIsNone(
pin.dev_pad,
f'Device pad is not none: {pin.dev_pad}')
self.assertEqual(
pin.dev_type,
DeviceType.UNDEFINED,
f'Device type is not {DeviceType.UNDEFINED}: {pin.dev_pad}')
self.assertEqual(
pin.pin_id,
pin_id,
f'Pin id does not match: {pin.pin_id} != {pin_id}')
self.assertIsNone(
pin.pin_board_label,
f'DPin board label is not none: {pin.pin_board_label}')
self.assertIsNone(
pin.pin_panel_label,
f'Pin panel label is not none: {pin.pin_panel_label}')
self.assertIsNone(
pin.pin_package_label,
f'Pin package label is not none: {pin.pin_package_label}')
self.assertEqual(
pin.pin_type,
pin_type,
f'Pin type does not match: {pin.pin_type} != {pin_type}')
self.assertEqual(
pin.pin_state,
PinState.UNDEFINED,
f'Pin state is not {PinState.UNDEFINED}: {pin.pin_state}')
self.assertIsNone(
pin.pin_priority,
f'Pin priority is not none: {pin.pin_priority}')
self.assertIsNone(
pin.pin_phase_offset,
f'Pin phase offset is not none: {pin.pin_phase_offset}')
self.assertEqual(
pin.pin_direction,
PinDirection.UNDEFINED,
f'Pin direction is not {PinDirection.UNDEFINED}: {pin.pin_direction}')
self.assertEqual(
pin.lock_status,
LockStatus.UNDEFINED,
f'Device lock status is not {LockStatus.UNDEFINED}: {pin.dev_pad}')
self.assertEqual(
pin.lock_status_error,
LockStatusError.UNDEFINED,
f'Device lock status error is not {LockStatusError.UNDEFINED}: {pin.lock_status_error}')
def test_load_pin_raises_exception_when_device_is_none(self):
with self.assertRaises(ValueError) as ctx:
DpllPin.loadPin(None, {})
self.assertIsInstance(ctx.exception, ValueError)
self.assertEqual('Device parameter must be informed.', str(ctx.exception))
def test_load_pin_raises_exception_when_pin_is_none(self):
with self.assertRaises(ValueError) as ctx:
DpllPin.loadPin(DpllDevice(1, 'clock-id1'), None)
self.assertIsInstance(ctx.exception, ValueError)
self.assertEqual('Pin parameter must be informed.', str(ctx.exception))
def test_load_pin_from_dict_with_parent_device_and_all_fields_filled(self):
device = DpllDevice(
dev_id=1,
dev_clock_id='5799633565437375000',
dev_clock_quality_level='itu-opt1-eec1',
dev_mode=DeviceMode.AUTO,
dev_mode_supported=[DeviceMode.AUTO, DeviceMode.MANUAL],
dev_module_name='module1',
dev_pad='pad-value',
dev_type=DeviceType.EEC,
lock_status=LockStatus.LOCKED_AND_HOLDOVER,
lock_status_error=LockStatusError.NONE,
)
pin_dict = {
"id": 3,
"board-label": "board-label1",
"panel-label": "panel-label1",
"package-label": "package-label1",
"capabilities": ["state-can-change", "priority-can-change"],
"clock-id": "5799633565437375000",
"frequency": 1,
"frequency-supported": [
{
"frequency-max": 25000000,
"frequency-min": 1
}
],
"module-name": "module1",
"parent-device": [
{
"direction": "input",
"parent-id": 0,
"phase-offset": -346758571483350,
"prio": 3,
"state": "selectable"
},
{
"direction": "input",
"parent-id": 1,
"phase-offset": 349600,
"prio": 3,
"state": "connected"
}
],
"phase-adjust": 0,
"phase-adjust-max": 559030611,
"phase-adjust-min": -559030611,
"type": "ext",
}
pin = DpllPin.loadPin(device, pin_dict)
# Device
self.assert_device_fields(pin, device)
# Pin
self.assertEqual(
pin.pin_id,
pin_dict[PinFields.ID],
f'Pin id does not match: {pin.pin_id} != {pin_dict[PinFields.ID]}')
self.assertEqual(
pin.pin_board_label,
pin_dict[PinFields.BOARD_LABEL],
f'Pin board label does not match: {pin.pin_board_label} != {pin_dict[PinFields.BOARD_LABEL]}')
self.assertEqual(
pin.pin_panel_label,
pin_dict[PinFields.PANEL_LABEL],
f'Pin panel label does not match: {pin.pin_panel_label} != {pin_dict[PinFields.PANEL_LABEL]}')
self.assertEqual(
pin.pin_package_label,
pin_dict[PinFields.PACKAGE_LABEL],
f'Pin package label does not match: {pin.pin_package_label} != {pin_dict[PinFields.PACKAGE_LABEL]}')
self.assertEqual(
pin.pin_type,
PinType(pin_dict[PinFields.TYPE]),
f'Pin type does not match: {pin.pin_type} != {PinType(pin_dict[PinFields.TYPE])}')
# Pin Parent Device
pin_parent_dict = pin_dict[PinFields.PARENT_DEVICE][1]
self.assertEqual(
pin.pin_state,
PinState(pin_parent_dict[PinParentFields.STATE]),
f'Pin state does not match: {pin.pin_state} != {PinState(pin_parent_dict[PinParentFields.STATE])}')
self.assertEqual(
pin.pin_priority,
pin_parent_dict[PinParentFields.PRIORITY],
f'Pin priority does not match: {pin.pin_priority} != {pin_parent_dict[PinParentFields.PRIORITY]}')
self.assertEqual(
pin.pin_phase_offset,
pin_parent_dict[PinParentFields.PHASE_OFFSET],
f'Pin phase offset does not match: {pin.pin_phase_offset} != \
{pin_parent_dict[PinParentFields.PHASE_OFFSET]}')
self.assertEqual(
pin.pin_direction,
PinDirection(pin_parent_dict[PinParentFields.DIRECTION]),
f'Pin direction does not match: {pin.pin_direction} != \
{PinDirection(pin_parent_dict[PinParentFields.DIRECTION])}')
def test_load_pin_from_dict_with_parent_device_and_only_required_fields(self):
device = DpllDevice(
dev_id=2,
dev_clock_id='5799633565437375000',
)
pin_dict = {
"id": 11,
"type": "int-oscillator",
"parent-device": [{"parent-id": 2}],
}
pin = DpllPin.loadPin(device, pin_dict)
# Device
self.assert_device_fields(pin, device)
# Pin
self.assertEqual(
pin.pin_id,
pin_dict[PinFields.ID],
f'Pin id does not match: {pin.pin_id} != {pin_dict[PinFields.ID]}')
self.assertIsNone(
pin.pin_board_label,
f'Pin board label is not none: {pin.pin_board_label}')
self.assertIsNone(
pin.pin_panel_label,
f'Pin panel label is not none: {pin.pin_panel_label}')
self.assertIsNone(
pin.pin_package_label,
f'Pin package label is not none: {pin.pin_package_label}')
self.assertEqual(
pin.pin_type,
PinType(pin_dict[PinFields.TYPE]),
f'Pin type does not match: {pin.pin_type} != {PinType(pin_dict[PinFields.TYPE])}')
self.assertEqual(
pin.pin_state,
PinState.UNDEFINED,
f'Pin state is not {PinState.UNDEFINED}: {pin.pin_state}')
self.assertIsNone(
pin.pin_priority,
f'Pin priority is not none: {pin.pin_priority}')
self.assertIsNone(
pin.pin_phase_offset,
f'Pin phase offset is not none: {pin.pin_phase_offset}')
self.assertEqual(
pin.pin_direction,
PinDirection.UNDEFINED,
f'Pin direction is not {PinDirection.UNDEFINED}: {pin.pin_direction}')
def test_load_pin_from_dict_without_parent_device_and_all_fields_filled(self):
device = DpllDevice(
dev_id=0,
dev_clock_id='13007330308713495000',
dev_clock_quality_level='itu-opt1-eec1',
dev_mode=DeviceMode.MANUAL,
dev_mode_supported=[DeviceMode.AUTO, DeviceMode.MANUAL],
dev_module_name='module1',
dev_pad='pad-value',
dev_type=DeviceType.EEC,
lock_status=LockStatus.LOCKED,
lock_status_error=LockStatusError.NONE,
)
pin_dict = {
"id": 1,
"board-label": "board-label1",
"panel-label": "panel-label1",
"package-label": "package-label1",
"capabilities": ["state-can-change", "priority-can-change"],
"clock-id": "13007330308713495000",
"frequency": 156250000,
"frequency-supported": [
{
"frequency-max": 25000000,
"frequency-min": 1
}
],
"module-name": "module1",
"parent-id": 0,
"direction": "input",
"phase-offset": -191040,
"prio": 1,
"state": "connected",
"phase-adjust": 0,
"phase-adjust-max": 480307,
"phase-adjust-min": -480307,
"type": "gnss",
}
pin = DpllPin.loadPin(device, pin_dict)
# Device
self.assert_device_fields(pin, device)
# Pin
self.assertEqual(
pin.pin_id,
pin_dict[PinFields.ID],
f'Pin id does not match: {pin.pin_id} != {pin_dict[PinFields.ID]}')
self.assertEqual(
pin.pin_board_label,
pin_dict[PinFields.BOARD_LABEL],
f'Pin board label does not match: {pin.pin_board_label} != {pin_dict[PinFields.BOARD_LABEL]}')
self.assertEqual(
pin.pin_panel_label,
pin_dict[PinFields.PANEL_LABEL],
f'Pin panel label does not match: {pin.pin_panel_label} != {pin_dict[PinFields.PANEL_LABEL]}')
self.assertEqual(
pin.pin_package_label,
pin_dict[PinFields.PACKAGE_LABEL],
f'Pin package label does not match: {pin.pin_package_label} != {pin_dict[PinFields.PACKAGE_LABEL]}')
self.assertEqual(
pin.pin_type,
PinType(pin_dict[PinFields.TYPE]),
f'Pin type does not match: {pin.pin_type} != {PinType(pin_dict[PinFields.TYPE])}')
self.assertEqual(
pin.pin_state,
PinState(pin_dict[PinFields.STATE]),
f'Pin state does not match: {pin.pin_state} != {PinState(pin_dict[PinFields.STATE])}')
self.assertEqual(
pin.pin_priority,
pin_dict[PinFields.PRIORITY],
f'Pin priority does not match: {pin.pin_priority} != {pin_dict[PinFields.PRIORITY]}')
self.assertEqual(
pin.pin_phase_offset,
pin_dict[PinFields.PHASE_OFFSET],
f'Pin phase offset does not match: {pin.pin_phase_offset} != {pin_dict[PinFields.PHASE_OFFSET]}')
self.assertEqual(
pin.pin_direction,
PinDirection(pin_dict[PinFields.DIRECTION]),
f'Pin direction does not match: {pin.pin_direction} != {PinDirection(pin_dict[PinFields.DIRECTION])}')
def test_load_pin_from_dict_using_only_required_fields(self):
device = DpllDevice(
dev_id=5,
dev_clock_id='13007330308713495000',
)
pin_dict = {
"id": 23,
"type": "synce-eth-port",
"parent-id": 5,
}
pin = DpllPin.loadPin(device, pin_dict)
# Device
self.assert_device_fields(pin, device)
# Pin
self.assertEqual(
pin.pin_id,
pin_dict[PinFields.ID],
f'Pin id does not match: {pin.pin_id} != {pin_dict[PinFields.ID]}')
self.assertIsNone(
pin.pin_board_label,
f'Pin board label is not none: {pin.pin_board_label}')
self.assertIsNone(
pin.pin_panel_label,
f'Pin panel label is not none: {pin.pin_panel_label}')
self.assertIsNone(
pin.pin_package_label,
f'Pin package label is not none: {pin.pin_package_label}')
self.assertEqual(
pin.pin_type,
PinType(pin_dict[PinFields.TYPE]),
f'Pin type does not match: {pin.pin_type} != {PinType(pin_dict[PinFields.TYPE])}')
self.assertEqual(
pin.pin_state, PinState.UNDEFINED,
f'Pin state is not {PinState.UNDEFINED}: {pin.pin_state}')
self.assertIsNone(
pin.pin_priority,
f'Pin priority is not none: {pin.pin_priority}')
self.assertIsNone(
pin.pin_phase_offset,
f'Pin phase offset is not none: {pin.pin_phase_offset}')
self.assertEqual(
pin.pin_direction, PinDirection.UNDEFINED,
f'Pin direction is not {PinDirection.UNDEFINED}: {pin.pin_direction}')
def test_raises_exception_on_load_pin_from_dict_without_parent_device_and_parent_id(self):
device = DpllDevice(
dev_id=3,
dev_clock_id='clock-id1',
dev_clock_quality_level='itu-opt1-eec1',
dev_mode=DeviceMode.AUTO,
dev_mode_supported=[DeviceMode.AUTO, DeviceMode.MANUAL],
dev_module_name='module1',
dev_pad='pad-value',
dev_type=DeviceType.EEC,
lock_status=LockStatus.UNLOCKED,
lock_status_error=LockStatusError.NONE,
)
pin_dict = {
"id": 1,
"board-label": "board-label1",
"panel-label": "panel-label1",
"package-label": "package-label1",
"capabilities": ["state-can-change", "priority-can-change"],
"clock-id": "clock-id1",
"frequency": 0,
"frequency-supported": [
{
"frequency-max": 25000000,
"frequency-min": 1
}
],
"module-name": "module1",
"phase-adjust": 0,
"phase-adjust-max": 0,
"phase-adjust-min": 0,
"type": "mux",
}
with self.assertRaises(ValueError) as ctx:
DpllPin.loadPin(device, pin_dict)
self.assertIsInstance(ctx.exception, ValueError)
self.assertEqual(f'No pin reference found for device id {device.dev_id}', str(ctx.exception))
class DpllPinsTestCase(TestCase):
@classmethod
def setUpClass(cls):
cur_path = os.path.dirname(__file__) + os.sep
with open(cur_path + 'dump_get_pin.json', 'r', encoding='utf-8') as f_pin, \
open(cur_path + 'dump_get_device.json', 'r', encoding='utf-8') as f_device:
cls.dump_get_devices = json.load(f_device)
cls.dump_get_pins = json.load(f_pin)
cls.pins = DpllPins.loadPins(cls.dump_get_devices, cls.dump_get_pins)
def test_load_pins_from_list_of_dicts(self):
self.assertIsInstance(self.pins, DpllPins)
self.assertTrue(len(self.pins) == 58,
f'Expected 58 devices. Got {len(self.pins)}')
# ID
def test_filter_pin_by_existing_id(self):
pin_id = 27
pin = self.pins.filter_by_pin_id(pin_id)
self.assertIsNotNone(pin, 'Should not be none. Got {pin}')
self.assertEqual(pin_id, pin.pin_id, f'Pin should have identifier {pin_id}')
def test_filter_pin_by_not_existing_id(self):
pin = self.pins.filter_by_device_id(99999)
self.assertIsNone(pin, 'Should be none. Got {pin}')
# Direction
def test_filter_pins_by_existing_direction(self):
direction = PinDirection.OUTPUT
filtered_pins = self.pins.filter_by_pin_direction(direction)
self.assertTrue(len(filtered_pins) == 26,
f'Expected 26 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertEqual(direction,
pin.pin_direction,
f'Contains invalid pin direction {pin.pin_direction}')
def test_filter_pins_by_not_existing_direction(self):
filtered_pins = self.pins.filter_by_pin_direction(PinDirection.UNDEFINED)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
def test_filter_pins_by_existing_directions(self):
directions = [PinDirection.OUTPUT, PinDirection.INPUT]
filtered_pins = self.pins.filter_by_pin_directions(directions)
self.assertTrue(len(filtered_pins) == 58,
f'Expected 58 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertIn(pin.pin_direction,
directions,
f'Contains invalid pin direction {pin.pin_direction}')
def test_filter_pins_by_not_existing_directions(self):
invalid_directions = [PinDirection.UNDEFINED]
filtered_pins = self.pins.filter_by_pin_direction(invalid_directions)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
# Board label
def test_filter_pins_by_existing_board_label(self):
board_label = 'SMA1'
filtered_pins = self.pins.filter_by_pin_board_label(board_label)
self.assertTrue(len(filtered_pins) == 4,
f'Expected 4 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertEqual(board_label,
pin.pin_board_label,
f'Contains invalid board label {pin.pin_board_label}')
def test_filter_pins_by_not_existing_board_label(self):
filtered_pins = self.pins.filter_by_pin_board_label('invalid-board-label')
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
def test_filter_pins_by_existing_board_labels(self):
board_labels = ['SMA1', 'MAC-CLK']
filtered_pins = self.pins.filter_by_pin_board_labels(board_labels)
self.assertTrue(len(filtered_pins) == 8,
f'Expected 8 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertIn(pin.pin_board_label,
board_labels,
f'Contains invalid board label {pin.pin_board_label}')
def test_filter_pins_by_not_existing_board_labels(self):
invalid_board_labels = ['invalid-board-label1', 'invalid-board-label2']
filtered_pins = self.pins.filter_by_pin_board_labels(invalid_board_labels)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
# Panel label
def test_filter_pins_by_existing_panel_label(self):
panel_label = 'PNL-SMA1'
filtered_pins = self.pins.filter_by_pin_panel_label(panel_label)
self.assertTrue(len(filtered_pins) == 4,
f'Expected 4 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertEqual(panel_label,
pin.pin_panel_label,
f'Contains invalid panel label {pin.pin_panel_label}')
def test_filter_pins_by_not_existing_panel_label(self):
filtered_pins = self.pins.filter_by_pin_panel_label('invalid-panel-label')
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
def test_filter_pins_by_existing_panel_labels(self):
panel_labels = ['PNL-SMA1', 'PNL-MAC-CLK']
filtered_pins = self.pins.filter_by_pin_panel_labels(panel_labels)
self.assertTrue(len(filtered_pins) == 8,
f'Expected 8 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertIn(pin.pin_panel_label,
panel_labels,
f'Contains invalid panel label {pin.pin_panel_label}')
def test_filter_pins_by_not_existing_panel_labels(self):
invalid_panel_labels = ['invalid-panel-label1', 'invalid-panel-label2']
filtered_pins = self.pins.filter_by_pin_panel_labels(invalid_panel_labels)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
# Package label
def test_filter_pins_by_existing_package_label(self):
package_label = 'PKG-SMA1'
filtered_pins = self.pins.filter_by_pin_package_label(package_label)
self.assertTrue(len(filtered_pins) == 4,
f'Expected 4 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertEqual(package_label,
pin.pin_package_label,
f'Contains invalid package label {pin.pin_package_label}')
def test_filter_pins_by_not_existing_package_label(self):
filtered_pins = self.pins.filter_by_pin_package_label('invalid-package-label')
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
def test_filter_pins_by_existing_package_labels(self):
package_labels = ['PKG-SMA1', 'PKG-MAC-CLK']
filtered_pins = self.pins.filter_by_pin_package_labels(package_labels)
self.assertTrue(len(filtered_pins) == 8,
f'Expected 8 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertIn(pin.pin_package_label,
package_labels,
f'Contains invalid package label {pin.pin_package_label}')
def test_filter_pins_by_not_existing_package_labels(self):
invalid_package_labels = ['invalid-package-label1', 'invalid-package-label2']
filtered_pins = self.pins.filter_by_pin_package_labels(invalid_package_labels)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
# Type
def test_filter_pins_by_existing_type(self):
pin_type = PinType.GNSS
filtered_pins = self.pins.filter_by_pin_type(pin_type)
self.assertTrue(len(filtered_pins) == 4,
f'Expected 4 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertEqual(pin_type,
pin.pin_type,
f'Contains invalid pin type {pin.pin_type}')
def test_filter_pins_by_not_existing_type(self):
filtered_pins = self.pins.filter_by_pin_type(PinType.UNDEFINED)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
def test_filter_pins_by_existing_types(self):
types = [PinType.EXT, PinType.GNSS]
filtered_pins = self.pins.filter_by_pin_types(types)
self.assertTrue(len(filtered_pins) == 28,
f'Expected 28 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertIn(pin.pin_type,
types,
f'Contains invalid pin type {pin.pin_type}')
def test_filter_pins_by_not_existing_types(self):
invalid_types = [PinType.UNDEFINED]
filtered_pins = self.pins.filter_by_pin_types(invalid_types)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
# State
def test_filter_pins_by_existing_state(self):
state = PinState.CONNECTED
filtered_pins = self.pins.filter_by_pin_state(state)
self.assertTrue(len(filtered_pins) == 17,
f'Expected 17 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertEqual(state,
pin.pin_state,
f'Contains invalid pin state {pin.pin_state}')
def test_filter_pins_by_not_existing_state(self):
filtered_pins = self.pins.filter_by_pin_state(PinState.UNDEFINED)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
def test_filter_pins_by_existing_states(self):
states = [PinState.CONNECTED, PinState.SELECTABLE]
filtered_pins = self.pins.filter_by_pin_states(states)
self.assertTrue(len(filtered_pins) == 45,
f'Expected 45 pins. Got {len(filtered_pins)}')
for pin in filtered_pins:
self.assertIn(pin.pin_state,
states,
f'Contains invalid pin state {pin.pin_state}')
def test_filter_pins_by_not_existing_states(self):
invalid_states = [PinState.UNDEFINED]
filtered_pins = self.pins.filter_by_pin_states(invalid_states)
self.assertTrue(len(filtered_pins) == 0,
f'Should be empty. Got {len(filtered_pins)}')
# Priority
def test_order_pins_by_priority(self):
ordered_pins = self.pins.order_by_pin_priority()
self.assertEqual(
len(ordered_pins),
len(self.pins),
f'Different sizes - Should be {len(self.pins)}. Got {len(ordered_pins)}')
priority_ref: int = 0
for pin in ordered_pins:
priority = 999 if pin.pin_priority is None else pin.pin_priority
self.assertGreaterEqual(
priority,
priority_ref,
f'Out ot order: {pin.pin_priority} lower than {priority_ref}')
priority_ref = priority
def test_order_pins_by_reverse_priority(self):
ordered_pins = self.pins.order_by_pin_priority(reverse=True)
self.assertEqual(
len(ordered_pins),
len(self.pins),
f'Different sizes - Should be {len(self.pins)}. Got {len(ordered_pins)}')
priority_ref: int = 999
for pin in ordered_pins:
priority = 999 if pin.pin_priority is None else pin.pin_priority
self.assertLessEqual(
priority,
priority_ref,
f'Out ot order: {pin.pin_priority} greater than {priority_ref}')
priority_ref = priority

View File

@ -0,0 +1,313 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module tests the NetlinkDPLL class"""
import json
import os
import random
from unittest import TestCase
from unittest.mock import Mock
from unittest.mock import call
from unittest.mock import patch
from ..base import YnlFamily
from ..dpll import NetlinkDPLL
from ..dpll import DPLLCommands
from ..dpll import DpllDevices
from ..dpll import DpllDevice
from ..dpll import DeviceFields
from ..dpll import DpllPins
from ..dpll import PinFields
class NetlinkDPLLTestCase(TestCase):
@classmethod
def setUpClass(cls):
cur_path = os.path.dirname(__file__) + os.sep
with open(cur_path + 'dump_get_pin.json', 'r', encoding='utf-8') as f_pin,\
open(cur_path + 'dump_get_device.json', 'r', encoding='utf-8') as f_device:
cls.dump_get_pins = json.load(f_pin)
cls.dump_get_devices = json.load(f_device)
def test_get_shared_dpll_instance(self):
dpll_shared1 = NetlinkDPLL()
dpll_shared2 = NetlinkDPLL()
self.assertIs(dpll_shared1._ynl, # pylint: disable=W0212
dpll_shared2._ynl, # pylint: disable=W0212
'DPLL instances share the same YNL instance')
def test_get_dedicated_dpll_instance(self):
dpll_dedicated1 = NetlinkDPLL(True)
dpll_dedicated2 = NetlinkDPLL(True)
self.assertIsNot(dpll_dedicated1._ynl, # pylint: disable=W0212
dpll_dedicated2._ynl, # pylint: disable=W0212
'DPLL instances use dedicated YNL instances')
self.assertIsNot(NetlinkDPLL._ynl, # pylint: disable=W0212
dpll_dedicated1._ynl, # pylint: disable=W0212
'DPLL shared and dedicated instances use different YNL instances')
@patch.object(YnlFamily, 'dump')
def test_list_all_clock_devices_available_in_raw_format(self, mock_ynl_dump: Mock):
mock_ynl_dump.return_value = self.dump_get_devices
dpll = NetlinkDPLL()
devices_dict = dpll._get_all_devices() # pylint: disable=W0212
self.assertEqual(devices_dict,
self.dump_get_devices,
'List of devices should be identical')
mock_ynl_dump.assert_called_once_with(DPLLCommands.DEVICE_GET, {})
@patch.object(YnlFamily, 'dump')
def test_list_all_devices_available(self, mock_ynl_dump: Mock):
expected_device_ids = list(map(lambda dev: dev[DeviceFields.ID], self.dump_get_devices))
mock_ynl_dump.return_value = self.dump_get_devices
dpll = NetlinkDPLL()
devices = dpll.get_all_devices()
self.assertIsInstance(devices,
DpllDevices,
f'Should return DpllDevices instance - Got {type(devices)}')
self.assertEqual(len(devices),
len(self.dump_get_devices),
f'#items: Should be {len(self.dump_get_devices)} Got {len(devices)}')
for device in devices:
self.assertIn(device.dev_id,
expected_device_ids,
f'Contains wrong device: {device.dev_id}')
mock_ynl_dump.assert_called_once_with(DPLLCommands.DEVICE_GET, {})
@patch.object(YnlFamily, 'do')
def test_get_device_with_specific_id_in_raw_format(self, mock_ynl_do: Mock):
dev_id = random.randint(0, len(self.dump_get_devices) - 1)
expected_result = self.dump_get_devices[dev_id]
mock_ynl_do.return_value = expected_result
dpll = NetlinkDPLL()
device_dict = dpll._get_device_by_id(dev_id) # pylint: disable=W0212
self.assertEqual(device_dict,
expected_result,
f'Wrong device ID: Expected {dev_id}. Got {device_dict[DeviceFields.ID]}')
mock_ynl_do.assert_called_once_with(DPLLCommands.DEVICE_GET, {DeviceFields.ID: dev_id})
@patch.object(YnlFamily, 'do')
def test_get_device_with_specific_id(self, mock_ynl_do: Mock):
dev_id = random.randint(0, len(self.dump_get_devices) - 1)
expected_result = self.dump_get_devices[dev_id]
mock_ynl_do.return_value = expected_result
dpll = NetlinkDPLL()
device = dpll.get_device_by_id(dev_id)
self.assertIsInstance(device,
DpllDevice,
f'Should return DpllDevices instance - Got {type(device)}')
self.assertEqual(device.dev_id,
dev_id,
f'Wrong device ID: Expected {dev_id}. Got {device.dev_id}')
mock_ynl_do.assert_called_once_with(DPLLCommands.DEVICE_GET, {DeviceFields.ID: dev_id})
@patch.object(YnlFamily, 'dump')
def test_get_clock_device_using_clock_id(self, mock_ynl_dump: Mock):
clock_id = 5799633565437375000
expected_device_ids = list(
map(lambda dev: dev[DeviceFields.ID],
filter(lambda item: item[DeviceFields.CLOCK_ID] == clock_id,
self.dump_get_devices)))
mock_ynl_dump.return_value = self.dump_get_devices
dpll = NetlinkDPLL()
devices = dpll.get_devices_by_clock_id(clock_id)
self.assertIsInstance(devices,
DpllDevices,
f'Should return DpllDevices instance - Got {type(devices)}')
self.assertEqual(len(devices),
len(expected_device_ids),
f'#items: Should be {len(expected_device_ids)} Got {len(devices)}')
for device in devices:
self.assertIn(device.dev_id,
expected_device_ids,
f'Contains wrong device: {device.dev_id}')
mock_ynl_dump.assert_called_once_with(DPLLCommands.DEVICE_GET, {})
@patch.object(YnlFamily, 'dump')
def test_returns_empty_DpllDevices_when_device_clock_id_is_not_found(self, mock_ynl_dump: Mock):
clock_id = 0
mock_ynl_dump.return_value = self.dump_get_devices
dpll = NetlinkDPLL()
devices = dpll.get_devices_by_clock_id(clock_id)
self.assertIsInstance(devices,
DpllDevices,
f'Should return DpllDevices instance. Got {type(devices)}')
self.assertTrue(len(devices) == 0, f'Should have no device. Got {len(devices)}')
mock_ynl_dump.assert_called_once_with(DPLLCommands.DEVICE_GET, {})
@patch.object(YnlFamily, 'dump')
def test_list_all_pins_available_in_raw_format(self, mock_ynl_dump: Mock):
mock_ynl_dump.return_value = self.dump_get_pins
dpll = NetlinkDPLL()
pins_dict = dpll._get_all_pins() # pylint: disable=W0212
self.assertListEqual(pins_dict,
self.dump_get_pins,
'List of pins should be identical')
mock_ynl_dump.assert_called_once_with(DPLLCommands.PIN_GET, {})
@patch.object(YnlFamily, 'dump')
def test_list_all_pins_available(self, mock_ynl_dump: Mock):
expected_pin_ids = list(
map(lambda pin: pin[PinFields.ID],
filter(lambda item: PinFields.PARENT_PIN not in item,
self.dump_get_pins)))
mock_ynl_dump.side_effect = [self.dump_get_devices, self.dump_get_pins]
dpll = NetlinkDPLL()
pins = dpll.get_all_pins()
self.assertIsInstance(pins,
DpllPins,
f'Should return DpllPins instance. Got {type(pins)}')
for pin in pins:
self.assertIn(pin.pin_id,
expected_pin_ids,
f'Contains wrong pin: {pin.pin_id}')
mock_ynl_dump.assert_has_calls([
call(DPLLCommands.DEVICE_GET, {}),
call(DPLLCommands.PIN_GET, {})
])
@patch.object(YnlFamily, 'do')
def test_get_pin_with_specific_id_in_raw_format(self, mock_ynl_do: Mock):
pin_id = random.randint(0, len(self.dump_get_pins) - 1)
expected_pin = self.dump_get_pins[pin_id]
mock_ynl_do.return_value = expected_pin
dpll = NetlinkDPLL()
pin_dict = dpll._get_pin_by_id(pin_id) # pylint: disable=W0212
self.assertEqual(pin_dict,
expected_pin,
f'Wrong pin ID: Expected {pin_id}. Got {pin_dict[PinFields.ID]}')
mock_ynl_do.assert_called_once_with(DPLLCommands.PIN_GET, {PinFields.ID: pin_id})
@patch.object(YnlFamily, 'do')
@patch.object(YnlFamily, 'dump')
def test_get_pins_with_specific_id(self, mock_ynl_dump: Mock, mock_ynl_do: Mock):
pin_pict_idx = random.randint(0, 8)
pin_dict = self.dump_get_pins[pin_pict_idx]
pin_id = pin_dict[PinFields.ID]
mock_ynl_dump.return_value = self.dump_get_devices
mock_ynl_do.return_value = pin_dict
dpll = NetlinkDPLL()
pins = dpll.get_pins_by_id(pin_id)
self.assertIsInstance(pins,
DpllPins,
f'Should return DpllPins instance. Got {type(pins)}')
for pin in pins:
self.assertEqual(pin.pin_id,
pin_id,
f'Wrong pin ID: Expected {pin_id}. Got {pin.pin_id}')
mock_ynl_dump.assert_called_once_with(DPLLCommands.DEVICE_GET, {})
mock_ynl_do.assert_called_once_with(DPLLCommands.PIN_GET, {PinFields.ID: pin_id})
@patch.object(YnlFamily, 'dump')
def test_get_pins_from_clock_id(self, mock_ynl_dump: Mock):
clock_id: int = 5799633565437375000
expected_pin_ids = list(
map(lambda pin: pin[PinFields.ID],
filter(lambda item: item[PinFields.CLOCK_ID] == clock_id,
self.dump_get_pins)))
mock_ynl_dump.side_effect = [self.dump_get_devices, self.dump_get_pins]
dpll = NetlinkDPLL()
pins = dpll.get_pins_by_clock_id(clock_id)
self.assertIsInstance(pins,
DpllPins,
f'Should return DpllPins instance. Got {type(pins)}')
for pin in pins:
self.assertIn(pin.pin_id,
expected_pin_ids,
f'Contains wrong pin: {pin.pin_id}')
mock_ynl_dump.assert_has_calls([
call(DPLLCommands.DEVICE_GET, {}),
call(DPLLCommands.PIN_GET, {})
])
@patch.object(YnlFamily, 'dump')
def test_returns_empty_DpllPins_when_device_clock_id_is_not_found(self, mock_ynl_dump: Mock):
clock_id: int = 0
mock_ynl_dump.side_effect = [self.dump_get_devices, self.dump_get_pins]
dpll = NetlinkDPLL()
pins = dpll.get_pins_by_clock_id(clock_id)
self.assertIsInstance(pins,
DpllPins,
f'Should return DpllPins instance. Got {type(pins)}')
self.assertTrue(len(pins) == 0, f'Should have no pin. Got {len(pins)}')
mock_ynl_dump.assert_has_calls([
call(DPLLCommands.DEVICE_GET, {}),
call(DPLLCommands.PIN_GET, {})
])

View File

@ -0,0 +1,50 @@
[
{
"clock-id": 13007330308713495000,
"id": 0,
"lock-status": "unlocked",
"mode": "automatic",
"mode-supported": [
"automatic",
"manual"
],
"module-name": "ice",
"type": "eec"
},
{
"clock-id": 13007330308713495000,
"id": 1,
"lock-status": "unlocked",
"mode": "automatic",
"mode-supported": [
"automatic",
"manual"
],
"module-name": "ice",
"type": "pps"
},
{
"clock-id": 5799633565437375000,
"id": 2,
"lock-status": "locked-ho-acq",
"lock-status-error": "none",
"mode": "automatic",
"mode-supported": [
"automatic"
],
"module-name": "ice",
"type": "eec"
},
{
"clock-id": 5799633565437375000,
"id": 3,
"lock-status": "locked-ho-acq",
"lock-status-error": "none",
"mode": "automatic",
"mode-supported": [
"automatic"
],
"module-name": "ice",
"type": "pps"
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module tests common Netlink class"""
from unittest import TestCase
from ..base import YnlFamily
from ..common import NetlinkFactory
class NetlinkUtilsTestCase(TestCase):
def test_get_instance_with_default_params(self):
new_instance = NetlinkFactory.get_instance('dpll.yaml')
self.assertIsInstance(new_instance, YnlFamily)
def test_get_instance_using_specific_params(self):
new_instance = NetlinkFactory.get_instance('dpll.yaml', 'genetlink-legacy.yaml')
self.assertIsInstance(new_instance, YnlFamily)
def test_get_instance_raises_exception_with_passing_wrong_params(self):
with self.assertRaises(FileNotFoundError) as cm_exc:
NetlinkFactory.get_instance('spec-not-exists.yaml')
self.assertEqual(FileNotFoundError, cm_exc.exception, 'Invalid YAML spec file')
with self.assertRaises(FileNotFoundError) as cm_exc:
NetlinkFactory.get_instance('dpll.yaml', 'schema-not-exists.yaml')
self.assertEqual(FileNotFoundError, cm_exc.exception, 'Invalid YAML schema file')

View File

@ -0,0 +1,28 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
"""This module execute the unit tests"""
import os
import unittest
def suite():
loader = unittest.TestLoader()
loader.testMethodPrefix = 'test_'
test_path = os.path.dirname(__file__)
base_path = os.path.abspath(
test_path + os.sep + os.pardir + os.sep + os.pardir)
return loader.discover('pynetlink.tests', '*test.py', base_path)
if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite())

View File

@ -0,0 +1,32 @@
[metadata]
name = pynetlink
version = 1.0.0
description = Python Netlink Library
license_files = LICENSE
classifiers =
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.9
keywords =
netlink
dpll
[options]
include_package_data = True
packages = find:
python_requires = >=3.9
setup_requires =
setuptools
wheel
install_require =
jsonschema
importlib
yaml
[options.packages.find]
exclude = pynetlink.tests
[bdist_wheel]
universal = 1

View File

@ -0,0 +1,11 @@
#
# Copyright (c) 2025 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# All Rights Reserved.
#
from setuptools import setup
setup()

View File

@ -0,0 +1,47 @@
[tox]
envlist = pep8,pylint
skipsdist = True
usedevelop = False
stxdir = {toxinidir}/../../..
[testenv]
basepython = python3
install_command =
pip install -v -v -v \
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/starlingx/root/raw/branch/master/build-tools/requirements/debian/upper-constraints.txt} \
{opts} {packages}
deps =
-r{[tox]stxdir}/test-requirements.txt
setenv = VIRTUAL_ENV={envdir}
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
[testenv:venv]
basepython = python3
commands = {posargs}
[flake8]
# H series are hacking
# H104: File contains nothing but comments
# H238: old style class declaration, use new style
# H306: imports not in alphabetical order
ignore = H104,H201,H238,H306
exclude = base
max-line-length = 120
[testenv:pep8]
commands = flake8 {posargs} pynetlink
[testenv:pylint]
deps = pylint
# E1101 (no-member): %s %r has no %r member%s
# E1121 (too-many-function-args): Too many positional arguments for %s call
# W0141 (bad-builtin): Used builtin function %s
# W0212 (protected-access): Access to a protected member %s of a client class
commands = pylint --rcfile={[tox]stxdir}/.pylintrc --ignore=base --disable=E1101,E1121,W0141 pynetlink

10
tox.ini
View File

@ -35,8 +35,13 @@ commands =
-name \*.sh \
-print0 | xargs -n 1 -0 bashate -v \
-i E006,E041,E042,E043,E044 -e E*"
# TODO (pynetlink):
# Remove the paths of pynetlink project from the exception list after upgrading to Linux 6.12
# or higher, since these files will no longer be needed in the repository.
bash -c "find {toxinidir} \
\( -path {toxinidir}/.tox \) -a -prune \
-o -path {toxinidir}/python/pynetlink/src/pynetlink/specs -prune \
-o -path {toxinidir}/python/pynetlink/src/pynetlink/schemas -prune \
-o -type f -name '*.yaml' \
-print0 | xargs -0 yamllint"
@ -48,7 +53,10 @@ deps =
flake8-bugbear<=19.3.0
flake8<3.8.3
commands =
flake8
# TODO (pynetlink):
# Remove the pynetlink from the exception list after upgrading to Linux 6.12 or higher,
# since these files will no longer be needed in the repository.
flake8 --extend-exclude {toxinidir}/python/pynetlink/src/pynetlink/base
[testenv:pylint]
deps = -r{toxinidir}/test-requirements.txt