@@ -57,6 +57,26 @@ use crate::{
5757/// AMI uses BIOS attribute SETUP001 for Administrator Password (UEFI password)
5858const UEFI_PASSWORD_NAME : & str = "SETUP001" ;
5959
60+ /// LenovoGB300 has no "EndlessBoot" BIOS attribute; infinite
61+ /// boot is expressed via the LEM0003 boot-retry count, where 50 is the
62+ /// firmware's representation for endless retries.
63+ const GB300_INFINITE_BOOT_RETRY : i64 = 50 ;
64+
65+ /// Build a lockdown `Status` from the fully-locked / fully-unlocked booleans,
66+ /// defaulting to `Partial` when the readout is neither.
67+ fn lockdown_status_from ( message : String , is_locked : bool , is_unlocked : bool ) -> Status {
68+ Status {
69+ message,
70+ status : if is_locked {
71+ StatusInternal :: Enabled
72+ } else if is_unlocked {
73+ StatusInternal :: Disabled
74+ } else {
75+ StatusInternal :: Partial
76+ } ,
77+ }
78+ }
79+
6080pub struct Bmc {
6181 s : RedfishStandard ,
6282}
@@ -66,6 +86,58 @@ impl Bmc {
6686 Ok ( Bmc { s } )
6787 }
6888
89+ /// Serial-console BIOS attributes as `(key, enabled_value, disabled_value)`.
90+ /// A `disabled_value` of "any" means any value counts as correctly disabled.
91+ ///
92+ /// LenovoGB300's BIOS registry mostly prefixes enum values with the
93+ /// attribute id, but irregularly: TER001/TER010 stay bare, the port is COM0
94+ /// not COM1, and the hyphen in VT-UTF8 is dropped. So both forms are listed
95+ /// explicitly per attribute rather than derived via a prefix rule.
96+ fn serial_console_attrs ( & self ) -> Vec < ( & ' static str , & ' static str , & ' static str ) > {
97+ let gb300 = self . s . vendor == Some ( RedfishVendor :: LenovoGB300 ) ;
98+ // (key, generic_enabled, gb300_enabled, disabled)
99+ const ATTRS : & [ ( & str , & str , & str , & str ) ] = & [
100+ ( "TER001" , "Enabled" , "Enabled" , "Disabled" ) , // Console Redirection
101+ ( "TER010" , "Enabled" , "Enabled" , "Disabled" ) , // Console Redirection EMS
102+ ( "TER06B" , "COM1" , "TER06BCOM0" , "any" ) , // Out-of-Band Mgmt Port
103+ ( "TER0021" , "115200" , "TER0021115200" , "any" ) , // Bits per second
104+ ( "TER0020" , "115200" , "TER0020115200" , "any" ) , // Bits per second EMS
105+ ( "TER012" , "VT100Plus" , "TER012VT100Plus" , "any" ) , // Terminal Type
106+ ( "TER011" , "VT-UTF8" , "TER011VTUTF8" , "any" ) , // Terminal Type EMS
107+ ( "TER05D" , "None" , "TER05DNone" , "any" ) , // Flow Control
108+ ] ;
109+ ATTRS
110+ . iter ( )
111+ . map ( |& ( key, generic, gb300_val, disabled) | {
112+ ( key, if gb300 { gb300_val } else { generic } , disabled)
113+ } )
114+ . collect ( )
115+ }
116+
117+ /// LenovoGB300 lockdown status: USB support (attribute-id prefixed enum,
118+ /// e.g. "USB000Disabled") plus the host interface. There is no KCS BIOS
119+ /// attribute on this platform, so it is not part of the status.
120+ async fn lockdown_status_gb300 ( & self ) -> Result < Status , RedfishError > {
121+ let bios = self . s . bios ( ) . await ?;
122+ let url = format ! ( "Systems/{}/Bios" , self . s. system_id( ) ) ;
123+ let attrs = jsonmap:: get_object ( & bios, "Attributes" , & url) ?;
124+ let usb000 = jsonmap:: get_str ( attrs, "USB000" , "Bios Attributes" ) ?;
125+
126+ let hi_url = format ! ( "Managers/{}/HostInterfaces/Self" , self . s. manager_id( ) ) ;
127+ let ( _status, hi) : ( _ , serde_json:: Value ) = self . s . client . get ( & hi_url) . await ?;
128+ let hi_enabled = hi
129+ . get ( "InterfaceEnabled" )
130+ . and_then ( |v| v. as_bool ( ) )
131+ . unwrap_or ( true ) ;
132+
133+ let message = format ! ( "usb_support={usb000}, host_interface={hi_enabled}" ) ;
134+
135+ let is_locked = usb000 == "USB000Disabled" && !hi_enabled;
136+ let is_unlocked = usb000 == "USB000Enabled" && hi_enabled;
137+
138+ Ok ( lockdown_status_from ( message, is_locked, is_unlocked) )
139+ }
140+
69141 /// LenovoAMI-specific lockdown status via OEM ConfigBMC endpoint.
70142 async fn lockdown_status_lenovo_ami ( & self ) -> Result < Status , RedfishError > {
71143 const LOCKDOWN_FIELDS : & [ & str ] = & [
@@ -75,8 +147,8 @@ impl Bmc {
75147 "LockdownBiosUpgradeDowngrade" ,
76148 ] ;
77149
78- let ( _status , body ) : ( _ , serde_json :: Value ) =
79- self . s . client . get ( "Managers/Self/Oem/ConfigBMC" ) . await ?;
150+ let config_bmc_url = format ! ( "Managers/{}/Oem/ConfigBMC" , self . s . manager_id ( ) ) ;
151+ let ( _status , body ) : ( _ , serde_json :: Value ) = self . s . client . get ( & config_bmc_url ) . await ?;
80152
81153 let values: Vec < & str > = LOCKDOWN_FIELDS
82154 . iter ( )
@@ -93,16 +165,7 @@ impl Bmc {
93165 let is_locked = values. iter ( ) . all ( |& v| v == "Enable" ) ;
94166 let is_unlocked = values. iter ( ) . all ( |& v| v == "Disable" ) ;
95167
96- Ok ( Status {
97- message,
98- status : if is_locked {
99- StatusInternal :: Enabled
100- } else if is_unlocked {
101- StatusInternal :: Disabled
102- } else {
103- StatusInternal :: Partial
104- } ,
105- } )
168+ Ok ( lockdown_status_from ( message, is_locked, is_unlocked) )
106169 }
107170}
108171impl Redfish for Bmc {
@@ -443,47 +506,59 @@ impl Redfish for Bmc {
443506 ( "LockdownBiosSettingsChange" , value) ,
444507 ( "LockdownBiosUpgradeDowngrade" , value) ,
445508 ] ) ;
446- return self
447- . s
448- . client
449- . post ( "Managers/Self/Oem/ConfigBMC" , body)
450- . await
451- . map ( |_| ( ) ) ;
509+ let config_bmc_url = format ! ( "Managers/{}/Oem/ConfigBMC" , self . s. manager_id( ) ) ;
510+ return self . s . client . post ( & config_bmc_url, body) . await . map ( |_| ( ) ) ;
452511 }
453512
454- let ( kcsacp, usb, hi_enabled) = match target {
455- Enabled => ( "Deny All" , "Disabled" , false ) ,
456- Disabled => ( "Allow All" , "Enabled" , true ) ,
513+ // LenovoGB300 has neither the OEM ConfigBMC endpoint nor
514+ // the generic AMI `KCSACP` BIOS attribute, and its USB enum values
515+ // are attribute-id prefixed (e.g. "USB000Disabled").
516+ let hi_enabled = target == Disabled ;
517+ let bios_attrs = if self . s . vendor == Some ( RedfishVendor :: LenovoGB300 ) {
518+ let usb = match target {
519+ Enabled => "USB000Disabled" ,
520+ Disabled => "USB000Enabled" ,
521+ } ;
522+ HashMap :: from ( [ ( "USB000" . to_string ( ) , usb. into ( ) ) ] )
523+ } else {
524+ let ( kcsacp, usb) = match target {
525+ Enabled => ( "Deny All" , "Disabled" ) ,
526+ Disabled => ( "Allow All" , "Enabled" ) ,
527+ } ;
528+ HashMap :: from ( [
529+ ( "KCSACP" . to_string ( ) , kcsacp. into ( ) ) ,
530+ ( "USB000" . to_string ( ) , usb. into ( ) ) ,
531+ ] )
457532 } ;
458- self . set_bios ( HashMap :: from ( [
459- ( "KCSACP" . to_string ( ) , kcsacp. into ( ) ) ,
460- ( "USB000" . to_string ( ) , usb. into ( ) ) ,
461- ] ) )
462- . await ?;
533+ self . set_bios ( bios_attrs) . await ?;
534+
535+ let hi_url = format ! ( "Managers/{}/HostInterfaces/Self" , self . s. manager_id( ) ) ;
463536 let hi_body = HashMap :: from ( [ ( "InterfaceEnabled" , hi_enabled) ] ) ;
464- self . s
465- . client
466- . patch_with_if_match ( "Managers/Self/HostInterfaces/Self" , hi_body)
467- . await
537+ self . s . client . patch_with_if_match ( & hi_url, hi_body) . await
468538 } )
469539 }
470540
471541 /// AMI lockdown status - checks KCS access, USB support, and Host Interface.
472- /// On LenovoAMI, reads the OEM ConfigBMC endpoint instead.
542+ /// On LenovoAMI, reads the OEM ConfigBMC endpoint instead. On LenovoGB300,
543+ /// checks USB support and the host interface (no KCS/ConfigBMC there).
473544 fn lockdown_status < ' a > ( & ' a self ) -> crate :: RedfishFuture < ' a , Result < Status , RedfishError > > {
474545 Box :: pin ( async move {
475546 if self . s . vendor == Some ( RedfishVendor :: LenovoAMI ) {
476547 return self . lockdown_status_lenovo_ami ( ) . await ;
477548 }
478549
550+ if self . s . vendor == Some ( RedfishVendor :: LenovoGB300 ) {
551+ return self . lockdown_status_gb300 ( ) . await ;
552+ }
553+
479554 let bios = self . s . bios ( ) . await ?;
480555 let url = format ! ( "Systems/{}/Bios" , self . s. system_id( ) ) ;
481556 let attrs = jsonmap:: get_object ( & bios, "Attributes" , & url) ?;
482557 let kcsacp = jsonmap:: get_str ( attrs, "KCSACP" , "Bios Attributes" ) ?;
483558 let usb000 = jsonmap:: get_str ( attrs, "USB000" , "Bios Attributes" ) ?;
484559
485- let hi_url = "Managers/Self /HostInterfaces/Self" ;
486- let ( _status, hi) : ( _ , serde_json:: Value ) = self . s . client . get ( hi_url) . await ?;
560+ let hi_url = format ! ( "Managers/{} /HostInterfaces/Self" , self . s . manager_id ( ) ) ;
561+ let ( _status, hi) : ( _ , serde_json:: Value ) = self . s . client . get ( & hi_url) . await ?;
487562 let hi_enabled = hi
488563 . get ( "InterfaceEnabled" )
489564 . and_then ( |v| v. as_bool ( ) )
@@ -497,34 +572,18 @@ impl Redfish for Bmc {
497572 let is_locked = kcsacp == "Deny All" && usb000 == "Disabled" && !hi_enabled;
498573 let is_unlocked = kcsacp == "Allow All" && usb000 == "Enabled" && hi_enabled;
499574
500- Ok ( Status {
501- message,
502- status : if is_locked {
503- StatusInternal :: Enabled
504- } else if is_unlocked {
505- StatusInternal :: Disabled
506- } else {
507- StatusInternal :: Partial
508- } ,
509- } )
575+ Ok ( lockdown_status_from ( message, is_locked, is_unlocked) )
510576 } )
511577 }
512578
513579 /// Setup serial console for AMI BMC via BIOS attributes.
514580 fn setup_serial_console < ' a > ( & ' a self ) -> crate :: RedfishFuture < ' a , Result < ( ) , RedfishError > > {
515581 Box :: pin ( async move {
516- use serde_json:: Value ;
517-
518- let attributes: HashMap < String , Value > = HashMap :: from ( [
519- ( "TER001" . to_string ( ) , "Enabled" . into ( ) ) , // Console Redirection
520- ( "TER010" . to_string ( ) , "Enabled" . into ( ) ) , // Console Redirection EMS
521- ( "TER06B" . to_string ( ) , "COM1" . into ( ) ) , // Out-of-Band Mgmt Port
522- ( "TER0021" . to_string ( ) , "115200" . into ( ) ) , // Bits per second
523- ( "TER0020" . to_string ( ) , "115200" . into ( ) ) , // Bits per second EMS
524- ( "TER012" . to_string ( ) , "VT100Plus" . into ( ) ) , // Terminal Type
525- ( "TER011" . to_string ( ) , "VT-UTF8" . into ( ) ) , // Terminal Type EMS
526- ( "TER05D" . to_string ( ) , "None" . into ( ) ) , // Flow Control
527- ] ) ;
582+ let attributes: HashMap < String , serde_json:: Value > = self
583+ . serial_console_attrs ( )
584+ . into_iter ( )
585+ . map ( |( key, enabled, _) | ( key. to_string ( ) , enabled. into ( ) ) )
586+ . collect ( ) ;
528587
529588 self . set_bios ( attributes) . await
530589 } )
@@ -539,16 +598,7 @@ impl Redfish for Bmc {
539598 let url = format ! ( "Systems/{}/Bios" , self . s. system_id( ) ) ;
540599 let attrs = jsonmap:: get_object ( & bios, "Attributes" , & url) ?;
541600
542- let expected = vec ! [
543- ( "TER001" , "Enabled" , "Disabled" ) ,
544- ( "TER010" , "Enabled" , "Disabled" ) ,
545- ( "TER06B" , "COM1" , "any" ) ,
546- ( "TER0021" , "115200" , "any" ) ,
547- ( "TER0020" , "115200" , "any" ) ,
548- ( "TER012" , "VT100Plus" , "any" ) ,
549- ( "TER011" , "VT-UTF8" , "any" ) ,
550- ( "TER05D" , "None" , "any" ) ,
551- ] ;
601+ let expected = self . serial_console_attrs ( ) ;
552602
553603 let mut message = String :: new ( ) ;
554604 let mut enabled = true ;
@@ -672,7 +722,15 @@ impl Redfish for Bmc {
672722
673723 fn clear_tpm < ' a > ( & ' a self ) -> crate :: RedfishFuture < ' a , Result < ( ) , RedfishError > > {
674724 Box :: pin ( async move {
675- self . set_bios ( HashMap :: from ( [ ( "TCG006" . to_string ( ) , "TPM Clear" . into ( ) ) ] ) )
725+ // GB300's Grace BIOS registry prefixes the TCG006 enum value with
726+ // the attribute id ("TCG006TPMClear"); other AMI platforms use the
727+ // bare "TPM Clear".
728+ let clear_value = if self . s . vendor == Some ( RedfishVendor :: LenovoGB300 ) {
729+ "TCG006TPMClear"
730+ } else {
731+ "TPM Clear"
732+ } ;
733+ self . set_bios ( HashMap :: from ( [ ( "TCG006" . to_string ( ) , clear_value. into ( ) ) ] ) )
676734 . await
677735 } )
678736 }
@@ -1047,8 +1105,8 @@ impl Redfish for Bmc {
10471105 Box :: pin ( async move {
10481106 let interface_enabled = target == EnabledDisabled :: Disabled ;
10491107 let hi_body = HashMap :: from ( [ ( "InterfaceEnabled" , interface_enabled) ] ) ;
1050- let hi_url = "Managers/Self /HostInterfaces/Self" ;
1051- self . s . client . patch_with_if_match ( hi_url, hi_body) . await
1108+ let hi_url = format ! ( "Managers/{} /HostInterfaces/Self" , self . s . manager_id ( ) ) ;
1109+ self . s . client . patch_with_if_match ( & hi_url, hi_body) . await
10521110 } )
10531111 }
10541112
@@ -1078,6 +1136,13 @@ impl Redfish for Bmc {
10781136 /// AMI clear_nvram - sets RECV000 (Reset NVRAM) to "Enabled"
10791137 fn clear_nvram < ' a > ( & ' a self ) -> crate :: RedfishFuture < ' a , Result < ( ) , RedfishError > > {
10801138 Box :: pin ( async move {
1139+ // The GB300 Grace BIOS registry has no RECV000 (Reset NVRAM)
1140+ // attribute, so there is no equivalent knob to set.
1141+ if self . s . vendor == Some ( RedfishVendor :: LenovoGB300 ) {
1142+ return Err ( RedfishError :: NotSupported (
1143+ "clear_nvram: no RECV000 BIOS attribute on LenovoGB300" . to_string ( ) ,
1144+ ) ) ;
1145+ }
10811146 self . set_bios ( HashMap :: from ( [ ( "RECV000" . to_string ( ) , "Enabled" . into ( ) ) ] ) )
10821147 . await
10831148 } )
@@ -1098,6 +1163,14 @@ impl Redfish for Bmc {
10981163
10991164 fn enable_infinite_boot < ' a > ( & ' a self ) -> crate :: RedfishFuture < ' a , Result < ( ) , RedfishError > > {
11001165 Box :: pin ( async move {
1166+ if self . s . vendor == Some ( RedfishVendor :: LenovoGB300 ) {
1167+ return self
1168+ . set_bios ( HashMap :: from ( [ (
1169+ "LEM0003" . to_string ( ) ,
1170+ GB300_INFINITE_BOOT_RETRY . into ( ) ,
1171+ ) ] ) )
1172+ . await ;
1173+ }
11011174 self . set_bios ( HashMap :: from ( [ (
11021175 "EndlessBoot" . to_string ( ) ,
11031176 "Enabled" . into ( ) ,
@@ -1113,6 +1186,16 @@ impl Redfish for Bmc {
11131186 let bios = self . s . bios ( ) . await ?;
11141187 let url = format ! ( "Systems/{}/Bios" , self . s. system_id( ) ) ;
11151188 let attrs = jsonmap:: get_object ( & bios, "Attributes" , & url) ?;
1189+ if self . s . vendor == Some ( RedfishVendor :: LenovoGB300 ) {
1190+ // LEM0003 may be reported as a JSON number or a numeric string.
1191+ let Some ( value) = attrs. get ( "LEM0003" ) else {
1192+ return Ok ( None ) ;
1193+ } ;
1194+ let retry = value
1195+ . as_i64 ( )
1196+ . or_else ( || value. as_str ( ) . and_then ( |s| s. parse :: < i64 > ( ) . ok ( ) ) ) ;
1197+ return Ok ( retry. map ( |r| r == GB300_INFINITE_BOOT_RETRY ) ) ;
1198+ }
11161199 let endless_boot = jsonmap:: get_str ( attrs, "EndlessBoot" , "Bios Attributes" ) ?;
11171200 Ok ( Some ( endless_boot == "Enabled" ) )
11181201 } )
@@ -1348,6 +1431,24 @@ impl Bmc {
13481431
13491432 /// Get the BIOS attributes for machine setup.
13501433 fn machine_setup_attrs ( & self ) -> HashMap < String , serde_json:: Value > {
1434+ // The LenovoGB300 (Grace-based) uses a distinct BIOS registry: enum
1435+ // values are prefixed with the attribute ID (e.g. "PCIS007Enabled"),
1436+ // there is no Intel VMX knob (VMXEN) or boot-mode selector (FBO001),
1437+ // and "Infinite Boot" is expressed via the LEM0003 retry count (50 =
1438+ // endless boot) instead of the "EndlessBoot" attribute.
1439+ if self . s . vendor == Some ( RedfishVendor :: LenovoGB300 ) {
1440+ return HashMap :: from ( [
1441+ ( "PCIS007" . to_string ( ) , "PCIS007Enabled" . into ( ) ) , // SR-IOV Support
1442+ ( "LEM0001" . to_string ( ) , 3 . into ( ) ) , // PXE retry count
1443+ ( "NWSK000" . to_string ( ) , "NWSK000Enabled" . into ( ) ) , // Network Stack
1444+ ( "NWSK001" . to_string ( ) , "NWSK001Disabled" . into ( ) ) , // IPv4 PXE Support
1445+ ( "NWSK006" . to_string ( ) , "NWSK006Enabled" . into ( ) ) , // IPv4 HTTP Support
1446+ ( "NWSK002" . to_string ( ) , "NWSK002Disabled" . into ( ) ) , // IPv6 PXE Support
1447+ ( "NWSK007" . to_string ( ) , "NWSK007Disabled" . into ( ) ) , // IPv6 HTTP Support
1448+ ( "LEM0003" . to_string ( ) , GB300_INFINITE_BOOT_RETRY . into ( ) ) , // Infinite Boot
1449+ ] ) ;
1450+ }
1451+
13511452 HashMap :: from ( [
13521453 ( "VMXEN" . to_string ( ) , "Enable" . into ( ) ) , // VMX (Intel Virtualization)
13531454 ( "PCIS007" . to_string ( ) , "Enabled" . into ( ) ) , // SR-IOV Support
0 commit comments