rust/pv*: Split pvapconfig::ap to pv_core::apdevice

Move appropriate parts into new pv_core::ap module.

Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Jakob Naucke
2025-02-03 13:16:49 +01:00
committed by Jan Höppner
parent e1245ed4e5
commit 4942504a9a
6 changed files with 544 additions and 502 deletions

1
rust/Cargo.lock generated
View File

@@ -805,6 +805,7 @@ dependencies = [
"lazy_static",
"libc",
"log",
"regex",
"serde",
"serde_test",
"thiserror",

View File

@@ -22,6 +22,7 @@ thiserror = "2.0.11"
zerocopy = {version = "0.7", features = ["derive"]}
serde = { version = "1.0.217", features = ["derive"]}
byteorder = "1.5"
regex = "1.10"
[dev-dependencies]
serde_test = "1.0.177"

View 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."),
}
}

View File

@@ -2,6 +2,7 @@
//
// Copyright IBM Corp. 2023, 2024
#![doc = include_str!("../README.md")]
mod apdevice;
mod confidential;
mod error;
mod macros;
@@ -72,5 +73,29 @@ pub mod secret {
pub use crate::uvsecret::UserDataType;
}
/// Functionalities for the AP bus
pub mod ap {
pub use crate::apdevice::Apqn;
pub use crate::apdevice::RE_QUEUE_DIR;
pub use crate::apdevice::{get_apqn_bind_state, set_apqn_bind_state};
/// AP modes
pub mod apqn_mode {
pub use crate::apdevice::ApqnMode::{self, *};
}
/// AP info for each state
pub mod apqn_info {
pub use crate::apdevice::ApqnInfo::{self, *};
pub use crate::apdevice::{ApqnInfoAccel, ApqnInfoCca, ApqnInfoEp11};
}
/// AP bind states
pub mod bind_state {
pub use crate::apdevice::BindState::{self, *};
}
/// AP association states
pub mod assoc_state {
pub use crate::apdevice::AssocState::{self, *};
}
}
// Internal definitions/ imports
const PAGESIZE: usize = 0x1000;

View File

@@ -6,38 +6,22 @@
//
use crate::helper::*;
use pv_core::{
misc::{read_file_string, write_file},
Error,
};
use regex::Regex;
use std::fmt;
use pv_core::ap::*;
use pv_core::misc::read_file_string;
use std::path::Path;
use std::slice::Iter;
use std::thread;
use std::time;
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
const PATH_SYS_BUS_AP: &str = "/sys/bus/ap";
const PATH_SYS_BUS_AP_FEATURES: &str = "/sys/bus/ap/features";
const PATH_SYS_BUS_AP_BINDINGS: &str = "/sys/bus/ap/bindings";
const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap";
const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$";
/// 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_BINDINGS_POLL_MS: u64 = 500;
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;
/// Check if AP bus support is available.
/// Returns Result with Ok(()) or Err(failurestring).
pub fn check_ap_bus_support() -> Result<(), String> {
@@ -97,264 +81,6 @@ pub fn wait_for_ap_bus_bindings_complete() -> bool {
}
}
/// 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) -> pv_core::Result<Self> {
Ok(Self::Accel(ApqnInfoAccel {}))
}
fn cca_info(carddir: &str, queuedir: &str) -> pv_core::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) -> pv_core::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) -> pv_core::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) -> pv_core::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_core::misc::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_core::misc::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) -> pv_core::Result<BindState> {
get_apqn_bind_state(self.card, self.domain)
}
/// Set bind state of the APQN.
pub fn set_bind_state(&self, state: BindState) -> pv_core::Result<()> {
set_apqn_bind_state(self.card, self.domain, state)
}
/// Read associate state of the APQN.
pub fn associate_state(&self) -> pv_core::Result<AssocState> {
get_apqn_associate_state(self.card, self.domain)
}
/// Set associate state of the APQN.
pub fn set_associate_state(&self, state: AssocState) -> pv_core::Result<()> {
set_apqn_associate_state(self.card, self.domain, state)
}
}
/// Wrapper object around Vector of Apqns
#[derive(Debug)]
pub struct ApqnList(Vec<Apqn>);
@@ -431,7 +157,7 @@ impl ApqnList {
if apqn.info.is_none() {
eprintln!("Warning: Failure gathering info for APQN {queue_dir}");
}
if let Some(ApqnInfo::Cca(ref cca_info)) = apqn.info {
if let Some(apqn_info::Cca(ref cca_info)) = apqn.info {
if cca_info.mkvp_aes.is_empty() {
eprintln!("Warning: APQN {queue_dir} has no valid AES master key set.");
}
@@ -439,7 +165,7 @@ impl ApqnList {
eprintln!("Warning: APQN {queue_dir} has no valid APKA master key set.");
}
}
if let Some(ApqnInfo::Ep11(ref ep11_info)) = apqn.info {
if let Some(apqn_info::Ep11(ref ep11_info)) = apqn.info {
if ep11_info.mkvp.is_empty() {
eprintln!("Warning: APQN {queue_dir} has no valid wrapping key set.");
}
@@ -467,16 +193,16 @@ impl ApqnList {
for a2 in self.0.iter() {
if a1.card == a2.card
&& a1.domain < a2.domain
&& a1.mode == ApqnMode::Ep11
&& a1.mode == apqn_mode::Ep11
&& a1.info.is_some()
&& a2.info.is_some()
{
let i1 = match a1.info.as_ref().unwrap() {
ApqnInfo::Ep11(i) => i,
apqn_info::Ep11(i) => i,
_ => continue,
};
let i2 = match a2.info.as_ref().unwrap() {
ApqnInfo::Ep11(i) => i,
apqn_info::Ep11(i) => i,
_ => continue,
};
if i1.mkvp.is_empty() || i2.mkvp.is_empty() {
@@ -493,186 +219,6 @@ impl ApqnList {
}
}
/// 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) -> pv_core::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) -> pv_core::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) -> pv_core::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) -> pv_core::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) -> pv_core::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) -> pv_core::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."),
}
}
#[cfg(test)]
mod tests {
@@ -724,16 +270,16 @@ mod tests {
let v = l.to_apqn_vec();
for a in v {
match a.mode {
ApqnMode::Accel => {
apqn_mode::Accel => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
}
ApqnMode::Ep11 => {
apqn_mode::Ep11 => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
let info = a.info.unwrap();
let i = match &info {
ApqnInfo::Ep11(i) => i,
apqn_info::Ep11(i) => i,
_ => panic!("ApqnInfo attached onto Ep11 APQN is NOT ApqnInfoEp11 ?!?"),
};
// fail if no serialnr
@@ -741,12 +287,12 @@ mod tests {
// mkvp is either empty (no WK set) or has exact 32 characters
assert!(i.mkvp.is_empty() || i.mkvp.len() == 32);
}
ApqnMode::Cca => {
apqn_mode::Cca => {
// fail if no ApqnInfo is attached
assert!(a.info.is_some());
let info = a.info.unwrap();
let i = match &info {
ApqnInfo::Cca(i) => i,
apqn_info::Cca(i) => i,
_ => panic!("ApqnInfo attached onto Cca APQN is NOT ApqnInfoCca ?!?"),
};
// fail if no serialnr

View File

@@ -12,10 +12,11 @@ mod config;
mod helper;
mod uv;
use ap::{Apqn, ApqnList};
use ap::ApqnList;
use cli::ARGS;
use config::{ApConfigEntry, ApConfigList};
use helper::{LockFile, PATH_PVAPCONFIG_LOCK};
use pv_core::ap::{self as pvap, Apqn};
use pv_core::misc::encode_hex;
use pv_core::uv::{ListableSecretType, SecretList};
use std::process::ExitCode;
@@ -225,14 +226,14 @@ fn do_ap_config(
continue;
}
match apqn.mode {
ap::ApqnMode::Accel => {
pvap::apqn_mode::Accel => {
// check bind state of this APQN
let bind_state_ok = match apqn.bind_state() {
Err(err) => {
eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}");
false
}
Ok(ap::BindState::Bound) => true,
Ok(pvap::bind_state::Bound) => true,
Ok(_) => false,
};
if !bind_state_ok {
@@ -247,7 +248,7 @@ fn do_ap_config(
resolved_entries += 1;
break;
}
ap::ApqnMode::Ep11 => {
pvap::apqn_mode::Ep11 => {
// check association state of this APQN
let (assoc_state_ok, assoc_idx) = match apqn.associate_state() {
Err(err) => {
@@ -256,7 +257,7 @@ fn do_ap_config(
);
(false, 0)
}
Ok(ap::AssocState::Associated(idx)) => (true, idx),
Ok(pvap::assoc_state::Associated(idx)) => (true, idx),
Ok(_) => (false, 0),
};
if !assoc_state_ok {
@@ -298,10 +299,10 @@ fn do_ap_config(
}
match apqn.bind_state() {
Err(err) => eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"),
Ok(ap::BindState::Bound) => {
Ok(pvap::bind_state::Bound) => {
info!("Unbind APQN {apqn} as this bind/associate does not match to any AP config entry.\n");
if !ARGS.dryrun() {
if let Err(err) = apqn.set_bind_state(ap::BindState::Unbound) {
if let Err(err) = apqn.set_bind_state(pvap::bind_state::Unbound) {
return Err(format!("Failure unbinding APQN {apqn}: {err}"));
}
}
@@ -331,15 +332,15 @@ fn do_ap_config(
continue;
}
match apqn.mode {
ap::ApqnMode::Accel => {
pvap::apqn_mode::Accel => {
// try to bind this accelerator APQN
if ARGS.verbose() || fntest {
println!("Bind APQN {apqn} to match to AP config entry {cistr}.");
}
if !(ARGS.dryrun() || fntest) {
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
if let Err(err) = apqn.set_bind_state(pvap::bind_state::Bound) {
// bind failed, unbind/reset this apqn, return with failure
let _ = apqn.set_bind_state(ap::BindState::Unbound);
let _ = apqn.set_bind_state(pvap::bind_state::Unbound);
return Err(format!("Failure binding APQN {apqn}: {err}"));
}
}
@@ -348,7 +349,7 @@ fn do_ap_config(
resolved_entries += 1;
break;
}
ap::ApqnMode::Ep11 => {
pvap::apqn_mode::Ep11 => {
// EP11 needs bind and associate, but before doing this let's
// check out which secret index to use with the associate
let se = match secrets.iter().find(|&se| {
@@ -370,9 +371,9 @@ fn do_ap_config(
);
}
if !(ARGS.dryrun() || fntest) {
if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) {
if let Err(err) = apqn.set_bind_state(pvap::bind_state::Bound) {
// bind failed, unbind/reset this apqn, return with failure
let _ = apqn.set_bind_state(ap::BindState::Unbound);
let _ = apqn.set_bind_state(pvap::bind_state::Unbound);
return Err(format!("Failure binding APQN {}: {}", apqn, err));
}
}
@@ -384,7 +385,7 @@ fn do_ap_config(
);
}
if !(ARGS.dryrun() || fntest) {
let apas = ap::AssocState::Associated(se.index());
let apas = pvap::assoc_state::Associated(se.index());
apqn.set_associate_state(apas)
.map_err(|err| format!("Failure associating APQN {apqn}: {err}"))?;
}
@@ -409,7 +410,7 @@ fn do_ap_config(
/// Please note this can not happen, as mingen is already checked via RE
/// during storing the value into mingen.
fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
if apc.mode == config::STR_MODE_ACCEL && apqn.mode == ap::ApqnMode::Accel {
if apc.mode == config::STR_MODE_ACCEL && apqn.mode == pvap::apqn_mode::Accel {
// config and apqn are accelerators
// maybe check mingen
if !apc.mingen.is_empty() {
@@ -419,10 +420,10 @@ fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool {
}
}
return true;
} else if apc.mode == config::STR_MODE_EP11 && apqn.mode == ap::ApqnMode::Ep11 {
} else if apc.mode == config::STR_MODE_EP11 && apqn.mode == pvap::apqn_mode::Ep11 {
// config and apqn are ep11
let info = match &apqn.info {
Some(ap::ApqnInfo::Ep11(i)) => i,
Some(pvap::apqn_info::Ep11(i)) => i,
_ => return false,
};
// maybe check mingen
@@ -458,64 +459,64 @@ mod tests {
fn make_test_apqns() -> Vec<Apqn> {
vec![
ap::Apqn {
pvap::Apqn {
name: String::from("10.0007"),
card: 16,
domain: 7,
gen: 8,
mode: ap::ApqnMode::Accel,
info: Option::Some(ap::ApqnInfo::Accel(ap::ApqnInfoAccel {})),
mode: pvap::apqn_mode::Accel,
info: Option::Some(pvap::apqn_info::Accel(pvap::apqn_info::ApqnInfoAccel {})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("11.0008"),
card: 17,
domain: 8,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
mode: pvap::apqn_mode::Ep11,
info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 {
serialnr: String::from("93AADFK719460083"),
mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"),
})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("12.0009"),
card: 18,
domain: 9,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
mode: pvap::apqn_mode::Ep11,
info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 {
serialnr: String::from("93AADHZU42082261"),
mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"),
})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("12.000a"),
card: 18,
domain: 10,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
mode: pvap::apqn_mode::Ep11,
info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 {
serialnr: String::from("93AADHZU42082261"),
mkvp: String::from("383d2a9ab781f35343554c5b3d9337cd"),
})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("13.000d"),
card: 19,
domain: 13,
gen: 8,
mode: ap::ApqnMode::Ep11,
info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 {
mode: pvap::apqn_mode::Ep11,
info: Option::Some(pvap::apqn_info::Ep11(pvap::apqn_info::ApqnInfoEp11 {
serialnr: String::from("87HU397G150TZGR"),
mkvp: String::new(),
})),
},
ap::Apqn {
pvap::Apqn {
name: String::from("13.000f"),
card: 19,
domain: 15,
gen: 8,
mode: ap::ApqnMode::Ep11,
mode: pvap::apqn_mode::Ep11,
info: Option::None,
},
]