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:
parent
94fec17864
commit
77732293d6
@ -378,6 +378,9 @@ python3-setuptools
|
||||
#python3.9
|
||||
python3.9
|
||||
|
||||
#pynetlink
|
||||
pynetlink
|
||||
|
||||
#pyzmq
|
||||
python3-zmq
|
||||
|
||||
|
@ -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
1
debian_stable_wheels.inc
Normal file
@ -0,0 +1 @@
|
||||
pynetlink-wheel
|
5
python/pynetlink/debian/deb_folder/changelog
Normal file
5
python/pynetlink/debian/deb_folder/changelog
Normal 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
|
33
python/pynetlink/debian/deb_folder/control
Normal file
33
python/pynetlink/debian/deb_folder/control
Normal 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.
|
42
python/pynetlink/debian/deb_folder/copyright
Normal file
42
python/pynetlink/debian/deb_folder/copyright
Normal 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'.
|
1
python/pynetlink/debian/deb_folder/pynetlink.install
Normal file
1
python/pynetlink/debian/deb_folder/pynetlink.install
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/python*/dist-packages/*
|
23
python/pynetlink/debian/deb_folder/rules
Normal file
23
python/pynetlink/debian/deb_folder/rules
Normal 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
|
1
python/pynetlink/debian/deb_folder/source/format
Normal file
1
python/pynetlink/debian/deb_folder/source/format
Normal file
@ -0,0 +1 @@
|
||||
3.0 (quilt)
|
7
python/pynetlink/debian/meta_data.yaml
Normal file
7
python/pynetlink/debian/meta_data.yaml
Normal 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
6
python/pynetlink/src/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
*.egg-info/
|
||||
__pycache__/
|
||||
dist/
|
||||
build/
|
||||
libs/
|
||||
.tox/
|
16
python/pynetlink/src/CONTRIBUTING.rst
Normal file
16
python/pynetlink/src/CONTRIBUTING.rst
Normal 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
|
202
python/pynetlink/src/LICENSE
Normal file
202
python/pynetlink/src/LICENSE
Normal 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.
|
2
python/pynetlink/src/MANIFEST.in
Normal file
2
python/pynetlink/src/MANIFEST.in
Normal file
@ -0,0 +1,2 @@
|
||||
include pynetlink/specs/*.yaml
|
||||
include pynetlink/schemas/*.yaml
|
101
python/pynetlink/src/README.rst
Normal file
101
python/pynetlink/src/README.rst
Normal 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.
|
39
python/pynetlink/src/pynetlink/__init__.py
Normal file
39
python/pynetlink/src/pynetlink/__init__.py
Normal 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']
|
4
python/pynetlink/src/pynetlink/base/README.rst
Normal file
4
python/pynetlink/src/pynetlink/base/README.rst
Normal file
@ -0,0 +1,4 @@
|
||||
pynetlink - Netlink Python library
|
||||
===================================
|
||||
|
||||
This folder contains the Python YNL libraries.
|
8
python/pynetlink/src/pynetlink/base/__init__.py
Normal file
8
python/pynetlink/src/pynetlink/base/__init__.py
Normal 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"]
|
614
python/pynetlink/src/pynetlink/base/nlspec.py
Normal file
614
python/pynetlink/src/pynetlink/base/nlspec.py
Normal 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
|
1063
python/pynetlink/src/pynetlink/base/ynl.py
Normal file
1063
python/pynetlink/src/pynetlink/base/ynl.py
Normal file
File diff suppressed because it is too large
Load Diff
12
python/pynetlink/src/pynetlink/common/__init__.py
Normal file
12
python/pynetlink/src/pynetlink/common/__init__.py
Normal 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']
|
52
python/pynetlink/src/pynetlink/common/netlink.py
Normal file
52
python/pynetlink/src/pynetlink/common/netlink.py
Normal 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')
|
46
python/pynetlink/src/pynetlink/dpll/__init__.py
Normal file
46
python/pynetlink/src/pynetlink/dpll/__init__.py
Normal 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']
|
144
python/pynetlink/src/pynetlink/dpll/constants.py
Normal file
144
python/pynetlink/src/pynetlink/dpll/constants.py
Normal 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 PHY’s 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
|
171
python/pynetlink/src/pynetlink/dpll/devices.py
Normal file
171
python/pynetlink/src/pynetlink/dpll/devices.py
Normal 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))
|
179
python/pynetlink/src/pynetlink/dpll/dpll.py
Normal file
179
python/pynetlink/src/pynetlink/dpll/dpll.py
Normal 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())
|
265
python/pynetlink/src/pynetlink/dpll/pins.py
Normal file
265
python/pynetlink/src/pynetlink/dpll/pins.py
Normal 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)
|
4
python/pynetlink/src/pynetlink/schemas/README.rst
Normal file
4
python/pynetlink/src/pynetlink/schemas/README.rst
Normal 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.
|
460
python/pynetlink/src/pynetlink/schemas/genetlink-legacy.yaml
Normal file
460
python/pynetlink/src/pynetlink/schemas/genetlink-legacy.yaml
Normal 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
|
349
python/pynetlink/src/pynetlink/schemas/genetlink.yaml
Normal file
349
python/pynetlink/src/pynetlink/schemas/genetlink.yaml
Normal 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
|
508
python/pynetlink/src/pynetlink/schemas/netlink-raw.yaml
Normal file
508
python/pynetlink/src/pynetlink/schemas/netlink-raw.yaml
Normal 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
|
4
python/pynetlink/src/pynetlink/specs/README.rst
Normal file
4
python/pynetlink/src/pynetlink/specs/README.rst
Normal 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.
|
623
python/pynetlink/src/pynetlink/specs/dpll.yaml
Normal file
623
python/pynetlink/src/pynetlink/specs/dpll.yaml
Normal 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
|
7
python/pynetlink/src/pynetlink/tests/__init__.py
Normal file
7
python/pynetlink/src/pynetlink/tests/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
451
python/pynetlink/src/pynetlink/tests/dpll_devices_test.py
Normal file
451
python/pynetlink/src/pynetlink/tests/dpll_devices_test.py
Normal 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)}')
|
901
python/pynetlink/src/pynetlink/tests/dpll_pins_test.py
Normal file
901
python/pynetlink/src/pynetlink/tests/dpll_pins_test.py
Normal 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
|
313
python/pynetlink/src/pynetlink/tests/dpll_test.py
Normal file
313
python/pynetlink/src/pynetlink/tests/dpll_test.py
Normal 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, {})
|
||||
])
|
50
python/pynetlink/src/pynetlink/tests/dump_get_device.json
Normal file
50
python/pynetlink/src/pynetlink/tests/dump_get_device.json
Normal 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"
|
||||
}
|
||||
]
|
1314
python/pynetlink/src/pynetlink/tests/dump_get_pin.json
Normal file
1314
python/pynetlink/src/pynetlink/tests/dump_get_pin.json
Normal file
File diff suppressed because it is too large
Load Diff
38
python/pynetlink/src/pynetlink/tests/netlink_test.py
Normal file
38
python/pynetlink/src/pynetlink/tests/netlink_test.py
Normal 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')
|
28
python/pynetlink/src/pynetlink/tests/run.py
Normal file
28
python/pynetlink/src/pynetlink/tests/run.py
Normal 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())
|
32
python/pynetlink/src/setup.cfg
Normal file
32
python/pynetlink/src/setup.cfg
Normal 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
|
11
python/pynetlink/src/setup.py
Normal file
11
python/pynetlink/src/setup.py
Normal 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()
|
47
python/pynetlink/src/tox.ini
Normal file
47
python/pynetlink/src/tox.ini
Normal 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
10
tox.ini
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user