rust/cpacfinfo: New CLI tool to provide CPACF information

The new cpacfinfo command provides information about CPACF, such as which CPACF functions are installed, and make use of the new MSA 13 Query Authentication Information function.

Signed-off-by: Finn Callies <fcallies@linux.ibm.com>
Reviewed-by: Harald Freudenberger <freude@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
Finn Callies
2024-09-26 08:43:28 +02:00
committed by Steffen Eiden
parent 03340afe88
commit 57a32d98ef
13 changed files with 1569 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024
use std::fs::File;
use std::io::Error;
use std::io::Read;
use std::io::Result as ioRes;
use std::ops::Index;
use std::result::Result;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
use crate::msa::InstructionKind;
use crate::msa::QueryAuthInfo;
/// Path to sysfs in which the query and qai informations are fetched from
const SYSFS_PATH: &str = "/sys/devices/system/cpu/cpacf/";
/// Every Instruction has a Query function to get information about what functions are available
pub const QUERY_FUNCTION_CODE: u8 = 0;
/// Number of bytes returned by this Query
pub const QUERY_PARAM_SIZE_IN_BYTES: usize = 16;
/// Starting with MSA 13 every Instruction has a Query Authentication Information function to get
/// information about the running firmware
pub const QAI_FUNCTION_CODE: u8 = 127;
/// Number of bytes returned by this Query Authentication Information
pub const QAI_PARAM_SIZE_IN_BYTES: usize = 256;
/// Query authentication information format identifier
const FORMAT_0: u8 = 0;
#[derive(FromBytes, FromZeroes)]
#[repr(C)]
struct QaiFmt0 {
res00: [u8; 6],
hash_length: u16,
res08: [u8; 4],
version: u32,
hash: [u8; 64],
}
#[allow(clippy::large_enum_variant)]
pub enum Param {
QueryParam([u8; QUERY_PARAM_SIZE_IN_BYTES]),
QaiParam([u8; QAI_PARAM_SIZE_IN_BYTES]),
}
impl Index<u8> for Param {
type Output = u8;
fn index(&self, index: u8) -> &Self::Output {
match self {
Self::QueryParam(p) => &p[index as usize],
Self::QaiParam(p) => &p[index as usize],
}
}
}
impl Param {
pub fn len(&self) -> usize {
match self {
Self::QueryParam(_) => QUERY_PARAM_SIZE_IN_BYTES,
Self::QaiParam(_) => QAI_PARAM_SIZE_IN_BYTES,
}
}
/// check if a specific bit is 1 in param
pub fn check_bit_in_param(&self, check_bit: usize) -> bool {
// get correct byte of param
let byte = check_bit / 8;
// get correct byte of param
if byte >= self.len() {
return false;
}
// get correct bit of param
let bit = 8 - ((check_bit % 8) + 1);
// return if specified bit is set
match self {
Self::QueryParam(param) => (param[byte] & (1 << bit)) > 0,
Self::QaiParam(param) => (param[byte] & (1 << bit)) > 0,
}
}
/// set given bit in param to 0
pub fn unset_bit_in_param(&mut self, flip_bit: u8) {
// get correct byte of param
let byte = flip_bit / 8;
if byte as usize >= self.len() {
return;
}
// get correct bit of param
let bit = 8 - ((flip_bit % 8) + 1);
// build template to logically AND against param byte
// i.e. (flip_bit = 0) template = 1000 0000
let mut template: u8 = 1 << bit;
// flip all bits in template
// i.e. (flip_bit = 0) template = 0111 1111
template = !template;
// set bit to 0 while not changing any other bit
// i.e. (flip_bit = 0) 0111 1111 & xxxx xxxx = 0xxx xxxx
match self {
Self::QueryParam(c) => c[byte as usize] &= template,
Self::QaiParam(c) => c[byte as usize] &= template,
}
}
/// set all bytes of self to value
#[cfg(test)]
pub fn set_param_to(&mut self, value: u8) {
match self {
Self::QueryParam(ref mut content) => *content = [value; QUERY_PARAM_SIZE_IN_BYTES],
Self::QaiParam(ref mut content) => *content = [value; QAI_PARAM_SIZE_IN_BYTES],
}
}
// Outsourced for potential future formats to be easily added in this match statement
pub fn parse_qai_based_on_format(
&self,
qai: &mut QueryAuthInfo,
) -> Result<bool, anyhow::Error> {
match self {
Self::QueryParam(_) => panic!("programming error"),
Self::QaiParam(bin) => {
// The third byte of the param block specifies which format to use to parse the rest
qai.format = bin[3];
// for new formats add a match case here along with a parsing function
match qai.format {
FORMAT_0 => {
parse_qai_format_0(qai, bin);
Ok(true)
}
_ => Ok(false),
}
}
}
}
}
// check if SYSFS_PATH exists
pub fn check_sysfs() -> bool {
match std::path::Path::new(SYSFS_PATH).exists() {
true => true,
false => {
println!("Warning: There seems to be an insufficient kernel level running (sysfs interface {SYSFS_PATH} is missing)\nNo information can be fetched from sysfs, application exits early.");
false
}
}
}
/// parsing the information supplied by sysfs into QueryAuthInfo struct
///
/// The following box shows the qai block with named fields each with a length in bytes.
/// The length of field IFCL Hash depends on IFCL Hash Length and is either 32 or 64 bytes long.
/// In case of a 32 bytes length the latter 32 bytes of the 64 bytes Hash are filled with zeros.
///
/// | BYTE | BYTE | BYTE | BYTE |
/// -----------------------------------------------------------------
/// | RESERVED (3) FORMAT (1) |
/// | RESERVED (2) IFCL HASH LENGTH (2) |
/// | RESERVED (4) |
/// | IFCL VERSION (4) |
/// | IFCL HASH (32 / 64) |
/// | RESERVED (176) |
/// -----------------------------------------------------------------
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");
// parse from temporary struct
qai.hash_len = tmp.hash_length;
qai.version = tmp.version;
// depending on the parsed hash length the hash is parsed
qai.hash = vec![0; qai.hash_len as usize];
qai.hash
.as_mut_slice()
.copy_from_slice(&tmp.hash[..qai.hash_len as usize]);
}
/// cpacfinfo does not execute the actual instruction with function code but uses information
/// provided by the sysfs
pub fn query(ins: &InstructionKind, fc: u8) -> Result<Param, Error> {
// query dependent file names
let auth_info;
let mut param;
match fc {
QUERY_FUNCTION_CODE => {
auth_info = "";
param = Param::QueryParam([0; QUERY_PARAM_SIZE_IN_BYTES]);
}
QAI_FUNCTION_CODE => {
auth_info = "_auth_info";
param = Param::QaiParam([0; QAI_PARAM_SIZE_IN_BYTES]);
}
_ => panic!("programming error"),
};
// depending on which query is performed the bytes to be read from sysfs vary
let bytes_to_be_read = param.len();
// build filepath
let filepath = format!(
"{SYSFS_PATH}{}_query{auth_info}_raw",
ins.to_string().to_lowercase()
);
// open file
let mut f = match File::open(filepath) {
ioRes::Ok(file) => file,
Err(e) => return Err(e),
};
// read file
let res = match param {
Param::QueryParam(ref mut c) => read_file_to_buf(&mut f, c),
Param::QaiParam(ref mut c) => read_file_to_buf(&mut f, c),
};
let bytes_read = match res {
Ok(b) => b,
Err(e) => return Err(e),
};
match bytes_read == bytes_to_be_read {
true => Ok(param),
false => Err(Error::new(std::io::ErrorKind::UnexpectedEof, "test")),
}
}
fn read_file_to_buf(file: &mut File, buf: &mut [u8]) -> Result<usize, Error> {
file.read(buf)
/* match file.read(buf) {
Result::Ok(bytes_read) => Ok(bytes_read),
Err(e) => Err(e),
} */
}
#[cfg(test)]
#[test]
fn test_param_funcs() {
// initialize param with all ones
let mut param = Param::QueryParam([0; QUERY_PARAM_SIZE_IN_BYTES]);
const NUMBER_OF_BITS: usize = 8 * QUERY_PARAM_SIZE_IN_BYTES;
for i in 0..NUMBER_OF_BITS {
// reset param to all ones
param.set_param_to(0xFF);
assert!(param.check_bit_in_param(i));
// set one bit to zero
param.unset_bit_in_param(i as u8);
// check if that bit is zero
assert!(!param.check_bit_in_param(i));
}
}