mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compare commits
297 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
940ce67900 | ||
|
|
c12faf0128 | ||
|
|
a3d50487ed | ||
|
|
b1d8e40bfe | ||
|
|
8d3949f77b | ||
|
|
2f49025d49 | ||
|
|
4c9ceeeb64 | ||
|
|
3a0c394fa2 | ||
|
|
20a4ebd83c | ||
|
|
4a9f66fc31 | ||
|
|
35d68b2101 | ||
|
|
522c67a2c3 | ||
|
|
f4ee6c2c8d | ||
|
|
377f2a59f5 | ||
|
|
176f0405b8 | ||
|
|
839d34e758 | ||
|
|
a68d1e323b | ||
|
|
549c7ef9e7 | ||
|
|
103c9444c9 | ||
|
|
fe7bef3f09 | ||
|
|
8a88ada9c5 | ||
|
|
538bc7b4e8 | ||
|
|
b56451e5a4 | ||
|
|
63f9a672d0 | ||
|
|
2c5c5ef4b4 | ||
|
|
5a3ffc2036 | ||
|
|
e576443859 | ||
|
|
43067df78e | ||
|
|
973059438f | ||
|
|
9d3567d9ce | ||
|
|
7687b0fcda | ||
|
|
6f70f34b79 | ||
|
|
37aed81745 | ||
|
|
5aa1824eef | ||
|
|
82603e589a | ||
|
|
3ba897a1be | ||
|
|
fa92a7f7c9 | ||
|
|
f569849e57 | ||
|
|
458c27a0e1 | ||
|
|
e4dcf084c5 | ||
|
|
bed1bc8676 | ||
|
|
8e315c4401 | ||
|
|
ae8fa4371f | ||
|
|
29ff9408e0 | ||
|
|
a24be25779 | ||
|
|
58337d7f90 | ||
|
|
fcaad5a8e2 | ||
|
|
4d598ade86 | ||
|
|
b455ee9a31 | ||
|
|
82b9328e48 | ||
|
|
f45a45c38c | ||
|
|
d1bf9e68c6 | ||
|
|
0356d78bc2 | ||
|
|
a4f48415a0 | ||
|
|
c9f0e3accf | ||
|
|
9dd432ca94 | ||
|
|
179bc57cdf | ||
|
|
e1d1c6df9c | ||
|
|
e894fb61d8 | ||
|
|
c3a84109b7 | ||
|
|
1ca5f63dee | ||
|
|
60bc1e38d0 | ||
|
|
431e4542ca | ||
|
|
731f00202c | ||
|
|
2945593991 | ||
|
|
f375327085 | ||
|
|
338bea49c2 | ||
|
|
d15e36cf24 | ||
|
|
4b76530788 | ||
|
|
cb1c002bed | ||
|
|
d6f7eff5b5 | ||
|
|
e1692b7079 | ||
|
|
54b3c9ef24 | ||
|
|
48743aa28b | ||
|
|
7ce988ac59 | ||
|
|
f4fec5f120 | ||
|
|
2739b8a488 | ||
|
|
ce9c518b97 | ||
|
|
bd2a2cc6a1 | ||
|
|
6547d1ae4c | ||
|
|
650265e296 | ||
|
|
b122acb422 | ||
|
|
9710f29770 | ||
|
|
ed01bac8e2 | ||
|
|
66d3aa849a | ||
|
|
95e4b3413a | ||
|
|
06e0d569f1 | ||
|
|
660bab6e68 | ||
|
|
3dd91bb84e | ||
|
|
8fadcd9fe5 | ||
|
|
e8237e6ae0 | ||
|
|
c746c5c267 | ||
|
|
b69b750ef5 | ||
|
|
120ad30f1e | ||
|
|
bfc9854eb5 | ||
|
|
619c466275 | ||
|
|
017377c093 | ||
|
|
7e38c7106b | ||
|
|
8dc06d14d7 | ||
|
|
f07504d92e | ||
|
|
02554fa6d8 | ||
|
|
3eb09e8842 | ||
|
|
3eb9711021 | ||
|
|
2349236bb4 | ||
|
|
2d86117167 | ||
|
|
a0265d1069 | ||
|
|
084792bdc6 | ||
|
|
5ece6a3373 | ||
|
|
14c977768e | ||
|
|
5bedcff65c | ||
|
|
479820528c | ||
|
|
486882a4df | ||
|
|
ce997c6d01 | ||
|
|
b9d1c86613 | ||
|
|
f9337f0576 | ||
|
|
a06302e7f0 | ||
|
|
7bc0fb809d | ||
|
|
bc511f84fe | ||
|
|
3a3cea0061 | ||
|
|
616d2f52f9 | ||
|
|
6dd671f24e | ||
|
|
443652dcd2 | ||
|
|
b11bb64732 | ||
|
|
2cd7c7a875 | ||
|
|
8929d21948 | ||
|
|
f7bba3a687 | ||
|
|
5d2c93bc7a | ||
|
|
f1055a62e3 | ||
|
|
4c4ab4fd70 | ||
|
|
34a5e47508 | ||
|
|
6d7cdb9e10 | ||
|
|
1fefdecbe7 | ||
|
|
e62cdf9a6e | ||
|
|
fa00d1eac1 | ||
|
|
0eac97542d | ||
|
|
b3db52fd16 | ||
|
|
a9eb80b1a2 | ||
|
|
e93668ae5b | ||
|
|
c68c3cb8b2 | ||
|
|
f63c548c2a | ||
|
|
1ab2fbb482 | ||
|
|
fed79474e4 | ||
|
|
8c1ffd8989 | ||
|
|
33fc534a81 | ||
|
|
7e832ead9e | ||
|
|
7f0f765bb4 | ||
|
|
69c7f45bb7 | ||
|
|
d843e08828 | ||
|
|
055c455571 | ||
|
|
ba387af804 | ||
|
|
8b9960abe9 | ||
|
|
efdadfcdb9 | ||
|
|
ec10654c35 | ||
|
|
76bddf9715 | ||
|
|
b4cc30ac10 | ||
|
|
9f9dddddd4 | ||
|
|
f821a3c174 | ||
|
|
ee4cb78dc2 | ||
|
|
6f82c5792a | ||
|
|
2c5d4073a7 | ||
|
|
9efd1df31d | ||
|
|
6f53d7d3cd | ||
|
|
92486dfcaf | ||
|
|
29007541e8 | ||
|
|
2278f112ba | ||
|
|
c39722aff2 | ||
|
|
1e8edb6524 | ||
|
|
bd5f03d1c4 | ||
|
|
f8ccd56bf1 | ||
|
|
72218c5bc3 | ||
|
|
b94566048b | ||
|
|
85a232a711 | ||
|
|
931c61f141 | ||
|
|
253ca8a30e | ||
|
|
829fc3b294 | ||
|
|
3c5fb0f885 | ||
|
|
9df45d4256 | ||
|
|
283ae4dd2f | ||
|
|
718907d9ba | ||
|
|
26cf3ec769 | ||
|
|
7150da3e4f | ||
|
|
f946ce27f7 | ||
|
|
4942504a9a | ||
|
|
e1245ed4e5 | ||
|
|
272eb5ad8c | ||
|
|
543d41cb59 | ||
|
|
8619f14c0a | ||
|
|
56aa3e2d26 | ||
|
|
a9fbfcc7ae | ||
|
|
a32adbf5d8 | ||
|
|
f9050799bb | ||
|
|
66a10d5e3e | ||
|
|
152f446d76 | ||
|
|
646e080217 | ||
|
|
3fd8c48245 | ||
|
|
c93b74b527 | ||
|
|
4fe6025b18 | ||
|
|
73b6a684ec | ||
|
|
40d9df5cc8 | ||
|
|
9ebb8da624 | ||
|
|
589ce29d41 | ||
|
|
b343672eb8 | ||
|
|
df47036f01 | ||
|
|
5febec5f44 | ||
|
|
84f44dd20f | ||
|
|
d86dd0658f | ||
|
|
00c36baee0 | ||
|
|
a40519fd16 | ||
|
|
b836b4fb83 | ||
|
|
1ab819cd81 | ||
|
|
ee50f20c11 | ||
|
|
0f17fe5183 | ||
|
|
650e2da843 | ||
|
|
426311f440 | ||
|
|
5333085c55 | ||
|
|
4c301d47dd | ||
|
|
aea2ba8728 | ||
|
|
594a554e56 | ||
|
|
a0c3a74019 | ||
|
|
efdd34a56c | ||
|
|
c80a0c8af0 | ||
|
|
d98a10f67f | ||
|
|
fb6e7e70c6 | ||
|
|
e49569db60 | ||
|
|
b3055154db | ||
|
|
6718c0edc9 | ||
|
|
28d9405f73 | ||
|
|
05f0aeb61e | ||
|
|
6004a7029c | ||
|
|
16add4a4ae | ||
|
|
ef752d1f45 | ||
|
|
793c037ded | ||
|
|
9f476217f5 | ||
|
|
49111d613b | ||
|
|
26d2e2d786 | ||
|
|
7bd159de1d | ||
|
|
605680d6fd | ||
|
|
f1a479a794 | ||
|
|
fa2d68e80c | ||
|
|
e1730585e9 | ||
|
|
ef55a5d677 | ||
|
|
bc4b7475f7 | ||
|
|
abcb93aacb | ||
|
|
b09a9b3490 | ||
|
|
caaf2b2116 | ||
|
|
382ec368fc | ||
|
|
9dd96144d4 | ||
|
|
efd41ca435 | ||
|
|
7bc12d0202 | ||
|
|
b49cd13d16 | ||
|
|
56e32dfcbb | ||
|
|
833a8e7309 | ||
|
|
a8eb2bd4e7 | ||
|
|
5276d408fd | ||
|
|
95bf7eb285 | ||
|
|
fdf66dc148 | ||
|
|
5ce79ea667 | ||
|
|
8c4b2872b8 | ||
|
|
7be364aa84 | ||
|
|
43858063ca | ||
|
|
739761f0d2 | ||
|
|
a8a3e7d49c | ||
|
|
ff04f76257 | ||
|
|
93216d916c | ||
|
|
256289a30a | ||
|
|
93da795520 | ||
|
|
a14f9d4edc | ||
|
|
fd024387d7 | ||
|
|
4af137f4fa | ||
|
|
01cd81ecf5 | ||
|
|
69eb06f39e | ||
|
|
d1636168b2 | ||
|
|
1e44ace41d | ||
|
|
a9b8caa411 | ||
|
|
6e48c5ebaa | ||
|
|
944581eaef | ||
|
|
3f6572e901 | ||
|
|
6520070a46 | ||
|
|
3b8fdcc892 | ||
|
|
352e2fe5d3 | ||
|
|
37b7b68f1b | ||
|
|
3db20bccef | ||
|
|
560b276f7e | ||
|
|
a19c43f613 | ||
|
|
5b6d7a467d | ||
|
|
b1fdbac5f9 | ||
|
|
cf51ac7860 | ||
|
|
cf5560a100 | ||
|
|
dff965465c | ||
|
|
79de37bbc8 | ||
|
|
e5c2fba0a8 | ||
|
|
04267470b1 | ||
|
|
cf7060ea08 | ||
|
|
f340a96fdf | ||
|
|
798bd752cf | ||
|
|
638f0aa4f9 | ||
|
|
d3b7581669 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -137,4 +137,6 @@ zkey/kmip/libkmipclient.dep
|
||||
zkey/kmip/zkey-kmip.so
|
||||
zkey/zkey
|
||||
zkey/zkey-cryptsetup
|
||||
zmemtopo/zmemtopo
|
||||
zpcictl/zpcictl
|
||||
zpwr/zpwr
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
exclude: \.(bin|crl|crt|key)$
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.1.0
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: end-of-file-fixer
|
||||
@@ -11,6 +11,9 @@ repos:
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
exclude_types: ['rust']
|
||||
- id: check-yaml
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: git-clang-format
|
||||
@@ -24,7 +27,7 @@ repos:
|
||||
minimum_pre_commit_version: "2.9.0"
|
||||
types_or: [c++, c]
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.1
|
||||
rev: v2.4.1
|
||||
hooks:
|
||||
- id: codespell
|
||||
exclude_types: ['rust']
|
||||
|
||||
@@ -43,6 +43,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Frank Munzert
|
||||
- Frank Pavlic
|
||||
- Fritz Elfert
|
||||
- Gautam Gala
|
||||
- Gerald Schaefer
|
||||
- Gerhard Tonn
|
||||
- Graham Inggs
|
||||
@@ -59,9 +60,11 @@ List of all individuals having contributed content to s390-tools
|
||||
- Horst Hummel
|
||||
- Ingo Franzki
|
||||
- Ingo Tuchscherer
|
||||
- Jakob Naucke
|
||||
- Jakub Čajka
|
||||
- Jan Glauber
|
||||
- Jan Höppner
|
||||
- Jan Polensky
|
||||
- Jan Willeke
|
||||
- Jason J. Herne
|
||||
- Javier Martinez Canillas
|
||||
@@ -69,6 +72,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Jens Remus
|
||||
- Jochen Roehrig
|
||||
- Joern Siglen
|
||||
- Jörn Siglen
|
||||
- Juergen Christ
|
||||
- Julian Wiedmann
|
||||
- Karsten Graul
|
||||
@@ -96,6 +100,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Nikita Dubrovskii
|
||||
- Niklas Schnelle
|
||||
- Nikolay Gueorguiev
|
||||
- Peter Jin
|
||||
- Peter Oberparleiter
|
||||
- Peter Tiedemann
|
||||
- Philipp Kern
|
||||
@@ -105,12 +110,15 @@ List of all individuals having contributed content to s390-tools
|
||||
- Raimund Schroeder
|
||||
- Ralph Wuerthner
|
||||
- Rene Trumpp
|
||||
- Richie Buturla
|
||||
- Rolf Schaefer
|
||||
- Sa Liu
|
||||
- Sascha Silbe
|
||||
- Sebastian Ott
|
||||
- Sertonix
|
||||
- Seshagiri N. Ippili
|
||||
- Shalini Chellathurai Saroja
|
||||
- Siglen
|
||||
- Simon Sturm
|
||||
- Stefan Bader
|
||||
- Stefan Haberland
|
||||
@@ -120,6 +128,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Steffen Eiden
|
||||
- Steffen Maier
|
||||
- Steffen Thoss
|
||||
- Sumanth Korikkar
|
||||
- Susanne Wintenberger
|
||||
- Sven Schnelle
|
||||
- Sven Schuetz
|
||||
|
||||
134
CHANGELOG.md
134
CHANGELOG.md
@@ -1,6 +1,132 @@
|
||||
Release history for s390-tools (MIT version)
|
||||
--------------------------------------------
|
||||
|
||||
* __v2.39.0 (2025-10-14)__
|
||||
|
||||
For Linux kernel version: 6.16 / 6.17
|
||||
|
||||
Changes of existing tools:
|
||||
- chpstat: Add options to select IEC units for scaling (SI units are default)
|
||||
- chzdev: Introduce --no-module-load option
|
||||
- cpi: Disable CPI for SEL guests by default
|
||||
- dbginfo.sh: Enhance logging on timeout triggered
|
||||
- iucvterm: Install symlink for lsiucvallow.8 man page
|
||||
- lshwc: Add command line flag to specify individual counters
|
||||
- lspai: Add command line flag for delta values
|
||||
- lspai: Add command line flag for short counter names
|
||||
- lspai: Add command line flag to specify individual counters
|
||||
- lspai: Add command line flags for all cpus
|
||||
- lspai: Add command line flags for hexadecimal output
|
||||
- man: Use CR for constant width font
|
||||
- pvimg: Add '--image-key' option
|
||||
- zdev: Allow dynamic control of module load
|
||||
- zipl/boot: Fix EBCDIC code page 500 conversion and decrease size by 200 bytes
|
||||
- zipl: Add support of heterogeneous mirrors (remove technical limitations
|
||||
on mirrored targets, thus allowing mirrored devices consist of partitions
|
||||
at different offsets on disks of different types and geometry).
|
||||
- zkey: Add support for generating and importing exportable secure keys
|
||||
|
||||
Bug Fixes:
|
||||
- chpstat: Fix scaling of DPU utilization calculation
|
||||
- zdev/dracut: Prevent loading of unused kernel modules
|
||||
- zdev: Fix double device configuration on DPM systems
|
||||
- zdev: Fix double device configuration with rd.dasd
|
||||
- zipl_helper.device-mapper: Fix segfault in an error path
|
||||
|
||||
* __v2.38.0 (2025-06-25)__
|
||||
|
||||
For Linux kernel version: 6.14 / 6.15
|
||||
|
||||
Add new tools:
|
||||
- udev: New rule to set newly hotplugged CPUs online
|
||||
- zmemtopo: Display memory topology information
|
||||
- zpwr: Display power readings of a partition and CPC
|
||||
|
||||
Removed tools / features:
|
||||
- check_hostkeydoc: Remove installation target
|
||||
- scsi_logging_level: Delete SCSI logging script (available in sg3_utils)
|
||||
- zdump: Drop build_arch for s390 DASD dumps
|
||||
- zdump: Drop non-extended multi-volume DASD dump support
|
||||
- zdump: Drop support of 32-bit dump architecture
|
||||
- zdump: Drop support of non-extended single volume DASD dumpers
|
||||
- zdump: Drop support of obsolete dumps and dumpers
|
||||
|
||||
Changes of existing tools / libraries:
|
||||
- Various man-pages fixes
|
||||
- check_hostkeydoc: Add deprecation warning
|
||||
- check_hostkeydoc: Move to scripts directory
|
||||
- cpuplugd: Allow cpu hotplugging on systems without polarization
|
||||
- dbginfo.sh: Add Ubuntu snap tool
|
||||
- dbginfo.sh: Add missing config data and logs
|
||||
- dbginfo.sh: Reworking the container section
|
||||
- dbginfo.sh: Update for network commands
|
||||
- dbginfo.sh: Updating info for disks and lvm
|
||||
- libutil: Add machine type definition for machines 9175 and 9176
|
||||
- lscpumf: Add support for IBM z17 counter sets
|
||||
- lshwc: Add command line flag for run time
|
||||
- lshwc: Add flags to display counter values in hex
|
||||
- lshwc: Add output '--format' option
|
||||
- lshwc: Add support for delta counter value display
|
||||
- lspai: Add output '--format' option
|
||||
- lsreipl: Add secure boot state to output
|
||||
- lswhc: Add short names to lshwc output
|
||||
- pv_tools: Add Bash and Zsh completions
|
||||
- pvapconfig: Add '--unbind' option
|
||||
- pvimg/boot: Print error messages from stage3a bootloader
|
||||
- pvimg: Add support for CCK update
|
||||
- pvsecret: Add support for CCK update
|
||||
- pvsecret: Allow retrieving secrets by index & warn for duplicated entries
|
||||
- pvsecret: Deny adding secrets with duplicated secret IDs
|
||||
- zdev: Add support for virtio devices
|
||||
- zipl: Enhance mirror support
|
||||
- zipl: Implement '--dry-run' option for all dump jobs
|
||||
- zipl_helper.device-mapper: Support mirrors over NVMe devices
|
||||
- zkey/dracut: Add a dracut config file for zkey
|
||||
- zkey/initramfs: Update initramfs hook to correct drivers and include zkey plugins
|
||||
- zkey: Add support for converting a clear-key LUKS2 volume to use a secure key
|
||||
|
||||
Bug Fixes:
|
||||
- chpstat: Add missing CMG 5 data fields
|
||||
- chpstat: Fix DPU utilization calculation
|
||||
- libutil/util_file: Handle over-read in util_file_read_fd()
|
||||
- pvattest: Fix successful 'check' evaluation
|
||||
- pvsecret: Fix some edge cases for plaintext keys
|
||||
- zipl_helper.device-mapper: Fix imprecise is_device_mapper() predicate
|
||||
- zkey: Fix EP11 secure key reencipher function
|
||||
- zpcictl: Fix command line parsing for invalid options
|
||||
|
||||
* __v2.37.0 (2025-02-07)__
|
||||
|
||||
For Linux kernel version: 6.13
|
||||
|
||||
Changes of existing tools:
|
||||
- dbginfo.sh: Add details on CPU-measurement
|
||||
- dbginfo.sh: Add new crypto command
|
||||
- dbginfo.sh: Add overview commands and crypto update
|
||||
- dbginfo.sh: Adding kdump info
|
||||
- dbginfo.sh: Removing outdated email references
|
||||
- dbginfo.sh: Rework network section
|
||||
- dbginfo.sh: Update copyright 2nd year
|
||||
- pvimg: Add '--(enable|disable)-image-encryption' flags to 'pvimg create'
|
||||
- pvimg: Add '--cck <FILE>' command line option and make '--comm-key' an alias
|
||||
- pvimg: Add '--hdr-key' command line option to 'pvimg create'
|
||||
- pvimg: Rename '--key' into '--hdr-key' and use '--key' as an alias (for 'pvimg info')
|
||||
- pvsecret: Add support for retrievable secrets
|
||||
- ziorep_config: Add PCHID field to adapter report
|
||||
- ziorep_traffic: Add DEVBUSID column to traffic report
|
||||
- ziorep_utilization: Add --fcp-device parameter to print virtual adapter report
|
||||
- ziorep_utilization: Add PCHID column to physical adapter report
|
||||
- ziorep_utilization: Now prints only physical adapter report by default
|
||||
- ziorep_utilization: Swap Bus-ID and CHPID columns in virtual adapter report
|
||||
- zipl/boot: Increase section size for eckd_mv dumper
|
||||
- zkey: Add support for listing and importing protected virtualization secrets
|
||||
|
||||
Bug Fixes:
|
||||
- chpstat: Fix invalid utilization data on older kernels
|
||||
- opticsmon: Fix runaway loop in on_link_change()
|
||||
- zipl: Update inline assembly for GCC 15
|
||||
- zipl_helper.device-mapper: Add missed step in logical device resolution
|
||||
|
||||
* __v2.36.0 (2024-12-06)__
|
||||
|
||||
For Linux kernel version: 6.12
|
||||
@@ -17,6 +143,14 @@ Release history for s390-tools (MIT version)
|
||||
- chpstat: Add support for full CMCB
|
||||
- chpstat: Add support for new CMG types
|
||||
- dbginfo.sh: add overview commands and crypto update
|
||||
- genprotimg:
|
||||
1. genprotimg is now a symbolic link to the new tool `pvimg create`
|
||||
2. Breaking API changes in genprotimg:
|
||||
1. An existing output file is no longer silently overwritten; to revert
|
||||
to the old behavior use the `--overwrite` option.
|
||||
2. The Linux kernel component is now checked to determine whether it is a
|
||||
binary s390x kernel. To disable this behavior use the
|
||||
`--no-component-check` option.
|
||||
- hyptop: Support for structured output (json, json-seq, csv)
|
||||
- lszfcp: Add missing fallback marker for non-good fc_host port_state
|
||||
- lszfcp: Improve speed with many SCSI devices
|
||||
|
||||
3
Makefile
3
Makefile
@@ -15,7 +15,8 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
|
||||
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
|
||||
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
|
||||
systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
|
||||
lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon
|
||||
lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon \
|
||||
zpwr zmemtopo
|
||||
|
||||
else
|
||||
BASELIB_DIRS =
|
||||
|
||||
15
README.md
15
README.md
@@ -128,9 +128,6 @@ Package contents
|
||||
* sclpdbf:
|
||||
Display debug data for the sclp kernel component.
|
||||
|
||||
* scsi_logging_level:
|
||||
Create, get or set the logging level for the SCSI logging facility.
|
||||
|
||||
* zconf:
|
||||
Set of scripts to configure and list status information of Linux on s390
|
||||
devices.
|
||||
@@ -417,6 +414,18 @@ the different tools are provided:
|
||||
dumping).
|
||||
Removing the inflate modules and function prototypes are the only major
|
||||
modifications made to the kernel version of zlib.
|
||||
For stand-alone NVMe and list-directed DASD dump, the following tools/packages
|
||||
are required to populate dumper's initramfs:
|
||||
- cat
|
||||
- cut
|
||||
- findmnt
|
||||
- logsave
|
||||
- makedumpfile
|
||||
- mount
|
||||
- poweroff
|
||||
- sha256sum
|
||||
- sync
|
||||
- umount
|
||||
|
||||
* zgetdump
|
||||
For building zgetdump you need OpenSSL version 1.1.0 or newer
|
||||
|
||||
@@ -1,182 +1,170 @@
|
||||
.\" Automatically generated by Pandoc 2.16.1
|
||||
.\" Automatically generated by Pandoc 3.1.11.1
|
||||
.\"
|
||||
.TH "CHREIPL-FCP-MPATH" "7" "2022-02-24" "s390-tools 2.20.0-build-20220224" "Administrator Manual"
|
||||
.hy
|
||||
.TH "CHREIPL\-FCP\-MPATH" "7" "2025\-03\-12" "s390-tools 2.37.0-build-20250312" "Administrator Manual"
|
||||
.SH NAME
|
||||
.PP
|
||||
chreipl-fcp-mpath - use multipath information for re-IPL path failover
|
||||
on a running Linux instance
|
||||
chreipl\-fcp\-mpath \- use multipath information for re\-IPL path
|
||||
failover on a running Linux instance
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The IPL process of Linux on Z or LinuxONE from an FCP-attached SCSI
|
||||
The IPL process of Linux on Z or LinuxONE from an FCP\-attached SCSI
|
||||
volume uses exactly one path to the volume.
|
||||
If this path is unavailable, the IPL fails.
|
||||
.PP
|
||||
The \f[B]chreipl-fcp-mpath\f[R] toolset monitors \f[B]udev\f[R] events
|
||||
about paths to the re-IPL volume.
|
||||
If the currently configured re-IPL path becomes unavailable, the toolset
|
||||
checks for operational paths to the same volume.
|
||||
If available, it reconfigures the re-IPL settings to use an operational
|
||||
The \f[B]chreipl\-fcp\-mpath\f[R] toolset monitors \f[B]udev\f[R] events
|
||||
about paths to the re\-IPL volume.
|
||||
If the currently configured re\-IPL path becomes unavailable, the
|
||||
toolset checks for operational paths to the same volume.
|
||||
If available, it reconfigures the re\-IPL settings to use an operational
|
||||
path.
|
||||
.PP
|
||||
Thus, re-IPL from an FCP-attached SCSI volume can be successful despite
|
||||
path failures on a running Linux instance if at least one path to the
|
||||
re-IPL volume remains operational.
|
||||
Thus, re\-IPL from an FCP\-attached SCSI volume can be successful
|
||||
despite path failures on a running Linux instance if at least one path
|
||||
to the re\-IPL volume remains operational.
|
||||
.PP
|
||||
\f[B]Chreipl-fcp-mpath\f[R] requires \f[B]udev\f[R],
|
||||
\f[B]multipathd\f[R] and \f[B]dm-multipath\f[R].
|
||||
\f[B]Chreipl\-fcp\-mpath\f[R] requires \f[B]udev\f[R],
|
||||
\f[B]multipathd\f[R] and \f[B]dm\-multipath\f[R].
|
||||
Once installed, the toolset runs automatically and autonomously.
|
||||
No user intervention is possible or required.
|
||||
.PP
|
||||
Other than installing the toolset, there is no user interface for
|
||||
\f[B]chreipl-fcp-mpath\f[R].
|
||||
\f[B]chreipl\-fcp\-mpath\f[R].
|
||||
.SS Requirements
|
||||
.PP
|
||||
The \f[B]chreipl-fcp-mpath\f[R] tool has the following requirements on
|
||||
The \f[B]chreipl\-fcp\-mpath\f[R] tool has the following requirements on
|
||||
the Linux instance that is being monitored:
|
||||
.IP \[bu] 2
|
||||
The Linux instance must have started successfully, during IPL.
|
||||
.IP \[bu] 2
|
||||
The running Linux instance must use \f[B]dm-multipath\f[R] and
|
||||
\f[B]multipathd\f[R] for the configured re-IPL volume - a volume that
|
||||
contains a zipl boot record and has one of its paths used in the re-IPL
|
||||
The running Linux instance must use \f[B]dm\-multipath\f[R] and
|
||||
\f[B]multipathd\f[R] for the configured re\-IPL volume \- a volume that
|
||||
contains a zipl boot record and has one of its paths used in the re\-IPL
|
||||
configuration.
|
||||
.IP \[bu] 2
|
||||
\f[B]udev\f[R] must run.
|
||||
.IP \[bu] 2
|
||||
The toolset must observe at least one event about the configured re-IPL
|
||||
The toolset must observe at least one event about the configured re\-IPL
|
||||
path.
|
||||
Examples for such events are: the SCSI disk comes online, or a path of
|
||||
the corresponding multipath device goes down or comes back online.
|
||||
.RS 2
|
||||
.IP \[bu] 2
|
||||
The WWID of the re-IPL volume must not change while the Linux instance
|
||||
The WWID of the re\-IPL volume must not change while the Linux instance
|
||||
is running.
|
||||
.RE
|
||||
.IP \[bu] 2
|
||||
When the configured re-IPL path becomes unavailable while the Linux
|
||||
instance is running, at least one operational path to the re-IPL volume
|
||||
When the configured re\-IPL path becomes unavailable while the Linux
|
||||
instance is running, at least one operational path to the re\-IPL volume
|
||||
must be available, or must become available.
|
||||
If no such path is available when the Linux instance is rebooted, the
|
||||
re-IPL path is not changed.
|
||||
re\-IPL path is not changed.
|
||||
.IP \[bu] 2
|
||||
The tool assumes that any manually reconfigured re-IPL device is valid
|
||||
The tool assumes that any manually reconfigured re\-IPL device is valid
|
||||
and operational.
|
||||
.RS 2
|
||||
.PP
|
||||
The tool treats a newly configured re-IPL device like the initially
|
||||
configured re-IPL device.
|
||||
In particular, if the newly configured re-IPL device fulfills the
|
||||
requirements of the tool, re-IPL path failover takes place if the
|
||||
configured re-IPL path becomes unavailable.
|
||||
The tool treats a newly configured re\-IPL device like the initially
|
||||
configured re\-IPL device.
|
||||
In particular, if the newly configured re\-IPL device fulfills the
|
||||
requirements of the tool, re\-IPL path failover takes place if the
|
||||
configured re\-IPL path becomes unavailable.
|
||||
.RE
|
||||
.SS Caution with Manual Changes to the Configured re-IPL Target
|
||||
.PP
|
||||
\f[B]chreipl-fcp-mpath\f[R] is designed to accept operator-inititated
|
||||
changes of the re-IPL device.
|
||||
.SS Caution with Manual Changes to the Configured re\-IPL Target
|
||||
\f[B]chreipl\-fcp\-mpath\f[R] is designed to accept operator\-inititated
|
||||
changes of the re\-IPL device.
|
||||
However, concurrent changes by the operator and tool driven changes can
|
||||
result in the operator change being overwritten.
|
||||
.PP
|
||||
To avoid this problem, change the re-IPL device only during steady-state
|
||||
operations, when no path events happen.
|
||||
To avoid this problem, change the re\-IPL device only during
|
||||
steady\-state operations, when no path events happen.
|
||||
Alternatively, make sure that no events are processed while you change
|
||||
the device.
|
||||
See EXAMPLES for one way to suspend event processing.
|
||||
.SH MESSAGES
|
||||
.PP
|
||||
During monitoring and event processing, \f[B]chreipl-fcp-mpath\f[R]
|
||||
During monitoring and event processing, \f[B]chreipl\-fcp\-mpath\f[R]
|
||||
writes messages to the syslog.
|
||||
.PP
|
||||
When the configured re-IPL path is changed to a different path to the
|
||||
When the configured re\-IPL path is changed to a different path to the
|
||||
same volume (priority \f[I]daemon.notice\f[R]):
|
||||
.RS
|
||||
.PP
|
||||
Changed re-IPL path to: <device-bus-id>:<wwpn>:<lun>.
|
||||
Changed re\-IPL path to: <device\-bus\-id>:<wwpn>:<lun>.
|
||||
.RE
|
||||
.PP
|
||||
When a path event indicates that the last available path has become
|
||||
non-operational (priority \f[I]daemon.alert\f[R]):
|
||||
non\-operational (priority \f[I]daemon.alert\f[R]):
|
||||
.RS
|
||||
.PP
|
||||
The re-IPL device cannot be changed because no operational path to the
|
||||
re-IPL volume remains.
|
||||
The next re-IPL might fail unless you re-attach or enable at least one
|
||||
valid path to the re-IPL volume.
|
||||
The re\-IPL device cannot be changed because no operational path to the
|
||||
re\-IPL volume remains.
|
||||
The next re\-IPL might fail unless you re\-attach or enable at least one
|
||||
valid path to the re\-IPL volume.
|
||||
.RE
|
||||
.PP
|
||||
When changing the configured re-IPL device failed because of an error
|
||||
When changing the configured re\-IPL device failed because of an error
|
||||
with the used Linux kernel interface (priority \f[I]daemon.crit\f[R]):
|
||||
.RS
|
||||
.PP
|
||||
Changing the re-IPL device failed.
|
||||
The current re-IPL settings might be inconsistent.
|
||||
Check and correct the settings (see the README.md of chreipl-fcp-mpath)
|
||||
to make sure that the current re-IPL device is valid.
|
||||
Changing the re\-IPL device failed.
|
||||
The current re\-IPL settings might be inconsistent.
|
||||
Check and correct the settings (see the README.md of
|
||||
chreipl\-fcp\-mpath) to make sure that the current re\-IPL device is
|
||||
valid.
|
||||
.RE
|
||||
.PP
|
||||
A failure to change the re-IPL device can indicate an inconsistent
|
||||
A failure to change the re\-IPL device can indicate an inconsistent
|
||||
setting that cannot be corrected automatically by
|
||||
\f[B]chreipl-fcp-mpath\f[R].
|
||||
As a result, the next re-IPL might fail or might not use the intended
|
||||
re-IPL device.
|
||||
\f[B]chreipl\-fcp\-mpath\f[R].
|
||||
As a result, the next re\-IPL might fail or might not use the intended
|
||||
re\-IPL device.
|
||||
.PP
|
||||
You can use the following tools to check and correct the current
|
||||
settings:
|
||||
.IP \[bu] 2
|
||||
\f[B]lsreipl\f[R] to confirm that the intended re-IPL device is
|
||||
\f[B]lsreipl\f[R] to confirm that the intended re\-IPL device is
|
||||
configured;
|
||||
.IP \[bu] 2
|
||||
\f[B]chreipl\f[R] to change the re-IPL device;
|
||||
\f[B]chreipl\f[R] to change the re\-IPL device;
|
||||
.IP \[bu] 2
|
||||
\f[B]lszfcp\f[R] to inspect the state of available paths to the re-IPL
|
||||
\f[B]lszfcp\f[R] to inspect the state of available paths to the re\-IPL
|
||||
device.
|
||||
.SH EXAMPLES
|
||||
.SS Manual Changes to the Configured re-IPL Device
|
||||
.PP
|
||||
.SS Manual Changes to the Configured re\-IPL Device
|
||||
As outlined in DESCRIPTION, be cautious when manually changing the
|
||||
configured re-IPL device.
|
||||
configured re\-IPL device.
|
||||
Assure that your reconfiguration actions do not collide with concurrent
|
||||
automatic event processing by \f[B]chreipl-fcp-mpath\f[R].
|
||||
automatic event processing by \f[B]chreipl\-fcp\-mpath\f[R].
|
||||
You can avoid such collisions, by stopping event processing, making your
|
||||
changes, and then re-enabling event processing.
|
||||
changes, and then re\-enabling event processing.
|
||||
You need \f[I]root\f[R] privileges for running the commands in the
|
||||
following example:
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
.EX
|
||||
\[ti] # udevadm settle
|
||||
\[ti] # udevadm control --stop-exec-queue
|
||||
\[ti] # udevadm control \-\-stop\-exec\-queue
|
||||
\[ti] # chreipl ...
|
||||
\[ti] # udevadm control --start-exec-queue
|
||||
\f[R]
|
||||
.fi
|
||||
\[ti] # udevadm control \-\-start\-exec\-queue
|
||||
.EE
|
||||
.SS Listing messages with journalctl
|
||||
.PP
|
||||
If your Linux instance includes \f[B]journalctl\f[R], use the following
|
||||
command to list all messages that are issued by
|
||||
\f[B]chreipl-fcp-mpath\f[R]:
|
||||
\f[B]chreipl\-fcp\-mpath\f[R]:
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
\[ti] # journalctl -t chreipl-fcp-mpath
|
||||
\f[R]
|
||||
.fi
|
||||
.EX
|
||||
\[ti] # journalctl \-t chreipl\-fcp\-mpath
|
||||
.EE
|
||||
.PP
|
||||
To list only messages that were issued since the last IPL, use this
|
||||
command:
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
\[ti] # journalctl -t chreipl-fcp-mpath -b
|
||||
\f[R]
|
||||
.fi
|
||||
.EX
|
||||
\[ti] # journalctl \-t chreipl\-fcp\-mpath \-b
|
||||
.EE
|
||||
.SH REPORTING BUGS
|
||||
.PP
|
||||
Use the \f[B]Issues\f[R] functionality on GitHub to report any bugs in
|
||||
\f[B]chreipl-fcp-mpath\f[R]: s390-tools
|
||||
Issues (https://github.com/ibm-s390-linux/s390-tools/issues).
|
||||
\f[B]chreipl\-fcp\-mpath\f[R]: \c
|
||||
.UR https://github.com/ibm-s390-linux/s390-tools/issues
|
||||
s390\-tools Issues
|
||||
.UE \c
|
||||
\&.
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
\f[B]chreipl\f[R](8), \f[B]dracut\f[R](8), \f[B]journalctl\f[R](1),
|
||||
\f[B]lsreipl(8)\f[R], \f[B]lszfcp\f[R](8), \f[B]multipath\f[R](8),
|
||||
\f[B]multipathd\f[R](8), \f[B]udev\f[R](7), \f[B]udevadm\f[R](8),
|
||||
|
||||
@@ -32,7 +32,7 @@ endif
|
||||
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
||||
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
||||
VERSION := 2
|
||||
RELEASE := 36
|
||||
RELEASE := 39
|
||||
PATCHLEVEL := 0
|
||||
DISTRELEASE := build-$(shell date +%Y%m%d)
|
||||
S390_TOOLS_RELEASE := $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
||||
@@ -265,9 +265,12 @@ LIBDIR = $(INSTALLDIR)/lib
|
||||
USRLIBDIR = $(INSTALLDIR)/usr/lib
|
||||
USRLIB64DIR = $(INSTALLDIR)/usr/lib64
|
||||
SYSCONFDIR = $(INSTALLDIR)/etc
|
||||
MANDIR = $(INSTALLDIR)/usr/share/man
|
||||
DATADIR = $(INSTALLDIR)/usr/share
|
||||
MANDIR = $(DATADIR)/man
|
||||
BASHCOMPLETIONDIR = $(DATADIR)/bash-completion/completions
|
||||
ZSHCOMPLETIONDIR = $(DATADIR)/zsh/site-functions
|
||||
VARDIR = $(INSTALLDIR)/var
|
||||
TOOLS_DATADIR = $(INSTALLDIR)/usr/share/s390-tools
|
||||
TOOLS_DATADIR = $(DATADIR)/s390-tools
|
||||
TOOLS_LIBDIR = $(INSTALLDIR)/lib/s390-tools
|
||||
ZFCPDUMP_DIR = $(TOOLS_LIBDIR)/zfcpdump
|
||||
# Systemd support files are installed only if a directory is specified
|
||||
|
||||
@@ -22,7 +22,7 @@ ALL_CPPFLAGS += -DVERSION=$(VERSION)
|
||||
all: check_dep cpacfstats cpacfstatsd
|
||||
|
||||
cpacfstatsd: cpacfstatsd.o stats_sock.o perf_crypto.o cpacfstats_common.o \
|
||||
$(rootdir)/libutil/libutil.a
|
||||
$(rootdir)/libcpumf/libcpumf.a $(rootdir)/libutil/libutil.a
|
||||
$(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -ludev -lpthread -o $@
|
||||
|
||||
cpacfstats: cpacfstats.o stats_sock.o cpacfstats_common.o
|
||||
|
||||
@@ -46,8 +46,8 @@ All counters are initially disabled and must be switched on to measure
|
||||
CPACF activities of the system. There is a slight performance penalty with
|
||||
CPACF counters enabled.
|
||||
|
||||
CPACF activity counters come in two flavors: CPU-MF and PAI. CPU-MF
|
||||
counters are only available on LPARs and have to be authorized. If
|
||||
CPACF activity counters come in two flavors: CPU-MF and PAI. CPU-MF
|
||||
counters are only available on LPARs and have to be authorized. If
|
||||
they are available, the counters
|
||||
.B des,
|
||||
.B aes,
|
||||
@@ -55,14 +55,131 @@ they are available, the counters
|
||||
.B rng,
|
||||
and
|
||||
.B ecc
|
||||
are made available. These counters can individually be activated,
|
||||
reset, printed, or deactivated. PAI counters are a lot more detailed.
|
||||
are made available. These counters can individually be activated,
|
||||
reset, printed, or deactivated. PAI counters are a lot more detailed.
|
||||
The user interface only offers the counters
|
||||
.B pai_user
|
||||
and
|
||||
.B pai_kernel
|
||||
to count CPACF usage in user-space or kernel-space. When printing
|
||||
these counters, detailed counters are shown. The detailed names are:
|
||||
to count CPACF usage in user-space or kernel-space. When printing
|
||||
these counters, detailed counters are shown.
|
||||
|
||||
A complete list of counters can be found at the end of this manpage.
|
||||
|
||||
Note that the counters starting with PCKMO and Reserved are only
|
||||
available in the pai_kernel set. Also note that the counters are
|
||||
designed to count successful operations. In the case of KMA this
|
||||
means only complete GCM operations including final hashing are
|
||||
counted.
|
||||
|
||||
Note: CPU-MF based CPACF performance counters are available on LPARs
|
||||
only. PAI counters are available on all hypervisors. For security
|
||||
reasons only members of the group \fIcpacfstats\fR are allowed to run
|
||||
the cpacfstats client application.
|
||||
|
||||
Example usage scenario:
|
||||
.P
|
||||
1. Start the cpacfstatsd daemon with root privileges.
|
||||
.P
|
||||
2. Check for successful startup by using the ps and syslog commands.
|
||||
.P
|
||||
3. Enable the CPACF counters of interest. For example, enable all counters
|
||||
by issuing cpacfstats -e.
|
||||
.P
|
||||
4. Run your applications.
|
||||
.P
|
||||
5. Display counter values by using the cpacfstats command. Reset the
|
||||
cryptographic counters as required. To reset, use, for example, cpacfstats
|
||||
-r.
|
||||
.P
|
||||
6. Disable all the CPACF measurements, for example, by using cpacfstats
|
||||
-d.
|
||||
.P
|
||||
7. Shutdown the cpacfstatsd daemon by using killall cpacfstatsd.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-h\fR or \fB\-\-help\fR
|
||||
Display help information for the command.
|
||||
.TP
|
||||
\fB\-v\fR or \fB\-\-version\fR
|
||||
Display version and copyright information for the command.
|
||||
.TP
|
||||
\fB\-e\fR or \fB\-\-enable\fR [counter]
|
||||
Enable one or all CPACF performance counters. The optional counter
|
||||
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
|
||||
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
|
||||
performance counters are enabled. Enabling a counter does not reset
|
||||
it. New events are added to the current counter value.
|
||||
.TP
|
||||
\fB\-d\fR or \fB\-\-disable\fR [counter]
|
||||
Disable one or all CPACF performance counters. The optional counter
|
||||
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
|
||||
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
|
||||
performance counters are disabled. Disabling a counter does not reset
|
||||
it. The counter value is preserved when a counter is disabled, and
|
||||
counting will resume using the preserved value when the counter is
|
||||
re-enabled.
|
||||
.TP
|
||||
\fB\-r\fR or \fB\-\-reset\fR [counter]
|
||||
Reset one or all CPACF performance counters. The optional counter
|
||||
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
|
||||
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
|
||||
performance counters are reset to 0.
|
||||
.TP
|
||||
\fB\-p\fR or \fB\-\-print\fR [\fB\-n\fR or \fB\-\-nonzero\fR] [counter]
|
||||
Display the value of one or all CPACF performance counters. The
|
||||
optional counter argument can be one of: \fBdes\fR, \fBaes\fR,
|
||||
\fBsha\fR, \fBprng\fR, \fBecc\fR, \fBpai_user\fR, \fBpai_kernel\fR, or
|
||||
\fBall\fR. If the counter argument is omitted or if there is no
|
||||
argument, all performance counters are displayed. If the optional
|
||||
\fB\-n\fR or \fB\-\-nonzero\fR argument is given, then only PAI
|
||||
counters that have a non-zero value are printed.
|
||||
.TP
|
||||
\fB\-j\fR or \fB\-\-json\fR
|
||||
Display all activated counters in JSON format. The JSON contains an
|
||||
array of counter objects. Each object contains the property
|
||||
.B counter
|
||||
specifying either a CPU-MF counter of one of the detailed PAI counter.
|
||||
Additional properties include
|
||||
.B error
|
||||
an error number if the counter could not be read,
|
||||
.B value
|
||||
the counter value if the counter could be read,
|
||||
.B space
|
||||
for PAI counters to specify
|
||||
.B user
|
||||
or
|
||||
.B kernel
|
||||
space counter set, and
|
||||
.B counterid
|
||||
for PAI counters to specify the PAI counter number as specified in the
|
||||
Principles of Operation.
|
||||
.TP
|
||||
The default command is --print all.
|
||||
.
|
||||
.SH FILES
|
||||
.nf
|
||||
/run/cpacfstatsd_socket
|
||||
.fi
|
||||
.
|
||||
.SH RETURN VALUE
|
||||
.IP 0
|
||||
Successful program execution.
|
||||
.IP 1
|
||||
An error occurred, reasons include: invalid argument, cpacfstatsd could not
|
||||
be reached (check that the daemon is running), insufficient access rights,
|
||||
version mismatch between client and daemon, or the application is out of
|
||||
memory. The application prints a message with the details of the error and
|
||||
the errno value.
|
||||
.
|
||||
.SH NOTES
|
||||
ECC counters are only available since z15. cpacfstats will show the
|
||||
counters as \fIunsupported\fR if the hardware does not support ECC
|
||||
counters.
|
||||
.
|
||||
.SH APPENDIX
|
||||
The detailed pai counter names are:
|
||||
.RS
|
||||
.IP \(bu
|
||||
KM DES,
|
||||
@@ -376,118 +493,39 @@ PCKMO ECC Ed448,
|
||||
Reserved 1, and
|
||||
.IP \(bu
|
||||
Reserved 2.
|
||||
.IP \(bu
|
||||
KM AES-XTS (full) 128bit
|
||||
.IP \(bu
|
||||
KM AES-XTS (full) 256bit
|
||||
.IP \(bu
|
||||
KM AES-XTS (full) 128bit protected key
|
||||
.IP \(bu
|
||||
KM AES-XTS (full) 256bit protected key
|
||||
.IP \(bu
|
||||
KMAC HMAC SHA 224
|
||||
.IP \(bu
|
||||
KMAC HMAC SHA 256
|
||||
.IP \(bu
|
||||
KMAC HMAC SHA 384
|
||||
.IP \(bu
|
||||
KMAC HMAC SHA 512
|
||||
.IP \(bu
|
||||
KMAC HMAC SHA 224 protected key
|
||||
.IP \(bu
|
||||
KMAC HMAC SHA 256 protected key
|
||||
.IP \(bu
|
||||
KMAC HMAC SHA 384 protected key
|
||||
.IP \(bu
|
||||
KMAC HMAC SHA 512 protected key
|
||||
.IP \(bu
|
||||
PCKMO HMAC 512 protected key
|
||||
.IP \(bu
|
||||
PCKMO HMAC 1024 protected key
|
||||
.IP \(bu
|
||||
PCKMO AES-XTS 128bit double key protected key
|
||||
.IP \(bu
|
||||
PCKMO AES-XTS 256bit double key protected key
|
||||
.RE
|
||||
Note that the counters starting with PCKMO and Reserved are only
|
||||
available in the pai_kernel set. Also note that the counters are
|
||||
designed to count successful operations. In the case of KMA this
|
||||
means only complete GCM operations including final hashing are
|
||||
counted.
|
||||
|
||||
Note: CPU-MF based CPACF performance counters are available on LPARs
|
||||
only. PAI counters are available on all hypervisors. For security
|
||||
reasons only members of the group \fIcpacfstats\fR are allowed to run
|
||||
the cpacfstats client application.
|
||||
|
||||
Example usage scenario:
|
||||
.P
|
||||
1. Start the cpacfstatsd daemon with root privileges.
|
||||
.P
|
||||
2. Check for successful startup by using the ps and syslog commands.
|
||||
.P
|
||||
3. Enable the CPACF counters of interest. For example, enable all counters
|
||||
by issuing cpacfstats -e.
|
||||
.P
|
||||
4. Run your applications.
|
||||
.P
|
||||
5. Display counter values by using the cpacfstats command. Reset the
|
||||
cryptographic counters as required. To reset, use, for example, cpacfstats
|
||||
-r.
|
||||
.P
|
||||
6. Disable all the CPACF measurements, for example, by using cpacfstats
|
||||
-d.
|
||||
.P
|
||||
7. Shutdown the cpacfstatsd daemon by using killall cpacfstatsd.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-h\fR or \fB\-\-help\fR
|
||||
Display help information for the command.
|
||||
.TP
|
||||
\fB\-v\fR or \fB\-\-version\fR
|
||||
Display version and copyright information for the command.
|
||||
.TP
|
||||
\fB\-e\fR or \fB\-\-enable\fR [counter]
|
||||
Enable one or all CPACF performance counters. The optional counter
|
||||
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
|
||||
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
|
||||
performance counters are enabled. Enabling a counter does not reset
|
||||
it. New events are added to the current counter value.
|
||||
.TP
|
||||
\fB\-d\fR or \fB\-\-disable\fR [counter]
|
||||
Disable one or all CPACF performance counters. The optional counter
|
||||
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
|
||||
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
|
||||
performance counters are disabled. Disabling a counter does not reset
|
||||
it. The counter value is preserved when a counter is disabled, and
|
||||
counting will resume using the preserved value when the counter is
|
||||
re-enabled.
|
||||
.TP
|
||||
\fB\-r\fR or \fB\-\-reset\fR [counter]
|
||||
Reset one or all CPACF performance counters. The optional counter
|
||||
argument can be one of: \fBdes\fR, \fBaes\fR, \fBsha\fR, \fBprng\fR,
|
||||
\fBecc\fR, or \fBall\fR. If the counter argument is omitted, all
|
||||
performance counters are reset to 0.
|
||||
.TP
|
||||
\fB\-p\fR or \fB\-\-print\fR [\fB\-n\fR or \fB\-\-nonzero\fR] [counter]
|
||||
Display the value of one or all CPACF performance counters. The
|
||||
optional counter argument can be one of: \fBdes\fR, \fBaes\fR,
|
||||
\fBsha\fR, \fBprng\fR, \fBecc\fR, \fBpai_user\fR, \fBpai_kernel\fR, or
|
||||
\fBall\fR. If the counter argument is omitted or if there is no
|
||||
argument, all performance counters are displayed. If the optional
|
||||
\fB\-n\fR or \fB\-\-nonzero\fR argument is given, then only PAI
|
||||
counters that have a non-zero value are printed.
|
||||
.TP
|
||||
\fB\-j\fR or \fB\-\-json\fR
|
||||
Display all activated counters in JSON format. The JSON contains an
|
||||
array of counter objects. Each object contains the property
|
||||
.B counter
|
||||
specifying either a CPU-MF counter of one of the detailed PAI counter.
|
||||
Additional properties include
|
||||
.B error
|
||||
an error number if the counter could not be read,
|
||||
.B value
|
||||
the counter value if the counter could be read,
|
||||
.B space
|
||||
for PAI counters to specify
|
||||
.B user
|
||||
or
|
||||
.B kernel
|
||||
space counter set, and
|
||||
.B counterid
|
||||
for PAI counters to specify the PAI counter number as specified in the
|
||||
Principles of Operation.
|
||||
.TP
|
||||
The default command is --print all.
|
||||
.
|
||||
.SH FILES
|
||||
.nf
|
||||
/run/cpacfstatsd_socket
|
||||
.fi
|
||||
.
|
||||
.SH RETURN VALUE
|
||||
.IP 0
|
||||
Successful program execution.
|
||||
.IP 1
|
||||
An error occurred, reasons include: invalid argument, cpacfstatsd could not
|
||||
be reached (check that the daemon is running), insufficient access rights,
|
||||
version mismatch between client and daemon, or the application is out of
|
||||
memory. The application prints a message with the details of the error and
|
||||
the errno value.
|
||||
.
|
||||
.SH NOTES
|
||||
ECC counters are only available since z15. cpacfstats will show the
|
||||
counters as \fIunsupported\fR if the hardware does not support ECC
|
||||
counters.
|
||||
.
|
||||
.SH SEE ALSO
|
||||
.BR cpacfstatsd (8)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <libudev.h>
|
||||
|
||||
#include "cpacfstats.h"
|
||||
#include "lib/libcpumf.h"
|
||||
#include "../include/lib/zt_common.h"
|
||||
|
||||
/* correlation between counter and perf counter string */
|
||||
@@ -123,16 +124,6 @@ static struct percpucounter *findcpu(unsigned int cpunum, int unlinkflag)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
|
||||
int cpu, int group_fd, unsigned long flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
|
||||
group_fd, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int perf_supported(void)
|
||||
{
|
||||
return !access("/proc/sys/kernel/perf_event_paranoid", R_OK);
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_prg.h"
|
||||
#include "lib/util_base.h"
|
||||
|
||||
#include "lib/libcpumf.h"
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_prg.h"
|
||||
|
||||
static unsigned int verbose;
|
||||
static unsigned long min_sdb, max_sdb;
|
||||
@@ -88,21 +88,22 @@ static long parse_buffersize(char *string)
|
||||
static int write_sfb(unsigned int min, unsigned int max)
|
||||
{
|
||||
int rc = EXIT_SUCCESS;
|
||||
char text[64];
|
||||
char text[64], *path;
|
||||
size_t len;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(S390_CPUMSF_BUFFERSZ, "w");
|
||||
path = util_path_sysfs(S390_CPUMSF_BUFFERSZ);
|
||||
fp = fopen(path, "w");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, S390_CPUMSF_BUFFERSZ);
|
||||
err(EXIT_FAILURE, "%s", path);
|
||||
snprintf(text, sizeof(text), "%u,%u", min, max);
|
||||
len = strlen(text) + 1;
|
||||
if (fwrite(text, 1, len, fp) != len) {
|
||||
warn(S390_CPUMSF_BUFFERSZ);
|
||||
warn("%s", path);
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
if (fclose(fp)) {
|
||||
warn(S390_CPUMSF_BUFFERSZ);
|
||||
warn("%s", path);
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
if (verbose && rc != EXIT_FAILURE)
|
||||
@@ -110,6 +111,7 @@ static int write_sfb(unsigned int min, unsigned int max)
|
||||
" Minimum:%7d sample-data-blocks\n"
|
||||
" Maximum:%7d sample-data-blocks\n",
|
||||
min, max);
|
||||
free(path);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
666
cpumf/lscpumf.c
666
cpumf/lscpumf.c
@@ -39,7 +39,7 @@
|
||||
static bool actions[ACTION_SAMPLE + 1]; /* Specified command line options */
|
||||
|
||||
/* This defines the number of pages a Sample Data Buffer Table (SDBT) can hold
|
||||
* as payload data. Each SDBT is one PAGE (4096 bytes) and continas 512 eight
|
||||
* as payload data. Each SDBT is one PAGE (4096 bytes) and contains 512 eight
|
||||
* byte data pointers to Sample Data Buffers (SDB). The last entry of a SDBT
|
||||
* points to another SDBT and can not store payload.
|
||||
*/
|
||||
@@ -139,7 +139,7 @@ static struct samples { /* Sample definition for all machines */
|
||||
* The CPU Measurement facility has a first and second version number.
|
||||
*
|
||||
* The first version number governs basic counter set and the
|
||||
* problem state counter set. Currently used are first verion numbers 1 and 3.
|
||||
* problem state counter set. Currently used are first version numbers 1 and 3.
|
||||
* The counter numbers are identifical for version number 1 and 3, but
|
||||
* have different purpose and description.
|
||||
*
|
||||
@@ -162,13 +162,6 @@ static struct samples { /* Sample definition for all machines */
|
||||
* Second version number: >3 Range 448 to 495 inclusive (48 counters)
|
||||
*/
|
||||
|
||||
#define CPUMF_CTRSET_NONE 0
|
||||
#define CPUMF_CTRSET_BASIC 2
|
||||
#define CPUMF_CTRSET_PROBLEM_STATE 4
|
||||
#define CPUMF_CTRSET_CRYPTO 8
|
||||
#define CPUMF_CTRSET_EXTENDED 1
|
||||
#define CPUMF_CTRSET_MT_DIAG 32
|
||||
|
||||
struct counters {
|
||||
int ctrnum;
|
||||
int ctrset;
|
||||
@@ -634,7 +627,7 @@ static struct counters cpumcf_z10_counters[] = {
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1D_L3_LOCAL_WRITES",
|
||||
.desc = "A directory write to the Level-1 D-Cache directory"
|
||||
"\n\t\twhere the installtion cache line was source from"
|
||||
"\n\t\twhere the installation cache line was source from"
|
||||
"\n\t\tthe Level-3 cache that is on the same book as the"
|
||||
"\n\t\tData cache (Local L2 cache)",
|
||||
},
|
||||
@@ -1923,7 +1916,7 @@ static struct counters cpumcf_z14_counters[] = {
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1I_ONCHIP_L3_SOURCED_WRITES",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache ine was sourced"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-3 cache without intervention",
|
||||
},
|
||||
{
|
||||
@@ -1931,7 +1924,7 @@ static struct counters cpumcf_z14_counters[] = {
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1I_ONCHIP_MEMORY_SOURCED_WRITES",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache ine was sourced"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom On-Chip memory",
|
||||
},
|
||||
{
|
||||
@@ -1939,7 +1932,7 @@ static struct counters cpumcf_z14_counters[] = {
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1I_ONCHIP_L3_SOURCED_WRITES_IV",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache ine was sourced"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-3 cache with intervention",
|
||||
},
|
||||
{
|
||||
@@ -2349,7 +2342,7 @@ static struct counters cpumcf_z15_counters[] = {
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1I_ONCHIP_L3_SOURCED_WRITES",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache ine was sourced"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-3 cache without intervention",
|
||||
},
|
||||
{
|
||||
@@ -2357,7 +2350,7 @@ static struct counters cpumcf_z15_counters[] = {
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1I_ONCHIP_MEMORY_SOURCED_WRITES",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache ine was sourced"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom On-Chip memory",
|
||||
},
|
||||
{
|
||||
@@ -2365,7 +2358,7 @@ static struct counters cpumcf_z15_counters[] = {
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1I_ONCHIP_L3_SOURCED_WRITES_IV",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache ine was sourced"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-3 cache with intervention",
|
||||
},
|
||||
{
|
||||
@@ -3136,6 +3129,641 @@ static struct counters cpumcf_z16_counters[] = {
|
||||
},
|
||||
};
|
||||
|
||||
static struct counters cpumcf_z17_counters[] = {
|
||||
{
|
||||
.ctrnum = 128,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1D_RO_EXCL_WRITES",
|
||||
.desc = "A directory write to the Level-1 Data cache where"
|
||||
"\n\t\tthe line was originally in a Read-Only state in the"
|
||||
"\n\t\tcache but has been updated to be in the Exclusive"
|
||||
"\n\t\tstate that allows stores to the cache line.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 129,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DTLB2_WRITES",
|
||||
.desc = "A translation has been written into The Translation"
|
||||
"\n\t\tLookaside Buffer 2 (TLB2) and the request was made"
|
||||
"\n\t\tby the Level-1 Data cache. This is a replacement"
|
||||
"\n\t\tfor what was provided for the DTLB on z13 and prior"
|
||||
"\n\t\tmachines.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 130,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DTLB2_MISSES",
|
||||
.desc = "A TLB2 miss is in progress for a request made by"
|
||||
"\n\t\tthe Level-1 Data cache. Incremented by one for"
|
||||
"\n\t\tevery TLB2 miss in progress for the Level-1 Data"
|
||||
"\n\t\tcache on this cycle. This is a replacement for what"
|
||||
"\n\t\twas provided for the DTLB on z13 and prior"
|
||||
"\n\t\tmachines.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 131,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "CRSTE_1MB_WRITES",
|
||||
.desc = "A translation entry was written into the Combined"
|
||||
"\n\t\tRegion and Segment Table Entry array in the Level-2"
|
||||
"\n\t\tTLB for a one-megabyte page.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 132,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DTLB2_GPAGE_WRITES",
|
||||
.desc = "A translation entry for a two-gigabyte page was"
|
||||
"\n\t\twritten into the Level-2 TLB.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 134,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ITLB2_WRITES",
|
||||
.desc = "A translation entry has been written into the"
|
||||
"\n\t\tTranslation Lookaside Buffer 2 (TLB2) and the"
|
||||
"\n\t\trequest was made by the Level-1 Instruction cache."
|
||||
"\n\t\tThis is a replacement for what was provided for the"
|
||||
"\n\t\tITLB on z13 and prior machines.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 135,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ITLB2_MISSES",
|
||||
.desc = "A TLB2 miss is in progress for a request made by"
|
||||
"\n\t\tthe Level-1 Instruction cache. Incremented by one"
|
||||
"\n\t\tfor every TLB2 miss in progress for the Level-1"
|
||||
"\n\t\tInstruction cache in a cycle. This is a replacement"
|
||||
"\n\t\tfor what was provided for the ITLB on z13 and prior"
|
||||
"\n\t\tmachines.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 137,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TLB2_PTE_WRITES",
|
||||
.desc = "A translation entry was written into the Page Table"
|
||||
"\n\t\tEntry array in the Level-2 TLB.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 138,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TLB2_CRSTE_WRITES",
|
||||
.desc = "Translation entries were written into the Combined"
|
||||
"\n\t\tRegion and Segment Table Entry array and the Page"
|
||||
"\n\t\tTable Entry array in the Level-2 TLB.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 139,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TLB2_ENGINES_BUSY",
|
||||
.desc = "The number of Level-2 TLB translation engines busy"
|
||||
"\n\t\tin a cycle.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 140,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_C_TEND",
|
||||
.desc = "A TEND instruction has completed in a constrained"
|
||||
"\n\t\ttransactional-execution mode.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 141,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_NC_TEND",
|
||||
.desc = "A TEND instruction has completed in a non-"
|
||||
"\n\t\tconstrained transactional-execution mode.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 143,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "L1C_TLB2_MISSES",
|
||||
.desc = "Increments by one for any cycle where a Level-1"
|
||||
"\n\t\tcache or Level-2 TLB miss is in progress.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 145,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_REQ",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 146,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_REQ_IV",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache with"
|
||||
"\n\t\tintervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 147,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_REQ_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache after using"
|
||||
"\n\t\tchip level horizontal persistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 148,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_REQ_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache after using"
|
||||
"\n\t\tdrawer level horizontal persistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 149,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 150,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP_IV",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 151,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache after using chip"
|
||||
"\n\t\tlevel horizontal persistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 152,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache after using drawer"
|
||||
"\n\t\tlevel horizontal persistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 153,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_MODULE",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Module Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 154,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_DRAWER",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Drawer Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 155,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_OFF_DRAWER",
|
||||
.desc = "A directory write to the Level-1 Data cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an Off-Drawer Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 156,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_CHIP_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from On-Chip memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 157,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_MODULE_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from On-Module memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 158,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_ON_DRAWER_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from On-Drawer memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 159,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DCW_OFF_DRAWER_MEMORY",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from Off-Drawer memory.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 160,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_MODULE_IV",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Module Level-2"
|
||||
"\n\t\tcache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 161,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_MODULE_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Module Level-2"
|
||||
"\n\t\tcache after using chip level horizontal"
|
||||
"\n\t\tpersistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 162,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_MODULE_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Module Level-2"
|
||||
"\n\t\tcache after using drawer level horizontal"
|
||||
"\n\t\tpersistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 163,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_DRAWER_IV",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Drawer Level-2"
|
||||
"\n\t\tcache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 164,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_DRAWER_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tinstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Drawer Level-2"
|
||||
"\n\t\tcache after using chip level horizontal"
|
||||
"\n\t\tpersistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 165,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_ON_DRAWER_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tinstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an On-Drawer Level-2"
|
||||
"\n\t\tcache after using drawer level horizontal"
|
||||
"\n\t\tpersistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 166,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_OFF_DRAWER_IV",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tinstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
|
||||
"\n\t\tcache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 167,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_OFF_DRAWER_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tinstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
|
||||
"\n\t\tcache after using chip level horizontal"
|
||||
"\n\t\tpersistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 168,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "IDCW_OFF_DRAWER_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Data or Level-1"
|
||||
"\n\t\tInstruction cache directory where the returned"
|
||||
"\n\t\tcache line was sourced from an Off-Drawer Level-2"
|
||||
"\n\t\tcache after using drawer level horizontal"
|
||||
"\n\t\tpersistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 169,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_REQ",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tthe requestors Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 170,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_REQ_IV",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache with"
|
||||
"\n\t\tintervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 171,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_REQ_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache after using"
|
||||
"\n\t\tchip level horizontal persistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 172,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_REQ_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom the requestors Level-2 cache after using"
|
||||
"\n\t\tdrawer level horizontal persistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 173,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 174,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP_IV",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache with intervention.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 175,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP_CHIP_HIT",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip Level-2 cache after using chip"
|
||||
"\n\t\tlevel horizontal persistence, Chip-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 176,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_CHIP_DRAWER_HIT",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Chip level 2 cache after using drawer"
|
||||
"\n\t\tlevel horizontal persistence, Drawer-HP hit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 177,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_MODULE",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Module Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 178,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_ON_DRAWER",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an On-Drawer Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 179,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "ICW_OFF_DRAWER",
|
||||
.desc = "A directory write to the Level-1 Instruction cache"
|
||||
"\n\t\tdirectory where the returned cache line was sourced"
|
||||
"\n\t\tfrom an Off-Drawer Level-2 cache.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 202,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "CYCLES_SAMETHRD",
|
||||
.desc = "The number of cycles the CPU is not in wait state"
|
||||
"\n\t\tand the CPU is running by itself on the Core.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 203,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "CYCLES_DIFFTHRD",
|
||||
.desc = "The number of cycles the CPU is not in wait state"
|
||||
"\n\t\tand the CPU is running with another thread on the"
|
||||
"\n\t\tCore.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 204,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "INST_SAMETHRD",
|
||||
.desc = "The number of instructions executed on the CPU and"
|
||||
"\n\t\tthe CPU is running by itself on the Core.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 205,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "INST_DIFFTHRD",
|
||||
.desc = "The number of instructions executed on the CPU and"
|
||||
"\n\t\tthe CPU is running with another thread on the Core.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 206,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "WRONG_BRANCH_PREDICTION",
|
||||
.desc = "A count of the number of branches that were"
|
||||
"\n\t\tpredicted incorrectly by the branch prediction"
|
||||
"\n\t\tlogic in the Core. This includes incorrectly"
|
||||
"\n\t\tpredicted branches that are executed in Firmware."
|
||||
"\n\t\tExamples of instructions implemented in Firmware"
|
||||
"\n\t\tare complicated instructions like MVCL (Move"
|
||||
"\n\t\tCharacter Long) and PC (Program Call).",
|
||||
},
|
||||
{
|
||||
.ctrnum = 225,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "VX_BCD_EXECUTION_SLOTS",
|
||||
.desc = "Count of floating point execution slots used for"
|
||||
"\n\t\tfinished vector arithmetic Binary Coded Decimal"
|
||||
"\n\t\tinstructions. Instructions: VAP, VSP, VMP, VMSP,"
|
||||
"\n\t\tVDP, VSDP, VRP, VLIP, VSRP, VPSOP, VCP, VTP, VPKZ,"
|
||||
"\n\t\tVUPKZ, VCVB, VCVBG, VCVD, VCVDG, VSCHP, VSCSHP,"
|
||||
"\n\t\tVCSPH, VCLZDP, VPKZR, VSRPR, VUPKZH, VUPKZL, VTZ,"
|
||||
"\n\t\tVUPH, VUPL, VCVBX, VCVDX.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 226,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DECIMAL_INSTRUCTIONS",
|
||||
.desc = "Decimal instruction dispatched. Instructions: CVB,"
|
||||
"\n\t\tCVD, AP, CP, DP, ED, EDMK, MP, SRP, SP, ZAP, TP.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 232,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "LAST_HOST_TRANSLATIONS",
|
||||
.desc = "Last Host Translation done.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 244,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_NC_TABORT",
|
||||
.desc = "A transaction abort has occurred in a non-"
|
||||
"\n\t\tconstrained transactional-execution mode.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 245,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_C_TABORT_NO_SPECIAL",
|
||||
.desc = "A transaction abort has occurred in a constrained"
|
||||
"\n\t\ttransactional-execution mode and the CPU is not"
|
||||
"\n\t\tusing any special logic to allow the transaction to"
|
||||
"\n\t\tcomplete.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 246,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "TX_C_TABORT_SPECIAL",
|
||||
.desc = "A transaction abort has occurred in a constrained"
|
||||
"\n\t\ttransactional-execution mode and the CPU is using"
|
||||
"\n\t\tspecial logic to allow the transaction to complete.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 248,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DFLT_ACCESS",
|
||||
.desc = "Cycles CPU spent obtaining access to Deflate unit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 253,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DFLT_CYCLES",
|
||||
.desc = "Cycles CPU is using Deflate unit.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 256,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "SORTL",
|
||||
.desc = "Increments by one for every SORT LISTS (SORTL)"
|
||||
"\n\t\tinstruction executed.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 265,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DFLT_CC",
|
||||
.desc = "Increments by one for every DEFLATE CONVERSION CALL"
|
||||
"\n\t\t(DFLTCC) instruction executed.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 266,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "DFLT_CCFINISH",
|
||||
.desc = "Increments by one for every DEFLATE CONVERSION CALL"
|
||||
"\n\t\t(DFLTCC) instruction executed that ended in"
|
||||
"\n\t\tCondition Codes 0, 1 or 2.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 267,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_INVOCATIONS",
|
||||
.desc = "Increments by one for every NEURAL NETWORK"
|
||||
"\n\t\tPROCESSING ASSIST (NNPA) instruction executed.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 268,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_COMPLETIONS",
|
||||
.desc = "Increments by one for every NEURAL NETWORK"
|
||||
"\n\t\tPROCESSING ASSIST (NNPA) instruction executed that"
|
||||
"\n\t\tended in Condition Code 0.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 269,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_WAIT_LOCK",
|
||||
.desc = "Cycles CPU spent obtaining access to IBM Z"
|
||||
"\n\t\tIntegrated Accelerator for AI.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 270,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_HOLD_LOCK",
|
||||
.desc = "Cycles CPU is using IBM Z Integrated Accelerator"
|
||||
"\n\t\tfor AI.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 272,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_INST_ONCHIP",
|
||||
.desc = "A NEURAL NETWORK PROCESSING ASSIST (NNPA)"
|
||||
"\n\t\tinstruction has used the Local On-Chip IBM Z"
|
||||
"\n\t\tIntegrated Accelerator for AI during its execution",
|
||||
},
|
||||
{
|
||||
.ctrnum = 273,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_INST_OFFCHIP",
|
||||
.desc = "A NEURAL NETWORK PROCESSING ASSIST (NNPA)"
|
||||
"\n\t\tinstruction has used an Off-Chip IBM Z Integrated"
|
||||
"\n\t\tAccelerator for AI during its execution.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 274,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_INST_DIFF",
|
||||
.desc = "A NEURAL NETWORK PROCESSING ASSIST (NNPA)"
|
||||
"\n\t\tinstruction has used a different IBM Z Integrated"
|
||||
"\n\t\tAccelerator for AI since it was last executed.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 276,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_4K_PREFETCH",
|
||||
.desc = "Number of 4K prefetches done for a remote IBM Z"
|
||||
"\n\t\tIntegated Accelerator for AI.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 277,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_COMPL_LOCK",
|
||||
.desc = "A PERFORM LOCKED OPERATION (PLO) has completed.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 278,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_RETRY_LOCK",
|
||||
.desc = "A PERFORM LOCKED OPERATION (PLO) has been retried and"
|
||||
"\n\t\tthe CPU did not use any special logic to allow the"
|
||||
"\n\t\tPLO to complete.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 279,
|
||||
.ctrset = CPUMF_CTRSET_EXTENDED,
|
||||
.name = "NNPA_RETRY_LOCK_WITH_PLO",
|
||||
.desc = "A PERFORM LOCKED OPERATION (PLO) has been retried and"
|
||||
"\n\t\tthe CPU is using special logic to allow PLO to"
|
||||
"\n\t\tcomplete.",
|
||||
},
|
||||
{
|
||||
.ctrnum = 448,
|
||||
.ctrset = CPUMF_CTRSET_MT_DIAG,
|
||||
.name = "MT_DIAG_CYCLES_ONE_THR_ACTIVE",
|
||||
.desc = "Cycle count with one thread active",
|
||||
},
|
||||
{
|
||||
.ctrnum = 449,
|
||||
.ctrset = CPUMF_CTRSET_MT_DIAG,
|
||||
.name = "MT_DIAG_CYCLES_TWO_THR_ACTIVE",
|
||||
.desc = "Cycle count with two threads active",
|
||||
},
|
||||
};
|
||||
|
||||
/* Return the type number of the CPU Measurement facility from the sysfs file.
|
||||
* If the type number is equal to PERF_TYPE_RAW, then the prefix is 'r' to
|
||||
* specify the raw counter number by the perf tool.
|
||||
@@ -3476,6 +4104,12 @@ static struct counters *get_counter(int ctrset, size_t *len)
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z16_A02:
|
||||
cp = cpumcf_z16_counters;
|
||||
*len = ARRAY_SIZE(cpumcf_z16_counters);
|
||||
break;
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z17:
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z17_2:
|
||||
cp = cpumcf_z17_counters;
|
||||
*len = ARRAY_SIZE(cpumcf_z17_counters);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
375
cpumf/lshwc.c
375
cpumf/lshwc.c
@@ -1,4 +1,4 @@
|
||||
/* Copyright IBM Corp. 2021
|
||||
/* Copyright IBM Corp. 2021, 2024
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
@@ -37,8 +37,9 @@
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_str.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_fmt.h"
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
#include "lshwc.h"
|
||||
@@ -51,18 +52,92 @@
|
||||
|
||||
static unsigned int read_interval = IOCTLSLEEP;
|
||||
static int cfvn, csvn, authorization;
|
||||
static unsigned long loop_count = 1;
|
||||
static unsigned long loop_count = 1, timeout;
|
||||
static unsigned char *ioctlbuffer;
|
||||
static bool allcpu;
|
||||
static char *ctrformat = "%ld";
|
||||
static bool shortname;
|
||||
static bool hideundef;
|
||||
static bool delta, firstread;
|
||||
static int output_format = FMT_CSV;
|
||||
static bool quote_all;
|
||||
static char *ctrlist; /* Comma separated list of counter to extract */
|
||||
|
||||
static unsigned int max_possible_cpus; /* No of possible CPUs */
|
||||
static struct ctrname { /* List of defined counters */
|
||||
char *name; /* Counter name */
|
||||
char *label; /* Output name */
|
||||
bool hitcnt; /* Counter number read from ioctl() */
|
||||
unsigned long total; /* Total counter value */
|
||||
unsigned long *ccv; /* Per CPU counter value */
|
||||
unsigned long *ccvprv; /* Per CPU counter value (previous read) */
|
||||
} ctrname[MAXCTRS];
|
||||
|
||||
struct time_formats {
|
||||
char epoch[32];
|
||||
char date_time[32];
|
||||
char date[16];
|
||||
char time[16];
|
||||
};
|
||||
|
||||
static void mk_labels(void)
|
||||
{
|
||||
char label[64];
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (shortname) {
|
||||
if (ctrname[i].name)
|
||||
snprintf(label, sizeof(label), "%s", ctrname[i].name);
|
||||
else
|
||||
snprintf(label, sizeof(label), "U%ld", i);
|
||||
} else {
|
||||
if (output_format == FMT_CSV)
|
||||
snprintf(label, sizeof(label), "%s(%ld)",
|
||||
ctrname[i].name ?: "Counter", i);
|
||||
else if (ctrname[i].name)
|
||||
snprintf(label, sizeof(label), "%s", ctrname[i].name);
|
||||
else
|
||||
label[0] = 0;
|
||||
}
|
||||
if (output_format != FMT_CSV)
|
||||
util_str_tolower(label);
|
||||
ctrname[i].label = util_strdup(label);
|
||||
}
|
||||
}
|
||||
|
||||
static char *mk_name(int ctr, char *name)
|
||||
{
|
||||
char ctrset[8];
|
||||
|
||||
if (!shortname)
|
||||
return util_strdup(name);
|
||||
|
||||
switch (libcpumf_ctrset(ctr, cfvn, csvn)) {
|
||||
case CPUMF_CTRSET_BASIC:
|
||||
ctrset[0] = 'B';
|
||||
break;
|
||||
case CPUMF_CTRSET_PROBLEM_STATE:
|
||||
ctrset[0] = 'P';
|
||||
break;
|
||||
case CPUMF_CTRSET_CRYPTO:
|
||||
ctrset[0] = 'C';
|
||||
break;
|
||||
case CPUMF_CTRSET_EXTENDED:
|
||||
ctrset[0] = 'E';
|
||||
break;
|
||||
case CPUMF_CTRSET_MT_DIAG:
|
||||
ctrset[0] = 'M';
|
||||
break;
|
||||
default:
|
||||
ctrset[0] = 'U';
|
||||
break;
|
||||
}
|
||||
sprintf(ctrset, "%c%d", ctrset[0], ctr);
|
||||
|
||||
return util_strdup(ctrset);
|
||||
}
|
||||
|
||||
static bool read_counternames(void)
|
||||
{
|
||||
struct dirent **namelist = NULL;
|
||||
@@ -77,9 +152,11 @@ static bool read_counternames(void)
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < count && ctr >= 0; i++) {
|
||||
if (!ctr_in_list(namelist[i]->d_name, ctrlist))
|
||||
continue;
|
||||
util_asprintf(&ctrpath, "%s/%s", path, namelist[i]->d_name);
|
||||
if (util_file_read_va(ctrpath, "event=%x", &ctr) == 1)
|
||||
ctrname[ctr].name = util_strdup(namelist[i]->d_name);
|
||||
ctrname[ctr].name = mk_name(ctr, namelist[i]->d_name);
|
||||
else
|
||||
warnx("Cannot parse %s", ctrpath);
|
||||
free(ctrpath);
|
||||
@@ -93,7 +170,9 @@ static void free_counternames(void)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
free(ctrname[i].name);
|
||||
free(ctrname[i].label);
|
||||
free(ctrname[i].ccv);
|
||||
free(ctrname[i].ccvprv);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,77 +377,111 @@ static bool check_setpossible(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void show_header(void)
|
||||
static void safe_strtime(char *dest, size_t size, const char *fmt, const struct tm *tm)
|
||||
{
|
||||
static bool header;
|
||||
bool comma = false;
|
||||
|
||||
if (header)
|
||||
return; /* Printed already */
|
||||
printf("Date,Time,CPU,"); /* Print counter name and number */
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf("%s(%ld)", ctrname[i].name ?: "Counter", i);
|
||||
comma = true;
|
||||
}
|
||||
putchar('\n');
|
||||
header = true;
|
||||
if (!strftime(dest, size, fmt, tm))
|
||||
dest[0] = 0;
|
||||
}
|
||||
|
||||
static void line(char *header)
|
||||
static void generate_timestamp(struct time_formats *date)
|
||||
{
|
||||
bool comma;
|
||||
time_t now = time(NULL);
|
||||
struct tm *now_tm = localtime(&now);
|
||||
|
||||
show_header();
|
||||
if (allcpu) {
|
||||
for (unsigned int h = 0; h < max_possible_cpus; ++h) {
|
||||
char txt[16];
|
||||
safe_strtime(date->date_time, sizeof(date->date_time), "%F %T%z", now_tm);
|
||||
safe_strtime(date->date, sizeof(date->date), "%F", now_tm);
|
||||
safe_strtime(date->time, sizeof(date->time), "%T", now_tm);
|
||||
safe_strtime(date->epoch, sizeof(date->epoch), "%s", now_tm);
|
||||
}
|
||||
|
||||
if (!check[h].cpu_hit)
|
||||
continue;
|
||||
comma = false;
|
||||
snprintf(txt, sizeof(txt), "CPU%d,", h);
|
||||
printf("%s%s", header, txt);
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf("%ld", ctrname[i].ccv[h]);
|
||||
comma = true;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
static void output_times(struct time_formats date)
|
||||
{
|
||||
if (output_format == FMT_CSV) {
|
||||
util_fmt_pair(FMT_PERSIST, "Date", "%s", date.date);
|
||||
util_fmt_pair(FMT_PERSIST, "Time", "%s", date.time);
|
||||
} else {
|
||||
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "date_time", "%s", date.date_time);
|
||||
util_fmt_pair(FMT_PERSIST, "time_epoch", "%s", date.epoch);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print total count of all CPUs */
|
||||
printf("%sTotal,", header);
|
||||
comma = false;
|
||||
static void prepare_counter(size_t id, unsigned long value)
|
||||
{
|
||||
if (output_format == FMT_CSV) {
|
||||
util_fmt_pair(FMT_PERSIST, ctrname[id].label, ctrformat, value);
|
||||
} else {
|
||||
util_fmt_obj_start(FMT_ROW, NULL);
|
||||
if (strlen(ctrname[id].label))
|
||||
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "name", ctrname[id].label);
|
||||
util_fmt_pair(FMT_PERSIST, "id", ctrformat, id);
|
||||
util_fmt_pair(FMT_PERSIST, "value", ctrformat, value);
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
}
|
||||
|
||||
static void output_per_cpu(struct time_formats date)
|
||||
{
|
||||
for (unsigned int h = 0; h < max_possible_cpus; ++h) {
|
||||
if (!check[h].cpu_hit)
|
||||
continue;
|
||||
|
||||
char txt[16];
|
||||
|
||||
snprintf(txt, sizeof(txt), "CPU%d", h);
|
||||
util_fmt_obj_start(FMT_ROW, "cpu_%d", h);
|
||||
output_times(date);
|
||||
if (output_format == FMT_CSV) {
|
||||
util_fmt_pair(FMT_PERSIST, "CPU", "CPU%d", h);
|
||||
} else {
|
||||
util_fmt_pair(FMT_PERSIST, "cpu", "%d", h);
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
continue;
|
||||
prepare_counter(i, ctrname[i].ccv[h]);
|
||||
}
|
||||
if (output_format != FMT_CSV)
|
||||
util_fmt_obj_end();
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
}
|
||||
|
||||
static void output_total(struct time_formats date)
|
||||
{
|
||||
util_fmt_obj_start(FMT_ROW, "total");
|
||||
output_times(date);
|
||||
if (output_format == FMT_CSV) {
|
||||
util_fmt_pair(FMT_PERSIST, "CPU", "%s", delta && !firstread ? "Delta" : "Total");
|
||||
} else {
|
||||
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, "cpu", "%s",
|
||||
delta && !firstread ? "delta" : "total");
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf("%ld", ctrname[i].total);
|
||||
comma = true;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
continue;
|
||||
prepare_counter(i, ctrname[i].total);
|
||||
ctrname[i].total = 0;
|
||||
ctrname[i].hitcnt = false;
|
||||
}
|
||||
putchar('\n');
|
||||
if (output_format != FMT_CSV)
|
||||
util_fmt_obj_end();
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
|
||||
static void show(void)
|
||||
static void show_format(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
struct tm *now_tm;
|
||||
char now_text[32];
|
||||
struct time_formats now;
|
||||
|
||||
now_tm = localtime(&now);
|
||||
strftime(now_text, sizeof(now_text), "%F,%T,", now_tm);
|
||||
line(now_text);
|
||||
generate_timestamp(&now);
|
||||
if (allcpu)
|
||||
output_per_cpu(now);
|
||||
output_total(now);
|
||||
}
|
||||
|
||||
/* Return Counter set size numbers (in counters) */
|
||||
@@ -442,11 +555,18 @@ static bool add_countervalue(size_t idx, unsigned int cpu, unsigned long value)
|
||||
warnx("Invalid CPU number %d", cpu);
|
||||
return false;
|
||||
}
|
||||
if (!ctrname[idx].ccv) /* Unknown counter */
|
||||
ctrname[idx].ccv = calloc(max_possible_cpus,
|
||||
sizeof(unsigned long));
|
||||
if (ctrname[idx].ccv)
|
||||
if (delta) {
|
||||
if (firstread) {
|
||||
ctrname[idx].ccvprv[cpu] = value;
|
||||
ctrname[idx].ccv[cpu] = value;
|
||||
} else {
|
||||
ctrname[idx].ccv[cpu] = value - ctrname[idx].ccvprv[cpu];
|
||||
ctrname[idx].ccvprv[cpu] = value;
|
||||
value = ctrname[idx].ccv[cpu];
|
||||
}
|
||||
} else {
|
||||
ctrname[idx].ccv[cpu] = value;
|
||||
}
|
||||
ctrname[idx].total += value;
|
||||
ctrname[idx].hitcnt = true;
|
||||
return true;
|
||||
@@ -493,7 +613,8 @@ static int test_read(struct s390_hwctr_read *read)
|
||||
}
|
||||
}
|
||||
}
|
||||
show();
|
||||
show_format();
|
||||
firstread = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -556,6 +677,7 @@ static void do_sleep(void)
|
||||
static int do_it(char *s)
|
||||
{
|
||||
struct s390_hwctr_start start;
|
||||
unsigned int flags = FMT_WARN;
|
||||
int ioctlfd;
|
||||
int rc;
|
||||
|
||||
@@ -575,6 +697,24 @@ static int do_it(char *s)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (output_format == FMT_CSV)
|
||||
flags |= FMT_NOMETA;
|
||||
if (output_format == FMT_JSON || output_format == FMT_JSONSEQ)
|
||||
flags |= FMT_HANDLEINT;
|
||||
if (quote_all)
|
||||
flags |= FMT_QUOTEALL;
|
||||
|
||||
mk_labels();
|
||||
util_fmt_init(stdout, output_format, flags, 1);
|
||||
util_fmt_obj_start(FMT_DEFAULT, "lshwc");
|
||||
if (output_format == FMT_JSON || output_format == FMT_JSONSEQ) {
|
||||
util_fmt_obj_start(FMT_ROW, "cpumcf info");
|
||||
util_fmt_pair(FMT_PERSIST, "counter first", "%d", cfvn);
|
||||
util_fmt_pair(FMT_PERSIST, "counter second", "%d", csvn);
|
||||
util_fmt_pair(FMT_PERSIST, "authorization", "%d", authorization);
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
util_fmt_obj_start(FMT_LIST, "measurements");
|
||||
for (unsigned long i = 0; !rc && i < loop_count; ++i) {
|
||||
rc = do_read(ioctlfd);
|
||||
if (rc) {
|
||||
@@ -584,6 +724,9 @@ static int do_it(char *s)
|
||||
if (read_interval && i + 1 < loop_count)
|
||||
do_sleep();
|
||||
}
|
||||
util_fmt_obj_end();
|
||||
util_fmt_obj_end();
|
||||
util_fmt_exit();
|
||||
rc = do_stop(ioctlfd);
|
||||
close(ioctlfd);
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
@@ -605,6 +748,46 @@ static struct util_opt opt_vec[] = {
|
||||
.argument = "NUMBER",
|
||||
.desc = "Specifies interval between read operations (seconds)"
|
||||
},
|
||||
{
|
||||
.option = { "short", no_argument, NULL, 's' },
|
||||
.desc = "Abbreviate counter name with counter set letter and number"
|
||||
},
|
||||
{
|
||||
.option = { "hex0x", no_argument, NULL, 'X' },
|
||||
.desc = "Counter values in hexadecimal format with leading 0x"
|
||||
},
|
||||
{
|
||||
.option = { "hex", no_argument, NULL, 'x' },
|
||||
.desc = "Counter values in hexadecimal format"
|
||||
},
|
||||
{
|
||||
.option = { "hide", no_argument, NULL, 'H' },
|
||||
.desc = "Do not display undefined counters of a counter set"
|
||||
},
|
||||
{
|
||||
.option = { "delta", no_argument, NULL, 'd' },
|
||||
.desc = "Display delta counter values"
|
||||
},
|
||||
{
|
||||
.option = { "timeout", required_argument, NULL, 't' },
|
||||
.argument = "NUMBER",
|
||||
.desc = "run time in s (seconds) m (minutes) h (hours) and d (days)"
|
||||
},
|
||||
{
|
||||
.option = { "quote-all", no_argument, NULL, 'q' },
|
||||
.desc = "Apply quoting to all output elements"
|
||||
},
|
||||
{
|
||||
.option = { "format", required_argument, NULL, 'f' },
|
||||
.argument = "FORMAT",
|
||||
.desc = "List counters in specified FORMAT (" FMT_TYPE_NAMES ")"
|
||||
},
|
||||
{
|
||||
.option = { "counters", required_argument, NULL, 'c' },
|
||||
.argument = "LIST",
|
||||
.flags = UTIL_OPT_FLAG_NOSHORT,
|
||||
.desc = "Specify comma separated list of counters to display"
|
||||
},
|
||||
UTIL_OPT_HELP,
|
||||
UTIL_OPT_VERSION,
|
||||
UTIL_OPT_END
|
||||
@@ -634,6 +817,8 @@ static void have_support(void)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
enum util_fmt_t fmt;
|
||||
unsigned long no;
|
||||
char *slash;
|
||||
int ch;
|
||||
|
||||
@@ -665,12 +850,71 @@ int main(int argc, char **argv)
|
||||
if (errno || *slash)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
|
||||
break;
|
||||
case 'H':
|
||||
hideundef = true;
|
||||
break;
|
||||
case 's':
|
||||
shortname = true;
|
||||
break;
|
||||
case 'x':
|
||||
ctrformat = "%lx";
|
||||
break;
|
||||
case 'X':
|
||||
ctrformat = "%#lx";
|
||||
break;
|
||||
case 'a':
|
||||
allcpu = true;
|
||||
break;
|
||||
case 'd':
|
||||
delta = true;
|
||||
firstread = true;
|
||||
break;
|
||||
case 't':
|
||||
errno = 0;
|
||||
no = strtoul(optarg, &slash, 0);
|
||||
if (errno)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
|
||||
switch (*slash) {
|
||||
case 's':
|
||||
case '\0':
|
||||
timeout += no;
|
||||
break;
|
||||
case 'm':
|
||||
timeout += no * 60;
|
||||
break;
|
||||
case 'h':
|
||||
timeout += no * 60 * 60;
|
||||
break;
|
||||
case 'd':
|
||||
timeout += no * 60 * 60 * 24;
|
||||
break;
|
||||
default:
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
quote_all = true;
|
||||
break;
|
||||
case 'f':
|
||||
if (!util_fmt_name_to_type(optarg, &fmt))
|
||||
errx(EXIT_FAILURE, "Supported formats:" FMT_TYPE_NAMES);
|
||||
output_format = fmt;
|
||||
break;
|
||||
case 'c':
|
||||
hideundef = true;
|
||||
ctrlist = util_strdup(optarg);
|
||||
util_str_rm_whitespace(optarg, ctrlist);
|
||||
util_str_toupper(ctrlist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout && timeout < read_interval)
|
||||
read_interval = timeout;
|
||||
/* If no timeout specified, simply add zero */
|
||||
loop_count += timeout / read_interval;
|
||||
|
||||
have_support();
|
||||
if (!libcpumf_cpumcf_info(&cfvn, &csvn, &authorization))
|
||||
return EXIT_FAILURE;
|
||||
@@ -681,6 +925,11 @@ int main(int argc, char **argv)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
ctrname[i].ccv = util_zalloc(max_possible_cpus * sizeof(unsigned long));
|
||||
ctrname[i].ccvprv = util_zalloc(max_possible_cpus * sizeof(unsigned long));
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
ch = do_it(NULL);
|
||||
} else {
|
||||
@@ -692,5 +941,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
free_counternames();
|
||||
free(check);
|
||||
free(ioctlbuffer);
|
||||
free(ctrlist);
|
||||
return ch;
|
||||
}
|
||||
|
||||
544
cpumf/lspai.c
544
cpumf/lspai.c
@@ -6,7 +6,6 @@
|
||||
|
||||
/* List available Processor Assist Instrumentation (PAI) counters. */
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
@@ -17,29 +16,84 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/time.h>
|
||||
#include <linux/perf_event.h>
|
||||
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_prg.h"
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_fmt.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_list.h"
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_prg.h"
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/util_str.h"
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
#define STR_SUB(x) #x
|
||||
#define STR(x) STR_SUB(x)
|
||||
|
||||
#define OPT_FORMAT 256 /* --format XXX option */
|
||||
#define DEFAULT_LOOP_INTERVAL 60 /* loop interval in seconds */
|
||||
|
||||
static struct util_opt opt_vec[] = {
|
||||
UTIL_OPT_SECTION("OPTIONS"),
|
||||
{
|
||||
.option = { "all", no_argument, NULL, 'a' },
|
||||
.desc = "Displays all CPUs in output"
|
||||
},
|
||||
{
|
||||
.option = { "delta", no_argument, NULL, 'd' },
|
||||
.desc = "Display delta counter values"
|
||||
},
|
||||
{
|
||||
.option = { "counters", required_argument, NULL, 'c' },
|
||||
.argument = "LIST",
|
||||
.desc = "Specify comma separated list of counters to display"
|
||||
},
|
||||
{
|
||||
.option = { "format", required_argument, NULL, OPT_FORMAT },
|
||||
.argument = "FORMAT",
|
||||
.flags = UTIL_OPT_FLAG_NOSHORT,
|
||||
.desc = "List counters in specified FORMAT (" FMT_TYPE_NAMES ")"
|
||||
},
|
||||
{
|
||||
.option = { "loops", required_argument, NULL, 'l' },
|
||||
.argument = "COUNT",
|
||||
.desc = "Number of read operations"
|
||||
},
|
||||
{
|
||||
.option = { "interval", required_argument, NULL, 'i' },
|
||||
.argument = "SECONDS",
|
||||
.desc = "Time to wait between loop iterations (default "
|
||||
STR(DEFAULT_LOOP_INTERVAL) "s)"
|
||||
},
|
||||
{
|
||||
.option = { "numeric", no_argument, NULL, 'n' },
|
||||
.desc = "Sort PAI counters by counter number"
|
||||
},
|
||||
{
|
||||
.option = { "short", no_argument, NULL, 's' },
|
||||
.desc = "Abbreviate counter name with counter set letter and number"
|
||||
},
|
||||
{
|
||||
.option = { "type", required_argument, NULL, 't' },
|
||||
.argument = "TYPE",
|
||||
.desc = "Type of PAI counters to show: crypto, nnpa"
|
||||
},
|
||||
{
|
||||
.option = { "hex0x", no_argument, NULL, 'X' },
|
||||
.desc = "Counter values in hexadecimal format with leading 0x"
|
||||
},
|
||||
{
|
||||
.option = { "hex", no_argument, NULL, 'x' },
|
||||
.desc = "Counter values in hexadecimal format"
|
||||
},
|
||||
UTIL_OPT_HELP,
|
||||
UTIL_OPT_VERSION,
|
||||
UTIL_OPT_END
|
||||
@@ -51,13 +105,24 @@ static const struct util_prg prg = {
|
||||
{
|
||||
.owner = "IBM Corp.",
|
||||
.pub_first = 2023,
|
||||
.pub_last = 2023,
|
||||
.pub_last = 2025,
|
||||
},
|
||||
UTIL_PRG_COPYRIGHT_END
|
||||
}
|
||||
};
|
||||
|
||||
static bool allcpu; /* Show counter values per CPU */
|
||||
static bool numsort; /* If true sort counter numerically */
|
||||
static bool shortname; /* Use abbreviated counter names */
|
||||
static bool delta, firstread; /* Display delta values */
|
||||
static int output_format = -1; /* Generate style if >= 0 */
|
||||
static unsigned int max_cpus; /* # of CPUs to read counter values from */
|
||||
static unsigned int max_fds; /* # of file descriptor to read counter values */
|
||||
static unsigned long loops; /* # loops */
|
||||
static unsigned long read_interval = DEFAULT_LOOP_INTERVAL;
|
||||
static cpu_set_t cpu_online_mask;
|
||||
static char *ctrformat = "%ld"; /* Default counter output format */
|
||||
static char *ctrlist; /* Comma separated list of counter to extract */
|
||||
|
||||
#define PAI_PATH "/bus/event_source/devices/%s"
|
||||
|
||||
@@ -69,9 +134,18 @@ enum pai_types { /* Bit mask for supported PAI counters */
|
||||
|
||||
static int pai_types_show;
|
||||
|
||||
struct pai_cpudata { /* Event data per CPU */
|
||||
int fd; /* Event file descriptor */
|
||||
int cpu; /* CPU number */
|
||||
unsigned long value; /* Event value */
|
||||
unsigned long prev_value; /* Previous value for deltas */
|
||||
};
|
||||
|
||||
struct pai_ctrname { /* List of defined counters */
|
||||
char *name; /* Counter name */
|
||||
unsigned long nr; /* Counter number */
|
||||
unsigned long total; /* Total count on all CPus */
|
||||
struct pai_cpudata *data; /* Counter data per CPU */
|
||||
};
|
||||
|
||||
struct pai_node { /* Head for PAI counter sets */
|
||||
@@ -104,6 +178,20 @@ static unsigned long pai_type_base(enum pai_types t)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return character for this counter set. */
|
||||
static char pai_type_char(enum pai_types t)
|
||||
{
|
||||
switch (t) {
|
||||
case pai_type_crypto:
|
||||
return 'C';
|
||||
case pai_type_nnpa:
|
||||
return 'N';
|
||||
case pai_type_max:
|
||||
break;
|
||||
}
|
||||
return 'U';
|
||||
}
|
||||
|
||||
/* Test PAI counter name from command line option. */
|
||||
static const char *pai_type_name(enum pai_types t)
|
||||
{
|
||||
@@ -145,16 +233,6 @@ static int pai_ctrcmp(const void *p1, const void *p2)
|
||||
return l->nr > r->nr ? 1 : -1;
|
||||
}
|
||||
|
||||
/* Convert string to upper case. */
|
||||
static char *str2uc(const char *s)
|
||||
{
|
||||
char *uc = util_strdup(s), *old_uc = uc;
|
||||
|
||||
for (; *uc; ++uc)
|
||||
*uc = toupper(*uc);
|
||||
return old_uc;
|
||||
}
|
||||
|
||||
/* Read counter names and assigned event number from sysfs file tree.
|
||||
* Exit when sysfs directory can not be scanned.
|
||||
*/
|
||||
@@ -162,7 +240,7 @@ static void read_counternames(struct pai_node *node)
|
||||
{
|
||||
int i, more = 0, ctr = 0, count = 0;
|
||||
struct dirent **namelist = NULL;
|
||||
char *path, *ctrpath;
|
||||
char *path, *ctrpath, sname[128];
|
||||
|
||||
/* Read counter names and assigned event number. */
|
||||
path = util_path_sysfs(PAI_PATH "/events", node->sysfs_name);
|
||||
@@ -175,9 +253,18 @@ static void read_counternames(struct pai_node *node)
|
||||
for (i = 0; i < count && ctr >= 0; i++) {
|
||||
util_asprintf(&ctrpath, "%s/%s", path, namelist[i]->d_name);
|
||||
if (util_file_read_va(ctrpath, "event=%x", &ctr) == 1) {
|
||||
snprintf(sname, sizeof(sname), "%c%ld",
|
||||
pai_type_char(node->type), ctr - node->base);
|
||||
if (!ctr_in_list(sname, ctrlist) &&
|
||||
!ctr_in_list(namelist[i]->d_name, ctrlist)) {
|
||||
/* Counter not listed in --counters option */
|
||||
continue;
|
||||
}
|
||||
node->ctrlist[node->ctridx].data = NULL;
|
||||
node->ctrlist[node->ctridx].name = util_strdup(namelist[i]->d_name);
|
||||
node->ctrlist[node->ctridx++].nr = ctr;
|
||||
more++;
|
||||
max_fds++;
|
||||
} else {
|
||||
warnx("Cannot parse %s", ctrpath);
|
||||
}
|
||||
@@ -190,12 +277,47 @@ static void read_counternames(struct pai_node *node)
|
||||
qsort(node->ctrlist, more, sizeof(*node->ctrlist), pai_ctrcmp);
|
||||
}
|
||||
|
||||
static void show_painode(void)
|
||||
static void format_painode(enum util_fmt_t fmt)
|
||||
{
|
||||
struct pai_node *node;
|
||||
|
||||
util_fmt_init(stdout, fmt, FMT_HANDLEINT, 1);
|
||||
util_fmt_obj_start(FMT_DEFAULT, NULL);
|
||||
util_list_iterate(&pai_list, node) {
|
||||
util_fmt_obj_start(FMT_DEFAULT, "pmu");
|
||||
util_fmt_pair(FMT_PERSIST, "base", "%d", node->base);
|
||||
util_fmt_pair(FMT_PERSIST, "type", "%d", node->pmu);
|
||||
util_fmt_pair(FMT_QUOTE | FMT_PERSIST, "pmu-name", "%s", node->sysfs_name);
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
util_fmt_obj_start(FMT_ROW, "counter");
|
||||
util_fmt_pair(FMT_QUOTE, "name", "%s", node->ctrlist[i].name);
|
||||
if (shortname)
|
||||
util_fmt_pair(FMT_QUOTE, "name", "%c%d",
|
||||
pai_type_char(node->type),
|
||||
node->ctrlist[i].nr - node->base);
|
||||
util_fmt_pair(FMT_DEFAULT, "config", "%d", node->ctrlist[i].nr);
|
||||
util_fmt_pair(FMT_DEFAULT, "id", "%d", node->ctrlist[i].nr - node->base);
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
util_fmt_obj_end(); /* Counters */
|
||||
util_fmt_obj_end(); /* PMU */
|
||||
}
|
||||
util_fmt_obj_end();
|
||||
util_fmt_exit();
|
||||
}
|
||||
|
||||
static void list_painode(void)
|
||||
{
|
||||
struct pai_node *node;
|
||||
int indent = 0;
|
||||
int offset = 0;
|
||||
|
||||
if (output_format != -1) {
|
||||
format_painode(output_format);
|
||||
return;
|
||||
}
|
||||
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i)
|
||||
indent = MAX((size_t)indent, strlen(node->ctrlist[i].name));
|
||||
@@ -223,8 +345,10 @@ static void free_painode(void)
|
||||
|
||||
util_list_iterate_safe(&pai_list, node, next) {
|
||||
free(node->name_uc);
|
||||
for (int i = 0; i < node->ctridx; ++i)
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
free(node->ctrlist[i].name);
|
||||
free(node->ctrlist[i].data);
|
||||
}
|
||||
free(node->ctrlist);
|
||||
free(node);
|
||||
}
|
||||
@@ -238,12 +362,13 @@ static void make_painode(enum pai_types t)
|
||||
node->type = t;
|
||||
node->sysfs_name = pai_type_sysfs(t);
|
||||
node->name = pai_type_name(t);
|
||||
node->name_uc = str2uc(node->name);
|
||||
node->name_uc = util_strdup(node->name);
|
||||
util_str_toupper(node->name_uc);
|
||||
node->filter_name = pai_type_filter(t);
|
||||
node->base = pai_type_base(t);
|
||||
|
||||
/* Read PMU type number. */
|
||||
path = util_path_sysfs(PAI_PATH, node->sysfs_name);
|
||||
util_asprintf(&path, PAI_PATH, node->sysfs_name);
|
||||
node->pmu = libcpumf_pmutype(path);
|
||||
if (node->pmu < 0)
|
||||
errx(EXIT_FAILURE, "Cannot open %s", path);
|
||||
@@ -267,6 +392,267 @@ static void sort_painode(void)
|
||||
util_list_sort(&pai_list, painode_cmp, NULL);
|
||||
}
|
||||
|
||||
/* Read counter value. */
|
||||
static unsigned long event_read(int fd)
|
||||
{
|
||||
unsigned long count;
|
||||
int rc;
|
||||
|
||||
rc = read(fd, &count, sizeof(count));
|
||||
if (rc != sizeof(count))
|
||||
err(EXIT_FAILURE, "Failed to read counter value");
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Write header. */
|
||||
static void line_header(void)
|
||||
{
|
||||
struct pai_node *node;
|
||||
static bool header;
|
||||
bool comma = false;
|
||||
|
||||
if (header)
|
||||
return; /* Printed already */
|
||||
printf("Date,Time,CPU,"); /* Print counter name and number */
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
if (comma)
|
||||
putchar(',');
|
||||
if (shortname) {
|
||||
printf("%c%ld", pai_type_char(node->type),
|
||||
node->ctrlist[i].nr - node->base);
|
||||
} else {
|
||||
printf("%s(%ld)", node->ctrlist[i].name ?: node->name_uc,
|
||||
node->ctrlist[i].nr - node->base);
|
||||
}
|
||||
comma = true;
|
||||
}
|
||||
}
|
||||
putchar('\n');
|
||||
header = true;
|
||||
}
|
||||
|
||||
/* Print CPU specific counter values. */
|
||||
static void line_cpu(char *header)
|
||||
{
|
||||
struct pai_cpudata *data;
|
||||
struct pai_node *node;
|
||||
bool comma;
|
||||
char txt[16];
|
||||
|
||||
for (unsigned int h = 0; h < max_cpus; ++h) {
|
||||
comma = false;
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
data = &node->ctrlist[i].data[h];
|
||||
if (!comma) {
|
||||
snprintf(txt, sizeof(txt), "CPU%d,", data->cpu);
|
||||
printf("%s,%s", header, txt);
|
||||
} else {
|
||||
putchar(',');
|
||||
}
|
||||
printf(ctrformat, data->value);
|
||||
comma = true;
|
||||
}
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/* Write an output line. */
|
||||
static void line_out(char *header)
|
||||
{
|
||||
struct pai_node *node;
|
||||
bool comma;
|
||||
|
||||
line_header();
|
||||
if (allcpu)
|
||||
line_cpu(header);
|
||||
|
||||
/* Print total count of all CPUs */
|
||||
printf("%s,%s,", header, delta && !firstread ? "Delta" : "Total");
|
||||
comma = false;
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
if (comma)
|
||||
putchar(',');
|
||||
printf(ctrformat, node->ctrlist[i].total);
|
||||
comma = true;
|
||||
}
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
/* Write a formatted line. */
|
||||
static void format_line_out(time_t now, char *now_text)
|
||||
{
|
||||
static unsigned int called;
|
||||
struct pai_node *node;
|
||||
char cpuid[16];
|
||||
|
||||
if (!called) {
|
||||
util_fmt_init(stdout, output_format, FMT_DEFAULT | FMT_HANDLEINT, 1);
|
||||
util_fmt_obj_start(FMT_DEFAULT, NULL);
|
||||
util_fmt_obj_start(FMT_LIST, "measurements");
|
||||
}
|
||||
util_list_iterate(&pai_list, node) {
|
||||
if (!node->ctridx) /* Counter set not selected */
|
||||
continue;
|
||||
util_fmt_obj_start(FMT_DEFAULT, "entry");
|
||||
util_fmt_pair(FMT_PERSIST, "iteration", "%d", called++);
|
||||
util_fmt_pair(FMT_PERSIST, "time_epoch", "%d", now);
|
||||
util_fmt_pair(FMT_QUOTE | FMT_PERSIST, "time", "%s", now_text);
|
||||
util_fmt_pair(FMT_QUOTE | FMT_PERSIST, "valuetype",
|
||||
(delta && !firstread) ? "delta" : "total");
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
util_fmt_obj_start(FMT_ROW, "counter");
|
||||
util_fmt_pair(FMT_QUOTE, "name", "%s", node->ctrlist[i].name);
|
||||
if (shortname)
|
||||
util_fmt_pair(FMT_QUOTE, "shortname", "%c%d",
|
||||
pai_type_char(node->type),
|
||||
node->ctrlist[i].nr - node->base);
|
||||
util_fmt_pair(FMT_DEFAULT, "config", "%d", node->ctrlist[i].nr);
|
||||
util_fmt_pair(FMT_DEFAULT, "id", "%d", node->ctrlist[i].nr - node->base);
|
||||
util_fmt_pair(FMT_DEFAULT, "value", ctrformat, node->ctrlist[i].total);
|
||||
if (allcpu) {
|
||||
for (unsigned int j = 0; j < max_cpus; ++j) {
|
||||
snprintf(cpuid, sizeof(cpuid), "cpu%d", j);
|
||||
util_fmt_pair(FMT_DEFAULT, cpuid, ctrformat,
|
||||
node->ctrlist[i].data[j].value);
|
||||
}
|
||||
}
|
||||
util_fmt_obj_end();
|
||||
}
|
||||
util_fmt_obj_end(); /* Counters */
|
||||
util_fmt_obj_end(); /* Entry */
|
||||
}
|
||||
}
|
||||
|
||||
/* Terminate formatted output. */
|
||||
static void format_line_end(void)
|
||||
{
|
||||
util_fmt_obj_end(); /* Iteration */
|
||||
util_fmt_obj_end(); /* Default */
|
||||
util_fmt_exit();
|
||||
}
|
||||
|
||||
/* Display counter values. */
|
||||
static void show_values(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
struct tm *now_tm;
|
||||
char now_text[32];
|
||||
|
||||
now_tm = localtime(&now);
|
||||
if (output_format != -1) {
|
||||
strftime(now_text, sizeof(now_text), "%F %T%z", now_tm);
|
||||
format_line_out(now, now_text);
|
||||
} else {
|
||||
strftime(now_text, sizeof(now_text), "%F,%T", now_tm);
|
||||
line_out(now_text);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read each counter value. */
|
||||
static void read_painode(void)
|
||||
{
|
||||
struct pai_cpudata *data;
|
||||
struct pai_node *node;
|
||||
unsigned long value;
|
||||
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
node->ctrlist[i].total = 0;
|
||||
for (size_t j = 0; j < max_cpus; ++j) {
|
||||
data = &node->ctrlist[i].data[j];
|
||||
value = event_read(data->fd);
|
||||
if (delta) {
|
||||
data->value = value - data->prev_value;
|
||||
data->prev_value = value;
|
||||
} else {
|
||||
data->value = value;
|
||||
}
|
||||
node->ctrlist[i].total += data->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void wait_painode(void)
|
||||
{
|
||||
for (unsigned long i = 0; i < loops; ++i) {
|
||||
read_painode();
|
||||
show_values();
|
||||
if (i + 1 < loops)
|
||||
sleep(read_interval);
|
||||
firstread = false;
|
||||
}
|
||||
format_line_end();
|
||||
}
|
||||
|
||||
/* Install one event. */
|
||||
static int event_add(int cpu, int idx, struct pai_node *node)
|
||||
{
|
||||
struct perf_event_attr attr;
|
||||
int fd;
|
||||
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
attr.size = sizeof(attr);
|
||||
attr.config = node->ctrlist[idx].nr;
|
||||
attr.type = node->pmu;
|
||||
fd = perf_event_open(&attr, -1, cpu, -1, 0);
|
||||
if (fd == -1)
|
||||
err(EXIT_FAILURE, "Failed to open perf event: file descriptor not available");
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* Increase number of file descriptors this process can open. */
|
||||
static void event_fdlimit(void)
|
||||
{
|
||||
unsigned int needed = 3 + max_fds * max_cpus;
|
||||
struct rlimit rlimit;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE, &rlimit) == -1)
|
||||
err(EXIT_FAILURE, "Failed to read RLIMIT_NOFILE");
|
||||
if (needed > rlimit.rlim_cur)
|
||||
rlimit.rlim_cur = needed;
|
||||
if (setrlimit(RLIMIT_NOFILE, &rlimit) == -1)
|
||||
err(EXIT_FAILURE, "Failed to set RLIMIT_NOFILE");
|
||||
}
|
||||
|
||||
/* Install all events and iterate over requested read operations. */
|
||||
static void event_painode(void)
|
||||
{
|
||||
size_t pai_cpudata_sz = sizeof(struct pai_cpudata) * max_cpus;
|
||||
struct pai_cpudata *data;
|
||||
struct pai_node *node;
|
||||
|
||||
event_fdlimit();
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
node->ctrlist[i].data = util_malloc(pai_cpudata_sz);
|
||||
data = node->ctrlist[i].data;
|
||||
for (unsigned int j = 0; j < CPU_SETSIZE; ++j) {
|
||||
if (CPU_ISSET(j, &cpu_online_mask)) {
|
||||
data->cpu = j;
|
||||
data->fd = event_add(j, i, node);
|
||||
data->value = 0;
|
||||
data->prev_value = 0;
|
||||
++data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wait_painode();
|
||||
util_list_iterate(&pai_list, node) {
|
||||
for (int i = 0; i < node->ctridx; ++i) {
|
||||
for (unsigned int j = 0; j < max_cpus; ++j)
|
||||
close(node->ctrlist[i].data[j].fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for hardware support and return false if not available. */
|
||||
static bool have_support(enum pai_types t)
|
||||
{
|
||||
@@ -303,8 +689,40 @@ static void check_type_name(const char *type)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -t %s", type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get list of specified CPUs from command line. Check if these CPUs
|
||||
* exist and are online. Ignore those CPUs which are not available and
|
||||
* issue one warning when CPUs have been specified but are not online.
|
||||
*/
|
||||
static void get_cpulist(char *parm)
|
||||
{
|
||||
bool warned = false;
|
||||
cpu_set_t cpulist;
|
||||
int i, rc;
|
||||
|
||||
CPU_ZERO(&cpulist);
|
||||
rc = libcpumf_cpuset(parm, &cpulist);
|
||||
if (rc)
|
||||
err(EXIT_FAILURE, "Cannot parse cpulist %s", parm);
|
||||
for (i = 0; i < CPU_SETSIZE; ++i) {
|
||||
if (CPU_ISSET(i, &cpulist) && !CPU_ISSET(i, &cpu_online_mask)) {
|
||||
if (!warned) {
|
||||
warnx("some CPU(s) are offline, ignored");
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
if (!CPU_ISSET(i, &cpulist) && CPU_ISSET(i, &cpu_online_mask))
|
||||
CPU_CLR(i, &cpu_online_mask);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool list_only = true;
|
||||
enum util_fmt_t fmt;
|
||||
bool i_flag = false;
|
||||
bool l_flag = false;
|
||||
char *endchar;
|
||||
int ch;
|
||||
|
||||
util_list_init(&pai_list, struct pai_node, node);
|
||||
@@ -316,6 +734,15 @@ int main(int argc, char **argv)
|
||||
default:
|
||||
util_opt_print_parse_error(ch, argv);
|
||||
return EXIT_FAILURE;
|
||||
case 'a':
|
||||
allcpu = true;
|
||||
list_only = false;
|
||||
break;
|
||||
case 'd':
|
||||
delta = true;
|
||||
firstread = true;
|
||||
list_only = false;
|
||||
break;
|
||||
case 'h':
|
||||
util_prg_print_help();
|
||||
util_opt_print_help();
|
||||
@@ -323,16 +750,80 @@ int main(int argc, char **argv)
|
||||
case 'v':
|
||||
util_prg_print_version();
|
||||
return EXIT_SUCCESS;
|
||||
case 'i':
|
||||
i_flag = true;
|
||||
list_only = false;
|
||||
errno = 0;
|
||||
read_interval = strtoul(optarg, &endchar, 0);
|
||||
if (errno || *endchar)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
|
||||
break;
|
||||
case 'l':
|
||||
l_flag = true;
|
||||
list_only = false;
|
||||
errno = 0;
|
||||
loops = strtoul(optarg, &endchar, 0);
|
||||
if (errno || *endchar)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
|
||||
break;
|
||||
case 'n':
|
||||
numsort = true;
|
||||
break;
|
||||
case 's':
|
||||
list_only = false;
|
||||
shortname = true;
|
||||
break;
|
||||
case 't':
|
||||
check_type_name(optarg);
|
||||
break;
|
||||
case 'x':
|
||||
list_only = false;
|
||||
ctrformat = "%lx";
|
||||
break;
|
||||
case 'X':
|
||||
list_only = false;
|
||||
ctrformat = "%#lx";
|
||||
break;
|
||||
case OPT_FORMAT:
|
||||
if (!util_fmt_name_to_type(optarg, &fmt))
|
||||
errx(EXIT_FAILURE, "Supported formats:" FMT_TYPE_NAMES);
|
||||
output_format = fmt;
|
||||
break;
|
||||
case 'c':
|
||||
ctrlist = util_strdup(optarg);
|
||||
util_str_rm_whitespace(optarg, ctrlist);
|
||||
util_str_toupper(ctrlist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing specified, show all PAI counters */
|
||||
if (i_flag && !l_flag) {
|
||||
util_prg_print_help();
|
||||
util_opt_print_help();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read currently online CPUs and create a bit mask.
|
||||
* This bitmap of online CPUs is used to check command line parameter
|
||||
* for valid CPUs
|
||||
* When any of the flags which set variable list_only to false have
|
||||
* be specified, lets also show the counter value, not just list them.
|
||||
*/
|
||||
if (optind < argc) /* List of CPUs on command line */
|
||||
list_only = false;
|
||||
if (!list_only) { /* Show counter values */
|
||||
ch = libcpumf_cpuset_fn(S390_CPUS_ONLINE, &cpu_online_mask);
|
||||
if (ch)
|
||||
err(EXIT_FAILURE, "Cannot read file /sys/" S390_CPUS_ONLINE);
|
||||
while (optind < argc)
|
||||
get_cpulist(argv[optind++]);
|
||||
max_cpus = CPU_COUNT(&cpu_online_mask);
|
||||
if (!loops)
|
||||
loops = 1;
|
||||
}
|
||||
|
||||
/* Nothing specified, use all PAI counters */
|
||||
if (!pai_types_show)
|
||||
pai_types_show = (1 << pai_type_crypto) | (1 << pai_type_nnpa);
|
||||
|
||||
@@ -346,7 +837,12 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
sort_painode();
|
||||
show_painode();
|
||||
ch = EXIT_SUCCESS;
|
||||
if (!list_only)
|
||||
event_painode();
|
||||
else
|
||||
list_painode();
|
||||
free_painode();
|
||||
free(ctrlist);
|
||||
return ch;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ chcpumf \- manage the CPU-measurement facilities support
|
||||
.SH SYNOPSIS
|
||||
.B chcpumf
|
||||
.RB [ \-m | \-\-min
|
||||
.IR num_sdb ]
|
||||
.IR NUM_SDB ]
|
||||
.RB [ \-x | \-\-max
|
||||
.IR num_sdb ]
|
||||
.IR NUM_SDB ]
|
||||
.RB [ \-V | \-\-verbose ]
|
||||
.br
|
||||
.B chcpumf
|
||||
@@ -36,14 +36,14 @@ facilities for Linux on System z.
|
||||
.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-m ", " \-\-min " \fInum_sdb\fP"
|
||||
.BR \-m ", " \-\-min " \fINUM_SDB\fP"
|
||||
Specifies the minimum sampling facility buffer size in sample-data-blocks
|
||||
(SDB). A sample-data-block consumes about 4 kilobytes.
|
||||
|
||||
This is the initial buffer size when you start the sampling facility.
|
||||
.
|
||||
.TP
|
||||
.BR \-x ", " \-\-max " \fInum_sdb\fP"
|
||||
.BR \-x ", " \-\-max " \fINUM_SDB\fP"
|
||||
Specifies the maximum sampling facility buffer size in sample-data-blocks
|
||||
(SDB). A sample-data-block consumes about 4 kilobytes.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ counters for which the LPAR is not authorized. For counter measurements with
|
||||
the perf program, the raw event identifier is displayed.
|
||||
For Linux version 5.5 and later, the raw event identifier is
|
||||
displayed as <type>:<number>, where type is an integer that the kernel
|
||||
assignes to the CPU Measurement counter facility device driver.
|
||||
assigns to the CPU Measurement counter facility device driver.
|
||||
For earlier Linux versions the raw event identifier is displayed as r<number>.
|
||||
.
|
||||
.TP
|
||||
|
||||
@@ -7,19 +7,25 @@
|
||||
.\" ----------------------------------------------------------------------
|
||||
.ds c \fBlshwc\fP
|
||||
.
|
||||
.TH \*c "8" "May 2022" "s390-tools" "CPU-MF management programs"
|
||||
.TH \*c "8" "Mar 2025" "s390-tools" "CPU-MF management programs"
|
||||
.
|
||||
.SH NAME
|
||||
\*c \- extract CPU Measurement Facilities counter sets
|
||||
lshwc \- extract CPU Measurement Facilities counter sets
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
\*c
|
||||
.RB [ \-a ]
|
||||
.RB [ \-a ][ \-d ][ \-H ][ \-s ][ \-x ][ \-X ][ \-q ]
|
||||
.RB [ \-l
|
||||
.IR count ]
|
||||
.IR COUNT ]
|
||||
.RB [ \-i
|
||||
.IR interval ]
|
||||
\fR[\fIcpulist\fR][:\fIsets\fR]\fP
|
||||
.IR INTERVAL ]
|
||||
.RB [ \-t
|
||||
.IR TIME ]
|
||||
.RB [ \-f
|
||||
.IR FORMAT ]
|
||||
.RB [ \-c
|
||||
.IR LIST ]
|
||||
\fR[\fICPULIST\fR][:\fISETS\fR]\fP
|
||||
.br
|
||||
\*c
|
||||
.BR \-h | \-\-help
|
||||
@@ -51,17 +57,130 @@ Displays counter values from each CPU.
|
||||
The default is a total summary line of all counters from all CPUs.
|
||||
.
|
||||
.TP
|
||||
.BR \-i ", " \-\-interval \fI\ seconds\fP
|
||||
.BR \-d ", " \-\-delta
|
||||
Displays counter values in form of deltas.
|
||||
Each counter value shows the increment to the previous output line.
|
||||
Without this flag the total value of each counter is displayed.
|
||||
See Examples.
|
||||
.
|
||||
.TP
|
||||
.BR \-i ", " \-\-interval \fI\ SECONDS\fP
|
||||
Specifies a time interval, in seconds,
|
||||
that the command waits between read operations.
|
||||
The default is 60 seconds.
|
||||
.
|
||||
.TP
|
||||
.BR \-l ", " \-\-loop \fI\ count\fP
|
||||
.BR \-l ", " \-\-loop \fI\ COUNT\fP
|
||||
Performs the specified number of read operations.
|
||||
.
|
||||
.TP
|
||||
\fR[\fIcpulist\fR][:\fIsets\fR]\fP
|
||||
.BR \-H ", " \-\-hide
|
||||
Do not display values of undefined counters.
|
||||
.
|
||||
.TP
|
||||
.BR \-s ", " \-\-short
|
||||
Displays counter names as short names in the heading.
|
||||
A short name consists of
|
||||
a letter for the counter set followed by a number.
|
||||
For example,
|
||||
this flag displays counter number 0 in the basic counter
|
||||
set as
|
||||
.B B0
|
||||
instead of
|
||||
.BR Cycles .
|
||||
Valid counter set letters are:
|
||||
.RS
|
||||
.IP B
|
||||
Basic counter set
|
||||
.IP P
|
||||
Problem state counter set
|
||||
.IP C
|
||||
Crypto counter set
|
||||
.IP E
|
||||
Extended counter set
|
||||
.IP M
|
||||
MT-Diagnostic counter set
|
||||
.IP U
|
||||
Undefined counter.
|
||||
.RE
|
||||
.
|
||||
.TP
|
||||
.BR \-t ", " \-\-time
|
||||
Specifies the runtime as an integer in days,
|
||||
hours, minutes or seconds.
|
||||
This option can be specified multiple times:
|
||||
the runtime sums up.
|
||||
Specify time as an integer followed by one of the letters
|
||||
.RS
|
||||
.IP d
|
||||
Specifies the runtime in days.
|
||||
.IP h
|
||||
Specifies the runtime in hours.
|
||||
.IP m
|
||||
Specifies the runtime in minutes.
|
||||
.IP s
|
||||
Specifies the runtime in seconds.
|
||||
This is the default when no letter is given.
|
||||
.RE
|
||||
.
|
||||
.TP
|
||||
.BR \-x ", " \-\-hex
|
||||
Displays counter values as hexadecimal values.
|
||||
.
|
||||
.TP
|
||||
.BR \-X ", " \-\-hex0x
|
||||
Displays counter values as hexadecimal values with a leading 0x prefix.
|
||||
.
|
||||
.TP
|
||||
.BR \-q
|
||||
Apply quoting to every output element, regardless of content or format.
|
||||
.
|
||||
.TP
|
||||
.BR \-f ", " \-\-format \fI\ FORMAT\fP
|
||||
Retrieve output in one of the following formats:
|
||||
JSON, CSV, JSON-SEQ or PAIRS.
|
||||
If no format is specified, the output defaults to CSV.
|
||||
.
|
||||
.SS JSON Output Structure
|
||||
The JSON output contains two top-level objects:
|
||||
.B "meta"
|
||||
and
|
||||
.B "lshwc"
|
||||
|
||||
.TP
|
||||
.B meta
|
||||
General metadata such as API version, host, and timestamp.
|
||||
|
||||
.TP
|
||||
.B lshwc
|
||||
Measurement data and configuration.
|
||||
|
||||
.RS
|
||||
.IP \fBcpumcf info\fP
|
||||
CPU Measurement Counter Facility information.
|
||||
.IP \fBmeasurements\fP
|
||||
An array of objects, each representing a CPU or "total".
|
||||
Each object includes:
|
||||
.RS
|
||||
.IP \fBcpu\fP
|
||||
CPU identifier, such as 0, 1 or "delta" or "total".
|
||||
.IP \fBdate_time\fP
|
||||
Timestamp in ISO 8601 format.
|
||||
.IP \fBtime_epoch\fP
|
||||
The number of seconds since the epoch: 1970-01-01 00:00:00 UTC.
|
||||
.IP \fBcounters\fP
|
||||
An array of counter objects, each containing \fBname\fP (if available), \fBid\fP,
|
||||
and \fBvalue\fP.
|
||||
.RE
|
||||
.RE
|
||||
.
|
||||
.TP
|
||||
.BR \-c ", " \-\-counters \fI\ LIST\fP
|
||||
Specify a comma-separated list of counter names to be printed.
|
||||
If a counter name does not match any valid counter name, it is omitted. (Implies -H)
|
||||
.
|
||||
.TP
|
||||
\fR[\fICPULIST\fR][:\fISETS\fR]\fP
|
||||
A comma-separated list of CPUs.
|
||||
Each CPU can optionally be followed by characters that specify the counter set.
|
||||
See below for details.
|
||||
@@ -94,7 +213,7 @@ Two read operations are performed and a summary line is printed for each
|
||||
read operation.
|
||||
.sp 1
|
||||
.nf
|
||||
.ft CW
|
||||
.ft CR
|
||||
# lshwc -l2 0-1:BP
|
||||
Date,Time,CPU,CPU_CYCLES(0),INSTRUCTIONS(1),L1I_DIR_WRITES(2),L1I_PENALTY_CYCLES(3),L1D_DIR_WRITES(4),
|
||||
L1D_PENALTY_CYCLES(5),PROBLEM_STATE_CPU_CYCLES(32),PROBLEM_STATE_INSTRUCTIONS(33)
|
||||
@@ -105,10 +224,10 @@ Date,Time,CPU,CPU_CYCLES(0),INSTRUCTIONS(1),L1I_DIR_WRITES(2),L1I_PENALTY_CYCLES
|
||||
.ft
|
||||
.fi
|
||||
.sp 1
|
||||
This example shows the counter values of the problem state counter set
|
||||
per CPU. CPU 0 and CPU 1 is selected.
|
||||
This example shows the counter values of the problem state counter set per CPU.
|
||||
CPU 0 and CPU 1 are selected.
|
||||
.nf
|
||||
.ft CW
|
||||
.ft CR
|
||||
.sp 1
|
||||
# lshwc -l3 -a 0-1:P
|
||||
Date,Time,CPU,PROBLEM_STATE_CPU_CYCLES(32),PROBLEM_STATE_INSTRUCTIONS(33)
|
||||
@@ -123,6 +242,121 @@ Date,Time,CPU,PROBLEM_STATE_CPU_CYCLES(32),PROBLEM_STATE_INSTRUCTIONS(33)
|
||||
2021-04-01,11:56:47,Total,6432163447,2978400903
|
||||
.ft
|
||||
.fi
|
||||
.sp 1
|
||||
This example shows the counter values of the basic counter set
|
||||
using delta output format.
|
||||
.nf
|
||||
.ft CR
|
||||
.sp 1
|
||||
# lshwc -d -l 10 -i 5 -s :b
|
||||
Date,Time,CPU,B0,B1,B2,B3,B4,B5
|
||||
2025-03-26,10:34:19,Total,208075,117287,1950,50548,1082,49609
|
||||
2025-03-26,10:34:24,Delta,85800055,70353492,590286,13228290,364034,12945804
|
||||
2025-03-26,10:34:29,Delta,70654751,60656797,483047,10838672,305703,10570868
|
||||
2025-03-26,10:34:34,Delta,81043162,69476160,587141,13228161,376662,12868298
|
||||
2025-03-26,10:34:39,Delta,73434017,62675417,524857,11787256,333966,11543649
|
||||
2025-03-26,10:34:44,Delta,68367967,58452919,506712,11370740,310785,10589883
|
||||
2025-03-26,10:34:49,Delta,70351947,57607764,507675,11433377,312433,10676243
|
||||
2025-03-26,10:34:54,Delta,77154817,65371168,562153,12671030,349750,12311061
|
||||
2025-03-26,10:34:59,Delta,88871882,75441201,655310,14875963,392530,13773130
|
||||
2025-03-26,10:35:04,Delta,83763472,71730813,609260,13643680,366992,12672405
|
||||
.ft
|
||||
.fi
|
||||
.sp 1
|
||||
This example shows the counter values of the problem state counter set with
|
||||
CPU 3 selected.
|
||||
.nf
|
||||
.ft CR
|
||||
.sp 1
|
||||
# lshwc -l2 -a 3:P -H --format json
|
||||
{
|
||||
"meta": {
|
||||
"api_level": 1,
|
||||
"version": "2.37.0-build-20250616",
|
||||
"host": "b46lp08.lnxne.boe",
|
||||
"time_epoch": 1750094646,
|
||||
"time": "2025-06-16 19:24:06+0200"
|
||||
},
|
||||
"lshwc": {
|
||||
"cpumcf info": {
|
||||
"counter first": 3,
|
||||
"counter second": 8,
|
||||
"authorization": 47
|
||||
},
|
||||
"measurements": [
|
||||
{
|
||||
"date_time": "2025-06-16 19:24:06+0200",
|
||||
"time_epoch": 1750094646,
|
||||
"cpu": 3,
|
||||
"counters": [
|
||||
{
|
||||
"name": "problem_state_cpu_cycles",
|
||||
"id": 32,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "problem_state_instructions",
|
||||
"id": 33,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"date_time": "2025-06-16 19:24:06+0200",
|
||||
"time_epoch": 1750094646,
|
||||
"cpu": "total",
|
||||
"counters": [
|
||||
{
|
||||
"name": "problem_state_cpu_cycles",
|
||||
"id": 32,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "problem_state_instructions",
|
||||
"id": 33,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"date_time": "2025-06-16 19:25:06+0200",
|
||||
"time_epoch": 1750094706,
|
||||
"cpu": 3,
|
||||
"counters": [
|
||||
{
|
||||
"name": "problem_state_cpu_cycles",
|
||||
"id": 32,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "problem_state_instructions",
|
||||
"id": 33,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"date_time": "2025-06-16 19:25:06+0200",
|
||||
"time_epoch": 1750094706,
|
||||
"cpu": "total",
|
||||
"counters": [
|
||||
{
|
||||
"name": "problem_state_cpu_cycles",
|
||||
"id": 32,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "problem_state_instructions",
|
||||
"id": 33,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
.ft
|
||||
.fi
|
||||
.SH "SEE ALSO"
|
||||
.BR lscpumf (8)
|
||||
.BR chcpumf (8)
|
||||
|
||||
@@ -10,13 +10,30 @@
|
||||
.TH \*c "8" "August 2023" "s390-tools" "CPU-MF management programs"
|
||||
.
|
||||
.SH NAME
|
||||
\*c \- list Processor Activity Instrumentation (PAI) counters
|
||||
\*c \- list Processor Activity Instrumentation (PAI) counters and values
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
\*c
|
||||
.RB [ \-\-format
|
||||
.IR FORMAT ]
|
||||
.RB [ \-n ]
|
||||
.RB [ \-t
|
||||
.IR TYPE ]
|
||||
.br
|
||||
\*c
|
||||
.RB [ \-\-format
|
||||
.IR FORMAT ]
|
||||
.RB [ \-n ]
|
||||
.RB [ \-t
|
||||
.IR "\ TYPE" ]
|
||||
.RB [ \-a ][ \-d ][ \-i
|
||||
.IR SECONDS ]
|
||||
.RB [ \-l
|
||||
.IR COUNT ]
|
||||
.RB [ \-s ][ \-x ][ \-x ]
|
||||
.RB [ \-c
|
||||
.IR LIST ]
|
||||
.RI [ CPULIST ]
|
||||
.br
|
||||
\*c
|
||||
.BR \-h | \-\-help
|
||||
@@ -26,10 +43,34 @@
|
||||
.
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
On the first line
|
||||
\*c displays the Processor Activity Instrumentation (PAI) counters
|
||||
for Linux on IBM Z.
|
||||
The output is a human-readable list of available PAI counter
|
||||
names and numbers.
|
||||
The output lists the available PAI counter names
|
||||
and numbers in various formats.
|
||||
If
|
||||
.I CPULIST
|
||||
is specified or any of the options
|
||||
.BR \-a ,
|
||||
.BR \-d ,
|
||||
.BR \-s ,
|
||||
.BR \-x ,
|
||||
.BR \-X ,
|
||||
.B \-l
|
||||
or
|
||||
.BR \-i,
|
||||
are present on the command line,
|
||||
\*c command extracts counter values from the
|
||||
specified PAI counter sets.
|
||||
Counter sets can be specified and extracted for individual CPUs.
|
||||
By default the output is a comma-separated list of values.
|
||||
Each line starts with a timestamp
|
||||
followed by the extracted counter values.
|
||||
A header line shows each counter name and the
|
||||
counter number in parenthesis.
|
||||
For other formats, use the
|
||||
.B \-\-format
|
||||
option.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-h ", " \-\-help
|
||||
@@ -40,7 +81,44 @@ Displays help information, then exits.
|
||||
Displays version information, then exits.
|
||||
.
|
||||
.TP
|
||||
.BR \-t ", " \-\-type "\ TYPE"
|
||||
.BR \-a ", " \-\-allcpu
|
||||
Displays counter values from each CPU.
|
||||
The default is a total summary line of all counters from all CPUs.
|
||||
.
|
||||
.TP
|
||||
.BR \-d ", " \-\-delta
|
||||
Displays counter values in form of deltas.
|
||||
Each counter value shows the increment to the previous output line.
|
||||
Without this flag the total value of each counter is displayed.
|
||||
.
|
||||
.TP
|
||||
.BR \-c ", " \-\-counters \fI\ LIST\fP
|
||||
Specify a comma separated list of counter names to
|
||||
be printed.
|
||||
If a counter name does not match any valid counter
|
||||
name, it is omitted.
|
||||
.
|
||||
.TP
|
||||
.BR \-s ", " \-\-short
|
||||
Displays counter names as short names in the heading.
|
||||
A short name consists of
|
||||
a letter for the counter set followed by a number.
|
||||
For example,
|
||||
this flag displays counter number 0 in the PAI crypto counter
|
||||
set as
|
||||
.B C0
|
||||
instead of
|
||||
.BR CRYPTO_ALL .
|
||||
Valid counter set letters are:
|
||||
.RS
|
||||
.IP C
|
||||
PAI crypto counter set
|
||||
.IP N
|
||||
PAI NNPA counter set
|
||||
.RE
|
||||
.
|
||||
.TP
|
||||
.BR \-t ", " \-\-type "\ \fITYPE\fP"
|
||||
Specifies the PAI counter set to list.
|
||||
Valid counter set values are
|
||||
.I crypto
|
||||
@@ -55,6 +133,32 @@ Crypto refers to the Cryptografic Processing Assist facility counter set.
|
||||
Shows the PAI counter sets sorted by counter number.
|
||||
Default sort order is PAI counter name.
|
||||
.
|
||||
.TP
|
||||
.BI \-\-format "\ FORMAT"
|
||||
Retrieve output in one of the following formats:
|
||||
JSON, csv, json-seq or pairs.
|
||||
If no format is specified,
|
||||
the output defaults to a human-readable format.
|
||||
.
|
||||
.TP
|
||||
.BR \-i ", " \-\-interval \fI\ SECONDS\fP
|
||||
Specifies a time interval, in seconds,
|
||||
that the command waits between read operations.
|
||||
The default is 60 seconds.
|
||||
.
|
||||
.TP
|
||||
.BR \-l ", " \-\-loop \fI\ COUNT\fP
|
||||
Performs the specified number of read operations.
|
||||
The default is one.
|
||||
.
|
||||
.TP
|
||||
.BR \-x ", " \-\-hex
|
||||
Displays counter values as hexadecimal values.
|
||||
.
|
||||
.TP
|
||||
.BR \-X ", " \-\-hex0x
|
||||
Displays counter values as hexadecimal values with a leading 0x prefix.
|
||||
.
|
||||
.SH "EXAMPLE"
|
||||
The \*c invocation lists all PAI Neural Network Processing Assist Facility
|
||||
(NNPA) counters in numeric order:
|
||||
@@ -75,6 +179,58 @@ suitable for
|
||||
.IR perf "(8)"
|
||||
event specification by name.
|
||||
The third gives a short explanation, if available.
|
||||
.sp 1
|
||||
The \*c invocation displays all PAI Neural Network Processing Assist Facility
|
||||
(NNPA) counters values.
|
||||
Ten iterations with a two second delay in between
|
||||
is printed on the screen.
|
||||
.nf
|
||||
# lspai -i 2 -l 10 -t nnpa -n
|
||||
Date,Time,CPU,NNPA_ALL(0),NNPA_ADD(1),NNPA_SUB(2),NNPA_MUL(3),NNPA_DIV(4),
|
||||
NNPA_MIN(5),NNPA_MAX(6),NNPA_LOG(7),NNPA_EXP(8),NNPA_IBM_RESERVED_9(9),
|
||||
NNPA_RELU(10),NNPA_TANH(11),NNPA_SIGMOID(12),NNPA_SOFTMAX(13),NNPA_BATCHNORM(14),
|
||||
NNPA_MAXPOOL2D(15),NNPA_AVGPOOL2D(16),NNPA_LSTMACT(17),NNPA_GRUACT(18),
|
||||
NNPA_CONVOLUTION(19),NNPA_MATMUL_OP(20),NNPA_MATMUL_OP_BCAST23(21),
|
||||
NNPA_SMALLBATCH(22),NNPA_LARGEDIM(23),NNPA_SMALLTENSOR(24),NNPA_1MFRAME(25),
|
||||
NNPA_2GFRAME(26),NNPA_ACCESSEXCEPT(27)
|
||||
2025-05-13,08:18:27,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:29,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:31,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:33,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:35,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:37,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:39,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:41,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:43,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
2025-05-13,08:18:45,Total,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
#
|
||||
.fi
|
||||
.sp 1
|
||||
This \*c example displays the value of PAI crypto counter number 7.
|
||||
It is field number 116 in the output.
|
||||
This counter is incremented after \*c is started and
|
||||
the table shows the delta values for PAI crypto counter 7.
|
||||
.nf
|
||||
# lspai -d -s -i 5 -l3 -t crypto | \
|
||||
awk -F , '{ print $1 "," $2 "," $3 "," $4 "," $116; }'
|
||||
Date,Time,CPU,C0,C7
|
||||
2025-06-24,13:02:24,Total,0,0
|
||||
2025-06-24,13:02:29,Delta,77122286,77122284
|
||||
2025-06-24,13:02:34,Delta,92574403,92574403
|
||||
#
|
||||
.fi
|
||||
.sp 1
|
||||
The following table shows the total value for PAI crypto counter 7
|
||||
after \*c is started.
|
||||
.nf
|
||||
# lspai -s -i 5 -l3 -t crypto | \
|
||||
awk -F , '{ print $1 "," $2 "," $3 "," $4 "," $116; }'
|
||||
Date,Time,CPU,C0,C7
|
||||
2025-06-24,13:03:01,Total,0,0
|
||||
2025-06-24,13:03:06,Total,72550754,72566121
|
||||
2025-06-24,13:03:11,Total,164633982,164650377
|
||||
#
|
||||
.fi
|
||||
.SH "SEE ALSO"
|
||||
.BR pai (8)
|
||||
.BR lscpumf (8)
|
||||
|
||||
@@ -10,29 +10,29 @@
|
||||
.TH \*c "8" "May 2022" "s390-tools" "PAI Management Programs"
|
||||
.
|
||||
.SH NAME
|
||||
\*c \- record and report Processor Activity Instrumentation (PAI) counters
|
||||
pai \- record and report Processor Activity Instrumentation (PAI) counters
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
\*c
|
||||
.RB [ \-V ][ \-m | \-\-mapsize
|
||||
.IR size ]
|
||||
.IR SIZE ]
|
||||
.RB [ \-i | \-\-interval
|
||||
.IR ms ]
|
||||
.IR MS ]
|
||||
.RB [ \-R | \-\-realtime
|
||||
.IR prio ]
|
||||
.BR \-c | \-\-crypto [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ]
|
||||
.IR PRIO ]
|
||||
.BR \-c | \-\-crypto [ \fICPULIST ][: \fIDATA\fR "] [" \fILOOPS\fP ]
|
||||
.br
|
||||
\*c
|
||||
.RB [ \-V ][ \-m | \-\-mapsize
|
||||
.IR size ]
|
||||
.IR SIZE ]
|
||||
.RB [ \-i | \-\-interval
|
||||
.IR ms ]
|
||||
.IR MS ]
|
||||
.RB [ \-R | \-\-realtime
|
||||
.IR prio ]
|
||||
.BR \-n | \-\-nnpa [ \fIcpulist ][: \fIdata\fR "] [" \fIloops\fP ]
|
||||
.IR PRIO ]
|
||||
.BR \-n | \-\-nnpa [ \fICPULIST ][: \fIDATA\fR "] [" \fILOOPS\fP ]
|
||||
.br
|
||||
\*c
|
||||
.RB [ \-V ][ \-H | \-\-humantime ][ \-S | \-\-summary "] " \-r | \-\-report " [" \fIfiles\fP ]
|
||||
.RB [ \-V ][ \-H | \-\-humantime ][ \-S | \-\-summary "] " \-r | \-\-report " [" \fIFILES\fP ]
|
||||
.br
|
||||
\*c
|
||||
.BR \-h | \-\-help
|
||||
@@ -73,7 +73,7 @@ Reporting evaluates files that are created by recording.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-c ", " \-\-crypto "\fR[\fIcpulist\fR][:\fIdata\fR]"
|
||||
.BR \-c ", " \-\-crypto "\fR[\fICPULIST\fR][:\fIDATA\fR]"
|
||||
Records data for all (default) or a specified list of CPUs.
|
||||
The CPU list is a comma-separated list of CPU numbers and ranges.
|
||||
In a range, a hyphen separates the first CPU number
|
||||
@@ -106,7 +106,7 @@ execution.
|
||||
.RE
|
||||
.
|
||||
.TP
|
||||
.BR \-n ", " \-\-nnpa "\fR[\fIcpulist\fR][:\fIdata\fR]"
|
||||
.BR \-n ", " \-\-nnpa "\fR[\fICPULIST\fR][:\fIDATA\fR]"
|
||||
Records data for all (default) or a specified list of CPUs.
|
||||
The CPU list is a comma-separated list of CPU numbers and ranges.
|
||||
In a range, a hyphen separates the first CPU number
|
||||
@@ -241,7 +241,7 @@ The program runs for 10 seconds
|
||||
(10 intervals of 1000 milliseconds).
|
||||
.sp 1
|
||||
.nf
|
||||
.ft CW
|
||||
.ft CR
|
||||
# \*c -c0 10
|
||||
.ft R
|
||||
.fi
|
||||
@@ -256,7 +256,7 @@ and a comma-separated list of counter number, colon (:),
|
||||
and the counter value n hexadecimal notation.
|
||||
.sp 1
|
||||
.nf
|
||||
.ft CW
|
||||
.ft CR
|
||||
# \*c -r
|
||||
0x62a668f2fa 0 event 4096 sample pid 4956/4956 9:0xa7,73:0x8,74:0x18
|
||||
0x6319c75653 0 event 4096 sample pid 4972/4972 32:0x1
|
||||
|
||||
10
cpumf/pai.c
10
cpumf/pai.c
@@ -59,14 +59,6 @@ static struct util_list list_pai_event;
|
||||
static struct util_list list_pmu_event;
|
||||
static bool summary;
|
||||
|
||||
/* System call to perf_event_open(2) */
|
||||
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
|
||||
int cpu, int group_fd, unsigned long flags)
|
||||
{
|
||||
return syscall(__NR_perf_event_open, hw_event, pid, cpu,
|
||||
group_fd, flags);
|
||||
}
|
||||
|
||||
static void ev_dealloc(void)
|
||||
{
|
||||
struct pai_event *next, *p;
|
||||
@@ -743,8 +735,8 @@ static int add_events(int type)
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
free(pmuname);
|
||||
warnx("failed building event list for %s", pmuname);
|
||||
free(pmuname);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -314,8 +314,7 @@ void check_config()
|
||||
cpuplugd_debug("Daemon started with vertical polarization.\n");
|
||||
cpuplugd_debug("Cpu adjustments won't be made until system ");
|
||||
cpuplugd_debug("is in horizontal polarization\n");
|
||||
} else if (saved_polarization == PLR_HORIZONTAL &&
|
||||
cpu == 1) {
|
||||
} else if (cpu == 1) {
|
||||
apply_cpu_config();
|
||||
}
|
||||
if (memory == 1) {
|
||||
|
||||
@@ -87,6 +87,8 @@ int get_polarization(void)
|
||||
char *path;
|
||||
|
||||
path = util_path_sysfs("devices/system/cpu/dispatching");
|
||||
if (!util_path_exists(path))
|
||||
return PLR_NONE;
|
||||
if (util_file_read_i(&polarization, NUM_BASE, path) < 0) {
|
||||
polarization = -1;
|
||||
cpuplugd_debug("failed to read system polarization\n");
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#define CPUSTATS 10
|
||||
#define PLR_HORIZONTAL 0
|
||||
#define PLR_VERTICAL 1
|
||||
#define PLR_NONE 2
|
||||
|
||||
/*
|
||||
* Precedence of C operators
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "lib/dasd_base.h"
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_opt.h"
|
||||
#include "lib/util_prg.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/zt_common.h"
|
||||
|
||||
#define RD_BUFFER_SIZE 80
|
||||
@@ -283,7 +283,7 @@ static int dinfo_create_devnode(dev_t dev, char **devno)
|
||||
char filename[] = "dasdinfo0000";
|
||||
mode_t mode;
|
||||
unsigned int path;
|
||||
int retry;
|
||||
unsigned int retry;
|
||||
int rc;
|
||||
int fd;
|
||||
|
||||
@@ -294,7 +294,7 @@ static int dinfo_create_devnode(dev_t dev, char **devno)
|
||||
if (pathname[path] == NULL)
|
||||
continue;
|
||||
for (retry = 0; retry < TEMP_DEV_MAX_RETRIES; retry++) {
|
||||
sprintf(filename, "dasdinfo%04d", retry);
|
||||
snprintf(filename, sizeof(filename), "dasdinfo%04d", retry);
|
||||
result = dinfo_make_path(pathname[path], filename);
|
||||
if (result == NULL)
|
||||
return -1;
|
||||
@@ -354,18 +354,26 @@ static int dinfo_extract_dev(dev_t *dev, char *str)
|
||||
static int dinfo_get_dev_from_blockdev(char *blockdev, dev_t *dev)
|
||||
{
|
||||
char *readbuf = NULL;
|
||||
char *path;
|
||||
|
||||
readbuf = dinfo_malloc(RD_BUFFER_SIZE);
|
||||
if (!readbuf) {
|
||||
warnx("Error: Not enough memory to allocate readbuffer");
|
||||
return -1;
|
||||
}
|
||||
if (util_file_read_line(readbuf, RD_BUFFER_SIZE,
|
||||
"/sys/block/%s/dev", blockdev) < 0)
|
||||
path = util_path_sysfs("block/%s/dev", blockdev);
|
||||
if (util_file_read_line(readbuf, RD_BUFFER_SIZE, path) < 0) {
|
||||
free(path);
|
||||
free(readbuf);
|
||||
return -1;
|
||||
if (dinfo_extract_dev(dev, readbuf) != 0)
|
||||
}
|
||||
free(path);
|
||||
if (dinfo_extract_dev(dev, readbuf) != 0) {
|
||||
free(readbuf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(readbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -373,8 +381,9 @@ static int
|
||||
dinfo_is_busiddir(const char *fpath, const struct stat *UNUSED(sb),
|
||||
int tflag, struct FTW *ftwbuf)
|
||||
{
|
||||
enum { LINK_DIR_SIZE = 128 };
|
||||
char linkdir[LINK_DIR_SIZE];
|
||||
char *tempdir;
|
||||
char linkdir[128];
|
||||
ssize_t i;
|
||||
|
||||
if (tflag != FTW_D || (strncmp((fpath + ftwbuf->base), searchbusid,
|
||||
@@ -387,12 +396,12 @@ dinfo_is_busiddir(const char *fpath, const struct stat *UNUSED(sb),
|
||||
*/
|
||||
if (asprintf(&tempdir, "%s/driver", fpath) < 0)
|
||||
return -1;
|
||||
i = readlink(tempdir, linkdir, 128);
|
||||
i = readlink(tempdir, linkdir, LINK_DIR_SIZE);
|
||||
free(tempdir);
|
||||
if ((i < 0) || (i >= 128))
|
||||
if (i < 0 || i >= LINK_DIR_SIZE)
|
||||
return -1;
|
||||
/* append '\0' because readlink returns non zero terminated string */
|
||||
tempdir[i + 1] = '\0';
|
||||
linkdir[i] = '\0';
|
||||
if (strstr(linkdir, "dasd") == NULL)
|
||||
return FTW_CONTINUE;
|
||||
free(busiddir);
|
||||
@@ -439,7 +448,7 @@ dinfo_get_blockdev_from_busid(char *busid, char **blkdev)
|
||||
|
||||
char *tempdir = NULL;
|
||||
char *result = NULL;
|
||||
char *sysfsdir = "/sys/devices/";
|
||||
char *sysfsdir = util_path_sysfs("devices/");
|
||||
|
||||
/* dinfo_is_devnode needs to know the busid */
|
||||
searchbusid = busid;
|
||||
@@ -475,6 +484,7 @@ out:
|
||||
out2:
|
||||
free(busiddir);
|
||||
free(result);
|
||||
free(sysfsdir);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -486,6 +496,7 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
|
||||
DIR *directory = NULL;
|
||||
struct dirent *dir_entry = NULL;
|
||||
int rc = 0;
|
||||
char *path;
|
||||
|
||||
if (stat(devnode, &stat_buffer) != 0) {
|
||||
warnx("Error: could not stat %s", devnode);
|
||||
@@ -495,32 +506,34 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
|
||||
sprintf(stat_dev, "%d:%d", major(stat_buffer.st_rdev),
|
||||
minor(stat_buffer.st_rdev));
|
||||
|
||||
directory = opendir("/sys/block/");
|
||||
path = util_path_sysfs("block/");
|
||||
directory = opendir(path);
|
||||
if (directory == NULL) {
|
||||
warnx("Error: could not open directory /sys/block");
|
||||
warnx("Error: could not open directory %s", path);
|
||||
free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
readbuf = dinfo_malloc(RD_BUFFER_SIZE);
|
||||
if (!readbuf) {
|
||||
warnx("Error: Not enough memory to allocate readbuffer");
|
||||
free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while ((dir_entry = readdir(directory)) != NULL) {
|
||||
if (util_file_read_line(readbuf, RD_BUFFER_SIZE,
|
||||
"/sys/block/%s/dev",
|
||||
if (util_file_read_line(readbuf, RD_BUFFER_SIZE, "%s%s/dev", path,
|
||||
dir_entry->d_name) < 0)
|
||||
continue;
|
||||
|
||||
if (strncmp(stat_dev, readbuf,
|
||||
MAX(strlen(stat_dev), strlen(readbuf) - 1)) == 0) {
|
||||
rc = snprintf(*uidfile, RD_BUFFER_SIZE,
|
||||
"/sys/block/%s/device/uid",
|
||||
rc = snprintf(*uidfile, RD_BUFFER_SIZE, "%s%s/device/uid", path,
|
||||
dir_entry->d_name);
|
||||
if (rc >= RD_BUFFER_SIZE) {
|
||||
fprintf(stderr,
|
||||
"Error: Device name was truncated\n");
|
||||
free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -529,6 +542,7 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
|
||||
}
|
||||
|
||||
closedir(directory);
|
||||
free(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -626,16 +640,18 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
readbuf = dinfo_malloc(RD_BUFFER_SIZE);
|
||||
uidfile = dinfo_malloc(RD_BUFFER_SIZE);
|
||||
if (!(readbuf && uidfile))
|
||||
if (!readbuf)
|
||||
exit(1);
|
||||
|
||||
/* try to read the uid attribute */
|
||||
if (busid) {
|
||||
sprintf(uidfile, "/sys/bus/ccw/devices/%s/uid", busid);
|
||||
uidfile = util_path_sysfs("bus/ccw/devices/%s/uid", busid);
|
||||
} else if (blockdev) {
|
||||
sprintf(uidfile, "/sys/block/%s/device/uid", blockdev);
|
||||
uidfile = util_path_sysfs("block/%s/device/uid", blockdev);
|
||||
} else if (devnode) {
|
||||
uidfile = dinfo_malloc(RD_BUFFER_SIZE);
|
||||
if (!uidfile)
|
||||
exit(1);
|
||||
if (dinfo_get_uid_from_devnode(&uidfile, devnode) != 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
. ds pfont \\n[.f]
|
||||
. nh
|
||||
. na
|
||||
. ft CW
|
||||
. ft CR
|
||||
\\$*
|
||||
. ft \\*[pfont]
|
||||
. ad
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
# Load protected key support module on s390 early at boot
|
||||
pkey
|
||||
pkey_pckmo
|
||||
pkey_ep11
|
||||
pkey_cca
|
||||
paes_s390
|
||||
|
||||
@@ -18,3 +18,17 @@ CPI_SYSTEM_NAME=""
|
||||
# CPI sysplex name
|
||||
#
|
||||
CPI_SYSPLEX_NAME=""
|
||||
|
||||
#
|
||||
# CPI permit on protected virtualization guests
|
||||
#
|
||||
# Important: Set CPI_PERMIT_ON_PVGUEST=1 only if you trust the host system.
|
||||
# Enabling these options allows the host to receive potentially sensitive
|
||||
# Control-Program Identification (CPI) data from the protected virtualization
|
||||
# guest, including:
|
||||
# - system_type
|
||||
# - system_level
|
||||
# - sysplex_name
|
||||
# - system_name
|
||||
#
|
||||
CPI_PERMIT_ON_PVGUEST=
|
||||
|
||||
4
etc/udev/rules.d/80-hotplug-cpu.rules
Normal file
4
etc/udev/rules.d/80-hotplug-cpu.rules
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# Rules to set online a newly hotplugged cpu on s390x
|
||||
#
|
||||
SUBSYSTEM=="cpu", ACTION=="add", CONST{arch}=="s390*", ATTR{configure}=="1", TEST=="online", ATTR{online}!="1", ATTR{online}="1"
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#define STAGE2_DESC _AC(0x78, UL)
|
||||
#define STAGE2_ENTRY _AC(0x2018, UL)
|
||||
#define ECKD2DUMP_MV_TAIL_ADDRESS _AC(0x5000, UL)
|
||||
#define ECKD2DUMP_MV_TAIL_ADDRESS _AC(0x6000, UL)
|
||||
#define STAGE2_HEAP_ADDRESS _AC(0x6000, UL)
|
||||
#define ECKD2DUMP_HEAP_ADDRESS _AC(0xb000, UL)
|
||||
#define STAGE2_HEAP_SIZE _AC(0x3000, UL)
|
||||
@@ -31,11 +31,9 @@
|
||||
#define ECKD2DUMP_STACK_SIZE _AC(0x2000, UL)
|
||||
#define STAGE2_MAX_SIZE _AC(0x3000, UL)
|
||||
|
||||
#define STAGE2_DUMPER_SIZE_V1 _AC(0x1000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_V2 _AC(0x2000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_V3 _AC(0x3000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_MV _AC(0x3000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_ZLIB _AC(0x8000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_SV _AC(0x3000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_MV _AC(0x4000, UL)
|
||||
#define STAGE2_DUMPER_SIZE_SV_ZLIB _AC(0x8000, UL)
|
||||
|
||||
#define STAGE3_ENTRY _AC(0xa000, UL)
|
||||
|
||||
|
||||
@@ -27,16 +27,9 @@
|
||||
#define DF_S390_CPU_MAX 512
|
||||
#define DF_S390_MAGIC_BLK_ECKD 3
|
||||
#define DF_S390_DUMPER_MAGIC_SIZE 7
|
||||
#define DF_S390_DUMPER_MAGIC32 "ZECKD31"
|
||||
#define DF_S390_DUMPER_MAGIC64 "ZECKD64"
|
||||
#define DF_S390_DUMPER_MAGIC_EXT "XECKD64"
|
||||
#define DF_S390_DUMPER_MAGIC32_FBA "ZDFBA31"
|
||||
#define DF_S390_DUMPER_MAGIC64_FBA "ZDFBA64"
|
||||
#define DF_S390_DUMPER_MAGIC_FBA_EXT "XDFBA64"
|
||||
#define DF_S390_DUMPER_MAGIC_MV "ZMULT64"
|
||||
#define DF_S390_DUMPER_MAGIC_MV_EXT "XMULT64"
|
||||
#define OLD_DUMPER_HEX_INSTR1 "\x0d\x10\x47\xf0" /* BASR + 1st halfword of BC */
|
||||
#define OLD_DUMPER_HEX_INSTR2 "\x0d\xd0" /* BASR 13,0 */
|
||||
|
||||
/*
|
||||
* Architecture of dumped system
|
||||
|
||||
@@ -9,17 +9,38 @@
|
||||
|
||||
#include <sched.h>
|
||||
#include <stdbool.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <linux/perf_event.h>
|
||||
|
||||
#define S390_CPUMF_CF "/sys/devices/cpum_cf/"
|
||||
#define S390_CPUMF_CFDIAG "/sys/devices/cpum_cf_diag/"
|
||||
#define S390_CPUMF_SF "/sys/devices/cpum_sf/"
|
||||
#define S390_CPUS_POSSIBLE "/sys/devices/system/cpu/possible"
|
||||
#define S390_CPUS_ONLINE "/sys/devices/system/cpu/online"
|
||||
#define S390_CPUMSF_BUFFERSZ "/sys/module/kernel/parameters/cpum_sfb_size"
|
||||
#define S390_SYSFS_PAI_CRYPTO "/sys/devices/pai_crypto/"
|
||||
#define S390_SYSFS_PAI_EXT "/sys/devices/pai_ext/"
|
||||
#define S390_CPUMF_CF "devices/cpum_cf/"
|
||||
#define S390_CPUMF_CFDIAG "devices/cpum_cf_diag/"
|
||||
#define S390_CPUMF_SF "devices/cpum_sf/"
|
||||
#define S390_CPUS_ONLINE "devices/system/cpu/online"
|
||||
#define S390_CPUMSF_BUFFERSZ "module/kernel/parameters/cpum_sfb_size"
|
||||
#define S390_SYSFS_PAI_CRYPTO "devices/pai_crypto/"
|
||||
#define S390_SYSFS_PAI_EXT "devices/pai_ext/"
|
||||
#define S390_SYSFS_PAI_NNPA S390_SYSFS_PAI_EXT "events/NNPA_ALL"
|
||||
|
||||
#define CPUMF_CTRSET_NONE 0
|
||||
#define CPUMF_CTRSET_EXTENDED 1
|
||||
#define CPUMF_CTRSET_BASIC 2
|
||||
#define CPUMF_CTRSET_PROBLEM_STATE 4
|
||||
#define CPUMF_CTRSET_CRYPTO 8
|
||||
#define CPUMF_CTRSET_MT_DIAG 32
|
||||
|
||||
/**
|
||||
* Return counter set a counter belongs to.
|
||||
*
|
||||
* Return the counter set a given counter belongs to, given the
|
||||
* CPU Measurement facility counter version first and second number.
|
||||
*
|
||||
* @param[in] ctr Counter number
|
||||
* @param[in] cfvn CPUM Counter facility first version number
|
||||
* @param[in] csvn CPUM Counter facility second version number
|
||||
* @retval >=0 Counter set number to counter belongs to
|
||||
*/
|
||||
int libcpumf_ctrset(int ctr, int cfvn, int csvn);
|
||||
|
||||
/**
|
||||
* Read out the PMU type from a given file.
|
||||
*
|
||||
@@ -96,7 +117,7 @@ bool libcpumf_have_cpumcf(void);
|
||||
*
|
||||
* @param[out] min Minimum supported sampling interval
|
||||
* @param[out] max Maximum supported sampling interval
|
||||
* @param[out] speed Current CPU speed, number of CPU cylces per
|
||||
* @param[out] speed Current CPU speed, number of CPU cycles per
|
||||
* microsecond
|
||||
* @param[out] basic_sz Basic sample size in bytes
|
||||
* @param[out] diag_sz Diagnostic sample size in bytes
|
||||
@@ -163,4 +184,37 @@ bool libcpumf_have_pai_ext(void);
|
||||
* @retval false PAI_NNPA counter Facility is not available
|
||||
*/
|
||||
bool libcpumf_have_pai_nnpa(void);
|
||||
|
||||
/**
|
||||
* Wrapper for the perf_event_open syscall used to configure performance events.
|
||||
* This function simplifies usage of perf_event_open and provides a consistent
|
||||
* interface for libcpumf internals.
|
||||
*
|
||||
* @param hw_event Pointer to perf_event_attr structure describing the event
|
||||
* @param pid Target process ID (0 for current process)
|
||||
* @param cpu Target CPU (-1 for all CPUs)
|
||||
* @param group_fd File descriptor of event group leader, or -1 if none
|
||||
* @param flags Additional flags (usually 0)
|
||||
*
|
||||
* @return File descriptor for the opened event on success
|
||||
* @return -1 on failure, errno is set appropriately
|
||||
*/
|
||||
|
||||
long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd,
|
||||
unsigned long flags);
|
||||
|
||||
/**
|
||||
* Check if a counter name is present in a comma-separated list of counters.
|
||||
*
|
||||
* This function checks whether the given counter name appears in the
|
||||
* provided list. If the list is NULL, the function assumes all counters
|
||||
* are allowed and returns true.
|
||||
*
|
||||
* @param name Name of the counter to search for
|
||||
* @param ctrlist Comma-separated list of counter names, or NULL
|
||||
*
|
||||
* @return true if the counter is in the list or list is NULL
|
||||
* @return false otherwise
|
||||
*/
|
||||
bool ctr_in_list(char *name, char *ctrlist);
|
||||
#endif
|
||||
|
||||
@@ -93,4 +93,7 @@ const char *zpci_pft_str(struct zpci_dev *zdev);
|
||||
const char *zpci_operstate_str(operstate_t state);
|
||||
operstate_t zpci_operstate_from_str(const char *oper_str);
|
||||
|
||||
struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name,
|
||||
struct zpci_netdev **netdev);
|
||||
|
||||
#endif /* LIB_ZPCI_PCI_LIST_H */
|
||||
|
||||
@@ -28,6 +28,8 @@ enum util_arch_machine_type {
|
||||
UTIL_ARCH_MACHINE_TYPE_Z15_T02 = 8562,
|
||||
UTIL_ARCH_MACHINE_TYPE_Z16 = 3931,
|
||||
UTIL_ARCH_MACHINE_TYPE_Z16_A02 = 3932,
|
||||
UTIL_ARCH_MACHINE_TYPE_Z17 = 9175,
|
||||
UTIL_ARCH_MACHINE_TYPE_Z17_2 = 9176,
|
||||
};
|
||||
|
||||
int util_arch_machine_type(void);
|
||||
|
||||
@@ -31,6 +31,7 @@ struct util_proc_dev_entry {
|
||||
#define UTIL_PROC_DEV_ENTRY_SD "sd"
|
||||
#define UTIL_PROC_DEV_ENTRY_BLKEXT "blkext"
|
||||
#define UTIL_PROC_DEV_ENTRY_MD "md"
|
||||
#define UTIL_PROC_DEV_ENTRY_DM "device-mapper"
|
||||
|
||||
int util_proc_part_get_entry(dev_t device, struct util_proc_part_entry *entry);
|
||||
void util_proc_part_free_entry(struct util_proc_part_entry *entry);
|
||||
|
||||
17
include/lib/util_str.h
Normal file
17
include/lib/util_str.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @defgroup util_str_h util_str: String functions
|
||||
* @{
|
||||
* @brief Manipulate and work with strings
|
||||
*
|
||||
* Copyright IBM Corp. 2025
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef LIB_UTIL_STR_H
|
||||
#define LIB_UTIL_STR_H
|
||||
|
||||
void util_str_rm_whitespace(const char *src, char *dest);
|
||||
|
||||
#endif /** LIB_UTIL_STR_H @} */
|
||||
@@ -9,7 +9,6 @@
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -49,19 +48,19 @@ static int device_sysfs_path(const char *device, char *path, const size_t path_s
|
||||
*/
|
||||
int ccw_is_device(const char *busid)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
static const char *const driver_paths[] = { "dasd-eckd", "virtio_ccw", "dasd-fba" };
|
||||
char *path;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(driver_paths); i++) {
|
||||
path = util_path_sysfs("bus/ccw/drivers/%s/%s", driver_paths[i], busid);
|
||||
if (access(path, R_OK) == 0) {
|
||||
free(path);
|
||||
return 1;
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path),
|
||||
"/sys/bus/ccw/drivers/dasd-eckd/%s", busid);
|
||||
if (access(path, R_OK) == 0)
|
||||
return 1;
|
||||
snprintf(path, sizeof(path),
|
||||
"/sys/bus/ccw/drivers/virtio_ccw/%s", busid);
|
||||
if (access(path, R_OK) == 0)
|
||||
return 1;
|
||||
snprintf(path, sizeof(path), "/sys/bus/ccw/drivers/dasd-fba/%s", busid);
|
||||
if (access(path, R_OK) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -72,6 +71,7 @@ int ccw_is_virtio_device(const char *device)
|
||||
{
|
||||
char path[PATH_MAX] = { '\0' };
|
||||
unsigned virtio = 0;
|
||||
char *path_pattern;
|
||||
|
||||
if (device_sysfs_path(device, path, sizeof(path)) != 0)
|
||||
return -1;
|
||||
@@ -80,76 +80,37 @@ int ccw_is_virtio_device(const char *device)
|
||||
* The output has the following format:
|
||||
* /sys/devices/css0/0.0.0000/0.0.0000/virtio0/block/vda
|
||||
*/
|
||||
if (sscanf(path, "/sys/devices/css0/%*[0-9a-f.]/%*[0-9a-f.]/virtio%u", &virtio) != 1)
|
||||
path_pattern = util_path_sysfs("devices/css0/%%*[0-9a-f.]/%%*[0-9a-f.]/virtio%%u");
|
||||
if (sscanf(path, path_pattern, &virtio) != 1) {
|
||||
free(path_pattern);
|
||||
return -1;
|
||||
}
|
||||
free(path_pattern);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return CCW Bus ID (old sysfs)
|
||||
* Return CCW Bus ID
|
||||
*/
|
||||
static int ccw_busid_get_sysfs_old(const char *device, char *busid)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
char buf[4096];
|
||||
int rc = 0;
|
||||
FILE *fh;
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/block/%s/uevent", device);
|
||||
fh = fopen(path, "r");
|
||||
if (fh == NULL)
|
||||
return -1;
|
||||
/*
|
||||
* The uevent file contains an entry like this:
|
||||
* PHYSDEVPATH=/devices/css0/0.0.206a/0.0.7e78
|
||||
*/
|
||||
while (fscanf(fh, "%s", buf) >= 0) {
|
||||
if (strstr(buf, "PHYSDEVPATH") != NULL) {
|
||||
strcpy(busid, strrchr(buf, '/') + 1);
|
||||
goto out_fclose;
|
||||
}
|
||||
}
|
||||
rc = -1;
|
||||
out_fclose:
|
||||
fclose(fh);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return CCW Bus ID (new sysfs)
|
||||
*/
|
||||
static int ccw_busid_get_sysfs_new(const char *device, char *busid)
|
||||
void ccw_busid_get(const char *device, char *busid)
|
||||
{
|
||||
char path[PATH_MAX] = { '\0' };
|
||||
char *path_pattern;
|
||||
|
||||
if (device_sysfs_path(device, path, sizeof(path)) != 0)
|
||||
return -1;
|
||||
ERR_EXIT("Could not lookup device number for \"%s\"", device);
|
||||
|
||||
/*
|
||||
* The output has the following format:
|
||||
* /sys/devices/css0/0.0.0119/0.0.3f19/block/dasda
|
||||
* /sys/devices/css0/0.0.0000/0.0.0000/virtio0/block/vda
|
||||
*/
|
||||
if (sscanf(path, "/sys/devices/css0/%*[0-9a-f.]/%[0-9a-f.]", busid) != 1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
path_pattern = util_path_sysfs("devices/css0/%%*[0-9a-f.]/%%[0-9a-f.]");
|
||||
if (sscanf(path, path_pattern, busid) != 1) {
|
||||
free(path_pattern);
|
||||
ERR_EXIT("Could not lookup device number for \"%s\"", device);
|
||||
}
|
||||
free(path_pattern);
|
||||
|
||||
/*
|
||||
* Return the device number for a device
|
||||
* dasda can be found in /sys/block/dasda/uevent or in a
|
||||
* symbolic link in the same directory. the first file only
|
||||
* contains the relevant information if we run on a kernel with
|
||||
* has the following kernel option enabled:
|
||||
* CONFIG_SYSFS_DEPRECATED
|
||||
*
|
||||
* This does not work when booting from tape
|
||||
*/
|
||||
void ccw_busid_get(const char *device, char *busid)
|
||||
{
|
||||
if (ccw_busid_get_sysfs_old(device, busid) == 0)
|
||||
return;
|
||||
if (ccw_busid_get_sysfs_new(device, busid) == 0)
|
||||
return;
|
||||
ERR_EXIT("Could not lookup device number for \"%s\"", device);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,17 @@
|
||||
|
||||
#include <ctype.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_proc.h"
|
||||
#include "lib/util_base.h"
|
||||
#include "lib/zt_common.h"
|
||||
#include "lib/util_path.h"
|
||||
|
||||
#include "ipl_tools.h"
|
||||
#include "proc.h"
|
||||
#include <linux/raid/md_u.h>
|
||||
|
||||
#define BOOTPARMS_NSS_MAX 56
|
||||
#define BOOTPARMS_CCW_MAX 64
|
||||
@@ -25,6 +30,8 @@
|
||||
|
||||
#define OPT_BRCHR 0x80
|
||||
|
||||
#define MD_MAJOR 9
|
||||
|
||||
enum target_type {
|
||||
TT_CCW,
|
||||
TT_FCP,
|
||||
@@ -414,15 +421,38 @@ static int set_reipl_type(const char *dev_name)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_chreipl_helper_cmd(dev_t dev, char cmd[PATH_MAX])
|
||||
static int is_md_device(const char *dev_name)
|
||||
{
|
||||
char abs_dev_name[PATH_MAX];
|
||||
mdu_array_info_t array;
|
||||
int is_md_device = 0;
|
||||
int fd;
|
||||
|
||||
if (snprintf(abs_dev_name, PATH_MAX, "/dev/%s", dev_name) >= PATH_MAX)
|
||||
return 0;
|
||||
fd = open(abs_dev_name, O_RDONLY);
|
||||
if (fd == -1)
|
||||
return 0;
|
||||
if (ioctl(fd, GET_ARRAY_INFO, &array) >= 0)
|
||||
is_md_device = 1;
|
||||
close(fd);
|
||||
return is_md_device;
|
||||
}
|
||||
|
||||
static int get_chreipl_helper_cmd(dev_t dev, char *dev_name, char cmd[PATH_MAX])
|
||||
{
|
||||
char *chreipl_helper;
|
||||
struct proc_dev_entry pde;
|
||||
char *chreipl_helper;
|
||||
char *driver_name;
|
||||
|
||||
if (proc_dev_get_entry(dev, 1, &pde) != 0)
|
||||
return -1;
|
||||
driver_name = pde.name;
|
||||
if (strcmp(driver_name, UTIL_PROC_DEV_ENTRY_BLKEXT) == 0 &&
|
||||
is_md_device(dev_name))
|
||||
driver_name = UTIL_PROC_DEV_ENTRY_MD;
|
||||
util_asprintf(&chreipl_helper,
|
||||
"%s/%s.%s", TOOLS_LIBDIR, "chreipl_helper", pde.name);
|
||||
"%s.%s", util_libdir_path("chreipl_helper"), driver_name);
|
||||
if (access(chreipl_helper, X_OK) != 0) {
|
||||
proc_dev_free_entry(&pde);
|
||||
free(chreipl_helper);
|
||||
@@ -437,7 +467,7 @@ static int get_chreipl_helper_cmd(dev_t dev, char cmd[PATH_MAX])
|
||||
/*
|
||||
* Use chreipl_helper (E.g. for device mapper devices)
|
||||
*/
|
||||
static int set_reipl_type_helper(int maj, int min)
|
||||
static int set_reipl_type_helper(int maj, int min, char *dev_name)
|
||||
{
|
||||
char helper_cmd[PATH_MAX], buf[4096];
|
||||
struct proc_part_entry ppe;
|
||||
@@ -445,7 +475,7 @@ static int set_reipl_type_helper(int maj, int min)
|
||||
dev_t dev;
|
||||
FILE *fh;
|
||||
|
||||
if (get_chreipl_helper_cmd(makedev(maj, min), helper_cmd) != 0)
|
||||
if (get_chreipl_helper_cmd(makedev(maj, min), dev_name, helper_cmd) != 0)
|
||||
return -1;
|
||||
fh = popen(helper_cmd, "r");
|
||||
if (fh == NULL)
|
||||
@@ -499,7 +529,7 @@ static void parse_node_args(char *nargv[], int nargc)
|
||||
ERR_EXIT("Invalid device node \"%s\" specified", path);
|
||||
if (set_reipl_type(ppe.name) == 0)
|
||||
goto out;
|
||||
if (set_reipl_type_helper(major(dev), minor(dev)) == 0)
|
||||
if (set_reipl_type_helper(major(dev), minor(dev), ppe.name) == 0)
|
||||
goto out;
|
||||
ERR_EXIT("Unsupported device node \"%s\" specified", path);
|
||||
out:
|
||||
@@ -719,11 +749,12 @@ static void parse_chreipl_options(int argc, char *argv[])
|
||||
|
||||
static void check_exists(const char *path, const char *attr)
|
||||
{
|
||||
char fpath[PATH_MAX];
|
||||
char *fpath;
|
||||
|
||||
snprintf(fpath, sizeof(fpath), "/sys/firmware/%s", path);
|
||||
fpath = util_path_sysfs("firmware/%s", path);
|
||||
if (access(fpath, F_OK) != 0)
|
||||
ERR_EXIT("System does not allow one to set %s", attr);
|
||||
free(fpath);
|
||||
}
|
||||
|
||||
static void write_str_optional(char *string, char *file, int exit_on_fail,
|
||||
@@ -739,7 +770,7 @@ static void write_str_optional(char *string, char *file, int exit_on_fail,
|
||||
* IMPLEMENTATION:
|
||||
*
|
||||
* "cio_ignore --is-ignored <busid>" returns 0 if the device is ignored,
|
||||
* 1 for internal errrors, and 2 if the device is not ignored.
|
||||
* 1 for internal errors, and 2 if the device is not ignored.
|
||||
*
|
||||
* We get the "cio_ignore" exit status by the return code of the system()
|
||||
* function via WEXITSTATUS().
|
||||
@@ -920,13 +951,14 @@ static void chreipl_nss(void)
|
||||
|
||||
static void chreipl_node(void)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
char *path;
|
||||
|
||||
if (!l.dev_set)
|
||||
ERR_EXIT("No device node specified");
|
||||
snprintf(path, sizeof(path), "/sys/block/%s/device", l.dev);
|
||||
path = util_path_sysfs("block/%s/device", l.dev);
|
||||
if (chdir(path) != 0)
|
||||
ERR_EXIT("Could not find device \"%s\"", l.dev);
|
||||
free(path);
|
||||
|
||||
switch (l.reipl_type) {
|
||||
case REIPL_CCW:
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_libc.h"
|
||||
|
||||
#include "ipl_tools.h"
|
||||
|
||||
static struct {
|
||||
@@ -42,24 +46,23 @@ static const char *get_ipl_banner(int show_ipl)
|
||||
void print_nss(int show_ipl)
|
||||
{
|
||||
char *dir = show_ipl ? "ipl" : "reipl/nss";
|
||||
char *path_bootparms = show_ipl ? "/sys/firmware/ipl/parm" :
|
||||
"/sys/firmware/reipl/nss/parm";
|
||||
char *path_bootparms = util_path_sysfs("firmware/%s/parm", dir);
|
||||
|
||||
printf("%-12s nss\n", get_ipl_banner(show_ipl));
|
||||
print_fw_str("Name: %s\n", dir, "name");
|
||||
if (access(path_bootparms, R_OK) == 0)
|
||||
print_fw_str("Bootparms: \"%s\"\n", dir, "parm");
|
||||
free(path_bootparms);
|
||||
}
|
||||
|
||||
void print_fcp(int show_ipl, int dump)
|
||||
{
|
||||
char *dir = show_ipl ? "ipl" : "reipl/fcp";
|
||||
char *path_bootparms = show_ipl ? "/sys/firmware/ipl/scp_data" :
|
||||
"/sys/firmware/reipl/fcp/scp_data";
|
||||
char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" :
|
||||
"/sys/firmware/reipl/fcp/loadparm";
|
||||
char loadparm[9], loadparm_path[PATH_MAX];
|
||||
char *path_reipl_clear = "/sys/firmware/reipl/fcp/clear";
|
||||
char *path_bootparms = util_path_sysfs("firmware/%s/scp_data", dir);
|
||||
char *path_loadparm = util_path_sysfs("firmware/%s/loadparm", dir);
|
||||
char *path_reipl_clear = util_path_sysfs("firmware/reipl/fcp/clear");
|
||||
char *path_secure_boot = util_path_sysfs("firmware/ipl/secure");
|
||||
char *loadparm;
|
||||
|
||||
if (dump)
|
||||
printf("%-12s fcp_dump\n", get_ipl_banner(show_ipl));
|
||||
@@ -72,27 +75,31 @@ void print_fcp(int show_ipl, int dump)
|
||||
print_fw_str("bootprog: %s\n", dir, "bootprog");
|
||||
print_fw_str("br_lba: %s\n", dir, "br_lba");
|
||||
if (access(path_loadparm, R_OK) == 0) {
|
||||
sprintf(loadparm_path, "%s/%s", dir, "loadparm");
|
||||
read_fw_str(loadparm, loadparm_path, sizeof(loadparm));
|
||||
if (strcmp(loadparm, " ") == 0)
|
||||
loadparm[0] = 0;
|
||||
loadparm = util_file_read_text_file(path_loadparm, 1);
|
||||
util_strstrip(loadparm);
|
||||
printf("Loadparm: \"%s\"\n", loadparm);
|
||||
free(loadparm);
|
||||
}
|
||||
if (access(path_bootparms, R_OK) == 0)
|
||||
print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data");
|
||||
if (!show_ipl && access(path_reipl_clear, R_OK) == 0)
|
||||
print_fw_str("clear: %s\n", dir, "clear");
|
||||
if (access(path_secure_boot, R_OK) == 0)
|
||||
print_fw_str("Secure boot: %s\n", "ipl", "secure");
|
||||
free(path_bootparms);
|
||||
free(path_loadparm);
|
||||
free(path_reipl_clear);
|
||||
free(path_secure_boot);
|
||||
}
|
||||
|
||||
void print_nvme(int show_ipl, int dump)
|
||||
{
|
||||
char *dir = show_ipl ? "ipl" : "reipl/nvme";
|
||||
char *path_bootparms = show_ipl ? "/sys/firmware/ipl/scp_data" :
|
||||
"/sys/firmware/reipl/nvme/scp_data";
|
||||
char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" :
|
||||
"/sys/firmware/reipl/nvme/loadparm";
|
||||
char loadparm[9], loadparm_path[PATH_MAX];
|
||||
char *path_reipl_clear = "/sys/firmware/reipl/nvme/clear";
|
||||
char *path_bootparms = util_path_sysfs("firmware/%s/scp_data", dir);
|
||||
char *path_loadparm = util_path_sysfs("firmware/%s/loadparm", dir);
|
||||
char *path_reipl_clear = util_path_sysfs("firmware/reipl/nvme/clear");
|
||||
char *path_secure_boot = util_path_sysfs("firmware/ipl/secure");
|
||||
char *loadparm;
|
||||
|
||||
if (dump)
|
||||
printf("%-12s nvme_dump\n", get_ipl_banner(show_ipl));
|
||||
@@ -104,49 +111,54 @@ void print_nvme(int show_ipl, int dump)
|
||||
print_fw_str("bootprog: %s\n", dir, "bootprog");
|
||||
print_fw_str("br_lba: %s\n", dir, "br_lba");
|
||||
if (access(path_loadparm, R_OK) == 0) {
|
||||
sprintf(loadparm_path, "%s/%s", dir, "loadparm");
|
||||
read_fw_str(loadparm, loadparm_path, sizeof(loadparm));
|
||||
if (strcmp(loadparm, " ") == 0)
|
||||
loadparm[0] = 0;
|
||||
loadparm = util_file_read_text_file(path_loadparm, 1);
|
||||
util_strstrip(loadparm);
|
||||
printf("Loadparm: \"%s\"\n", loadparm);
|
||||
free(loadparm);
|
||||
}
|
||||
if (access(path_bootparms, R_OK) == 0)
|
||||
print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data");
|
||||
if (!show_ipl && access(path_reipl_clear, R_OK) == 0)
|
||||
print_fw_str("clear: %s\n", dir, "clear");
|
||||
if (access(path_secure_boot, R_OK) == 0)
|
||||
print_fw_str("Secure boot: %s\n", "ipl", "secure");
|
||||
free(path_bootparms);
|
||||
free(path_loadparm);
|
||||
free(path_reipl_clear);
|
||||
free(path_secure_boot);
|
||||
}
|
||||
|
||||
void print_ccw(int show_ipl)
|
||||
{
|
||||
char loadparm[9], loadparm_path[PATH_MAX];
|
||||
char *dir = show_ipl ? "ipl" : "reipl/ccw";
|
||||
char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" :
|
||||
"/sys/firmware/reipl/ccw/loadparm";
|
||||
char *path_bootparms = show_ipl ? "/sys/firmware/ipl/parm" :
|
||||
"/sys/firmware/reipl/ccw/parm";
|
||||
char *path_reipl_clear = "/sys/firmware/reipl/ccw/clear";
|
||||
char *path_loadparm = util_path_sysfs("firmware/%s/loadparm", dir);
|
||||
char *path_bootparms = util_path_sysfs("firmware/%s/parm", dir);
|
||||
char *path_reipl_clear = util_path_sysfs("firmware/reipl/ccw/clear");
|
||||
char *loadparm;
|
||||
|
||||
printf("%-12s ccw\n", get_ipl_banner(show_ipl));
|
||||
print_fw_str("Device: %s\n", dir, "device");
|
||||
if (access(path_loadparm, R_OK) == 0) {
|
||||
sprintf(loadparm_path, "%s/%s", dir, "loadparm");
|
||||
read_fw_str(loadparm, loadparm_path, sizeof(loadparm));
|
||||
if (strcmp(loadparm, " ") == 0)
|
||||
loadparm[0] = 0;
|
||||
loadparm = util_file_read_text_file(path_loadparm, 1);
|
||||
util_strstrip(loadparm);
|
||||
printf("Loadparm: \"%s\"\n", loadparm);
|
||||
free(loadparm);
|
||||
}
|
||||
if (access(path_bootparms, R_OK) == 0)
|
||||
print_fw_str("Bootparms: \"%s\"\n", dir, "parm");
|
||||
if (!show_ipl && access(path_reipl_clear, R_OK) == 0)
|
||||
print_fw_str("clear: %s\n", dir, "clear");
|
||||
free(path_loadparm);
|
||||
free(path_bootparms);
|
||||
free(path_reipl_clear);
|
||||
}
|
||||
|
||||
void print_eckd(int show_ipl, const char *name)
|
||||
{
|
||||
char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" :
|
||||
"/sys/firmware/reipl/eckd/loadparm";
|
||||
char *dir = show_ipl ? "ipl" : "reipl/eckd";
|
||||
char loadparm[9], loadparm_path[PATH_MAX];
|
||||
char *path_loadparm = util_path_sysfs("firmware/%s/loadparm", dir);
|
||||
char *path_secure_boot = util_path_sysfs("firmware/ipl/secure");
|
||||
char *loadparm;
|
||||
|
||||
printf("%-12s %s\n", get_ipl_banner(show_ipl), name);
|
||||
|
||||
@@ -155,14 +167,17 @@ void print_eckd(int show_ipl, const char *name)
|
||||
print_fw_str("br_chr: %s\n", dir, "br_chr");
|
||||
print_fw_str("Bootparm: \"%s\"\n", dir, "scp_data");
|
||||
if (access(path_loadparm, R_OK) == 0) {
|
||||
sprintf(loadparm_path, "%s/%s", dir, "loadparm");
|
||||
read_fw_str(loadparm, loadparm_path, sizeof(loadparm));
|
||||
if (strcmp(loadparm, " ") == 0)
|
||||
loadparm[0] = 0;
|
||||
loadparm = util_file_read_text_file(path_loadparm, 1);
|
||||
util_strstrip(loadparm);
|
||||
printf("Loadparm: \"%s\"\n", loadparm);
|
||||
free(loadparm);
|
||||
}
|
||||
if (!show_ipl)
|
||||
print_fw_str("clear: %s\n", dir, "clear");
|
||||
if (access(path_secure_boot, R_OK) == 0)
|
||||
print_fw_str("Secure boot: %s\n", "ipl", "secure");
|
||||
free(path_loadparm);
|
||||
free(path_secure_boot);
|
||||
}
|
||||
|
||||
static void parse_lsreipl_options(int argc, char *argv[])
|
||||
@@ -196,16 +211,14 @@ static void parse_lsreipl_options(int argc, char *argv[])
|
||||
|
||||
void cmd_lsreipl(int argc, char *argv[])
|
||||
{
|
||||
char reipl_type_str[1024];
|
||||
char *reipl_type_str;
|
||||
|
||||
parse_lsreipl_options(argc, argv);
|
||||
|
||||
if (l.ipl_set)
|
||||
read_fw_str(reipl_type_str, "ipl/ipl_type",
|
||||
sizeof(reipl_type_str));
|
||||
reipl_type_str = read_fw_str("ipl/ipl_type");
|
||||
else
|
||||
read_fw_str(reipl_type_str, "reipl/reipl_type",
|
||||
sizeof(reipl_type_str));
|
||||
reipl_type_str = read_fw_str("reipl/reipl_type");
|
||||
|
||||
if (strcmp(reipl_type_str, "fcp") == 0)
|
||||
print_fcp(l.ipl_set, 0);
|
||||
@@ -225,5 +238,6 @@ void cmd_lsreipl(int argc, char *argv[])
|
||||
else
|
||||
printf("%s: %s (unknown)\n", get_ipl_banner(l.ipl_set),
|
||||
reipl_type_str);
|
||||
free(reipl_type_str);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "ipl_tools.h"
|
||||
|
||||
static const char *const usage_lsshut =
|
||||
@@ -60,12 +62,13 @@ static void parse_lsshut_options(int argc, char *argv[])
|
||||
*/
|
||||
static void read_vmcmd(char *str, const char *path)
|
||||
{
|
||||
char buf[128], tmp[512];
|
||||
char *ptr_old, *ptr;
|
||||
char tmp[512];
|
||||
char *buf;
|
||||
|
||||
*str = 0;
|
||||
buf = read_fw_str(path);
|
||||
ptr_old = ptr = buf;
|
||||
read_fw_str(buf, path, sizeof(buf));
|
||||
while ((ptr = strchr(ptr_old, '\n'))) {
|
||||
*ptr = 0;
|
||||
sprintf(tmp, "\"%s\",", ptr_old);
|
||||
@@ -74,23 +77,31 @@ static void read_vmcmd(char *str, const char *path)
|
||||
}
|
||||
sprintf(tmp, "\"%s\"", ptr_old);
|
||||
strcat(str, tmp);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void print_kdump(void)
|
||||
{
|
||||
struct stat sb;
|
||||
char tmp[1024];
|
||||
char *path;
|
||||
char *tmp;
|
||||
|
||||
if (stat("/sys/kernel/kexec_crash_loaded", &sb) != 0)
|
||||
path = util_path_sysfs("kernel/kexec_crash_loaded");
|
||||
if (stat(path, &sb) != 0) {
|
||||
free(path);
|
||||
return;
|
||||
read_str(tmp, "/sys/kernel/kexec_crash_loaded", sizeof(tmp));
|
||||
}
|
||||
tmp = util_file_read_text_file(path, 1);
|
||||
if (strncmp(tmp, "1", 1) == 0)
|
||||
printf("kdump,");
|
||||
free(path);
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
static void shutdown_trigger_print(struct shutdown_trigger *trigger)
|
||||
{
|
||||
char tmp[1024], cmd[1024], path[PATH_MAX];
|
||||
char cmd[1024], path[PATH_MAX];
|
||||
char *tmp;
|
||||
|
||||
sprintf(path, "shutdown_actions/%s", trigger->name_sysfs);
|
||||
|
||||
@@ -99,7 +110,7 @@ static void shutdown_trigger_print(struct shutdown_trigger *trigger)
|
||||
if ((trigger == &shutdown_trigger_panic ||
|
||||
trigger == &shutdown_trigger_restart))
|
||||
print_kdump();
|
||||
read_fw_str(tmp, path, sizeof(tmp));
|
||||
tmp = read_fw_str(path);
|
||||
if (strncmp(tmp, "vmcmd", strlen("vmcmd")) == 0) {
|
||||
sprintf(path, "vmcmd/%s", trigger->name_sysfs);
|
||||
read_vmcmd(cmd, path);
|
||||
@@ -107,6 +118,7 @@ static void shutdown_trigger_print(struct shutdown_trigger *trigger)
|
||||
} else {
|
||||
printf("%s\n", tmp);
|
||||
}
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void cmd_lsshut(int argc, char *argv[])
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "ipl_tools.h"
|
||||
|
||||
/*
|
||||
@@ -18,11 +19,14 @@
|
||||
*/
|
||||
int fcp_is_device(const char *devno)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
char *path;
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/bus/ccw/drivers/zfcp/%s", devno);
|
||||
if (chdir(path) != 0)
|
||||
path = util_path_sysfs("bus/ccw/drivers/zfcp/%s", devno);
|
||||
if (chdir(path) != 0) {
|
||||
free(path);
|
||||
return 0;
|
||||
}
|
||||
free(path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -31,11 +35,12 @@ int fcp_is_device(const char *devno)
|
||||
*/
|
||||
void fcp_wwpn_get(const char *device, char *wwpn)
|
||||
{
|
||||
char path[PATH_MAX], buf[20];
|
||||
char buf[20];
|
||||
char *path;
|
||||
FILE *fh;
|
||||
int rc;
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/block/%s/device/wwpn", device);
|
||||
path = util_path_sysfs("block/%s/device/wwpn", device);
|
||||
fh = fopen(path, "r");
|
||||
if (fh == NULL)
|
||||
ERR_EXIT_ERRNO("Could not open \"%s\"", path);
|
||||
@@ -44,19 +49,20 @@ void fcp_wwpn_get(const char *device, char *wwpn)
|
||||
ERR_EXIT("Could not lookup WWPN \"%s\"", path);
|
||||
util_strlcpy(wwpn, buf, 20);
|
||||
fclose(fh);
|
||||
free(path);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return the lun of a device
|
||||
*/
|
||||
void fcp_lun_get(const char *device, char *lun)
|
||||
{
|
||||
char path[PATH_MAX], buf[20];
|
||||
char buf[20];
|
||||
char *path;
|
||||
FILE *fh;
|
||||
int rc;
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/block/%s/device/fcp_lun", device);
|
||||
path = util_path_sysfs("block/%s/device/fcp_lun", device);
|
||||
fh = fopen(path, "r");
|
||||
if (fh == NULL)
|
||||
ERR_EXIT_ERRNO("Could not open \"%s\"", path);
|
||||
@@ -65,6 +71,7 @@ void fcp_lun_get(const char *device, char *lun)
|
||||
ERR_EXIT("Could not lookup LUN \"%s\"", path);
|
||||
util_strlcpy(lun, buf, 20);
|
||||
fclose(fh);
|
||||
free(path);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -72,11 +79,12 @@ void fcp_lun_get(const char *device, char *lun)
|
||||
*/
|
||||
void fcp_busid_get(const char *device, char *devno)
|
||||
{
|
||||
char buf[4096], path[PATH_MAX];
|
||||
char buf[4096];
|
||||
char *path;
|
||||
FILE *fh;
|
||||
int rc;
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/block/%s/device/hba_id", device);
|
||||
path = util_path_sysfs("block/%s/device/hba_id", device);
|
||||
fh = fopen(path, "r");
|
||||
if (fh == NULL)
|
||||
ERR_EXIT_ERRNO("Could not open \"%s\"", path);
|
||||
@@ -85,4 +93,5 @@ void fcp_busid_get(const char *device, char *devno)
|
||||
ERR_EXIT("Could not find device \"%s\"", path);
|
||||
strcpy(devno, buf);
|
||||
fclose(fh);
|
||||
free(path);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#ifndef IPL_TOOLS_H
|
||||
#define IPL_TOOLS_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
@@ -36,41 +35,38 @@ extern struct globals {
|
||||
/*
|
||||
* Commands
|
||||
*/
|
||||
extern void cmd_lsshut(int argc, char *argv[]);
|
||||
extern void cmd_chshut(int argc, char *argv[]);
|
||||
extern void cmd_lsreipl(int argc, char *argv[]);
|
||||
extern void cmd_chreipl(int argc, char *argv[]);
|
||||
void cmd_lsshut(int argc, char *argv[]);
|
||||
void cmd_chshut(int argc, char *argv[]);
|
||||
void cmd_lsreipl(int argc, char *argv[]);
|
||||
void cmd_chreipl(int argc, char *argv[]);
|
||||
|
||||
extern void print_ccw(int show_ipl);
|
||||
extern void print_fcp(int show_ipl, int dump);
|
||||
extern void print_nvme(int show_ipl, int dump);
|
||||
extern void print_nss(int show_ipl);
|
||||
extern void print_eckd(int show_ipl, const char *name);
|
||||
void print_ccw(int show_ipl);
|
||||
void print_fcp(int show_ipl, int dump);
|
||||
void print_nvme(int show_ipl, int dump);
|
||||
void print_nss(int show_ipl);
|
||||
void print_eckd(int show_ipl, const char *name);
|
||||
|
||||
/*
|
||||
* Helper
|
||||
*/
|
||||
extern int is_lpar(void);
|
||||
extern int is_root(void);
|
||||
int is_lpar(void);
|
||||
int is_root(void);
|
||||
|
||||
extern void strlow(char *s);
|
||||
void write_str(char *string, char *file);
|
||||
int write_str_errno(char *string, char *file);
|
||||
char *read_fw_str(const char *file);
|
||||
void print_fw_str(const char *fmt, const char *dir, const char *file);
|
||||
|
||||
extern void write_str(char *string, char *file);
|
||||
extern int write_str_errno(char *string, char *file);
|
||||
extern void read_str(char *string, const char *file, size_t len);
|
||||
extern void read_fw_str(char *string, const char *file, size_t len);
|
||||
extern void print_fw_str(const char *fmt, const char *dir, const char *file);
|
||||
|
||||
extern void __noreturn print_version_exit(void);
|
||||
extern void __noreturn print_help_hint_exit(void);
|
||||
void __noreturn print_version_exit(void);
|
||||
void __noreturn print_help_hint_exit(void);
|
||||
|
||||
/*
|
||||
* FCP
|
||||
*/
|
||||
extern int fcp_is_device(const char *devno);
|
||||
extern void fcp_lun_get(const char *device, char *lun);
|
||||
extern void fcp_wwpn_get(const char *device, char *wwpn);
|
||||
extern void fcp_busid_get(const char *device, char *devno);
|
||||
int fcp_is_device(const char *devno);
|
||||
void fcp_lun_get(const char *device, char *lun);
|
||||
void fcp_wwpn_get(const char *device, char *wwpn);
|
||||
void fcp_busid_get(const char *device, char *devno);
|
||||
|
||||
/*
|
||||
* NVME
|
||||
@@ -79,16 +75,16 @@ extern void fcp_busid_get(const char *device, char *devno);
|
||||
#define NVME_DEV_MAX_LEN 15 /* "nvme" + u32 in decimal + null */
|
||||
#define NVME_PATH_MAX (PATH_MAX + NAME_MAX + 1)
|
||||
|
||||
extern void nvme_fid_get(const char *device, char *fid);
|
||||
extern void nvme_nsid_get(const char *device, char *nsid);
|
||||
void nvme_fid_get(const char *device, char *fid);
|
||||
void nvme_nsid_get(const char *device, char *nsid);
|
||||
int nvme_is_device(char *fid_str, char *nsid_str);
|
||||
|
||||
/*
|
||||
* CCW
|
||||
*/
|
||||
extern int ccw_is_device(const char *devno);
|
||||
extern int ccw_is_virtio_device(const char *device);
|
||||
extern void ccw_busid_get(const char *device, char *devno);
|
||||
int ccw_is_device(const char *devno);
|
||||
int ccw_is_virtio_device(const char *device);
|
||||
void ccw_busid_get(const char *device, char *devno);
|
||||
|
||||
/*
|
||||
* Shutdown trigger
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "ipl_tools.h"
|
||||
|
||||
static void nvme_dev_from_bdev(char *dev_name)
|
||||
@@ -31,8 +32,9 @@ static void nvme_dev_from_bdev(char *dev_name)
|
||||
*/
|
||||
void nvme_fid_get(const char *device, char *fid)
|
||||
{
|
||||
char path[PATH_MAX], buf[FID_MAX_LEN];
|
||||
char nvme_dev[NVME_DEV_MAX_LEN];
|
||||
char buf[FID_MAX_LEN];
|
||||
char *path;
|
||||
|
||||
/*
|
||||
* An NVMe may present multiple namespaces and thus block devices, even
|
||||
@@ -42,24 +44,27 @@ void nvme_fid_get(const char *device, char *fid)
|
||||
util_strlcpy(nvme_dev, device, sizeof(nvme_dev));
|
||||
nvme_dev_from_bdev(nvme_dev);
|
||||
|
||||
snprintf(path, PATH_MAX, "/sys/class/nvme/%s/device/function_id", nvme_dev);
|
||||
path = util_path_sysfs("class/nvme/%s/device/function_id", nvme_dev);
|
||||
if (util_file_read_line(buf, FID_MAX_LEN, path))
|
||||
ERR_EXIT_ERRNO("Could not read from \"%s\"", path);
|
||||
|
||||
util_strlcpy(fid, buf, FID_MAX_LEN);
|
||||
free(path);
|
||||
}
|
||||
/*
|
||||
* Return the nsid of a device
|
||||
*/
|
||||
void nvme_nsid_get(const char *device, char *nsid)
|
||||
{
|
||||
char path[PATH_MAX], buf[FID_MAX_LEN];
|
||||
char buf[FID_MAX_LEN];
|
||||
char *path;
|
||||
|
||||
snprintf(path, PATH_MAX, "/sys/block/%s/nsid", device);
|
||||
path = util_path_sysfs("block/%s/nsid", device);
|
||||
if (util_file_read_line(buf, FID_MAX_LEN, path))
|
||||
ERR_EXIT_ERRNO("Could not read from \"%s\"", path);
|
||||
|
||||
util_strlcpy(nsid, buf, FID_MAX_LEN);
|
||||
free(path);
|
||||
}
|
||||
|
||||
static int next_entry(DIR *dir, char *in_path, char *out_path,
|
||||
@@ -93,13 +98,14 @@ static int next_entry(DIR *dir, char *in_path, char *out_path,
|
||||
|
||||
static int nvme_getdev_by_fid(char *fidstr, char *devpath)
|
||||
{
|
||||
char temp_path[PATH_MAX+19], real_path[PATH_MAX];
|
||||
char *sys_path = "/sys/class/nvme";
|
||||
char temp_path[PATH_MAX + 19], real_path[PATH_MAX];
|
||||
u_int64_t target_fid, curfid;
|
||||
char *sys_path;
|
||||
DIR *dir;
|
||||
char *end;
|
||||
int rc = -1;
|
||||
|
||||
sys_path = util_path_sysfs("class/nvme");
|
||||
target_fid = strtoul(fidstr, &end, 16);
|
||||
if (*end)
|
||||
ERR_EXIT("Invalid function_id given %s", fidstr);
|
||||
@@ -126,6 +132,7 @@ static int nvme_getdev_by_fid(char *fidstr, char *devpath)
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
free(sys_path);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "ipl_tools.h"
|
||||
#include "proc.h"
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#ifndef PROC_H
|
||||
#define PROC_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
struct proc_part_entry {
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "ipl_tools.h"
|
||||
|
||||
/*
|
||||
@@ -46,47 +47,18 @@ int is_root(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a string to lower case
|
||||
*/
|
||||
void strlow(char *s)
|
||||
{
|
||||
while (*s) {
|
||||
*s = tolower(*s);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a string from a particular file
|
||||
*/
|
||||
void read_str(char *string, const char *path, size_t len)
|
||||
{
|
||||
size_t rc;
|
||||
FILE *fh;
|
||||
|
||||
fh = fopen(path, "rb");
|
||||
if (fh == NULL)
|
||||
ERR_EXIT_ERRNO("Could not open \"%s\"", path);
|
||||
rc = fread(string, 1, len - 1, fh);
|
||||
if (rc == 0 && ferror(fh))
|
||||
ERR_EXIT_ERRNO("Could not read \"%s\"", path);
|
||||
fclose(fh);
|
||||
string[rc] = 0;
|
||||
if (string[strlen(string) - 1] == '\n')
|
||||
string[strlen(string) - 1] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a string from a particular /sys/firmware file
|
||||
*/
|
||||
void read_fw_str(char *string, const char *file, size_t len)
|
||||
char *read_fw_str(const char *file)
|
||||
{
|
||||
char *string;
|
||||
char *path;
|
||||
|
||||
util_asprintf(&path, "/sys/firmware/%s", file);
|
||||
read_str(string, path, len);
|
||||
path = util_path_sysfs("firmware/%s", file);
|
||||
string = util_file_read_text_file(path, 1);
|
||||
free(path);
|
||||
return string;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -94,11 +66,13 @@ void read_fw_str(char *string, const char *file, size_t len)
|
||||
*/
|
||||
void print_fw_str(const char *fmt, const char *dir, const char *file)
|
||||
{
|
||||
char path[PATH_MAX], str[4096];
|
||||
char path[PATH_MAX];
|
||||
char *str;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s", dir, file);
|
||||
read_fw_str(str, path, sizeof(str));
|
||||
str = read_fw_str(path);
|
||||
printf(fmt, str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -106,17 +80,19 @@ void print_fw_str(const char *fmt, const char *dir, const char *file)
|
||||
*/
|
||||
void write_str(char *string, char *file)
|
||||
{
|
||||
char path[PATH_MAX], value[4096];
|
||||
char value[4096];
|
||||
char *path;
|
||||
int fh;
|
||||
|
||||
path = util_path_sysfs("firmware/%s", file);
|
||||
snprintf(value, sizeof(value), "%s\n", string);
|
||||
snprintf(path, sizeof(path), "/sys/firmware/%s", file);
|
||||
fh = open(path, O_WRONLY);
|
||||
if (fh < 0)
|
||||
ERR_EXIT_ERRNO("Could not open \"%s\"", file);
|
||||
if (write(fh, value, strlen(value)) < 0)
|
||||
ERR_EXIT_ERRNO("Could not set \"%s\"", file);
|
||||
close(fh);
|
||||
free(path);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -124,17 +100,18 @@ void write_str(char *string, char *file)
|
||||
*/
|
||||
int write_str_errno(char *string, char *file)
|
||||
{
|
||||
char path[PATH_MAX], value[4096];
|
||||
char value[4096];
|
||||
char *path;
|
||||
int fh;
|
||||
|
||||
path = util_path_sysfs("firmware/%s", file);
|
||||
snprintf(value, sizeof(value), "%s\n", string);
|
||||
snprintf(path, sizeof(path), "/sys/firmware/%s", file);
|
||||
fh = open(path, O_WRONLY);
|
||||
if (fh < 0)
|
||||
return errno;
|
||||
if (write(fh, value, strlen(value)) < 0)
|
||||
return errno;
|
||||
close(fh);
|
||||
free(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ install-man: $(MANS)
|
||||
msection=`echo $$man |sed 's/.*\.\([1-9]\)$$/man\1/'` ; \
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 -D $$man $(DESTDIR)$(MANDIR)/$$msection/$$man ; \
|
||||
done
|
||||
ln -f -s chiucvallow.8 $(DESTDIR)$(MANDIR)/man8/lsiucvallow.8
|
||||
|
||||
clean:
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ terminal application
|
||||
To connect to the first z/VM IUCV HVC terminal device on the z/VM guest virtual
|
||||
machine "LNX1234", run:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
iucvconn LNX1234 lnxhvc0
|
||||
@@ -215,7 +215,7 @@ For each HVC terminal device, a \fBgetty\fP program must be started.
|
||||
To configure and start \fBgetty\fP on a HVC terminal, open
|
||||
.BR /etc/inittab (5),
|
||||
and add a new entry similar to this one:
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
|
||||
@@ -261,7 +261,7 @@ IUCV HVC terminal:
|
||||
.IP "1." 4
|
||||
To display the current terminal name, issue the command:
|
||||
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
user@host:~$ echo $TERM
|
||||
@@ -275,7 +275,7 @@ To connect to a z/VM IUCV HVC terminal, run
|
||||
.BR iucvconn (1)
|
||||
and log in as usual:
|
||||
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
user@host:~$ iucvconn MYLNX01 lnxhvc0
|
||||
@@ -289,7 +289,7 @@ user@MYLNX01:~$
|
||||
To assign the terminal name from step 1. to the terminal environment variable,
|
||||
issue the following command:
|
||||
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
user@MYLNX01:~$ export TERM=xterm
|
||||
@@ -304,7 +304,7 @@ environment variable.
|
||||
.PP
|
||||
For getty programs, a sample terminal environment configuration might look like:
|
||||
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
h0:2345:respawn:/sbin/agetty -L 9600 hvc0 linux
|
||||
|
||||
@@ -123,7 +123,7 @@ the characters to the connected terminal.
|
||||
To access the "lnxterm" terminal on the Linux instance in
|
||||
z/VM guest virtual machine LNXSYS01:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
iucvconn LNXSYS01 lnxterm
|
||||
@@ -134,7 +134,7 @@ iucvconn LNXSYS01 lnxterm
|
||||
To access the first z/VM IUCV HVC terminal on the Linux instance in
|
||||
z/VM guest virtual machine LNXSYS02:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
iucvconn LNXSYS02 lnxhvc0
|
||||
@@ -145,7 +145,7 @@ iucvconn LNXSYS02 lnxhvc0
|
||||
To create a transcript of the terminal session to the Linux instance in
|
||||
z/VM guest virtual machine LNXSYS99:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
iucvconn -s ~/transcripts/lnxsys99 LNXSYS99 lnxhvc0
|
||||
|
||||
@@ -94,7 +94,7 @@ additional options.
|
||||
.SH EXAMPLES
|
||||
To allow remote logins using the terminal identifier "lnxterm":
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
iucvtty lnxterm
|
||||
@@ -104,7 +104,7 @@ iucvtty lnxterm
|
||||
|
||||
To only allow users from LNXSYS01 to connect to terminal "lnxterm":
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
iucvtty -a LNXSYS01 lnxterm
|
||||
@@ -115,7 +115,7 @@ iucvtty -a LNXSYS01 lnxterm
|
||||
To only allow users from LNXSYS10 through LNXSYS19 to connect to terminal
|
||||
"lnxterm":
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
iucvtty -a "LNXSYS1[0-9]" lnxterm
|
||||
@@ -125,7 +125,7 @@ iucvtty -a "LNXSYS1[0-9]" lnxterm
|
||||
|
||||
To use \fB/sbin/sulogin\fP instead of \fB/bin/login\fP for terminal "suterm":
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
iucvtty suterm -- /sbin/sulogin
|
||||
@@ -136,7 +136,7 @@ iucvtty suterm -- /sbin/sulogin
|
||||
An entry in \fB/etc/inittab\fP to facilitate user logins on terminal "lnxterm"
|
||||
with \fB/bin/login\fP could be:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
t1:2345:respawn:/usr/bin/iucvtty lnxterm
|
||||
@@ -147,7 +147,7 @@ t1:2345:respawn:/usr/bin/iucvtty lnxterm
|
||||
An entry in \fB/etc/inittab\fP to facilitate user logins on terminal "suterm"
|
||||
with \fB/sbin/sulogin\fP in single user mode could be:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
s1:S:respawn:/usr/bin/iucvtty suterm -- /sbin/sulogin
|
||||
|
||||
@@ -79,7 +79,7 @@ users".
|
||||
|
||||
The output for "list" authorization is a list of z/VM guest virtual machines,
|
||||
for example:
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25i
|
||||
.nf
|
||||
|
||||
@@ -94,7 +94,7 @@ guest5
|
||||
.ft
|
||||
The output for "regex" authorization is a list of one or more
|
||||
regular expressions, for example:
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25i
|
||||
.nf
|
||||
|
||||
@@ -109,7 +109,7 @@ Regular expressions for your authorization:
|
||||
If \fBts-shell\fP is configured to connect to particular z/VM guest virtual
|
||||
machines only, the output for "regex" authorization is followed by a list of
|
||||
the user IDs that match at least one of the regular expressions:
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25i
|
||||
.nf
|
||||
|
||||
@@ -139,7 +139,7 @@ To change the default terminal identifier, use the \fBterminal\fP command.
|
||||
|
||||
In the following example, a user opens a terminal connection to the Linux
|
||||
instance in z/VM guest virtual machine LNXSYS01:
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25i
|
||||
.nf
|
||||
|
||||
@@ -164,7 +164,7 @@ If \fBterminal\fP is called with the \fIidentifier\fP being specified,
|
||||
If \fIidentifier\fP is not specified, the current default terminal identifier
|
||||
is displayed:
|
||||
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25i
|
||||
.nf
|
||||
user@ts-shell> terminal
|
||||
@@ -212,7 +212,7 @@ guest virtual machines.
|
||||
A typical \fBIUCV\fP authorization statement in the z/VM directory entry of the
|
||||
terminal server z/VM guest virtual machine might be:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
IUCV ANY
|
||||
@@ -301,7 +301,7 @@ instance.
|
||||
|
||||
For example, to create a list of all z/VM guest virtual machines with names that
|
||||
start with "LINUX" and are followed by digits, use:
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
|
||||
@@ -373,7 +373,7 @@ group name and prefixed with "@".
|
||||
|
||||
Here is an example of a Linux user and group authorization:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
alice = list:guest01,guest02
|
||||
@@ -395,7 +395,7 @@ name on a separate line.
|
||||
.PP
|
||||
The following example shows the usage of the \fIfile:\fP prefix:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
@testgrp = file:/etc/iucvterm/auth/test-systems.list
|
||||
@@ -427,7 +427,7 @@ To authorize user bob for all z/VM guest virtual machines with names that
|
||||
start with "lnx" and are followed with at least three but not more than five
|
||||
alphanumeric characters, use:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
bob = regex:lnx\\w{3,5}
|
||||
@@ -441,7 +441,7 @@ test or production environment: authorize all users in the "testgrp" group for
|
||||
all systems in the test environment; and respectively, authorize all users in
|
||||
the "prodgrp" group for all systems in the production environment:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
@testgrp = regex:test\\w+
|
||||
@@ -461,7 +461,7 @@ of the other type are ignored.
|
||||
|
||||
Example:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
@users = list:guest01,guest03,guest05
|
||||
@@ -484,7 +484,7 @@ To use the \fBts-shell\fP as the login shell for Linux users, follow these steps
|
||||
Add the path of the \fBts-shell\fP program to the \fI/etc/shells\fP file that
|
||||
contains the list of valid login shells:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
echo $(which ts-shell) >> /etc/shells
|
||||
@@ -498,7 +498,7 @@ Change the login shell of a particular Linux user using the
|
||||
.BR chsh (1)
|
||||
program:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
chsh -s $(which ts-shell) alice
|
||||
|
||||
@@ -116,7 +116,7 @@ specifies an exit status in this range.
|
||||
.SS inittab
|
||||
To start \fB/sbin/agetty\fP on terminal device "hvc1", specify:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
h1:2345:respawn:/sbin/\*s hvc1 /sbin/agetty -L 9600 %t linux
|
||||
@@ -128,7 +128,7 @@ h1:2345:respawn:/sbin/\*s hvc1 /sbin/agetty -L 9600 %t linux
|
||||
To start \fB/sbin/agetty\fP on terminal device "hvc1", add the following
|
||||
settings to the job file:
|
||||
.PP
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25in
|
||||
.nf
|
||||
respawn
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "lib/ccw.h"
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ examples = libcpumf_example
|
||||
all: $(lib)
|
||||
examples: $(lib) $(examples)
|
||||
|
||||
objects = libcpumf_pmutype.o libcpumf_cpuset.o libcpumf_support.o
|
||||
objects = libcpumf_pmutype.o libcpumf_cpuset.o libcpumf_support.o \
|
||||
libcpumf_ctrset.o
|
||||
|
||||
$(lib): $(objects)
|
||||
|
||||
|
||||
@@ -5,20 +5,23 @@
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lib/libcpumf.h"
|
||||
#include "lib/util_file.h"
|
||||
#include "lib/util_libc.h"
|
||||
#include "lib/util_path.h"
|
||||
|
||||
int libcpumf_cpuset(const char *parm, cpu_set_t *mask)
|
||||
{
|
||||
char *cp, *buffer = strdup(parm);
|
||||
char *cp, *buffer = util_strdup(parm);
|
||||
char *cp2 = buffer;
|
||||
int to, from, rc;
|
||||
|
||||
if (!buffer) /* Errno set to ENOMEM */
|
||||
return -1;
|
||||
/* Check for invalid characters, such as 11.12 instead 11-12
|
||||
* but allow blanks and newline. Newline is appended
|
||||
* when the string is taken from sysfs files, for example
|
||||
@@ -26,7 +29,8 @@ int libcpumf_cpuset(const char *parm, cpu_set_t *mask)
|
||||
*/
|
||||
if (strspn(buffer, "0123456789-,\n ") != strlen(buffer)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
CPU_ZERO(mask);
|
||||
for (; (cp = strtok(buffer, ",")); buffer = NULL) {
|
||||
@@ -51,25 +55,19 @@ int libcpumf_cpuset(const char *parm, cpu_set_t *mask)
|
||||
}
|
||||
rc = 0;
|
||||
out:
|
||||
free(buffer);
|
||||
free(cp2);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int libcpumf_cpuset_fn(const char *filename, cpu_set_t *mask)
|
||||
{
|
||||
char *txt = NULL;
|
||||
ssize_t ret = -1;
|
||||
size_t len = 0;
|
||||
FILE *fp;
|
||||
char *path = util_path_sysfs(filename);
|
||||
char txt[PATH_MAX];
|
||||
int ret = util_file_read_line(txt, sizeof(txt), "%s", path);
|
||||
|
||||
fp = fopen(filename, "r");
|
||||
if (!fp)
|
||||
return ret;
|
||||
free(path);
|
||||
/* Read out file, one line expected */
|
||||
ret = getline(&txt, &len, fp);
|
||||
fclose(fp);
|
||||
if (ret > 0)
|
||||
if (!ret)
|
||||
ret = libcpumf_cpuset(txt, mask);
|
||||
free(txt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
53
libcpumf/libcpumf_ctrset.c
Normal file
53
libcpumf/libcpumf_ctrset.c
Normal file
@@ -0,0 +1,53 @@
|
||||
/* Copyright IBM Corp. 2022, 2024
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lib/libcpumf.h"
|
||||
|
||||
int libcpumf_ctrset(int ctr, int cfvn, int csvn)
|
||||
{
|
||||
/* Governs basic and problem state counters */
|
||||
switch (cfvn) {
|
||||
case 1:
|
||||
if (ctr >= 0 && ctr < 32)
|
||||
return CPUMF_CTRSET_BASIC;
|
||||
if (ctr >= 32 && ctr < 38)
|
||||
return CPUMF_CTRSET_PROBLEM_STATE;
|
||||
break;
|
||||
case 3:
|
||||
if (ctr >= 0 && ctr < 32)
|
||||
return CPUMF_CTRSET_BASIC;
|
||||
if (ctr >= 32 && ctr < 34)
|
||||
return CPUMF_CTRSET_PROBLEM_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Governs crypto, extended and MT-Diagnositc counters */
|
||||
switch (csvn) {
|
||||
case 1 ... 5:
|
||||
if (ctr >= 64 && ctr < 80)
|
||||
return CPUMF_CTRSET_CRYPTO;
|
||||
if ((csvn == 1 && ctr >= 128 && ctr < 160) ||
|
||||
(csvn == 2 && ctr >= 128 && ctr < 176) ||
|
||||
(ctr >= 128 && ctr < 256))
|
||||
return CPUMF_CTRSET_EXTENDED;
|
||||
break;
|
||||
case 6 ... 8:
|
||||
if (ctr >= 64 && ctr < 84)
|
||||
return CPUMF_CTRSET_CRYPTO;
|
||||
if (ctr >= 128 && ctr < 288)
|
||||
return CPUMF_CTRSET_EXTENDED;
|
||||
break;
|
||||
}
|
||||
if (csvn >= 3 && ctr >= 448 && ctr < 496)
|
||||
return CPUMF_CTRSET_MT_DIAG;
|
||||
return CPUMF_CTRSET_NONE;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright IBM Corp. 2022
|
||||
/* Copyright IBM Corp. 2022, 2025
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
@@ -18,16 +18,13 @@
|
||||
|
||||
int libcpumf_pmutype(const char *dirname)
|
||||
{
|
||||
int ret = -1;
|
||||
FILE *file;
|
||||
char *fn;
|
||||
int ret;
|
||||
|
||||
ret = asprintf(&fn, "%s/type", dirname);
|
||||
if (ret == -1) /* No memory, errno set */
|
||||
return ret;
|
||||
fn = util_path_sysfs("%s/type", dirname);
|
||||
file = fopen(fn, "r");
|
||||
free(fn);
|
||||
ret = -1; /* Errno set on file open error */
|
||||
if (file) {
|
||||
/* Read out a single number from that file */
|
||||
if (fscanf(file, "%u", &ret) != 1)
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "lib/libcpumf.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_libc.h"
|
||||
|
||||
#define SERVICELEVEL "/proc/service_levels"
|
||||
|
||||
@@ -129,34 +131,70 @@ bool libcpumf_have_sfb(void)
|
||||
bool libcpumf_sfb_info(unsigned long *min, unsigned long *max)
|
||||
{
|
||||
int rc = false;
|
||||
char *path;
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(S390_CPUMSF_BUFFERSZ, "r");
|
||||
path = util_path_sysfs(S390_CPUMSF_BUFFERSZ);
|
||||
fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, "%s", S390_CPUMSF_BUFFERSZ);
|
||||
err(EXIT_FAILURE, "%s", path);
|
||||
if (fscanf(fp, "%lu,%lu", min, max) == 2)
|
||||
rc = true;
|
||||
fclose(fp);
|
||||
free(path);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static bool libcpumf_have_pai_sysfs(char *p)
|
||||
{
|
||||
char *path;
|
||||
bool ret;
|
||||
|
||||
path = util_path_sysfs(p);
|
||||
ret = util_path_exists(path);
|
||||
free(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool libcpumf_have_pai_crypto(void)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
return (stat(S390_SYSFS_PAI_CRYPTO, &statbuf) == -1) ? false : true;
|
||||
return libcpumf_have_pai_sysfs(S390_SYSFS_PAI_CRYPTO);
|
||||
}
|
||||
|
||||
bool libcpumf_have_pai_ext(void)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
return (stat(S390_SYSFS_PAI_EXT, &statbuf) == -1) ? false : true;
|
||||
return libcpumf_have_pai_sysfs(S390_SYSFS_PAI_EXT);
|
||||
}
|
||||
|
||||
bool libcpumf_have_pai_nnpa(void)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
return (stat(S390_SYSFS_PAI_NNPA, &statbuf) == -1) ? false : true;
|
||||
return libcpumf_have_pai_sysfs(S390_SYSFS_PAI_NNPA);
|
||||
}
|
||||
|
||||
long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd,
|
||||
unsigned long flags)
|
||||
{
|
||||
return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
|
||||
}
|
||||
|
||||
bool ctr_in_list(char *name, char *ctrlist)
|
||||
{
|
||||
char *token;
|
||||
char *list;
|
||||
|
||||
if (!ctrlist) /* No --counters means all counters */
|
||||
return true;
|
||||
|
||||
list = util_strdup(ctrlist);
|
||||
token = strtok(list, ",");
|
||||
while (token) {
|
||||
if (strcmp(token, name) == 0) {
|
||||
free(list);
|
||||
return true;
|
||||
}
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
free(list);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5099,14 +5099,15 @@ int ekmf_generate_csr(const struct ekmf_config *config,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_ekmf_copy_pss_params(rsa_pss_params, &pss_params);
|
||||
if (rsa_pss_params != NULL)
|
||||
_ekmf_copy_pss_params(rsa_pss_params, &pss_params);
|
||||
|
||||
rc = SK_OPENSSL_generate_csr(key_blob, key_blob_size,
|
||||
subject_rdns, num_subject_rdns,
|
||||
subject_utf8, cert,
|
||||
extensions, num_extensions,
|
||||
digest_nid, &pss_params, &req,
|
||||
digest_nid, rsa_pss_params != NULL ?
|
||||
&pss_params : NULL, &req,
|
||||
&ext_lib_info.ext_lib, verbose);
|
||||
if (rc != 0) {
|
||||
pr_verbose(verbose, "SK_OPENSSL_generate_csr failed "
|
||||
@@ -5245,14 +5246,16 @@ int ekmf_generate_ss_cert(const struct ekmf_config *config,
|
||||
}
|
||||
}
|
||||
|
||||
_ekmf_copy_pss_params(rsa_pss_params, &pss_params);
|
||||
if (rsa_pss_params != NULL)
|
||||
_ekmf_copy_pss_params(rsa_pss_params, &pss_params);
|
||||
|
||||
rc = SK_OPENSSL_generate_ss_cert(key_blob, key_blob_size,
|
||||
subject_rdns, num_subject_rdns,
|
||||
subject_utf8, rcert,
|
||||
extensions, num_extensions,
|
||||
validity_days, digest_nid,
|
||||
&pss_params, &cert,
|
||||
rsa_pss_params != NULL ?
|
||||
&pss_params : NULL, &cert,
|
||||
&ext_lib_info.ext_lib, verbose);
|
||||
if (rc != 0) {
|
||||
pr_verbose(verbose, "SK_OPENSSL_generate_ss_cert failed "
|
||||
|
||||
@@ -2432,7 +2432,7 @@ out:
|
||||
kmip_node_free(iv);
|
||||
kmip_node_free(tag);
|
||||
kmip_node_free(ffl);
|
||||
kmip_node_free(ffl);
|
||||
kmip_node_free(ifl);
|
||||
kmip_node_free(cnt);
|
||||
kmip_node_free(icv);
|
||||
kmip_node_free(salt);
|
||||
|
||||
@@ -1471,7 +1471,7 @@ retry:
|
||||
chain = SSL_get_peer_cert_chain(conn->plain_tls.ssl);
|
||||
if (chain == NULL) {
|
||||
kmip_debug(debug, "SSL_get_peer_cert_chain failed");
|
||||
return -EIO;
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,8 @@ struct kmip_node *kmip_new_request_header(const struct kmip_version *version,
|
||||
out:
|
||||
kmip_node_free(ver);
|
||||
kmip_node_free(max);
|
||||
kmip_node_free(ccorr);
|
||||
kmip_node_free(scorr);
|
||||
kmip_node_free(async);
|
||||
kmip_node_free(err);
|
||||
kmip_node_free(ord);
|
||||
|
||||
@@ -1369,7 +1369,7 @@ int kmip_get_get_response_payload(const struct kmip_node *node,
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (*unique_id != NULL) {
|
||||
if (unique_id != NULL && *unique_id != NULL) {
|
||||
kmip_node_free(*unique_id);
|
||||
*unique_id = NULL;
|
||||
}
|
||||
|
||||
@@ -351,7 +351,6 @@ int kmip_connection_tls_init(struct kmip_connection *conn, bool debug)
|
||||
if (tok == NULL) {
|
||||
kmip_debug(debug, "malformed IPv6 address");
|
||||
rc = -EINVAL;
|
||||
free(hostname);
|
||||
goto out;
|
||||
}
|
||||
tok++;
|
||||
|
||||
@@ -1501,6 +1501,9 @@ int SK_EP11_reencipher_key(const struct sk_ext_ep11_lib *ep11_lib,
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
memset(&rb, 0, sizeof(rb));
|
||||
memset(&lrb, 0, sizeof(lrb));
|
||||
|
||||
rb.domain = domain;
|
||||
lrb.domain = domain;
|
||||
|
||||
|
||||
@@ -97,6 +97,9 @@ const char *util_arch_machine_type_to_str(int type)
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z16:
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z16_A02:
|
||||
return "IBM z16";
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z17:
|
||||
case UTIL_ARCH_MACHINE_TYPE_Z17_2:
|
||||
return "IBM z17";
|
||||
default:
|
||||
return "Unknown machine type";
|
||||
}
|
||||
|
||||
@@ -616,6 +616,10 @@ char *util_file_read_fd(FILE *fd, int chomp)
|
||||
if (util_file_read_fd_buf(fd, (void **) &buffer, &done))
|
||||
return NULL;
|
||||
|
||||
/* Prevent over-read if buffer is larger than amount of read characters */
|
||||
if (buffer)
|
||||
done = MIN(done, strnlen(buffer, done));
|
||||
|
||||
/* Check if this is a text file at all (required to filter out
|
||||
* binary sysfs attributes).
|
||||
*/
|
||||
|
||||
@@ -307,9 +307,13 @@ static void add_key(const char *name, bool persist)
|
||||
key.persist = persist;
|
||||
util_add_array(&f.keys, &f.num_keys, key);
|
||||
if (f.type == FMT_CSV) {
|
||||
hdr = csv_quote(name);
|
||||
util_rec_def(f.csv_rec, name, UTIL_REC_ALIGN_LEFT, 0, hdr);
|
||||
free(hdr);
|
||||
if (f.quote_all) {
|
||||
hdr = csv_quote(name);
|
||||
util_rec_def(f.csv_rec, name, UTIL_REC_ALIGN_LEFT, 0, hdr);
|
||||
free(hdr);
|
||||
} else {
|
||||
util_rec_def(f.csv_rec, name, UTIL_REC_ALIGN_LEFT, 0, name);
|
||||
}
|
||||
util_rec_set(f.csv_rec, name, "\"\"");
|
||||
f.csv_hdr = true;
|
||||
}
|
||||
|
||||
33
libutil/util_str.c
Normal file
33
libutil/util_str.c
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* util - Utility function library
|
||||
*
|
||||
* Manipulate and work with strings
|
||||
*
|
||||
* Copyright IBM Corp. 2025
|
||||
*
|
||||
* s390-tools is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the MIT license. See LICENSE for details.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "lib/util_str.h"
|
||||
|
||||
/**
|
||||
* Copy characters from \a src to \a dest, omitting all blank characters
|
||||
* (spaces and tabs). The resulting string in \a dest will be NUL terminated.
|
||||
*
|
||||
* @param[in] src Source string
|
||||
* @param[out] dest Destination buffer
|
||||
*/
|
||||
void util_str_rm_whitespace(const char *src, char *dest)
|
||||
{
|
||||
while (*src != '\0') {
|
||||
if (!isblank(*src)) {
|
||||
*dest = *src;
|
||||
dest++;
|
||||
}
|
||||
src++;
|
||||
}
|
||||
*dest = '\0';
|
||||
}
|
||||
@@ -356,3 +356,34 @@ void zpci_free_dev_list(struct util_list *zpci_list)
|
||||
}
|
||||
util_list_free(zpci_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a PCI device given the name of a netdev
|
||||
*
|
||||
* This function allows finding a PCI device when only the name of one
|
||||
* of its netdevs is known.
|
||||
*
|
||||
* @param[in] zpci_list The device list to search
|
||||
* @param[in] netdev_name The name of the netdev
|
||||
* @param[out] netdev Pointer to store the netdev or NULL if
|
||||
* only the PCI device is needed
|
||||
*
|
||||
* @return The PCI device if one is found NULL otherwise
|
||||
*/
|
||||
struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name,
|
||||
struct zpci_netdev **netdev)
|
||||
{
|
||||
struct zpci_dev *zdev = NULL;
|
||||
int i;
|
||||
|
||||
util_list_iterate(zpci_list, zdev) {
|
||||
for (i = 0; i < zdev->num_netdevs; i++) {
|
||||
if (!strcmp(zdev->netdevs[i].name, netdev_name)) {
|
||||
if (netdev)
|
||||
*netdev = &zdev->netdevs[i];
|
||||
return zdev;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ Socket communication with applications utilizing CMS AF_IUCV support
|
||||
An AF_IUCV socket is represented by the following format:
|
||||
.PP
|
||||
.RS 8
|
||||
.ft CW
|
||||
.ft CR
|
||||
.nf
|
||||
#define AF_IUCV 32
|
||||
|
||||
@@ -290,7 +290,7 @@ It must not match any z/VM user ID in your environment.
|
||||
To set an identifier, issue a command like this:
|
||||
.PP
|
||||
.RS 8
|
||||
.ft CW
|
||||
.ft CR
|
||||
echo \fIidentifier\fP > /sys/devices/qeth/\fI<bus-ID>\fP/hsuid
|
||||
.ft
|
||||
.RE
|
||||
@@ -303,7 +303,7 @@ For example, to use "MYHOST01" to bind AF_IUCV sockets to the
|
||||
HiperSockets device with bus-ID 0.0.8000, run:
|
||||
.PP
|
||||
.RS 8
|
||||
.ft CW
|
||||
.ft CR
|
||||
.nf
|
||||
echo "MYHOST01" > /sys/devices/qeth/0.0.8000/hsuid
|
||||
.fi
|
||||
@@ -366,7 +366,7 @@ z/VM guest virtual machine with a maximum of 10\^000 outstanding messages for ea
|
||||
incoming connection. Your z/VM guest virtual machine is permitted to connect to
|
||||
all other z/VM guest virtual machines. The total number of connections for your
|
||||
z/VM guest virtual machine cannot exceed 100.
|
||||
.ft CW
|
||||
.ft CR
|
||||
.in +0.25i
|
||||
.nf
|
||||
|
||||
|
||||
@@ -171,8 +171,6 @@ static const char help_text[] =
|
||||
"-h, --help Print this help, then exit\n"
|
||||
"-v, --version Print version information, then exit\n"
|
||||
"-a, --attach Run in foreground\n"
|
||||
"-i, --interval=<seconds> Sample interval\n"
|
||||
"\n"
|
||||
"Please report bugs to: linux390@de.ibm.com\n";
|
||||
"-i, --interval=<seconds> Sample interval\n";
|
||||
#endif
|
||||
|
||||
|
||||
@@ -16,6 +16,19 @@
|
||||
. if !'\\$1'' .IP "\fB\-\-\\$1\fP \\*[args]" 4
|
||||
. PD
|
||||
..
|
||||
.\" Macro for inserting code line.
|
||||
.\" .CL <text>
|
||||
.de CL
|
||||
. ds pfont \fP
|
||||
. nh
|
||||
. na
|
||||
. ft CR
|
||||
\\$*
|
||||
. ft \\*[pfont]
|
||||
. ad
|
||||
. hy
|
||||
. br
|
||||
..
|
||||
.
|
||||
.TH opticsmon 8 "Oct 2024" s390-tools zpcictl
|
||||
.
|
||||
@@ -67,8 +80,7 @@ on".
|
||||
|
||||
.B Example: Extract module information for the first adapter
|
||||
|
||||
.CL
|
||||
opticsmon --module-info | jq -r '.adapters[0].netdevs[0].optics.module_info' | base64 -d | hexdump -C
|
||||
.CL opticsmon --module-info | jq -r '.adapters[0].netdevs[0].optics.module_info' | base64 -d | hexdump -C
|
||||
.PP
|
||||
.PP
|
||||
.
|
||||
|
||||
@@ -274,38 +274,33 @@ static int oneshot_mode(struct opticsmon_ctx *ctx)
|
||||
void on_link_change(struct zpci_netdev *netdev, void *arg)
|
||||
{
|
||||
struct opticsmon_ctx *ctx = arg;
|
||||
struct zpci_dev *zdev;
|
||||
int i, reloads = 1;
|
||||
struct zpci_netdev *found_netdev;
|
||||
struct zpci_dev *zdev = NULL;
|
||||
int reloads = 1;
|
||||
|
||||
if (!ctx->zpci_list || util_list_is_empty(ctx->zpci_list))
|
||||
zpci_list_reload(&ctx->zpci_list);
|
||||
|
||||
reload:
|
||||
util_list_iterate(ctx->zpci_list, zdev) {
|
||||
for (i = 0; i < zdev->num_netdevs; i++) {
|
||||
if (!strcmp(zdev->netdevs[i].name, netdev->name)) {
|
||||
reloads--;
|
||||
do {
|
||||
if (ctx->zpci_list) {
|
||||
zdev = zpci_find_by_netdev(ctx->zpci_list, netdev->name, &found_netdev);
|
||||
if (zdev) {
|
||||
/* Skip data collection if operational state is
|
||||
* unchanged
|
||||
*/
|
||||
if (zdev->netdevs[i].operstate == netdev->operstate)
|
||||
continue;
|
||||
if (found_netdev->operstate == netdev->operstate)
|
||||
return;
|
||||
/* Update operation state for VFs even though
|
||||
* they are skipped just for a consistent view
|
||||
*/
|
||||
zdev->netdevs[i].operstate = netdev->operstate;
|
||||
found_netdev->operstate = netdev->operstate;
|
||||
/* Only collect optics data for PFs */
|
||||
if (!zpci_is_vf(zdev))
|
||||
dump_adapter_data(ctx, zdev);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Might be a new device, reload list of devices and retry */
|
||||
if (reloads) {
|
||||
/* Could be uninitalized list or a new device, retry after reload */
|
||||
zpci_list_reload(&ctx->zpci_list);
|
||||
reloads--;
|
||||
goto reload;
|
||||
}
|
||||
} while (reloads > 0);
|
||||
}
|
||||
|
||||
#define MAX_EVENTS 8
|
||||
|
||||
585
rust/Cargo.lock
generated
585
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,3 @@ unstable_features = "warn"
|
||||
unused = "warn"
|
||||
unused_import_braces = "warn"
|
||||
unused_qualifications = "warn"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -14,7 +14,7 @@ ifneq (${HAVE_CARGO},0)
|
||||
CARGO_TARGETS :=
|
||||
|
||||
BUILD_TARGETS = $(CARGO_TARGETS)
|
||||
INSTALL_TARGETS := install-rust-tools install-man
|
||||
INSTALL_TARGETS := install-rust-tools install-man install-shell-completions
|
||||
CARGO_TEST_TARGETS = $(addsuffix .test, $(CARGO_TARGETS))
|
||||
|
||||
ifeq ($(HOST_ARCH),s390x)
|
||||
@@ -86,7 +86,6 @@ install: $(INSTALL_TARGETS)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvattest/tools/pvextract-hdr $(DESTDIR)$(USRBINDIR)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(PVIMG_PKGDATADIR)
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 pvimg/tools/check_hostkeydoc $(DESTDIR)$(PVIMG_PKGDATADIR)
|
||||
$(MAKE) -C pvimg/boot install
|
||||
ln -sf pvimg $(DESTDIR)$(USRBINDIR)/genprotimg
|
||||
|
||||
@@ -115,8 +114,21 @@ install-man:
|
||||
$(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;)
|
||||
$(foreach target,$(PV_TARGETS),\
|
||||
$(INSTALL) -m 644 $(target)/man/*.1 -t $(DESTDIR)$(MANDIR)/man1;)
|
||||
ln -sf pvimg-create.1 $(DESTDIR)$(MANDIR)/man1/genprotimg.1
|
||||
|
||||
.PHONY: all install clean skip-build install-rust-tools print-rust-targets install-man rust-test
|
||||
install-shell-completions: install-bash-completion install-zsh-completion
|
||||
|
||||
install-bash-completion: $(PV_TARGETS)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(BASHCOMPLETIONDIR)
|
||||
$(foreach target,$(PV_TARGETS),\
|
||||
$(INSTALL) -m 0644 --preserve-timestamps -- $(shell find $(shell ls --sort=time --reverse --directory target/release/build/$(target)-*/out/ |tail -n1) -name '*.bash') $(DESTDIR)$(BASHCOMPLETIONDIR);)
|
||||
|
||||
install-zsh-completion: $(PV_TARGETS)
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(ZSHCOMPLETIONDIR)
|
||||
$(foreach target,$(PV_TARGETS),\
|
||||
$(INSTALL) -m 0644 --preserve-timestamps -- $(shell find $(shell ls --sort=time --reverse --directory target/release/build/$(target)-*/out/ |tail -n1) -regex '.*/_[a-zA-Z0-9]+') $(DESTDIR)$(ZSHCOMPLETIONDIR);)
|
||||
|
||||
.PHONY: all install clean skip-build install-rust-tools print-rust-targets install-man rust-test install-bash-completion install-zsh-completion install-shell-completions
|
||||
|
||||
.check-cargo:
|
||||
ifeq ($(shell command -v $(CARGO)),)
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
[package]
|
||||
name = "cpacfinfo"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.71"
|
||||
clap = { version = "4.1", features = ["derive"] }
|
||||
anyhow = "1.0.95"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
libc = "0.2"
|
||||
serde = { version = "1.0.164", features = ["derive"] }
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
utils = { path = "../utils" }
|
||||
zerocopy = { version="0.7", features = ["derive"] }
|
||||
zerocopy = { version="0.8", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
clap = { version ="4.1", features = ["derive"]}
|
||||
lazy_static = "1.1"
|
||||
cc = "1.2"
|
||||
clap = { version ="4.5", features = ["derive"]}
|
||||
lazy_static = "1.5"
|
||||
|
||||
@@ -124,8 +124,8 @@ Possible values for
|
||||
Displays the CPACF subfunctions available on the system.
|
||||
.B cpacfinfo \-f/\-\-functions
|
||||
is the same as
|
||||
.B cpacfinfo \-f/\-\-functions \-a/\-\-available
|
||||
. If
|
||||
\fBcpacfinfo \-f/\-\-functions \-a/\-\-available\fR.
|
||||
If
|
||||
.B -n/\-\-not-available
|
||||
is specified no available functions are shown. To get available as well as functions not available use
|
||||
.B cpacfinfo \-f/\-\-functions \-a/\-\-available \-n/\-\-not-available.
|
||||
|
||||
@@ -9,7 +9,6 @@ use std::ops::Index;
|
||||
use std::result::Result;
|
||||
|
||||
use zerocopy::FromBytes;
|
||||
use zerocopy::FromZeroes;
|
||||
|
||||
use crate::msa::InstructionKind;
|
||||
use crate::msa::QueryAuthInfo;
|
||||
@@ -33,7 +32,7 @@ pub const QAI_PARAM_SIZE_IN_BYTES: usize = 256;
|
||||
/// Query authentication information format identifier
|
||||
const FORMAT_0: u8 = 0;
|
||||
|
||||
#[derive(FromBytes, FromZeroes)]
|
||||
#[derive(FromBytes)]
|
||||
#[repr(C)]
|
||||
struct QaiFmt0 {
|
||||
res00: [u8; 6],
|
||||
@@ -175,7 +174,7 @@ pub fn check_sysfs() -> bool {
|
||||
/// -----------------------------------------------------------------
|
||||
fn parse_qai_format_0(qai: &mut QueryAuthInfo, param: &[u8]) {
|
||||
// parse param to temporary struct to ease further conversion
|
||||
let tmp = QaiFmt0::read_from_prefix(param).expect("programming error");
|
||||
let (tmp, _) = QaiFmt0::read_from_prefix(param).expect("programming error");
|
||||
|
||||
// parse from temporary struct
|
||||
qai.hash_len = tmp.hash_length;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "s390_pv"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -14,18 +14,18 @@ readme = "README.md"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3"
|
||||
curl = "0.4.44"
|
||||
byteorder = "1.5"
|
||||
curl = "0.4.47"
|
||||
enum_dispatch = "0.3.13"
|
||||
foreign-types = "0.3.1"
|
||||
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.66"
|
||||
openssl-sys = "0.9.92"
|
||||
serde = { version = "1.0.139", features = ["derive"] }
|
||||
thiserror = "1.0.33"
|
||||
zerocopy = { version="0.7", features = ["derive"] }
|
||||
foreign-types = "0.3.2"
|
||||
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.70"
|
||||
openssl-sys = "0.9.105"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
thiserror = "2.0.11"
|
||||
zerocopy = { version="0.8", features = ["derive"] }
|
||||
|
||||
pv_core = { path = "../pv_core", package = "s390_pv_core", version = "0.11.0" }
|
||||
pv_core = { path = "../pv_core", package = "s390_pv_core", version = "0.12.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_test = "1.0.139"
|
||||
serde_test = "1.0.177"
|
||||
|
||||
28
rust/pv/examples/hash_hkd/main.rs
Normal file
28
rust/pv/examples/hash_hkd/main.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pv::request::EcPubKeyCoord;
|
||||
use pv_core::misc::encode_hex;
|
||||
use s390_pv as pv;
|
||||
|
||||
use std::env::args;
|
||||
|
||||
use pv::misc::{read_certs, read_file};
|
||||
use pv::{Error, Result};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let hkd = args().nth(1).expect("Expect one Host-key document");
|
||||
let hkd_bin = read_file(&hkd, "Host-key document")?;
|
||||
let certs = read_certs(hkd_bin).map_err(|source| Error::HkdNotPemOrDer { hkd, source })?;
|
||||
let hkd_cert = certs
|
||||
.first()
|
||||
.expect("Expect at least one certificate in the HKD file");
|
||||
|
||||
let pc: EcPubKeyCoord = hkd_cert
|
||||
.public_key()
|
||||
.expect("Expected a public key in the Host-key document")
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
println!("{}", encode_hex(pc.sha256().unwrap()));
|
||||
Ok(())
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
};
|
||||
|
||||
use log::{debug, warn};
|
||||
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32, U64};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
|
||||
|
||||
// (SE) boot request control block aka SE header
|
||||
use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGESIZE};
|
||||
@@ -21,7 +21,7 @@ use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGE
|
||||
/// Tweak List Digest (tld)
|
||||
/// SE-Header Tag (tag)
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, AsBytes, PartialEq, Eq, FromBytes, FromZeroes)]
|
||||
#[derive(Debug, Clone, Copy, IntoBytes, PartialEq, Eq, FromBytes, Immutable, KnownLayout)]
|
||||
pub struct BootHdrTags {
|
||||
pld: [u8; BootHdrHead::DIGEST_SIZE],
|
||||
ald: [u8; BootHdrHead::DIGEST_SIZE],
|
||||
@@ -40,8 +40,8 @@ impl TryFrom<Vec<u8>> for BootHdrTags {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
Self::ref_from(&value)
|
||||
.ok_or_else(|| Error::InvBootHdrSize(value.len()))
|
||||
Self::ref_from_bytes(&value)
|
||||
.map_err(|_| Error::InvBootHdrSize(value.len()))
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ impl TryFrom<Vec<u8>> for BootHdrTags {
|
||||
/// Struct representing the Secure Execution boot image metadata
|
||||
#[allow(unused)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Clone, FromBytes, FromZeroes, AsBytes, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, FromBytes, IntoBytes, PartialEq, Eq, Immutable, KnownLayout)]
|
||||
pub struct SeImgMetaData {
|
||||
/// Magic value
|
||||
magic: [u8; 8],
|
||||
@@ -106,7 +106,7 @@ impl SeImgMetaData {
|
||||
/// Gets the bytes of this value.
|
||||
#[inline(always)]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
<Self as AsBytes>::as_bytes(self)
|
||||
<Self as IntoBytes>::as_bytes(self)
|
||||
}
|
||||
|
||||
/// Returns the version of this [`SeImgMetaData`].
|
||||
@@ -154,7 +154,7 @@ where
|
||||
// read in the header
|
||||
img.read_exact(&mut img_metadata_bytes)?;
|
||||
// Cannot fail because the buffer has the same size as SeImgMetaData.
|
||||
let img_metadata = SeImgMetaData::ref_from(&img_metadata_bytes).unwrap();
|
||||
let img_metadata = SeImgMetaData::ref_from_bytes(&img_metadata_bytes).unwrap();
|
||||
let img_metadata_version = img_metadata.version();
|
||||
if img_metadata_version != SeImgMetaData::V1 {
|
||||
warn!("Unknown Secure Execution boot image version {img_metadata_version}");
|
||||
@@ -226,8 +226,8 @@ impl BootHdrTags {
|
||||
}
|
||||
|
||||
let hdr_head = match BootHdrHead::read_from_prefix(hdr.as_mut_slice()) {
|
||||
Some(hdr) => hdr,
|
||||
None => {
|
||||
Ok((hdr, _)) => hdr,
|
||||
Err(_) => {
|
||||
debug!("Boot hdr is too small");
|
||||
return Err(Error::InvBootHdr);
|
||||
}
|
||||
@@ -260,7 +260,7 @@ impl BootHdrTags {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, FromBytes, FromZeroes)]
|
||||
#[derive(Debug, Clone, FromBytes)]
|
||||
struct BootHdrHead {
|
||||
magic: U64<BigEndian>,
|
||||
version: U32<BigEndian>,
|
||||
@@ -388,7 +388,7 @@ mod tests {
|
||||
0, 1, 96, 0,
|
||||
];
|
||||
assert_eq!(metadata.as_bytes(), &data);
|
||||
assert_eq!(SeImgMetaData::ref_from(&data), Some(&metadata));
|
||||
assert_eq!(SeImgMetaData::ref_from_bytes(&data), Ok(&metadata));
|
||||
|
||||
assert_eq!(metadata.version(), SeImgMetaData::V1);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ pub type Aes256XtsKey = Confidential<[u8; SymKeyType::AES_256_XTS_KEY_LEN]>;
|
||||
|
||||
/// SHA-512 digest length (in bytes)
|
||||
pub const SHA_512_HASH_LEN: usize = 64;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const SHA_256_HASH_LEN: u32 = 32;
|
||||
#[allow(dead_code)]
|
||||
@@ -60,6 +59,8 @@ impl SymKeyType {
|
||||
pub const AES_256_XTS_KEY_LEN: usize = 64;
|
||||
/// AES256-XTS tweak length (in bytes)
|
||||
pub const AES_256_XTS_TWEAK_LEN: usize = 16;
|
||||
/// AES256 GCM Block length
|
||||
pub const AES_256_GCM_BLOCK_LEN: usize = 16;
|
||||
|
||||
/// Returns the tag length of the [`SymKeyType`] if it is an AEAD key
|
||||
pub const fn tag_len(&self) -> Option<usize> {
|
||||
@@ -360,7 +361,7 @@ pub fn encrypt_aead(
|
||||
aad: &[u8],
|
||||
conf: &[u8],
|
||||
) -> Result<AeadEncryptionResult> {
|
||||
let tag_len = key.key_type().tag_len().ok_or_else(|| Error::NoAeadKey)?;
|
||||
let tag_len = key.key_type().tag_len().ok_or(Error::NoAeadKey)?;
|
||||
|
||||
let nid = key.key_type().into();
|
||||
let cipher = Cipher::from_nid(nid).ok_or(Error::UnsupportedCipher(nid))?;
|
||||
|
||||
@@ -55,7 +55,7 @@ pub enum Error {
|
||||
#[error("Decryption failed. Probably due to a GCM tag mismatch.")]
|
||||
GcmTagMismatch,
|
||||
|
||||
#[error("Invalid {0} user-data for signing provided. Max {} bytes allowed", .0.max())]
|
||||
#[error("Invalid {0} user-data for signing provided. Max {max} bytes allowed", max=.0.max())]
|
||||
AsrcbInvSgnUserData(UserDataType),
|
||||
|
||||
#[error("Unsupported user data signing key provided. Only EC(secp521r1) and RSA(2048 & 3072 bit) are supported")]
|
||||
@@ -106,6 +106,20 @@ pub enum Error {
|
||||
)]
|
||||
AddDataMissing(&'static str),
|
||||
|
||||
#[error("An ASCII string was expected, but non-ASCII characters were received.")]
|
||||
NonAscii,
|
||||
|
||||
#[error("Incorrect {what} for a {kind}. Is: {value}; expected: {exp}")]
|
||||
RetrInvKey {
|
||||
what: &'static str,
|
||||
kind: String,
|
||||
value: String,
|
||||
exp: String,
|
||||
},
|
||||
|
||||
#[error("Invalid data from OpenSSL")]
|
||||
InvalSslData,
|
||||
|
||||
// errors from other crates
|
||||
#[error(transparent)]
|
||||
PvCore(#[from] pv_core::Error),
|
||||
|
||||
@@ -37,6 +37,7 @@ mod brcb;
|
||||
mod crypto;
|
||||
mod error;
|
||||
mod openssl_extensions;
|
||||
mod pem_utils;
|
||||
mod req;
|
||||
mod utils;
|
||||
mod uvattest;
|
||||
@@ -71,6 +72,11 @@ pub mod attest {
|
||||
};
|
||||
}
|
||||
|
||||
/// Definitions and functions to write objects in PEM format
|
||||
pub mod pem {
|
||||
pub use crate::pem_utils::Pem;
|
||||
}
|
||||
|
||||
/// Miscellaneous functions and definitions
|
||||
pub mod misc {
|
||||
pub use pv_core::misc::*;
|
||||
@@ -98,7 +104,12 @@ pub mod request {
|
||||
|
||||
/// Reexports some useful OpenSSL symbols
|
||||
pub mod openssl {
|
||||
pub use openssl::{error::ErrorStack, hash::DigestBytes, pkey, x509};
|
||||
pub use openssl::{error::ErrorStack, hash::DigestBytes, nid::Nid, pkey, x509};
|
||||
// rust-OpenSSL does not define these NIDs
|
||||
#[allow(missing_docs)]
|
||||
pub const NID_ED25519: Nid = Nid::from_raw(openssl_sys::NID_ED25519);
|
||||
#[allow(missing_docs)]
|
||||
pub const NID_ED448: Nid = Nid::from_raw(openssl_sys::NID_ED448);
|
||||
}
|
||||
|
||||
pub use pv_core::request::*;
|
||||
@@ -112,6 +123,7 @@ pub mod secret {
|
||||
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
|
||||
ext_secret::ExtSecret,
|
||||
guest_secret::GuestSecret,
|
||||
retr_secret::{IbmProtectedKey, RetrievedSecret},
|
||||
user_data::verify_asrcb_and_get_user_data,
|
||||
};
|
||||
}
|
||||
|
||||
85
rust/pv/src/openssl_extensions/bio.rs
Normal file
85
rust/pv/src/openssl_extensions/bio.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use core::slice;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl_sys::BIO_new_mem_buf;
|
||||
use std::ffi::c_int;
|
||||
use std::{marker::PhantomData, ptr};
|
||||
|
||||
pub struct BioMem(*mut openssl_sys::BIO);
|
||||
|
||||
impl Drop for BioMem {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: Pointer is valid. The pointer value is dropped after the free.
|
||||
unsafe {
|
||||
openssl_sys::BIO_free_all(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BioMem {
|
||||
pub fn new() -> Result<Self, ErrorStack> {
|
||||
openssl_sys::init();
|
||||
|
||||
// SAFETY: Returns a valid pointer or null. null-case is tested right after this.
|
||||
let bio = unsafe { openssl_sys::BIO_new(openssl_sys::BIO_s_mem()) };
|
||||
match bio.is_null() {
|
||||
true => Err(ErrorStack::get()),
|
||||
false => Ok(Self(bio)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Copies the content of this slice into a Vec
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
let buf;
|
||||
// SAFTEY: BIO provides a continuous memory that can be used to build a slice.
|
||||
unsafe {
|
||||
let mut ptr = ptr::null_mut();
|
||||
let len = openssl_sys::BIO_get_mem_data(self.0, &mut ptr);
|
||||
buf = slice::from_raw_parts(ptr as *const _ as *const _, len as usize)
|
||||
}
|
||||
buf.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BioMemSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
|
||||
impl Drop for BioMemSlice<'_> {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: Pointer is valid. The pointer value is dropped after the free.
|
||||
unsafe {
|
||||
openssl_sys::BIO_free_all(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BioMemSlice<'a> {
|
||||
pub fn new(buf: &'a [u8]) -> Result<BioMemSlice<'a>, ErrorStack> {
|
||||
openssl_sys::init();
|
||||
|
||||
// SAFETY: `buf` is a slice (i.e. pointer+size) pointing to a valid memory region.
|
||||
// So the resulting bio is valid. Lifetime of the slice is connected by this Rust
|
||||
// structure.
|
||||
assert!(buf.len() <= c_int::MAX as usize);
|
||||
let bio = unsafe {
|
||||
{
|
||||
let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
|
||||
match r.is_null() {
|
||||
true => Err(ErrorStack::get()),
|
||||
false => Ok(r),
|
||||
}
|
||||
}?
|
||||
};
|
||||
|
||||
Ok(BioMemSlice(bio, PhantomData))
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
/// Extensions to the rust-openssl crate
|
||||
mod akid;
|
||||
mod bio;
|
||||
mod crl;
|
||||
mod stackable_crl;
|
||||
|
||||
pub use akid::*;
|
||||
pub use bio::*;
|
||||
pub use crl::*;
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::{marker::PhantomData, ptr};
|
||||
|
||||
use crate::openssl_extensions::bio::BioMemSlice;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
stack::Stackable,
|
||||
x509::{X509Crl, X509CrlRef},
|
||||
};
|
||||
use openssl_sys::BIO_new_mem_buf;
|
||||
use std::ffi::c_int;
|
||||
use std::ptr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
|
||||
@@ -62,44 +60,11 @@ impl Stackable for StackableX509Crl {
|
||||
type StackType = openssl_sys::stack_st_X509_CRL;
|
||||
}
|
||||
|
||||
pub struct MemBioSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
|
||||
impl Drop for MemBioSlice<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
openssl_sys::BIO_free_all(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MemBioSlice<'a> {
|
||||
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
|
||||
openssl_sys::init();
|
||||
|
||||
assert!(buf.len() <= c_int::MAX as usize);
|
||||
let bio = unsafe {
|
||||
{
|
||||
let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
|
||||
if r.is_null() {
|
||||
Err(ErrorStack::get())
|
||||
} else {
|
||||
Ok(r)
|
||||
}
|
||||
}?
|
||||
};
|
||||
|
||||
Ok(MemBioSlice(bio, PhantomData))
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl StackableX509Crl {
|
||||
pub fn stack_from_pem(pem: &[u8]) -> Result<Vec<X509Crl>, ErrorStack> {
|
||||
unsafe {
|
||||
openssl_sys::init();
|
||||
let bio = MemBioSlice::new(pem)?;
|
||||
let bio = BioMemSlice::new(pem)?;
|
||||
|
||||
let mut crls = vec![];
|
||||
loop {
|
||||
|
||||
222
rust/pv/src/pem_utils.rs
Normal file
222
rust/pv/src/pem_utils.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::Result;
|
||||
use crate::{openssl_extensions::BioMem, Error};
|
||||
use openssl::error::ErrorStack;
|
||||
use pv_core::request::Confidential;
|
||||
use std::{
|
||||
ffi::{c_char, CString},
|
||||
fmt::Display,
|
||||
};
|
||||
|
||||
mod ffi {
|
||||
use openssl_sys::BIO;
|
||||
use std::ffi::{c_char, c_int, c_long, c_uchar};
|
||||
extern "C" {
|
||||
pub fn PEM_write_bio(
|
||||
bio: *mut BIO,
|
||||
name: *const c_char,
|
||||
header: *const c_char,
|
||||
data: *const c_uchar,
|
||||
len: c_long,
|
||||
) -> c_int;
|
||||
}
|
||||
}
|
||||
|
||||
/// Thin wrapper around [`CString`] only containing ASCII chars.
|
||||
#[derive(Debug)]
|
||||
struct AsciiCString(CString);
|
||||
|
||||
impl AsciiCString {
|
||||
/// Convert from string
|
||||
///
|
||||
/// # Returns
|
||||
/// Error if string is not ASCII or contains null chars
|
||||
pub(crate) fn from_str(s: &str) -> Result<Self> {
|
||||
match s.is_ascii() {
|
||||
true => Ok(Self(CString::new(s).map_err(|_| Error::NonAscii)?)),
|
||||
false => Err(Error::NonAscii),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const c_char {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct to construct the PEM format
|
||||
#[derive(Debug)]
|
||||
struct InnerPem<'d> {
|
||||
name: AsciiCString,
|
||||
header: Option<AsciiCString>,
|
||||
data: &'d [u8],
|
||||
}
|
||||
|
||||
impl<'d> InnerPem<'d> {
|
||||
fn new(name: &str, header: Option<String>, data: &'d [u8]) -> Result<Self> {
|
||||
Ok(Self {
|
||||
name: AsciiCString::from_str(name)?,
|
||||
header: match header {
|
||||
Some(h) => Some(AsciiCString::from_str(&h)?),
|
||||
None => None,
|
||||
},
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate PEM representation of the data
|
||||
fn to_pem(&self) -> Result<Vec<u8>> {
|
||||
let bio = BioMem::new()?;
|
||||
let hdr_ptr = match self.header {
|
||||
// avoid moving variable -> use reference
|
||||
Some(ref h) => h.as_ptr(),
|
||||
None => std::ptr::null(),
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// All pointers point to valid C strings or memory regions
|
||||
let rc = unsafe {
|
||||
ffi::PEM_write_bio(
|
||||
bio.as_ptr(),
|
||||
self.name.as_ptr(),
|
||||
hdr_ptr,
|
||||
self.data.as_ptr(),
|
||||
self.data.len() as std::ffi::c_long,
|
||||
)
|
||||
};
|
||||
|
||||
match rc {
|
||||
1 => Err(Error::InternalSsl("Could not write PEM", ErrorStack::get())),
|
||||
_ => Ok(bio.to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data in PEM format
|
||||
///
|
||||
/// Displays into a printable PEM structure.
|
||||
/// Must be constructed from another structure in this library.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let pem: Pem = ...;
|
||||
/// println!("PEM {pem}");
|
||||
/// ```
|
||||
/// ```PEM
|
||||
///-----BEGIN <name>-----
|
||||
///<header>
|
||||
///
|
||||
///<Base64 formatted binary data>
|
||||
///-----END <name>-----
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pem {
|
||||
pem: Confidential<String>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl Pem {
|
||||
/// Create a new PEM structure.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if name or header contain non-ASCII chars, or OpenSSL
|
||||
/// could not generate the PEM (very likely due to OOM).
|
||||
pub(crate) fn new<D, H>(name: &str, header: H, data: D) -> Result<Self>
|
||||
where
|
||||
D: AsRef<[u8]>,
|
||||
H: Into<Option<String>>,
|
||||
{
|
||||
let mut header = header.into();
|
||||
let header = match header {
|
||||
Some(h) if h.ends_with('\n') => Some(h),
|
||||
Some(h) if h.is_empty() => None,
|
||||
Some(mut h) => {
|
||||
h.push('\n');
|
||||
Some(h)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let inner_pem = InnerPem::new(name, header, data.as_ref())?;
|
||||
|
||||
// Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or OpenSSL Errors
|
||||
// Both error should be very unlikely
|
||||
// OpenSSL should be able to create PEM if there is enough memory and produce a non-null
|
||||
// terminated ASCII-string
|
||||
// Unwrap succeeds it's all ASCII
|
||||
// Std lib implements all the conversations without a copy
|
||||
let pem = CString::new(inner_pem.to_pem()?)
|
||||
.map_err(|_| Error::NonAscii)?
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
Ok(Self { pem })
|
||||
}
|
||||
|
||||
/// Converts the PEM-data into a byte vector.
|
||||
///
|
||||
/// This consumes the `PEM`.
|
||||
#[inline]
|
||||
#[must_use = "`self` will be dropped if the result is not used"]
|
||||
pub fn into_bytes(self) -> Confidential<Vec<u8>> {
|
||||
self.pem.into_inner().into_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Pem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.pem.value().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_data() {
|
||||
const EXP: &str =
|
||||
"-----BEGIN PEM test-----\ntest hdr value: 17\n\n-----END PEM test-----\n";
|
||||
let test_pem = Pem::new("PEM test", "test hdr value: 17".to_string(), []).unwrap();
|
||||
let pem_str = test_pem.to_string();
|
||||
assert_eq!(pem_str, EXP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_hdr() {
|
||||
const EXP: &str =
|
||||
"-----BEGIN PEM test-----\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
|
||||
let test_pem = Pem::new("PEM test", None, "very secret key").unwrap();
|
||||
let pem_str = test_pem.to_string();
|
||||
assert_eq!(pem_str, EXP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_data() {
|
||||
const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
|
||||
let test_pem = Pem::new(
|
||||
"PEM test",
|
||||
"test hdr value: 17".to_string(),
|
||||
"very secret key",
|
||||
)
|
||||
.unwrap();
|
||||
let pem_str = test_pem.to_string();
|
||||
assert_eq!(pem_str, EXP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_linebreak() {
|
||||
const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
|
||||
let test_pem = Pem::new(
|
||||
"PEM test",
|
||||
"test hdr value: 17\n".to_string(),
|
||||
"very secret key",
|
||||
)
|
||||
.unwrap();
|
||||
let pem_str = test_pem.to_string();
|
||||
assert_eq!(pem_str, EXP);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use openssl::{
|
||||
pkey::{PKey, PKeyRef, Private, Public},
|
||||
};
|
||||
use pv_core::request::{RequestMagic, RequestVersion};
|
||||
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
||||
|
||||
use crate::{
|
||||
assert_size,
|
||||
@@ -358,7 +358,7 @@ ecdh_from!(Public);
|
||||
/// Representation of the shared parts of the request header.
|
||||
/// Used by [`ReqEncrCtx`]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
|
||||
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)]
|
||||
struct RequestHdr {
|
||||
magic: [u8; 8],
|
||||
rqvn: U32<BigEndian>,
|
||||
@@ -449,7 +449,7 @@ impl<'a> BinReqValues<'a> {
|
||||
/// Does minimal sanity test, just tests to prevent panics.
|
||||
/// `req` may be larger than the actual request.
|
||||
pub(crate) fn get(req: &'a [u8]) -> Result<Self> {
|
||||
let hdr = RequestHdr::read_from_prefix(req).ok_or(Error::BinRequestSmall)?;
|
||||
let (hdr, _) = RequestHdr::read_from_prefix(req).map_err(|_| Error::BinRequestSmall)?;
|
||||
let rql = hdr.rql.get() as usize;
|
||||
let sea = hdr.sea.get() as usize;
|
||||
|
||||
@@ -506,9 +506,9 @@ impl<'a> BinReqValues<'a> {
|
||||
/// [`FromBytes::ref_from_prefix`]
|
||||
pub(crate) fn req_dep_aad<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: FromBytes + Sized,
|
||||
T: FromBytes + Sized + Immutable + KnownLayout,
|
||||
{
|
||||
T::ref_from_prefix(self.req_dep_aad)
|
||||
T::ref_from_prefix(self.req_dep_aad).map(|s| s.0).ok()
|
||||
}
|
||||
|
||||
/// Returns a reference to the tag of this [`BinReqValues`].
|
||||
@@ -615,7 +615,7 @@ mod tests {
|
||||
#[test]
|
||||
fn req_hdr2() {
|
||||
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
|
||||
let hdr_bin = hdr.as_bytes_mut();
|
||||
let hdr_bin = hdr.as_mut_bytes();
|
||||
let hdr_bin_exp = [
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // magic
|
||||
0, 0, 2, 0, // vers
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use openssl::pkey::{PKey, Public};
|
||||
use std::mem::size_of;
|
||||
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U32};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
||||
|
||||
use crate::{
|
||||
assert_size,
|
||||
@@ -276,7 +276,7 @@ impl From<AttestationVersion> for RequestVersion {
|
||||
|
||||
/// Authenticated additional Data of an [`AttestationRequest`]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, AsBytes, FromZeroes, FromBytes, Clone, Copy)]
|
||||
#[derive(Debug, IntoBytes, FromBytes, Clone, Copy, Immutable, KnownLayout)]
|
||||
pub struct AttestationAuthenticated {
|
||||
flags: AttestationFlags,
|
||||
mai: U32<BigEndian>,
|
||||
@@ -310,7 +310,7 @@ impl AttestationAuthenticated {
|
||||
|
||||
/// Attestation flags
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, AsBytes, FromZeroes, FromBytes, Clone, Copy)]
|
||||
#[derive(Default, Debug, IntoBytes, FromBytes, Clone, Copy, Immutable)]
|
||||
pub struct AttestationFlags(UvFlags);
|
||||
static_assert!(AttestationFlags::FLAG_TO_ADD_SIZE.len() < 64);
|
||||
|
||||
@@ -393,7 +393,7 @@ impl AttestationFlags {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, AsBytes)]
|
||||
#[derive(Debug, IntoBytes, Immutable)]
|
||||
struct ReqConfData {
|
||||
meas_key: [u8; 64],
|
||||
nonce: AttNonce,
|
||||
|
||||
@@ -12,7 +12,7 @@ use openssl::{
|
||||
pkey::{PKeyRef, Private},
|
||||
};
|
||||
use std::mem::size_of;
|
||||
use zerocopy::{AsBytes, BigEndian, U16, U32};
|
||||
use zerocopy::{BigEndian, IntoBytes, U16, U32};
|
||||
|
||||
#[cfg(doc)]
|
||||
use crate::attest::AttestationRequest;
|
||||
|
||||
@@ -10,4 +10,5 @@
|
||||
pub mod asrcb;
|
||||
pub mod ext_secret;
|
||||
pub mod guest_secret;
|
||||
pub mod retr_secret;
|
||||
pub mod user_data;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::user_data::UserData;
|
||||
use super::{guest_secret::ListableSecretHdr, user_data::UserData};
|
||||
use crate::{
|
||||
assert_size,
|
||||
crypto::{hkdf_rfc_5869, AeadEncryptionResult},
|
||||
@@ -17,12 +17,12 @@ use openssl::{
|
||||
md::Md,
|
||||
pkey::{PKey, Private, Public},
|
||||
};
|
||||
use pv_core::{request::RequestVersion, secret::AddSecretMagic};
|
||||
use zerocopy::AsBytes;
|
||||
use pv_core::{request::RequestVersion, secret::AddSecretMagic, uv::SecretId};
|
||||
use zerocopy::{Immutable, IntoBytes};
|
||||
|
||||
/// Authenticated data w/o user data
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, AsBytes)]
|
||||
#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
|
||||
struct ReqAuthData {
|
||||
flags: UvFlags,
|
||||
boot_tags: BootHdrTags,
|
||||
@@ -283,6 +283,13 @@ impl AddSecretRequest {
|
||||
.map(|res| res.into_buf())
|
||||
}
|
||||
|
||||
/// Get a copy of the secret ID if any
|
||||
pub fn bin_id(asrcb: &[u8]) -> Result<Option<SecretId>> {
|
||||
AddSecretMagic::try_from_bytes(asrcb)?;
|
||||
BinReqValues::get(asrcb)
|
||||
.map(|req| req.req_dep_aad::<ListableSecretHdr>().map(|a| a.id.clone()))
|
||||
}
|
||||
|
||||
/// Get a copy of the add secret request tag
|
||||
pub fn bin_tag(asrcb: &[u8]) -> Result<Vec<u8>> {
|
||||
AddSecretMagic::try_from_bytes(asrcb)?;
|
||||
|
||||
@@ -4,20 +4,35 @@
|
||||
|
||||
#[allow(unused_imports)] // used for more convenient docstring
|
||||
use super::asrcb::AddSecretRequest;
|
||||
use crate::assert_size;
|
||||
use crate::{
|
||||
crypto::{hash, random_array},
|
||||
request::Confidential,
|
||||
Result,
|
||||
assert_size,
|
||||
crypto::{hash, random_array, SymKeyType},
|
||||
request::{
|
||||
openssl::{NID_ED25519, NID_ED448},
|
||||
Confidential,
|
||||
},
|
||||
uv::{
|
||||
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
|
||||
RetrieveCmd, SecretId,
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
use byteorder::BigEndian;
|
||||
use openssl::hash::MessageDigest;
|
||||
use pv_core::uv::{ListableSecretType, SecretId};
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
nid::Nid,
|
||||
pkey::{Id, PKey, PKeyRef, Private},
|
||||
};
|
||||
use pv_core::static_assert;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{convert::TryInto, fmt::Display};
|
||||
use zerocopy::{AsBytes, U16, U32};
|
||||
use std::fmt::Display;
|
||||
use zerocopy::{BigEndian, KnownLayout};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, U16, U32};
|
||||
|
||||
const ASSOC_SECRET_SIZE: usize = 32;
|
||||
const CCK_SIZE: usize = 32;
|
||||
/// Maximum size of a plain-text secret payload (8190)
|
||||
pub(crate) const MAX_SIZE_PLAIN_PAYLOAD: usize = RetrieveCmd::MAX_SIZE - 2;
|
||||
static_assert!(MAX_SIZE_PLAIN_PAYLOAD == 8190);
|
||||
|
||||
/// A Secret to be added in [`AddSecretRequest`]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -36,13 +51,69 @@ pub enum GuestSecret {
|
||||
#[serde(skip)]
|
||||
secret: Confidential<[u8; ASSOC_SECRET_SIZE]>,
|
||||
},
|
||||
/// Retrievable key
|
||||
///
|
||||
/// Create Retrievables using [`GuestSecret::retrievable`]
|
||||
/// Secret size is always valid for the type/kind
|
||||
Retrievable {
|
||||
/// Retrievable secret type
|
||||
kind: RetrievableSecret,
|
||||
/// Name of the secret
|
||||
name: String,
|
||||
/// SHA256 hash of [`GuestSecret::RetrievableKey::name`]
|
||||
id: SecretId,
|
||||
/// Confidential actual retrievable secret
|
||||
#[serde(skip)]
|
||||
secret: Confidential<Vec<u8>>,
|
||||
},
|
||||
/// CCK update
|
||||
///
|
||||
/// Create CCK updates using [`GuestSecret::update_cck`]
|
||||
UpdateCck {
|
||||
/// Confidential actual CCK (32 bytes)
|
||||
#[serde(skip)]
|
||||
secret: Confidential<[u8; CCK_SIZE]>,
|
||||
},
|
||||
}
|
||||
|
||||
macro_rules! retr_constructor {
|
||||
($(#[$err:meta])* | $(#[$kind:meta])* => $type: ty, $func: ident) => {
|
||||
/// Create a new
|
||||
$(#[$kind])*
|
||||
/// [`GuestSecret::Retrievable`] secret.
|
||||
///
|
||||
/// * `name` - Name of the secret. Will be hashed into a 32 byte id
|
||||
/// * `secret` - the secret value
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
$(#[$err])*
|
||||
pub fn $func(name: &str, secret: $type) -> Result<Self> {
|
||||
let (kind, secret) = $func(secret)?;
|
||||
Ok(Self::Retrievable {
|
||||
kind,
|
||||
name: name.to_string(),
|
||||
id: Self::name_to_id(name)?,
|
||||
secret,
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl GuestSecret {
|
||||
/// Hashes the name with sha256
|
||||
pub fn name_to_id(name: &str) -> Result<SecretId> {
|
||||
let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())?
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
Ok(id.into())
|
||||
}
|
||||
|
||||
/// Create a new [`GuestSecret::Association`].
|
||||
///
|
||||
/// * `name` - Name of the secret. Will be hashed into a 32 byte id
|
||||
/// * `secret` - Value of the secret. Ranom if [`Option::None`]
|
||||
/// * `secret` - Value of the secret. Random if [`Option::None`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -51,10 +122,6 @@ impl GuestSecret {
|
||||
where
|
||||
O: Into<Option<[u8; ASSOC_SECRET_SIZE]>>,
|
||||
{
|
||||
let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())?
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let secret = match secret.into() {
|
||||
Some(s) => s,
|
||||
None => random_array()?,
|
||||
@@ -62,16 +129,51 @@ impl GuestSecret {
|
||||
|
||||
Ok(Self::Association {
|
||||
name: name.to_string(),
|
||||
id: id.into(),
|
||||
id: Self::name_to_id(name)?,
|
||||
secret: secret.into(),
|
||||
})
|
||||
}
|
||||
|
||||
retr_constructor!(#[doc = r"This function will return an error if the secret is larger than 8 pages"]
|
||||
| #[doc = r"plaintext"] => Confidential<Vec<u8>>, plaintext);
|
||||
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
|
||||
| #[doc = r"AES Key"] => Confidential<Vec<u8>>, aes);
|
||||
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
|
||||
| #[doc = r"AES-XTS Key"] => Confidential<Vec<u8>>, aes_xts);
|
||||
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
|
||||
| #[doc = r"HMAC-SHA Key"] => Confidential<Vec<u8>>, hmac_sha);
|
||||
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the curve is invalid"]
|
||||
| #[doc = r"EC PRIVATE Key"] => PKey<Private>, ec);
|
||||
|
||||
/// Create a new [`GuestSecret::UpdateCck`].
|
||||
///
|
||||
/// * `secret` - New CCK.
|
||||
pub fn update_cck(secret: [u8; CCK_SIZE]) -> Self {
|
||||
Self::UpdateCck {
|
||||
secret: secret.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the name as ID, do not hash it
|
||||
pub fn no_hash_name(&mut self) {
|
||||
match self {
|
||||
Self::Null | Self::UpdateCck { .. } => (),
|
||||
Self::Association {
|
||||
name, ref mut id, ..
|
||||
}
|
||||
| Self::Retrievable {
|
||||
name, ref mut id, ..
|
||||
} => id.clone_from(&SecretId::from_string(name)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to the confidential data
|
||||
pub(crate) fn confidential(&self) -> &[u8] {
|
||||
pub fn confidential(&self) -> &[u8] {
|
||||
match &self {
|
||||
Self::Null => &[],
|
||||
Self::Association { secret, .. } => secret.value().as_slice(),
|
||||
Self::Retrievable { secret, .. } => secret.value(),
|
||||
Self::UpdateCck { secret, .. } => secret.value(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +181,8 @@ impl GuestSecret {
|
||||
pub(crate) fn auth(&self) -> SecretAuth {
|
||||
match &self {
|
||||
Self::Null => SecretAuth::Null,
|
||||
// Panic: every non null secret type is listable -> no panic
|
||||
Self::UpdateCck { .. } => SecretAuth::UpdateCck,
|
||||
// Panic: other secret types are list-able -> no panic
|
||||
listable => {
|
||||
SecretAuth::Listable(ListableSecretHdr::from_guest_secret(listable).unwrap())
|
||||
}
|
||||
@@ -92,6 +195,8 @@ impl GuestSecret {
|
||||
// Null is not listable, but the ListableSecretType provides the type constant (1)
|
||||
Self::Null => ListableSecretType::NULL,
|
||||
Self::Association { .. } => ListableSecretType::ASSOCIATION,
|
||||
Self::Retrievable { kind, .. } => kind.into(),
|
||||
Self::UpdateCck { .. } => ListableSecretType::UPDATE_CCK,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,25 +205,181 @@ impl GuestSecret {
|
||||
match self {
|
||||
Self::Null => 0,
|
||||
Self::Association { secret, .. } => secret.value().len() as u32,
|
||||
Self::Retrievable { secret, .. } => secret.value().len() as u32,
|
||||
Self::UpdateCck { secret } => secret.value().len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the ID of the secret type (if any)
|
||||
fn id(&self) -> Option<SecretId> {
|
||||
match self {
|
||||
Self::Null => None,
|
||||
Self::Association { id, .. } => Some(id.to_owned()),
|
||||
Self::Null | Self::UpdateCck { .. } => None,
|
||||
Self::Association { id, .. } | Self::Retrievable { id, .. } => Some(id.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RetrKeyInfo = (RetrievableSecret, Confidential<Vec<u8>>);
|
||||
|
||||
fn extend_to_multiple(mut key: Vec<u8>, multiple: usize) -> Confidential<Vec<u8>> {
|
||||
match key.len().checked_rem(multiple) {
|
||||
Some(0) | None => key,
|
||||
Some(m) => {
|
||||
key.resize(key.len() + multiple - m, 0);
|
||||
key
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Get a plain-text key
|
||||
///
|
||||
/// ```none
|
||||
/// size U16<BigEndian> | payload (0-8190) bytes
|
||||
/// ```
|
||||
fn plaintext(inp: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
|
||||
let key_len = inp.value().len();
|
||||
if key_len > MAX_SIZE_PLAIN_PAYLOAD {
|
||||
return Err(Error::RetrInvKey {
|
||||
what: "key size",
|
||||
value: key_len.to_string(),
|
||||
kind: RetrievableSecret::PlainText.to_string(),
|
||||
exp: RetrievableSecret::PlainText.expected(),
|
||||
});
|
||||
}
|
||||
let mut key = Vec::with_capacity(2 + inp.value().len());
|
||||
let key_len: U16<BigEndian> = (key_len as u16).into();
|
||||
key.extend_from_slice(key_len.as_bytes());
|
||||
key.extend_from_slice(inp.value());
|
||||
let key = extend_to_multiple(key, SymKeyType::AES_256_GCM_BLOCK_LEN);
|
||||
|
||||
Ok((RetrievableSecret::PlainText, key))
|
||||
}
|
||||
|
||||
/// Get an AES-key
|
||||
fn aes(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
|
||||
let key_len = key.value().len() as u32;
|
||||
let bit_size = bitsize(key_len);
|
||||
match AesSizes::from_bits(bit_size) {
|
||||
Some(size) => Ok((RetrievableSecret::Aes(size), key)),
|
||||
None => {
|
||||
// Use some AES type to get exp sizes and name
|
||||
let kind = RetrievableSecret::Aes(AesSizes::Bits128);
|
||||
Err(Error::RetrInvKey {
|
||||
what: "key size",
|
||||
value: bit_size.to_string(),
|
||||
kind: format!("{kind:#}"),
|
||||
exp: kind.expected(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an AES-XTS-key
|
||||
fn aes_xts(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
|
||||
let key_len = key.value().len() as u32;
|
||||
let bit_size = bitsize(key_len / 2);
|
||||
match AesXtsSizes::from_bits(bit_size) {
|
||||
Some(size) => Ok((RetrievableSecret::AesXts(size), key)),
|
||||
None => {
|
||||
// Use some AES-XTS type to get exp sizes and name
|
||||
let kind = RetrievableSecret::AesXts(AesXtsSizes::Bits128);
|
||||
Err(Error::RetrInvKey {
|
||||
what: "key size",
|
||||
value: bit_size.to_string(),
|
||||
kind: format!("{kind:#}"),
|
||||
exp: kind.expected(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an HMAC-SHA-key
|
||||
fn hmac_sha(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
|
||||
let key_len = key.value().len() as u32;
|
||||
let size = bitsize(key_len / 2);
|
||||
match HmacShaSizes::from_sha_size(size) {
|
||||
Some(size) => Ok((RetrievableSecret::HmacSha(size), key)),
|
||||
None => {
|
||||
// Use some HMAC type to get exp sizes and name
|
||||
let kind = RetrievableSecret::HmacSha(HmacShaSizes::Sha256);
|
||||
Err(Error::RetrInvKey {
|
||||
what: "key size",
|
||||
value: size.to_string(),
|
||||
kind: format!("{kind:#}"),
|
||||
exp: kind.expected(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an EC-private-key
|
||||
fn ec(key: PKey<Private>) -> Result<RetrKeyInfo> {
|
||||
// reads & left-pads Edward EC keys
|
||||
fn pad_ed_key(pkey: &PKeyRef<Private>, curve: &EcCurves) -> Result<Vec<u8>> {
|
||||
let raw_key = pkey.raw_private_key()?;
|
||||
|
||||
match raw_key.len().cmp(&curve.exp_key_size()) {
|
||||
std::cmp::Ordering::Less => {
|
||||
let mut key = Vec::with_capacity(curve.exp_key_size());
|
||||
key.extend_from_slice(&vec![0u8; curve.exp_key_size() - raw_key.len()]);
|
||||
key.extend_from_slice(&raw_key);
|
||||
Ok(key)
|
||||
}
|
||||
std::cmp::Ordering::Equal => Ok(raw_key),
|
||||
std::cmp::Ordering::Greater => Err(Error::InvalSslData),
|
||||
}
|
||||
}
|
||||
|
||||
let nid = match key.id() {
|
||||
Id::EC => key.ec_key()?.group().curve_name().unwrap_or(Nid::UNDEF),
|
||||
id @ (Id::ED25519 | Id::ED448) => Nid::from_raw(id.as_raw()),
|
||||
_ => Nid::UNDEF,
|
||||
};
|
||||
|
||||
let kind = match nid {
|
||||
Nid::X9_62_PRIME256V1 => EcCurves::Secp256R1,
|
||||
Nid::SECP384R1 => EcCurves::Secp384R1,
|
||||
Nid::SECP521R1 => EcCurves::Secp521R1,
|
||||
NID_ED25519 => EcCurves::Ed25519,
|
||||
NID_ED448 => EcCurves::Ed448,
|
||||
nid => {
|
||||
// Use some EC type to get exp sizes and name
|
||||
let ec = RetrievableSecret::Ec(EcCurves::Secp521R1);
|
||||
return Err(Error::RetrInvKey {
|
||||
what: "curve or format",
|
||||
kind: format!("{ec:#}"),
|
||||
value: nid.long_name()?.to_string(),
|
||||
exp: ec.expected(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let key = match key.id() {
|
||||
Id::EC => key
|
||||
.ec_key()?
|
||||
.private_key()
|
||||
.to_vec_padded(kind.exp_key_size() as i32)?,
|
||||
// ED keys are not handled via the EC struct in OpenSSL.
|
||||
Id::ED25519 | Id::ED448 => pad_ed_key(&key, &kind)?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok((RetrievableSecret::Ec(kind), key.into()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn bitsize(bytesize: u32) -> u32 {
|
||||
bytesize * 8
|
||||
}
|
||||
|
||||
impl Display for GuestSecret {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Null => write!(f, "Meta"),
|
||||
gs => {
|
||||
let kind: U16<BigEndian> = gs.kind().into();
|
||||
let st: ListableSecretType = kind.into();
|
||||
let st: ListableSecretType = kind.get().into();
|
||||
write!(f, "{st}")
|
||||
}
|
||||
}
|
||||
@@ -129,44 +390,97 @@ impl Display for GuestSecret {
|
||||
pub(crate) enum SecretAuth {
|
||||
Null,
|
||||
Listable(ListableSecretHdr),
|
||||
UpdateCck,
|
||||
}
|
||||
|
||||
impl SecretAuth {
|
||||
const NULL_HDR: NullSecretHdr = NullSecretHdr::new();
|
||||
const UPDATE_CCK_HDR: UpdateCckHdr = UpdateCckHdr::new();
|
||||
|
||||
pub fn get(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Null => &[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
Self::Null => Self::NULL_HDR.as_bytes(),
|
||||
Self::Listable(h) => h.as_bytes(),
|
||||
Self::UpdateCck => Self::UPDATE_CCK_HDR.as_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, AsBytes)]
|
||||
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
||||
struct NullSecretHdr {
|
||||
res0: u16,
|
||||
kind: U16<BigEndian>,
|
||||
secret_len: U32<BigEndian>,
|
||||
res8: u64,
|
||||
}
|
||||
assert_size!(NullSecretHdr, 0x10);
|
||||
|
||||
impl NullSecretHdr {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
res0: 0,
|
||||
kind: U16::new(ListableSecretType::NULL),
|
||||
secret_len: U32::ZERO,
|
||||
res8: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
||||
pub(crate) struct ListableSecretHdr {
|
||||
res0: u16,
|
||||
kind: U16<BigEndian>,
|
||||
secret_len: U32<BigEndian>,
|
||||
res8: u64,
|
||||
id: SecretId,
|
||||
pub(crate) id: SecretId,
|
||||
}
|
||||
assert_size!(ListableSecretHdr, 0x30);
|
||||
|
||||
impl ListableSecretHdr {
|
||||
fn from_guest_secret(gs: &GuestSecret) -> Option<Self> {
|
||||
let id = gs.id()?;
|
||||
Some(Self {
|
||||
res0: 0,
|
||||
kind: gs.kind().into(),
|
||||
secret_len: gs.secret_len().into(),
|
||||
res8: 0,
|
||||
id,
|
||||
id: gs.id()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, IntoBytes, Default, Immutable)]
|
||||
struct UpdateCckHdr {
|
||||
res0: u16,
|
||||
kind: U16<BigEndian>,
|
||||
secret_len: U32<BigEndian>,
|
||||
res8: u64,
|
||||
res10: [u8; 0x20],
|
||||
}
|
||||
assert_size!(UpdateCckHdr, 0x30);
|
||||
|
||||
impl UpdateCckHdr {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
res0: 0,
|
||||
kind: U16::new(ListableSecretType::UPDATE_CCK),
|
||||
secret_len: U32::new(CCK_SIZE as u32),
|
||||
res8: 0,
|
||||
res10: [0; 0x20],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::HmacShaSizes as HmacSizes;
|
||||
use super::RetrievableSecret::*;
|
||||
use super::*;
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use pv_core::uv::AesSizes;
|
||||
use serde_test::{assert_tokens, Token};
|
||||
|
||||
#[test]
|
||||
@@ -187,8 +501,127 @@ mod test {
|
||||
assert_eq!(secret, exp);
|
||||
}
|
||||
|
||||
macro_rules! retr_test {
|
||||
($name: ident, $func: ident, $size: expr, $exp_kind: expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let secret_value = vec![0x11; $size];
|
||||
let name = "test retr secret".to_string();
|
||||
let secret = GuestSecret::$func(&name, secret_value.clone().into()).unwrap();
|
||||
let exp_id = [
|
||||
0x61, 0x2c, 0xd6, 0x3e, 0xa8, 0xf2, 0xc1, 0x15, 0xc1, 0xe, 0x15, 0xb8, 0x8a,
|
||||
0x90, 0x16, 0xc1, 0x55, 0xef, 0x9c, 0x7c, 0x2c, 0x8e, 0x56, 0xd0, 0x78, 0x4c,
|
||||
0x8a, 0x1d, 0xc9, 0x3a, 0x80, 0xba,
|
||||
];
|
||||
let exp = GuestSecret::Retrievable {
|
||||
kind: $exp_kind,
|
||||
name,
|
||||
id: exp_id.into(),
|
||||
secret: secret_value.into(),
|
||||
};
|
||||
assert_eq!(exp, secret);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
retr_test!(retr_aes_128, aes, 16, Aes(AesSizes::Bits128));
|
||||
retr_test!(retr_aes_192, aes, 24, Aes(AesSizes::Bits192));
|
||||
retr_test!(retr_aes_256, aes, 32, Aes(AesSizes::Bits256));
|
||||
retr_test!(retr_aes_xts_128, aes_xts, 32, AesXts(AesXtsSizes::Bits128));
|
||||
retr_test!(retr_aes_xts_256, aes_xts, 64, AesXts(AesXtsSizes::Bits256));
|
||||
retr_test!(retr_aes_hmac_256, hmac_sha, 64, HmacSha(HmacSizes::Sha256));
|
||||
retr_test!(retr_aes_hmac_512, hmac_sha, 128, HmacSha(HmacSizes::Sha512));
|
||||
|
||||
#[test]
|
||||
fn ap_asc_parse() {
|
||||
fn update_cck() {
|
||||
let new_cck = [11; 32];
|
||||
let req = GuestSecret::update_cck(new_cck);
|
||||
let exp = GuestSecret::UpdateCck {
|
||||
secret: new_cck.into(),
|
||||
};
|
||||
assert_eq!(req, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plaintext_no_pad() {
|
||||
let key = vec![0, 14, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7];
|
||||
let name = "PLAINTEXT_PAD".to_string();
|
||||
let secret = GuestSecret::plaintext(&name, key[2..].to_vec().into()).unwrap();
|
||||
let exp_id = [
|
||||
15, 123, 176, 210, 135, 231, 220, 232, 148, 93, 198, 195, 165, 212, 214, 129, 45, 1,
|
||||
94, 11, 167, 18, 151, 15, 120, 254, 13, 109, 173, 186, 37, 74,
|
||||
];
|
||||
let exp = GuestSecret::Retrievable {
|
||||
kind: PlainText,
|
||||
name,
|
||||
id: exp_id.into(),
|
||||
secret: key.into(),
|
||||
};
|
||||
|
||||
assert_eq!(secret, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plaintext_pad() {
|
||||
let key = vec![0, 10, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0];
|
||||
let name = "PLAINTEXT_PAD".to_string();
|
||||
let secret = GuestSecret::plaintext(&name, key[2..12].to_vec().into()).unwrap();
|
||||
let exp_id = [
|
||||
15, 123, 176, 210, 135, 231, 220, 232, 148, 93, 198, 195, 165, 212, 214, 129, 45, 1,
|
||||
94, 11, 167, 18, 151, 15, 120, 254, 13, 109, 173, 186, 37, 74,
|
||||
];
|
||||
let exp = GuestSecret::Retrievable {
|
||||
kind: PlainText,
|
||||
name,
|
||||
id: exp_id.into(),
|
||||
secret: key.into(),
|
||||
};
|
||||
|
||||
assert_eq!(secret, exp);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn gen_ec(nid: Nid) -> PKey<Private> {
|
||||
let group = EcGroup::from_curve_name(nid).unwrap();
|
||||
let key = EcKey::generate(&group).unwrap();
|
||||
PKey::from_ec_key(key).unwrap()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_ec(grp: Nid, exp_kind: EcCurves, exp_len: usize) {
|
||||
let key = match grp {
|
||||
NID_ED25519 => PKey::generate_ed25519().unwrap(),
|
||||
NID_ED448 => PKey::generate_ed448().unwrap(),
|
||||
nid => gen_ec(nid),
|
||||
};
|
||||
let (kind, key) = ec(key).unwrap();
|
||||
|
||||
assert_eq!(kind, Ec(exp_kind));
|
||||
assert_eq!(key.value().len(), exp_len);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retr_ec() {
|
||||
test_ec(Nid::X9_62_PRIME256V1, EcCurves::Secp256R1, 32);
|
||||
test_ec(Nid::SECP384R1, EcCurves::Secp384R1, 48);
|
||||
test_ec(Nid::SECP521R1, EcCurves::Secp521R1, 80);
|
||||
test_ec(NID_ED25519, EcCurves::Ed25519, 32);
|
||||
test_ec(NID_ED448, EcCurves::Ed448, 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retr_ec_pad() {
|
||||
let pkey = PKey::generate_ed448().unwrap();
|
||||
let (_, key) = ec(pkey).unwrap();
|
||||
assert_eq!(key.value()[..7], [0; 7]);
|
||||
|
||||
let pkey = gen_ec(Nid::SECP521R1);
|
||||
let (_, key) = ec(pkey).unwrap();
|
||||
assert_eq!(key.value()[..14], [0; 14]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asc_parse() {
|
||||
let id = [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
|
||||
0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
|
||||
@@ -217,6 +650,57 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrievable_parse() {
|
||||
let id = [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
|
||||
0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
|
||||
0x89, 0xab, 0xcd, 0xef,
|
||||
];
|
||||
let asc = GuestSecret::Retrievable {
|
||||
kind: PlainText,
|
||||
name: "test123".to_string(),
|
||||
id: id.into(),
|
||||
secret: vec![].into(),
|
||||
};
|
||||
|
||||
assert_tokens(
|
||||
&asc,
|
||||
&[
|
||||
Token::StructVariant {
|
||||
name: "GuestSecret",
|
||||
variant: "Retrievable",
|
||||
len: 3,
|
||||
},
|
||||
Token::String("kind"),
|
||||
Token::String("3 (PLAINTEXT)"),
|
||||
Token::String("name"),
|
||||
Token::String("test123"),
|
||||
Token::String("id"),
|
||||
Token::String("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
|
||||
Token::StructVariantEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_cck_parse() {
|
||||
let cck = GuestSecret::UpdateCck {
|
||||
secret: [0; 32].into(),
|
||||
};
|
||||
assert_tokens(
|
||||
&cck,
|
||||
&[
|
||||
Token::StructVariant {
|
||||
name: "GuestSecret",
|
||||
variant: "UpdateCck",
|
||||
len: 0,
|
||||
},
|
||||
Token::StructVariantEnd,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guest_secret_bin_null() {
|
||||
let gs = GuestSecret::Null;
|
||||
@@ -228,7 +712,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guest_secret_bin_ap() {
|
||||
fn guest_secret_bin_asoc() {
|
||||
let gs = GuestSecret::Association {
|
||||
name: "test".to_string(),
|
||||
id: [1; 32].into(),
|
||||
@@ -241,4 +725,34 @@ mod test {
|
||||
assert_eq!(exp, gs_bytes_auth.get());
|
||||
assert_eq!(&[2; 32], gs.confidential());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guest_secret_bin_retr() {
|
||||
let gs = GuestSecret::Retrievable {
|
||||
kind: PlainText,
|
||||
name: "test".to_string(),
|
||||
id: [1; 32].into(),
|
||||
secret: vec![2; 32].into(),
|
||||
};
|
||||
let auth = gs.auth();
|
||||
let gs_bytes_auth = auth.get();
|
||||
let mut exp = vec![0u8, 0, 0, 3, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
exp.extend([1; 32]);
|
||||
|
||||
assert_eq!(exp, gs_bytes_auth);
|
||||
assert_eq!(&[2; 32], gs.confidential());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guest_secret_bin_cck() {
|
||||
let gs = GuestSecret::UpdateCck {
|
||||
secret: [2; 32].into(),
|
||||
};
|
||||
let gs_bytes_auth = gs.auth();
|
||||
let mut exp = vec![0u8, 0, 0, 0x16, 0, 0, 0, 0x20];
|
||||
exp.extend([0; 40]);
|
||||
|
||||
assert_eq!(exp, gs_bytes_auth.get());
|
||||
assert_eq!(&[2; 32], gs.confidential());
|
||||
}
|
||||
}
|
||||
|
||||
236
rust/pv/src/uvsecret/retr_secret.rs
Normal file
236
rust/pv/src/uvsecret/retr_secret.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{crypto::SymKeyType, pem::Pem, uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD, Result};
|
||||
|
||||
use log::warn;
|
||||
use pv_core::{
|
||||
request::Confidential,
|
||||
uv::{ListableSecretType, RetrievableSecret, RetrieveCmd},
|
||||
};
|
||||
use zerocopy::BigEndian;
|
||||
use zerocopy::{FromBytes, U16};
|
||||
|
||||
/// An IBM Protected Key
|
||||
///
|
||||
/// A protected key, writeable as pem.
|
||||
///
|
||||
/// Will convert into PEM as:
|
||||
/// ```PEM
|
||||
///-----BEGIN IBM PROTECTED KEY-----
|
||||
///kind: <name>
|
||||
///
|
||||
///<protected key in base64>
|
||||
///-----END IBM PROTECTED KEY-----
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct IbmProtectedKey {
|
||||
kind: ListableSecretType,
|
||||
key: Confidential<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl IbmProtectedKey {
|
||||
/// Get the binary representation of the key.
|
||||
pub fn data(&self) -> &[u8] {
|
||||
self.key.value()
|
||||
}
|
||||
|
||||
/// Converts a [`IbmProtectedKey`] into a vector.
|
||||
pub fn into_bytes(self) -> Confidential<Vec<u8>> {
|
||||
self.key
|
||||
}
|
||||
|
||||
/// Get the data in PEM format.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the PEM conversion failed (very unlikely).
|
||||
pub fn to_pem(&self) -> Result<Pem> {
|
||||
Pem::new(
|
||||
"IBM PROTECTED KEY",
|
||||
format!("kind: {}", self.kind),
|
||||
self.key.value(),
|
||||
)
|
||||
}
|
||||
|
||||
fn new<K>(kind: ListableSecretType, key: K) -> Self
|
||||
where
|
||||
K: Into<Confidential<Vec<u8>>>,
|
||||
{
|
||||
Self {
|
||||
kind,
|
||||
key: key.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RetrieveCmd> for RetrievedSecret {
|
||||
fn from(value: RetrieveCmd) -> Self {
|
||||
let kind = value.meta_data().stype();
|
||||
let key = value.into_key();
|
||||
|
||||
match kind {
|
||||
ListableSecretType::Retrievable(RetrievableSecret::PlainText) => {
|
||||
// Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is the
|
||||
// minimum size
|
||||
let len = U16::<BigEndian>::read_from_prefix(key.value())
|
||||
.unwrap_or_default()
|
||||
.0
|
||||
.get() as usize;
|
||||
|
||||
// Test if the plain text secret has a size:
|
||||
// 1. len <= 8190
|
||||
// 2. first two bytes are max 15 less than buffer-size+2 i.e. smaller than the
|
||||
// block length
|
||||
// 3. bytes after len + 2 are zero
|
||||
match len <= MAX_SIZE_PLAIN_PAYLOAD
|
||||
&& key.value().len() - (len + 2) < SymKeyType::AES_256_GCM_BLOCK_LEN
|
||||
&& key.value()[len + 2..].iter().all(|c| *c == 0)
|
||||
{
|
||||
false => Self::Plaintext(key),
|
||||
true => Self::Plaintext(key.value()[2..len + 2].to_vec().into()),
|
||||
}
|
||||
}
|
||||
kind => {
|
||||
match kind {
|
||||
ListableSecretType::Retrievable(_) => (),
|
||||
_ => warn!("Retrieved an unretrievable Secret! Will continue; interpreting it as a protected key."),
|
||||
}
|
||||
Self::ProtectedKey(IbmProtectedKey::new(kind, key))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A retrieved Secret.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum RetrievedSecret {
|
||||
/// A plaintext secret
|
||||
Plaintext(Confidential<Vec<u8>>),
|
||||
/// An [`IbmProtectedKey`]
|
||||
ProtectedKey(IbmProtectedKey),
|
||||
}
|
||||
|
||||
impl RetrievedSecret {
|
||||
/// Create a new IBM PROTECTED KEY object
|
||||
pub fn from_cmd(cmd: RetrieveCmd) -> Self {
|
||||
cmd.into()
|
||||
}
|
||||
|
||||
/// Get the binary representation of the key.
|
||||
pub fn data(&self) -> &[u8] {
|
||||
match self {
|
||||
RetrievedSecret::Plaintext(p) => p.value(),
|
||||
RetrievedSecret::ProtectedKey(p) => p.data(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`IbmProtectedKey`] into a vector.
|
||||
pub fn into_bytes(self) -> Confidential<Vec<u8>> {
|
||||
match self {
|
||||
RetrievedSecret::Plaintext(p) => p,
|
||||
RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
|
||||
}
|
||||
}
|
||||
/// Get the data in PEM format.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the PEM conversion failed (very unlikely).
|
||||
pub fn to_pem(&self) -> Result<Pem> {
|
||||
match self {
|
||||
RetrievedSecret::Plaintext(p) => Pem::new("PLAINTEXT SECRET", None, p.value()),
|
||||
RetrievedSecret::ProtectedKey(p) => p.to_pem(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pv_core::uv::*;
|
||||
|
||||
fn mk_retr(secret: &[u8]) -> RetrievedSecret {
|
||||
let entry = SecretEntry::new(
|
||||
0,
|
||||
ListableSecretType::Retrievable(RetrievableSecret::PlainText),
|
||||
SecretId::default(),
|
||||
secret.len() as u32,
|
||||
);
|
||||
let mut cmd = RetrieveCmd::from_entry(entry).unwrap();
|
||||
cmd.data().unwrap().copy_from_slice(secret);
|
||||
RetrievedSecret::from_cmd(cmd)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_retr_cmd() {
|
||||
let secret = vec![0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0, 0, 0, 0];
|
||||
let prot_key = mk_retr(&secret);
|
||||
let exp = RetrievedSecret::Plaintext(secret[2..12].to_vec().into());
|
||||
assert_eq!(prot_key, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_retr_inv_size() {
|
||||
let secret = vec![0x20; 32];
|
||||
let prot_key = mk_retr(&secret);
|
||||
let exp = RetrievedSecret::Plaintext(secret.into());
|
||||
assert_eq!(prot_key, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_retr_inv_no_zero_after_end() {
|
||||
let secret = vec![0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 1, 0, 0, 0];
|
||||
let prot_key = mk_retr(&secret);
|
||||
let exp = RetrievedSecret::Plaintext(secret.into());
|
||||
assert_eq!(prot_key, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_retr_inv_to_much_padding() {
|
||||
let secret = vec![
|
||||
0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
];
|
||||
let prot_key = mk_retr(&secret);
|
||||
let exp = RetrievedSecret::Plaintext(secret.into());
|
||||
assert_eq!(prot_key, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_retr_0_size() {
|
||||
let secret = vec![0x00; 32];
|
||||
let prot_key = mk_retr(&secret);
|
||||
let exp = RetrievedSecret::Plaintext(secret.into());
|
||||
assert_eq!(prot_key, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_text_pem() {
|
||||
let exp = "\
|
||||
-----BEGIN PLAINTEXT SECRET-----\n\
|
||||
ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERER\n\
|
||||
-----END PLAINTEXT SECRET-----\n";
|
||||
let prot = RetrievedSecret::Plaintext(vec![17; 48].into());
|
||||
let pem = prot.to_pem().unwrap();
|
||||
let pem_str = pem.to_string();
|
||||
assert_eq!(pem_str, exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prot_key_pem() {
|
||||
let exp = "\
|
||||
-----BEGIN IBM PROTECTED KEY-----\n\
|
||||
kind: AES-128-KEY\n\n\
|
||||
ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERER\n\
|
||||
-----END IBM PROTECTED KEY-----\n";
|
||||
let prot = IbmProtectedKey::new(
|
||||
ListableSecretType::Retrievable(RetrievableSecret::Aes(AesSizes::Bits128)),
|
||||
vec![17; 48],
|
||||
);
|
||||
let pem = prot.to_pem().unwrap();
|
||||
let pem_str = pem.to_string();
|
||||
assert_eq!(pem_str, exp);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
};
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::nid::Nid;
|
||||
use zerocopy::{AsBytes, BigEndian, FromBytes, FromZeroes, U16};
|
||||
use zerocopy::{BigEndian, FromBytes, IntoBytes, KnownLayout, U16};
|
||||
|
||||
/// User data.
|
||||
///
|
||||
@@ -59,7 +59,7 @@ pub(super) enum UserData {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, AsBytes, FromBytes, FromZeroes)]
|
||||
#[derive(Debug, IntoBytes, FromBytes, KnownLayout)]
|
||||
struct EcUserData {
|
||||
data: [u8; 256],
|
||||
signature: [u8; EC_SIGN_MAX_SIZE],
|
||||
@@ -175,7 +175,7 @@ impl UserData {
|
||||
// insert signature
|
||||
if let UserDataType::SgnEcSECP521R1 = self.data_type() {
|
||||
// Panic: will not panic buffer is 512+ bytes long
|
||||
let buf_ec = EcUserData::mut_from_prefix(&mut buf[user_data_offset..]).unwrap();
|
||||
let (buf_ec, _) = EcUserData::mut_from_prefix(&mut buf[user_data_offset..]).unwrap();
|
||||
buf_ec.set_signature(&sgn);
|
||||
} else {
|
||||
// Panic: will not panic buffer is 512+ bytes long
|
||||
@@ -328,12 +328,15 @@ impl VerifiedUserData {
|
||||
|
||||
let (ret, sgn) = match kind {
|
||||
UserDataType::SgnEcSECP521R1 => {
|
||||
let EcUserData {
|
||||
data,
|
||||
signature,
|
||||
sgn_size,
|
||||
..
|
||||
} = EcUserData::mut_from_prefix(buf).unwrap();
|
||||
let (
|
||||
EcUserData {
|
||||
data,
|
||||
signature,
|
||||
sgn_size,
|
||||
..
|
||||
},
|
||||
_,
|
||||
) = EcUserData::mut_from_prefix(buf).unwrap();
|
||||
let data_len: usize = data.len();
|
||||
let data = data.to_vec();
|
||||
let mut signature = signature.to_vec();
|
||||
@@ -480,7 +483,7 @@ mod test {
|
||||
buf[..0x80].copy_from_slice(data);
|
||||
|
||||
user_data.sign(&mut buf, 0).unwrap();
|
||||
let buf_ec = EcUserData::mut_from(&mut buf).unwrap();
|
||||
let buf_ec = EcUserData::mut_from_bytes(&mut buf).unwrap();
|
||||
let EcUserData {
|
||||
data,
|
||||
signature,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "s390_pv_core"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -16,13 +16,14 @@ readme = "README.md"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.49"
|
||||
log = { version = "0.4.6", features = ["std", "release_max_level_debug"] }
|
||||
thiserror = "1.0.33"
|
||||
zerocopy = {version = "0.7", features = ["derive"]}
|
||||
serde = { version = "1.0.139", features = ["derive"]}
|
||||
byteorder = "1.3"
|
||||
libc = "0.2.169"
|
||||
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
|
||||
thiserror = "2.0.11"
|
||||
zerocopy = {version = "0.8", features = ["derive"]}
|
||||
serde = { version = "1.0.217", features = ["derive"]}
|
||||
byteorder = "1.5"
|
||||
regex = "1.10"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_test = "1.0.139"
|
||||
lazy_static = "1.1"
|
||||
serde_test = "1.0.177"
|
||||
lazy_static = "1.5"
|
||||
|
||||
468
rust/pv_core/src/apdevice.rs
Normal file
468
rust/pv_core/src/apdevice.rs
Normal file
@@ -0,0 +1,468 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
//
|
||||
//! AP support functions
|
||||
//
|
||||
|
||||
use crate::{
|
||||
utils::{pv_guest_bit_set, read_file_string, write_file},
|
||||
Error, Result,
|
||||
};
|
||||
use regex::Regex;
|
||||
use std::fmt;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";
|
||||
|
||||
/// Regular expression for AP queue directories
|
||||
pub const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$";
|
||||
const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$";
|
||||
const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)";
|
||||
const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)";
|
||||
const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)";
|
||||
|
||||
const SYS_BUS_AP_BIND_POLL_MS: u64 = 500;
|
||||
const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000;
|
||||
|
||||
const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500;
|
||||
const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000;
|
||||
|
||||
/// APQN mode
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ApqnMode {
|
||||
/// Accelerator mode
|
||||
Accel,
|
||||
/// EP11 (Enterprise PKCS #11) coprocessor mode
|
||||
Ep11,
|
||||
/// Common Cryptographic Architecture (CCA) coprocessor mode
|
||||
Cca,
|
||||
}
|
||||
|
||||
/// Info on an APQN configured for accelerator
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApqnInfoAccel {
|
||||
// empty
|
||||
}
|
||||
|
||||
/// Info on an APQN configured for EP11 coprocessor
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApqnInfoEp11 {
|
||||
/// Serial number of the Crypto Express adapter as a case-sensitive ASCII string
|
||||
pub serialnr: String,
|
||||
/// Master key verification pattern as hex string
|
||||
pub mkvp: String, // may be an empty string if no WK set
|
||||
}
|
||||
|
||||
/// Info on an APQN configured for CCA coprocessor
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct ApqnInfoCca {
|
||||
/// Serial number of the Crypto Express adapter as a case-sensitive ASCII string
|
||||
pub serialnr: String,
|
||||
/// Master key verification pattern as hex string for AES
|
||||
pub mkvp_aes: String, // may be an empty string if no MK set
|
||||
/// Master key verification pattern as hex string for asymmetric public key algorithms
|
||||
pub mkvp_apka: String, // may be an empty string if no MK set
|
||||
}
|
||||
|
||||
/// Info for an APQN's mode
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ApqnInfo {
|
||||
/// Info on an APQN configured for accelerator
|
||||
Accel(ApqnInfoAccel),
|
||||
/// Info on an APQN configured for EP11 coprocessor
|
||||
Ep11(ApqnInfoEp11),
|
||||
/// Info on an APQN configured for CCA coprocessor
|
||||
#[allow(dead_code)]
|
||||
Cca(ApqnInfoCca),
|
||||
}
|
||||
|
||||
macro_rules! parse_error {
|
||||
($subject:expr, $content:expr) => {
|
||||
Error::ParseError {
|
||||
subject: $subject,
|
||||
content: $content,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ApqnInfo {
|
||||
fn accel_info(_carddir: &str, _queuedir: &str) -> Result<Self> {
|
||||
Ok(Self::Accel(ApqnInfoAccel {}))
|
||||
}
|
||||
|
||||
fn cca_info(carddir: &str, queuedir: &str) -> Result<Self> {
|
||||
let serialnr_str = read_file_string(format!("{carddir}/serialnr"), "serialnr")?;
|
||||
let serialnr = serialnr_str.trim().to_string();
|
||||
let mkvps = read_file_string(format!("{carddir}/{queuedir}/mkvps"), "mkvps")?;
|
||||
let mut aes_mkvp = String::new();
|
||||
let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap();
|
||||
if !re_cca_aes_mkvp.is_match(&mkvps) {
|
||||
return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps));
|
||||
} else {
|
||||
let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap();
|
||||
if caps.get(1).unwrap().as_str().to_lowercase() == "valid" {
|
||||
aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
|
||||
if aes_mkvp.starts_with("0x") {
|
||||
aes_mkvp = String::from(&aes_mkvp[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut apka_mkvp = String::new();
|
||||
let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap();
|
||||
if !re_cca_apka_mkvp.is_match(&mkvps) {
|
||||
return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps));
|
||||
} else {
|
||||
let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap();
|
||||
if caps.get(1).unwrap().as_str().to_lowercase() == "valid" {
|
||||
apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase();
|
||||
if apka_mkvp.starts_with("0x") {
|
||||
apka_mkvp = String::from(&apka_mkvp[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Self::Cca(ApqnInfoCca {
|
||||
serialnr,
|
||||
mkvp_aes: aes_mkvp,
|
||||
mkvp_apka: apka_mkvp,
|
||||
}))
|
||||
}
|
||||
|
||||
fn ep11_info(carddir: &str, queuedir: &str) -> Result<Self> {
|
||||
let serialnr_str = read_file_string(format!("{carddir}/serialnr"), "serialnr")?;
|
||||
let serialnr = serialnr_str.trim().to_string();
|
||||
let mkvps = read_file_string(format!("{carddir}/{queuedir}/mkvps"), "mkvps")?;
|
||||
let mut mkvp = String::new();
|
||||
let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap();
|
||||
if !re_ep11_mkvp.is_match(&mkvps) {
|
||||
return Err(parse_error!(format!("APQN {queuedir} MKVPs"), mkvps));
|
||||
} else {
|
||||
let caps = re_ep11_mkvp.captures(&mkvps).unwrap();
|
||||
if caps.get(1).unwrap().as_str().to_lowercase() == "valid" {
|
||||
mkvp = caps.get(2).unwrap().as_str().to_lowercase();
|
||||
if mkvp.starts_with("0x") {
|
||||
mkvp = String::from(&mkvp[2..]);
|
||||
}
|
||||
if mkvp.len() > 32 {
|
||||
mkvp = String::from(&mkvp[..32])
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Self::Ep11(ApqnInfoEp11 { serialnr, mkvp }))
|
||||
}
|
||||
|
||||
/// Get mode-specific info
|
||||
pub fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> Result<Self> {
|
||||
match mode {
|
||||
ApqnMode::Accel => Self::accel_info(carddir, queuedir),
|
||||
ApqnMode::Cca => Self::cca_info(carddir, queuedir),
|
||||
ApqnMode::Ep11 => Self::ep11_info(carddir, queuedir),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Apqn` encodes an adjunct processor queue number.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Apqn {
|
||||
/// Name of the APQN
|
||||
#[allow(dead_code)]
|
||||
pub name: String,
|
||||
/// Card number
|
||||
pub card: u32,
|
||||
/// Domain number
|
||||
pub domain: u32,
|
||||
/// CryptoExpress generation
|
||||
pub gen: u32,
|
||||
/// Mode that adapter is configured to use
|
||||
pub mode: ApqnMode,
|
||||
/// Mode-specific info
|
||||
pub info: Option<ApqnInfo>,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Apqn {
|
||||
type Error = Error;
|
||||
|
||||
/// Create an `Apqn` struct from a CARD.DOMAIN-formatted APQN
|
||||
/// string, such as `28.0014`. Will not populate `info` upon
|
||||
/// failure to read it. Other failures to read required information
|
||||
/// are treated as an Error.
|
||||
/// # Panics
|
||||
/// Panics if the compilation of a static regular expression fails
|
||||
/// or a regex capture that is already format-checked does not
|
||||
/// parse, e.g. when the capture `([[:xdigit:]]{2})` does not
|
||||
/// parse as hex string.
|
||||
fn try_from(name: &str) -> Result<Self> {
|
||||
let re_card_type = Regex::new(RE_CARD_TYPE).unwrap();
|
||||
let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap();
|
||||
|
||||
let caps = re_queue_dir
|
||||
.captures(name)
|
||||
.ok_or_else(|| parse_error!("queue".to_string(), name.to_string()))?;
|
||||
let cardstr = caps.get(1).unwrap().as_str();
|
||||
let card = u32::from_str_radix(cardstr, 16).unwrap();
|
||||
let domstr = caps.get(2).unwrap().as_str();
|
||||
let domain = u32::from_str_radix(domstr, 16).unwrap();
|
||||
|
||||
let path = format!("{PATH_SYS_DEVICES_AP}/card{cardstr}");
|
||||
let card_type =
|
||||
read_file_string(format!("{path}/type"), "card type").map(|s| s.trim().to_string())?;
|
||||
let caps = re_card_type
|
||||
.captures(&card_type)
|
||||
.ok_or_else(|| parse_error!("card type".to_string(), card_type.to_string()))?;
|
||||
let gen = caps.get(1).unwrap().as_str().parse::<u32>().unwrap();
|
||||
let mode = match caps.get(2).unwrap().as_str().parse::<char>().unwrap() {
|
||||
'A' => ApqnMode::Accel,
|
||||
'C' => ApqnMode::Cca,
|
||||
'P' => ApqnMode::Ep11,
|
||||
_ => unreachable!("Code inconsistency between regex RE_CARD_TYPE and evaluation code."),
|
||||
};
|
||||
// the UV blocks requests to CCA cards within SE guest with AP
|
||||
// pass-through support. However, filter out CCA cards as
|
||||
// these cards cause hangs during information gathering.
|
||||
if mode == ApqnMode::Cca && pv_guest_bit_set() {
|
||||
return Err(Error::CcaSeIncompatible(card));
|
||||
}
|
||||
|
||||
match read_file_string(format!("{path}/{name}/online"), "AP queue online status")
|
||||
.map(|s| s.trim().parse::<i32>())
|
||||
{
|
||||
Ok(Ok(1)) => {}
|
||||
_ => return Err(Error::ApOffline { card, domain }),
|
||||
}
|
||||
// For the MKVP and serialnr to fetch from the APQN within a SE
|
||||
// guest the APQN needs to be bound to the guest. So if the APQN
|
||||
// is not bound, temporarily bind it here until the info has
|
||||
// been retrieved.
|
||||
let mut tempbound = false;
|
||||
if pv_guest_bit_set() {
|
||||
let cbs = get_apqn_bind_state(card, domain)?;
|
||||
if cbs == BindState::Unbound {
|
||||
set_apqn_bind_state(card, domain, BindState::Bound)?;
|
||||
tempbound = true;
|
||||
}
|
||||
}
|
||||
let info = ApqnInfo::info(&mode, &path, name).ok();
|
||||
if tempbound {
|
||||
set_apqn_bind_state(card, domain, BindState::Unbound)?;
|
||||
}
|
||||
|
||||
Ok(Apqn {
|
||||
name: name.to_string(),
|
||||
card,
|
||||
domain,
|
||||
gen,
|
||||
mode,
|
||||
info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Apqn {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({},{})", self.card, self.domain)
|
||||
}
|
||||
}
|
||||
|
||||
impl Apqn {
|
||||
/// Read bind state of the APQN.
|
||||
pub fn bind_state(&self) -> Result<BindState> {
|
||||
get_apqn_bind_state(self.card, self.domain)
|
||||
}
|
||||
|
||||
/// Set bind state of the APQN.
|
||||
pub fn set_bind_state(&self, state: BindState) -> Result<()> {
|
||||
set_apqn_bind_state(self.card, self.domain, state)
|
||||
}
|
||||
|
||||
/// Read associate state of the APQN.
|
||||
pub fn associate_state(&self) -> Result<AssocState> {
|
||||
get_apqn_associate_state(self.card, self.domain)
|
||||
}
|
||||
|
||||
/// Set associate state of the APQN.
|
||||
pub fn set_associate_state(&self, state: AssocState) -> Result<()> {
|
||||
set_apqn_associate_state(self.card, self.domain, state)
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind state of an APQN
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BindState {
|
||||
/// APQN is bound
|
||||
Bound,
|
||||
/// APQN is unbound
|
||||
Unbound,
|
||||
/// APQN does not support bind
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
/// Query bind state for this APQN.
|
||||
///
|
||||
/// Returns a BindState enum as defined above or on failure
|
||||
/// an error string. Does NOT print any error messages.
|
||||
pub fn get_apqn_bind_state(card: u32, dom: u32) -> Result<BindState> {
|
||||
let path = format!(
|
||||
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
|
||||
PATH_SYS_DEVICES_AP, card, card, dom
|
||||
);
|
||||
let state_str = read_file_string(path, "se_bind attribute")?;
|
||||
let state = state_str.trim();
|
||||
match state {
|
||||
"bound" => Ok(BindState::Bound),
|
||||
"unbound" => Ok(BindState::Unbound),
|
||||
"-" => Ok(BindState::NotSupported),
|
||||
_ => Err(Error::UnknownBindState(state.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind or unbind an APQN.
|
||||
///
|
||||
/// The action is determined by the BindState given in.
|
||||
/// But of course only Bound and Unbound is supported - otherwise
|
||||
/// this function panics!
|
||||
/// The function actively loops over the bind state until
|
||||
/// the requested bind state is reached or a timeout has
|
||||
/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS).
|
||||
/// On success () is returned, on failure an error string
|
||||
/// is returned. Does NOT print any error messages.
|
||||
/// # Panics
|
||||
/// Panics if a desired bind state other than Bound or Unbound is given.
|
||||
pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> Result<()> {
|
||||
let ctx = "bind APQN";
|
||||
let path = format!(
|
||||
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
|
||||
PATH_SYS_DEVICES_AP, card, card, dom
|
||||
);
|
||||
match state {
|
||||
BindState::Bound => write_file(path, 1.to_string(), ctx),
|
||||
BindState::Unbound => write_file(path, 0.to_string(), ctx),
|
||||
_ => panic!("set_apqn_bind_state called with invalid BindState."),
|
||||
}?;
|
||||
let mut ms: u64 = 0;
|
||||
loop {
|
||||
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS));
|
||||
ms += SYS_BUS_AP_BIND_POLL_MS;
|
||||
if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS {
|
||||
break Err(Error::Timeout(format!(
|
||||
"setting APQN({card},{dom}) bind state"
|
||||
)));
|
||||
}
|
||||
let newstate = get_apqn_bind_state(card, dom)?;
|
||||
if newstate == state {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Association state of an APQN
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AssocState {
|
||||
/// Associated with index
|
||||
Associated(u16),
|
||||
/// Association pending
|
||||
AssociationPending,
|
||||
/// Not associated
|
||||
Unassociated,
|
||||
/// APQN does not support association
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
/// Query association state for this APQN.
|
||||
///
|
||||
/// Returns an AssocState enum as defined above or on failure
|
||||
/// an error string. Does NOT print any error messages.
|
||||
pub fn get_apqn_associate_state(card: u32, dom: u32) -> Result<AssocState> {
|
||||
let path = format!(
|
||||
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
|
||||
PATH_SYS_DEVICES_AP, card, card, dom
|
||||
);
|
||||
let state_str = read_file_string(path, "se_associate attribute")?;
|
||||
let state = state_str.trim();
|
||||
match state.strip_prefix("associated ") {
|
||||
Some(prefix) => Ok(AssocState::Associated(prefix.parse()?)),
|
||||
_ => match state {
|
||||
"association pending" => Ok(AssocState::AssociationPending),
|
||||
"unassociated" => Ok(AssocState::Unassociated),
|
||||
"-" => Ok(AssocState::NotSupported),
|
||||
_ => Err(Error::UnknownAssocState(state.to_string())),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> Result<()> {
|
||||
let path = format!(
|
||||
"{}/card{:02x}/{:02x}.{:04x}/se_associate",
|
||||
PATH_SYS_DEVICES_AP, card, card, dom
|
||||
);
|
||||
write_file(path, idx.to_string(), "associate APQN")?;
|
||||
let mut ms: u64 = 0;
|
||||
loop {
|
||||
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
|
||||
ms += SYS_BUS_AP_ASSOC_POLL_MS;
|
||||
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
|
||||
break Err(Error::Timeout(format!(
|
||||
"setting APQN({card},{dom}) association index {idx} state",
|
||||
)));
|
||||
}
|
||||
match get_apqn_associate_state(card, dom)? {
|
||||
AssocState::Associated(i) if i == idx => return Ok(()),
|
||||
AssocState::Associated(i) => {
|
||||
return Err(Error::WrongAssocState {
|
||||
card,
|
||||
domain: dom,
|
||||
desired: idx,
|
||||
actual: i,
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> Result<()> {
|
||||
let bindpath = format!(
|
||||
"{}/card{:02x}/{:02x}.{:04x}/se_bind",
|
||||
PATH_SYS_DEVICES_AP, card, card, dom
|
||||
);
|
||||
write_file(bindpath, 0.to_string(), "unbind APQN")?;
|
||||
let mut ms: u64 = 0;
|
||||
loop {
|
||||
thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS));
|
||||
ms += SYS_BUS_AP_ASSOC_POLL_MS;
|
||||
if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS {
|
||||
break Err(Error::Timeout(format!(
|
||||
"setting APQN({card},{dom}) association unbind state",
|
||||
)));
|
||||
}
|
||||
let newstate = get_apqn_associate_state(card, dom)?;
|
||||
if newstate == AssocState::Unassociated {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate or Unassociate an APQN.
|
||||
///
|
||||
/// The action is determined by the AssocState given in.
|
||||
/// But of course only Associated and Unassociated is supported
|
||||
/// otherwise this function panics!
|
||||
/// The function actively loops over the association state until
|
||||
/// the requested state is reached or a timeout has
|
||||
/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS).
|
||||
/// The unassociate is in fact a unbind. So the code triggers
|
||||
/// an unbind and then loops over the sysfs se_associate until
|
||||
/// "unassociated" is reached.
|
||||
/// On success () is returned, on failure an error string
|
||||
/// is returned. Does NOT print any error messages.
|
||||
/// # Panics
|
||||
/// Panics if a desired bind state other than Associated or
|
||||
/// Unassociated is given.
|
||||
pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> Result<()> {
|
||||
match state {
|
||||
AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx),
|
||||
AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom),
|
||||
_ => panic!("set_apqn_associate_state called with invalid AssocState."),
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user