Repository: metal3-io/baremetal-operator
Pull request #302 (2019-09-13 05:11:42)

Extend BaremetalHost CRD to support BIOS configuration in baremetal server
This commit aims to add `BIOSConfig` as new property into
`BareMetalHostSpec`. The BaremetalHost CRD is also extend with `bios`
field. This option allow Metal3 user to set BIOS settings (#207) via `bios` field
in YAML file.

Co-Authored-By: Dao Cong Tien <tiendc@vn.fujitsu.com>
Co-Authored-By: Nguyen Phuong An <annp@vn.fujitsu.com>
Signed-off-byKim Bao Long <longkb@vn.fujitsu.com>

russellb: Just FYI since Doug had reviewed this - he's off on vacation for a couple of weeks.  Let me know if this can wait until he's back, or if you'd really like this looked at sooner and I'll pick it up.
metal3-io-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/302#" title="Author self-approved">longkb</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **dhellmann**
You can assign the PR to them by writing `/assign @dhellmann` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["dhellmann"]} -->
dhellmann: I found an interesting pattern in Hive today, where they also have platform-specific settings. They use a struct with pointers to platform-specific structs as a sort of "union" type. https://github.com/openshift/hive/blob/master/pkg/apis/hive/v1alpha1/clusterdeployment_types.go#L309
hardys: Having platform specific schema for this interface seems reasonable, but how can we handle differences in vendor support for specific settings on different hardware?

E.g as above something like:

```
  bios:
    irmc:
      bootMode: "uefi"
      enableHyperThreading: true
```

Is fine to expose features that may only be applicable when using irmc vs redfish or whatever, but how can we handle features which are accessible via e.g redfish but only when hardware exists - is there a standard subset we can reasonably expect?

I guess there will be some common features like bootmode, hyperthreading, virtualization support etch which probably are common, but my concern is when there are more "advanced" settings which depend on the installed hardware we may find it difficult to define a static schema that can support it?
dhellmann: > I guess there will be some common features like bootmode, hyperthreading, virtualization support etch which probably are common, but my concern is when there are more "advanced" settings which depend on the installed hardware we may find it difficult to define a static schema that can support it?

Shouldn't the user receive an error if they try to change a setting that cannot be applied because the host doesn't have the relevant hardware? I would expect that to trigger a provisioning failure.

The platform-specific options can still be optional, even if they're well-defined.
longkb: I have update `bios` with some advanced settings from iRMC drivers :)
dhellmann: I'm not comfortable taking any more API changes until we have #434 fixed. We shouldn't have to edit the CRD file by hand, and we should be able to enable the new generator test job to validate API changes.
zaneb: We should copy the settings to the Status at the time we start provisioning (and read them from there) so that if the user changes the Spec during/after provisioning there is a record of what settings were used. #495 provides a function in which to implement this, `saveHostProvisioningSettings()`.
dhellmann: I would like to freeze go code changes for a few days to try to land https://github.com/metal3-io/baremetal-operator/pull/650 without having to rebase it, because rebasing will mean redoing the work from scratch.

/hold

dtantsur: I've put up a proposal for ironic to try avoid hardcoding BIOS setting names here: https://storyboard.openstack.org/#!/story/2008495. Could you take a look and see if it satisfies our case?

Also please rebase.
dtantsur: /hold cancel

#650 has merged
Hellcatlk: @andfasano @dtantsur @zaneb Thanks for your help. Since PR292 has been merged, the functions required by this PR have been implemented, so I think we can start reviewing this PR.
metal3-io-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/302#" title="Author self-approved">longkb</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **zaneb**
You can assign the PR to them by writing `/assign @zaneb` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["zaneb"]} -->
demonCoder95: @Hellcatlk We'd discussed earlier with the community regarding firmware configurations and we have a proposal [merged here](https://github.com/metal3-io/metal3-docs/blob/master/design/baremetal-operator/bios-config.md). This might help you answer some of the questions you have.
Hellcatlk: > @Hellcatlk We'd discussed earlier with the community regarding firmware configurations and we have a proposal [merged here](https://github.com/metal3-io/metal3-docs/blob/master/design/baremetal-operator/bios-config.md). This might help you answer some of the questions you have.

Thanks for the reminder, it is helpful for me.
dhellmann: I have some serious reservations about the maintainability of this approach. It is based on the design I advocated for in https://github.com/metal3-io/metal3-docs/blob/master/design/baremetal-operator/bios-config.md, but the idea with that design was that we would identify a very _very_ small number of settings to expose, not everything. If we were focused on a smaller set of options, I think more of them would be common between vendors. But if our requirements are to allow users to manage every BIOS setting on a host, then I think we need to reconsider the design so that our API surface does not grow so large.

demonCoder95: > I have some serious reservations about the maintainability of this approach. It is based on the design I advocated for in https://github.com/metal3-io/metal3-docs/blob/master/design/baremetal-operator/bios-config.md, but the idea with that design was that we would identify a very _very_ small number of settings to expose, not everything. If we were focused on a smaller set of options, I think more of them would be common between vendors. But if our requirements are to allow users to manage every BIOS setting on a host, then I think we need to reconsider the design so that our API surface does not grow so large.

I agree. I propose that we can keep the discussed 3 parameters (``sriovEnabled, simultaneousMultiThreadingEnabled`` and ``virtualizaitonEnabled``) in this PR, which adds the base functionality for us, while we discuss the future approach for the vendor-specific options. So,
1. Merge this PR with only the 3 parameters discussed in [the proposal earlier](https://github.com/metal3-io/metal3-docs/blob/master/design/baremetal-operator/bios-config.md).
2. Open a separate PR in the ``metal3-docs`` repo, to further extend the BIOS configuration design to discuss vendor specific parameters implementation in more detail.

Also, @Hellcatlk is there a reason you would like to put every parameter in firmware config in the API? Is there an actual use case for this? Because in the best interest for keeping the API lean and clean, @dhellmann and I have already discussed in great detail that we should only add new parameters in the API if we can find a real use case for them, in a Kubernetes environment.


s3rj1k: @demonCoder95 IMHO: I would also consider putting `PowerProfile string `json:"powerProfile,omitempty"`` or similar, I would want to change that when switching nodes from one cluster to another based on expected load and performance.

But that could also be changed manually I guess.
demonCoder95: > @demonCoder95 IMHO: I would also consider putting `PowerProfile string `json:"powerProfile,omitempty"`` or similar, I would want to change that when switching nodes from one cluster to another based on expected load and performance.
> 
> But that could also be changed manually I guess.

Forgive my ignorance, but is this a vendor specific parameter? or does every vendor have a "power profile"-like parameter? That would help us determine where it needs to go in the API. 
s3rj1k: @demonCoder95 I am not 100% sure but I think that SuperMicro has that also.
SuperMicro mostly follows RFC and stock (vanilla) BIOS firmware, so if it exists in SM and some other vendor it can be safely assumed that it is vendor agnostic.
demonCoder95: > @demonCoder95 I am not 100% sure but I think that SuperMicro has that also.
> SuperMicro mostly follows RFC and stock (vanilla) BIOS firmware, so if it exists in SM and some other vendor it can be safely assumed that it is vendor agnostic.

Alright then, perhaps we can then patch the design document with this new parameter and let's see what @Hellcatlk and @dhellmann have to say about this. It's fine by me, to have this new parameter as part of this patch, since we have found a use case for a vendor-agnostic parameter.
Hellcatlk: > > I have some serious reservations about the maintainability of this approach. It is based on the design I advocated for in https://github.com/metal3-io/metal3-docs/blob/master/design/baremetal-operator/bios-config.md, but the idea with that design was that we would identify a very _very_ small number of settings to expose, not everything. If we were focused on a smaller set of options, I think more of them would be common between vendors. But if our requirements are to allow users to manage every BIOS setting on a host, then I think we need to reconsider the design so that our API surface does not grow so large.
> 
> I agree. I propose that we can keep the discussed 3 parameters (`sriovEnabled, simultaneousMultiThreadingEnabled` and `virtualizaitonEnabled`) in this PR, which adds the base functionality for us, while we discuss the future approach for the vendor-specific options. So,
> 
>     1. Merge this PR with only the 3 parameters discussed in [the proposal earlier](https://github.com/metal3-io/metal3-docs/blob/master/design/baremetal-operator/bios-config.md). Remove the rest of the parameters from implementation.
> 
>     2. Open a separate PR in the `metal3-docs` repo, to further extend the BIOS configuration design to discuss vendor specific parameters implementation in more detail.
> 
> 
> Also, @Hellcatlk is there a reason you would like to put every parameter in firmware config in the API? Is there an actual use case for this? Because in the best interest for keeping the API lean and clean, @dhellmann and I have already discussed in great detail that we should only add new parameters in the API if we can find a real use case for them, in a Kubernetes environment.

Thanks, I have removed the rest of the parameters from implementation. In addition, do I need to replace reflection according to the following logic? If there is a better way, please tell me.

```go
        settings := make([]map[string]string, 0)

	if firmwareConfig.SimultaneousMultithreadingEnabled != "" {
		if firmwareConfig.SimultaneousMultithreadingEnabled == "true" {
			settings = append(
				settings,
				map[string]string {
					"name":  "ProcHyperthreading",
					"value": "Enabled",
				},
			)
		} else {
			settings = append(
				settings,
				map[string]string{
					"name":  "ProcHyperthreading",
					"value": "Disabled",
				},
			)
		}
	}

	if firmwareConfig.VirtualizationEnabled != "" {
		if firmwareConfig.VirtualizationEnabled == "true" {
			settings = append(
				settings,
				map[string]string {
					"name":  "ProcVirtualization",
					"value": "Enabled",
				},
			)
		} else {
			settings = append(
				settings,
				map[string]string{
					"name":  "ProcVirtualization",
					"value": "Disabled",
				},
			)
		}
	}

        ......
        ......
```

About ``` PowerProfile string `json:"powerProfile,omitempty"` ```, I didn‘t find the corresponding setting in [IRMC ](https://docs.openstack.org/ironic/latest/admin/drivers/irmc.html#bios-configuration-support), please remind me if I missed it.
Hellcatlk: @demonCoder95 @dhellmann @dtantsur PTAL
dhellmann: @Hellcatlk see https://github.com/metal3-io/metal3-docs/pull/173 for an alternative from @bfournie 
Hellcatlk: > @Hellcatlk see [metal3-io/metal3-docs#173](https://github.com/metal3-io/metal3-docs/pull/173) for an alternative from @bfournie

@dhellmann Thanks. According to my understanding, we can completely replace the current design with the new design, right? 
dhellmann: > > @Hellcatlk see [metal3-io/metal3-docs#173](https://github.com/metal3-io/metal3-docs/pull/173) for an alternative from @bfournie
> 
> @dhellmann Thanks. I noticed that there is such a [paragraph](https://github.com/metal3-io/metal3-docs/pull/173/files#diff-3da142a93247ebdf7aa940cb9d35a191e2d7fceaeed5730ee3b8d9c43dce09e6R37-R40) in the new design, so I want to confirm whether the current implementation will be retained?

I do think it would be better if we only had 1 API for managing BIOS settings. That would reduce confusion and avoid any cases where we have to manage the precedence between 2 sets of inputs.
Hellcatlk: @zaneb @demonCoder95 In order to facilitate the coding of [PR173](https://github.com/metal3-io/metal3-docs/pull/173) in the future, do I need to modify this PR? 
Hellcatlk: @zaneb PTAL.
zaneb: /approve
/test-integration
Hellcatlk: @dhellmann @demonCoder95 @zaneb test-integration has passed, PTAL.
demonCoder95: Is the CI healthy again? Can we merge patches? This looks good and ready to be merged, I believe.
zaneb: CI is still down afaik, but once this has an lgtm someone can merge it manually.
Hellcatlk: @dhellmann PTAL.
fmuyassarov: /retest
fmuyassarov: Prow is back. @zaneb adding approve once more should get the label (prow was down when you did it earlier).
zaneb: Looks like this might need a rebase or something to deal with the lint issue, but
/approve
metal3-io-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/302#" title="Author self-approved">longkb</a>*, *<a href="https://github.com/metal3-io/baremetal-operator/pull/302#issuecomment-836738021" title="Approved">zaneb</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)~~ [zaneb]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
Hellcatlk: Thanks, I have rebase this PR.
Hellcatlk: /retest
metal3-io-bot: @Hellcatlk: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/metal3-io/baremetal-operator/pull/302#issuecomment-839438630):

>/retest


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
fmuyassarov: /retest
fmuyassarov: /test-integration
dtantsur: The code looks good. I honestly have reservations about trying to provide generic names for firmware settings. The proposed here interface will be practically impossible to implement for a generic driver like Redfish.
demonCoder95: > The code looks good. I honestly have reservations about trying to provide generic names for firmware settings. The proposed here interface will be practically impossible to implement for a generic driver like Redfish.

This patch has been long outstanding (almost 2 years) and was stuck behind another patch that handled RAID configuration #292 . This is what makes the merging of this patch a really time-sensitive case for us.

Also, can someone point out if there are efforts to extend the generic redfish driver in Ironic? What's our timeline on that? Unless I'm mistaken, it would be weeks or months away, at best? For completeness' sake, yes, this is suboptimal, but given the extremely slow pace of review and merging, there is no choice but to accept this as is, and accept improvements later. Would really appreciate moving on with this. I can help with future efforts to adapt to Redfish.
Hellcatlk: > The code looks good. I honestly have reservations about trying to provide generic names for firmware settings. The proposed here interface will be practically impossible to implement for a generic driver like Redfish.

Sorry for the late reply. Yes, I think that the current code can't support setting BIOS through redfish. If we need to add support for redfish,  we may need to add a field `bmcType`(or something similar) to let users indicate the actual BMC, but this seems to go against the original intention of using redfish.

Does redfish/ironic have an API to get the BMC type? If there is an API to get the BMC type, we will not need to add new fields.

If the above method is feasible, I will open a new PR to add support for redfish later.
dhellmann: > > The code looks good. I honestly have reservations about trying to provide generic names for firmware settings. The proposed here interface will be practically impossible to implement for a generic driver like Redfish.
> 
> @dtantsur Sorry for the late reply. Yes, I think that the current code can't support setting BIOS through redfish. If we need to add support for redfish, we may need to add a field `bmcType`(or something similar) to let users indicate the actual BMC, but this seems to go against the original intention of using redfish.

Can't we use the Redfish API to ask the BMC what it is?


Hellcatlk: > 
> 
> > > The code looks good. I honestly have reservations about trying to provide generic names for firmware settings. The proposed here interface will be practically impossible to implement for a generic driver like Redfish.
> > 
> > 
> > @dtantsur Sorry for the late reply. Yes, I think that the current code can't support setting BIOS through redfish. If we need to add support for redfish, we may need to add a field `bmcType`(or something similar) to let users indicate the actual BMC, but this seems to go against the original intention of using redfish.
> 
> Can't we use the Redfish API to ask the BMC what it is?

@dhellmann For iRMC I can get its BMC type through the following command:
```sh
# curl -s -k -u <username>:<password> -H "Content-type: application/json" -X GET https://<ip-address>/redfish/v1/Managers
{
  "@odata.id":"\/redfish\/v1\/Managers",
  "@odata.type":"#ManagerCollection.ManagerCollection",
  "Name":"Manager Collection",
  "Members@odata.count":1,
  "Members":[
    {
      "@odata.id":"\/redfish\/v1\/Managers\/iRMC"
    }
  ],
  "Oem":{
  },
  "@Redfish.Copyright":"Copyright 2014-2020 DMTF. For the full DMTF copyright policy, see http:\/\/www.dmtf.org\/about\/policies\/copyright",
  "@odata.etag":"1618479521"
}
```
But I'm not sure if this is a standard method(@dtantsur Do you know a better way? ). In addition, I think we also need to be able to get this information through `ironic`. 

By the way, could you please review this patch? As what demonCoder95 has said above, I also believe that this patch is ready to be merged.
metal3-io-bot: @longkb: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
Hellcatlk: I have rebase this PR, but it seems that I need to re-execute `
test-integration`. Could you give a `/test-integration` comment. @demonCoder95 @fmuyassarov @zaneb 
zaneb: /test-integration
Hellcatlk: @andfasano @dhellmann @dtantsur @zaneb This PR has been launched for more than a year and has been recognized by some people,and we have [another job](https://github.com/openshift/enhancements/pull/791) that depends on this PR. So I want to know what else needs to be done to help this PR merge?
As for how to support redfish BIOS settings based on this PR, I can open another PR for discussion. 
zaneb: > The code looks good. I honestly have reservations about trying to provide generic names for firmware settings. The proposed here interface will be practically impossible to implement for a generic driver like Redfish.

Thanks for pointing this out, I hadn't really thought about that earlier.

I do think we have some implementation options for this in future. For example, we can now get a list of all the available BIOS options from Ironic, which we're going to use to implement Bob's proposal metal3-io/metal3-docs#173. So we could make a list of all the names that e.g. VirtualizationEnabled is known by on hardware platforms that we know about and then compare it to the list of available BIOS settings for the Host to see which one shows up.

The existence of at least one plausible way of implementing this API makes me inclined to go ahead with this PR, so I am not removing my approval.
andfasano: > > The code looks good. I honestly have reservations about trying to provide generic names for firmware settings. The proposed here interface will be practically impossible to implement for a generic driver like Redfish.
> 
> Thanks for pointing this out, I hadn't really thought about that earlier.
> 
> I do think we have some implementation options for this in future. For example, we can now get a list of all the available BIOS options from Ironic, which we're going to use to implement Bob's proposal [metal3-io/metal3-docs#173](https://github.com/metal3-io/metal3-docs/pull/173). So we could make a list of all the names that e.g. VirtualizationEnabled is known by on hardware platforms that we know about and then compare it to the list of available BIOS settings for the Host to see which one shows up.
> 
> The existence of at least one plausible way of implementing this API makes me inclined to go ahead with this PR, so I am not removing my approval.

But Bob's proposal is mainly about defining a new CR for managing firmware settings (and they could be many), whereas in the current one the public API of BMH is directly modified to address just three of them. I also have some concerns similar to the ones raised by @dtantsur but mostly focusing on the fact that new fields for BMH are going to be defined. 
Even though in general the overall code of the PR looks ok, this particular design decision does not seem to scale well in future (when maybe other settings will be required), as the fields are directly hardcoded (and somehow not fully inline with the general idea of decomposing BMH).

I think that a more general approach could allow folks to focus on the settings they are currently interested in but will not affect too much the scalability.
zaneb: They have different purposes. Bulk set is good for onboarding a bunch of servers from a particular manufacturer. But if you want to e.g. create a Kubernetes node on baremetal with hyperthreading disabled froma pool of hardware, then there should be a vendor-independent way of requesting this in the API.
andfasano: Ok, so it looks like the current approach and the one in #901 could coexists, addressing some of my concerns. AFAICS the current BMH interface will remain vendor-neutral and will be expanded when required/needed.

FWIW, this PR had a pretty long history, and currently it supports the configuration of  the `VirtualizationEnabled`, `SimultaneousMultithreadingEnabled` and `SriovEnabled` settings just for `ilo4/ilo5/irmc`, but I didn't see any mention of that in the documentation. So I'd suggest to fix the documentation as required in a separate PR.

/lgtm
metal3-io-bot: @longkb: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
generate | 55ea09e1d3e68b9e68286c0ea88ff903f8ec0053 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/302/generate/1400841852881473536) | `/test generate`

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
Hellcatlk: Thanks. I will open a new PR to fix the documentation.
/retest
metal3-io-bot: @Hellcatlk: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/metal3-io/baremetal-operator/pull/302#issuecomment-855215874):

>Thanks. I will open a new PR to fix the documentation.
>/retest


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

Patch:
diff --git a/apis/metal3.io/v1alpha1/baremetalhost_types.go b/apis/metal3.io/v1alpha1/baremetalhost_types.go
index 827c732c..0e079cf4 100644
--- a/apis/metal3.io/v1alpha1/baremetalhost_types.go
+++ b/apis/metal3.io/v1alpha1/baremetalhost_types.go
@@ -289,6 +289,24 @@ type RAIDConfig struct {
 	SoftwareRAIDVolumes []SoftwareRAIDVolume `json:"softwareRAIDVolumes,omitempty"`
 }
 
+// FirmwareConfig contains the configuration that you want to configure BIOS settings in Bare metal server
+type FirmwareConfig struct {
+	// Supports the virtualization of platform hardware.
+	// This supports following options: true, false.
+	// +kubebuilder:validation:Enum=true;false
+	VirtualizationEnabled *bool `json:"virtualizationEnabled,omitempty"`
+
+	// Allows a single physical processor core to appear as several logical processors.
+	// This supports following options: true, false.
+	// +kubebuilder:validation:Enum=true;false
+	SimultaneousMultithreadingEnabled *bool `json:"simultaneousMultithreadingEnabled,omitempty"`
+
+	// SR-IOV support enables a hypervisor to create virtual instances of a PCI-express device, potentially increasing performance.
+	// This supports following options: true, false.
+	// +kubebuilder:validation:Enum=true;false
+	SriovEnabled *bool `json:"sriovEnabled,omitempty"`
+}
+
 // BareMetalHostSpec defines the desired state of BareMetalHost
 type BareMetalHostSpec struct {
 	// Important: Run "make generate manifests" to regenerate code
@@ -306,6 +324,9 @@ type BareMetalHostSpec struct {
 	// RAID configuration for bare metal server
 	RAID *RAIDConfig `json:"raid,omitempty"`
 
+	// BIOS configuration for bare metal server
+	Firmware *FirmwareConfig `json:"firmware,omitempty"`
+
 	// What is the name of the hardware profile for this host? It
 	// should only be necessary to set this when inspection cannot
 	// automatically determine the profile.
@@ -716,6 +737,9 @@ type ProvisionStatus struct {
 
 	// The Raid set by the user
 	RAID *RAIDConfig `json:"raid,omitempty"`
+
+	// The Bios set by the user
+	Firmware *FirmwareConfig `json:"firmware,omitempty"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
diff --git a/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go b/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
index 5cb07ddb..8993e77c 100644
--- a/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
@@ -130,6 +130,11 @@ func (in *BareMetalHostSpec) DeepCopyInto(out *BareMetalHostSpec) {
 		*out = new(RAIDConfig)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Firmware != nil {
+		in, out := &in.Firmware, &out.Firmware
+		*out = new(FirmwareConfig)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.RootDeviceHints != nil {
 		in, out := &in.RootDeviceHints, &out.RootDeviceHints
 		*out = new(RootDeviceHints)
@@ -256,6 +261,36 @@ func (in *Firmware) DeepCopy() *Firmware {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FirmwareConfig) DeepCopyInto(out *FirmwareConfig) {
+	*out = *in
+	if in.VirtualizationEnabled != nil {
+		in, out := &in.VirtualizationEnabled, &out.VirtualizationEnabled
+		*out = new(bool)
+		**out = **in
+	}
+	if in.SimultaneousMultithreadingEnabled != nil {
+		in, out := &in.SimultaneousMultithreadingEnabled, &out.SimultaneousMultithreadingEnabled
+		*out = new(bool)
+		**out = **in
+	}
+	if in.SriovEnabled != nil {
+		in, out := &in.SriovEnabled, &out.SriovEnabled
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareConfig.
+func (in *FirmwareConfig) DeepCopy() *FirmwareConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(FirmwareConfig)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *HardwareDetails) DeepCopyInto(out *HardwareDetails) {
 	*out = *in
@@ -421,6 +456,11 @@ func (in *ProvisionStatus) DeepCopyInto(out *ProvisionStatus) {
 		*out = new(RAIDConfig)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Firmware != nil {
+		in, out := &in.Firmware, &out.Firmware
+		*out = new(FirmwareConfig)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProvisionStatus.
diff --git a/config/crd/bases/metal3.io_baremetalhosts.yaml b/config/crd/bases/metal3.io_baremetalhosts.yaml
index ca6938ee..06f22eda 100644
--- a/config/crd/bases/metal3.io_baremetalhosts.yaml
+++ b/config/crd/bases/metal3.io_baremetalhosts.yaml
@@ -132,6 +132,28 @@ spec:
               externallyProvisioned:
                 description: ExternallyProvisioned means something else is managing the image running on the host and the operator should only manage the power status and hardware inventory inspection. If the Image field is filled in, this field is ignored.
                 type: boolean
+              firmware:
+                description: BIOS configuration for bare metal server
+                properties:
+                  simultaneousMultithreadingEnabled:
+                    description: 'Allows a single physical processor core to appear as several logical processors. This supports following options: true, false.'
+                    enum:
+                    - true
+                    - false
+                    type: boolean
+                  sriovEnabled:
+                    description: 'SR-IOV support enables a hypervisor to create virtual instances of a PCI-express device, potentially increasing performance. This supports following options: true, false.'
+                    enum:
+                    - true
+                    - false
+                    type: boolean
+                  virtualizationEnabled:
+                    description: 'Supports the virtualization of platform hardware. This supports following options: true, false.'
+                    enum:
+                    - true
+                    - false
+                    type: boolean
+                type: object
               hardwareProfile:
                 description: What is the name of the hardware profile for this host? It should only be necessary to set this when inspection cannot automatically determine the profile.
                 type: string
@@ -621,6 +643,28 @@ spec:
                     - UEFISecureBoot
                     - legacy
                     type: string
+                  firmware:
+                    description: The Bios set by the user
+                    properties:
+                      simultaneousMultithreadingEnabled:
+                        description: 'Allows a single physical processor core to appear as several logical processors. This supports following options: true, false.'
+                        enum:
+                        - true
+                        - false
+                        type: boolean
+                      sriovEnabled:
+                        description: 'SR-IOV support enables a hypervisor to create virtual instances of a PCI-express device, potentially increasing performance. This supports following options: true, false.'
+                        enum:
+                        - true
+                        - false
+                        type: boolean
+                      virtualizationEnabled:
+                        description: 'Supports the virtualization of platform hardware. This supports following options: true, false.'
+                        enum:
+                        - true
+                        - false
+                        type: boolean
+                    type: object
                   image:
                     description: Image holds the details of the last image successfully provisioned to the host.
                     properties:
diff --git a/config/render/capm3.yaml b/config/render/capm3.yaml
index 51f90b50..589c4170 100644
--- a/config/render/capm3.yaml
+++ b/config/render/capm3.yaml
@@ -130,6 +130,28 @@ spec:
               externallyProvisioned:
                 description: ExternallyProvisioned means something else is managing the image running on the host and the operator should only manage the power status and hardware inventory inspection. If the Image field is filled in, this field is ignored.
                 type: boolean
+              firmware:
+                description: BIOS configuration for bare metal server
+                properties:
+                  simultaneousMultithreadingEnabled:
+                    description: 'Allows a single physical processor core to appear as several logical processors. This supports following options: true, false.'
+                    enum:
+                    - true
+                    - false
+                    type: boolean
+                  sriovEnabled:
+                    description: 'SR-IOV support enables a hypervisor to create virtual instances of a PCI-express device, potentially increasing performance. This supports following options: true, false.'
+                    enum:
+                    - true
+                    - false
+                    type: boolean
+                  virtualizationEnabled:
+                    description: 'Supports the virtualization of platform hardware. This supports following options: true, false.'
+                    enum:
+                    - true
+                    - false
+                    type: boolean
+                type: object
               hardwareProfile:
                 description: What is the name of the hardware profile for this host? It should only be necessary to set this when inspection cannot automatically determine the profile.
                 type: string
@@ -619,6 +641,28 @@ spec:
                     - UEFISecureBoot
                     - legacy
                     type: string
+                  firmware:
+                    description: The Bios set by the user
+                    properties:
+                      simultaneousMultithreadingEnabled:
+                        description: 'Allows a single physical processor core to appear as several logical processors. This supports following options: true, false.'
+                        enum:
+                        - true
+                        - false
+                        type: boolean
+                      sriovEnabled:
+                        description: 'SR-IOV support enables a hypervisor to create virtual instances of a PCI-express device, potentially increasing performance. This supports following options: true, false.'
+                        enum:
+                        - true
+                        - false
+                        type: boolean
+                      virtualizationEnabled:
+                        description: 'Supports the virtualization of platform hardware. This supports following options: true, false.'
+                        enum:
+                        - true
+                        - false
+                        type: boolean
+                    type: object
                   image:
                     description: Image holds the details of the last image successfully provisioned to the host.
                     properties:
diff --git a/controllers/metal3.io/baremetalhost_controller.go b/controllers/metal3.io/baremetalhost_controller.go
index de5c9402..a742b67e 100644
--- a/controllers/metal3.io/baremetalhost_controller.go
+++ b/controllers/metal3.io/baremetalhost_controller.go
@@ -758,6 +758,7 @@ func (r *BareMetalHostReconciler) actionPreparing(prov provisioner.Provisioner,
 	prepareData := provisioner.PrepareData{
 		RAIDConfig:      newStatus.Provisioning.RAID.DeepCopy(),
 		RootDeviceHints: newStatus.Provisioning.RootDeviceHints.DeepCopy(),
+		FirmwareConfig:  newStatus.Provisioning.Firmware.DeepCopy(),
 	}
 	provResult, started, err := prov.Prepare(prepareData,
 		dirty || info.host.Status.ErrorType == metal3v1alpha1.PreparationError)
@@ -859,6 +860,7 @@ func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisione
 func clearHostProvisioningSettings(host *metal3v1alpha1.BareMetalHost) {
 	host.Status.Provisioning.RootDeviceHints = nil
 	host.Status.Provisioning.RAID = nil
+	host.Status.Provisioning.Firmware = nil
 }
 
 func (r *BareMetalHostReconciler) actionDeprovisioning(prov provisioner.Provisioner, info *reconcileInfo) actionResult {
@@ -1115,6 +1117,12 @@ func saveHostProvisioningSettings(host *metal3v1alpha1.BareMetalHost) (dirty boo
 		}
 	}
 
+	// Copy BIOS settings
+	if !reflect.DeepEqual(host.Status.Provisioning.Firmware, host.Spec.Firmware) {
+		host.Status.Provisioning.Firmware = host.Spec.Firmware.DeepCopy()
+		dirty = true
+	}
+
 	return
 }
 
diff --git a/pkg/bmc/access.go b/pkg/bmc/access.go
index 514bcdb0..e12594d2 100644
--- a/pkg/bmc/access.go
+++ b/pkg/bmc/access.go
@@ -7,6 +7,8 @@ import (
 	"strings"
 
 	"github.com/pkg/errors"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 // AccessDetailsFactory describes a callable that returns a new
@@ -62,6 +64,9 @@ type AccessDetails interface {
 
 	// Whether the driver supports changing secure boot state.
 	SupportsSecureBoot() bool
+
+	// Build bios clean steps for ironic
+	BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error)
 }
 
 func getParsedURL(address string) (parsedURL *url.URL, err error) {
diff --git a/pkg/bmc/ibmc.go b/pkg/bmc/ibmc.go
index fe39468e..88701227 100644
--- a/pkg/bmc/ibmc.go
+++ b/pkg/bmc/ibmc.go
@@ -1,8 +1,11 @@
 package bmc
 
 import (
+	"fmt"
 	"net/url"
 	"strings"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -101,3 +104,10 @@ func (a *ibmcAccessDetails) VendorInterface() string {
 func (a *ibmcAccessDetails) SupportsSecureBoot() bool {
 	return false
 }
+
+func (a *ibmcAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig != nil {
+		return nil, fmt.Errorf("firmware settings for %s are not supported", a.Driver())
+	}
+	return nil, nil
+}
diff --git a/pkg/bmc/idrac.go b/pkg/bmc/idrac.go
index 6270b3ee..7aabefb1 100644
--- a/pkg/bmc/idrac.go
+++ b/pkg/bmc/idrac.go
@@ -1,8 +1,11 @@
 package bmc
 
 import (
+	"fmt"
 	"net/url"
 	"strings"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -99,3 +102,10 @@ func (a *iDracAccessDetails) VendorInterface() string {
 func (a *iDracAccessDetails) SupportsSecureBoot() bool {
 	return false
 }
+
+func (a *iDracAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig != nil {
+		return nil, fmt.Errorf("firmware settings for %s are not supported", a.Driver())
+	}
+	return nil, nil
+}
diff --git a/pkg/bmc/idrac_virtualmedia.go b/pkg/bmc/idrac_virtualmedia.go
index ec1afba3..df619131 100644
--- a/pkg/bmc/idrac_virtualmedia.go
+++ b/pkg/bmc/idrac_virtualmedia.go
@@ -1,7 +1,10 @@
 package bmc
 
 import (
+	"fmt"
 	"net/url"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -90,3 +93,10 @@ func (a *redfishiDracVirtualMediaAccessDetails) VendorInterface() string {
 func (a *redfishiDracVirtualMediaAccessDetails) SupportsSecureBoot() bool {
 	return true
 }
+
+func (a *redfishiDracVirtualMediaAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig != nil {
+		return nil, fmt.Errorf("firmware settings for %s are not supported", a.Driver())
+	}
+	return nil, nil
+}
diff --git a/pkg/bmc/ilo4.go b/pkg/bmc/ilo4.go
index 9138c617..662f779e 100644
--- a/pkg/bmc/ilo4.go
+++ b/pkg/bmc/ilo4.go
@@ -4,6 +4,8 @@ package bmc
 
 import (
 	"net/url"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -93,3 +95,52 @@ func (a *iLOAccessDetails) VendorInterface() string {
 func (a *iLOAccessDetails) SupportsSecureBoot() bool {
 	return true
 }
+
+func (a *iLOAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig == nil {
+		return nil, nil
+	}
+
+	var value string
+
+	if firmwareConfig.VirtualizationEnabled != nil {
+		value = "Disabled"
+		if *firmwareConfig.VirtualizationEnabled {
+			value = "Enabled"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "ProcVirtualization",
+				"value": value,
+			},
+		)
+	}
+
+	if firmwareConfig.SimultaneousMultithreadingEnabled != nil {
+		value = "Disabled"
+		if *firmwareConfig.SimultaneousMultithreadingEnabled {
+			value = "Enabled"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "ProcHyperthreading",
+				"value": value,
+			},
+		)
+	}
+
+	if firmwareConfig.SriovEnabled != nil {
+		value = "Disabled"
+		if *firmwareConfig.SriovEnabled {
+			value = "Enabled"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "Sriov",
+				"value": value,
+			},
+		)
+	}
+
+	return
+}
diff --git a/pkg/bmc/ilo5.go b/pkg/bmc/ilo5.go
index ef266c57..821d000e 100644
--- a/pkg/bmc/ilo5.go
+++ b/pkg/bmc/ilo5.go
@@ -4,6 +4,8 @@ package bmc
 
 import (
 	"net/url"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -93,3 +95,52 @@ func (a *iLO5AccessDetails) VendorInterface() string {
 func (a *iLO5AccessDetails) SupportsSecureBoot() bool {
 	return true
 }
+
+func (a *iLO5AccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig == nil {
+		return nil, nil
+	}
+
+	var value string
+
+	if firmwareConfig.VirtualizationEnabled != nil {
+		value = "Disabled"
+		if *firmwareConfig.VirtualizationEnabled {
+			value = "Enabled"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "ProcVirtualization",
+				"value": value,
+			},
+		)
+	}
+
+	if firmwareConfig.SimultaneousMultithreadingEnabled != nil {
+		value = "Disabled"
+		if *firmwareConfig.SimultaneousMultithreadingEnabled {
+			value = "Enabled"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "ProcHyperthreading",
+				"value": value,
+			},
+		)
+	}
+
+	if firmwareConfig.SriovEnabled != nil {
+		value = "Disabled"
+		if *firmwareConfig.SriovEnabled {
+			value = "Enabled"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "Sriov",
+				"value": value,
+			},
+		)
+	}
+
+	return
+}
diff --git a/pkg/bmc/ipmi.go b/pkg/bmc/ipmi.go
index 8c13c9e4..ad8ce1e7 100644
--- a/pkg/bmc/ipmi.go
+++ b/pkg/bmc/ipmi.go
@@ -1,7 +1,10 @@
 package bmc
 
 import (
+	"fmt"
 	"net/url"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -96,3 +99,10 @@ func (a *ipmiAccessDetails) VendorInterface() string {
 func (a *ipmiAccessDetails) SupportsSecureBoot() bool {
 	return false
 }
+
+func (a *ipmiAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig != nil {
+		return nil, fmt.Errorf("firmware settings for %s are not supported", a.Driver())
+	}
+	return nil, nil
+}
diff --git a/pkg/bmc/irmc.go b/pkg/bmc/irmc.go
index 8cf78939..f6c45c9b 100644
--- a/pkg/bmc/irmc.go
+++ b/pkg/bmc/irmc.go
@@ -2,6 +2,8 @@ package bmc
 
 import (
 	"net/url"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -91,3 +93,56 @@ func (a *iRMCAccessDetails) VendorInterface() string {
 func (a *iRMCAccessDetails) SupportsSecureBoot() bool {
 	return true
 }
+
+func (a *iRMCAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig == nil {
+		return nil, nil
+	}
+
+	if firmwareConfig == nil {
+		return nil, nil
+	}
+
+	var value string
+
+	if firmwareConfig.VirtualizationEnabled != nil {
+		value = "False"
+		if *firmwareConfig.VirtualizationEnabled {
+			value = "True"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "cpu_vt_enabled",
+				"value": value,
+			},
+		)
+	}
+
+	if firmwareConfig.SimultaneousMultithreadingEnabled != nil {
+		value = "False"
+		if *firmwareConfig.SimultaneousMultithreadingEnabled {
+			value = "True"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "hyper_threading_enabled",
+				"value": value,
+			},
+		)
+	}
+
+	if firmwareConfig.SriovEnabled != nil {
+		value = "False"
+		if *firmwareConfig.SriovEnabled {
+			value = "True"
+		}
+		settings = append(settings,
+			map[string]string{
+				"name":  "single_root_io_virtualization_support_enabled",
+				"value": value,
+			},
+		)
+	}
+
+	return
+}
diff --git a/pkg/bmc/redfish.go b/pkg/bmc/redfish.go
index 7f9c6526..e26189bf 100644
--- a/pkg/bmc/redfish.go
+++ b/pkg/bmc/redfish.go
@@ -1,8 +1,11 @@
 package bmc
 
 import (
+	"fmt"
 	"net/url"
 	"strings"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -122,6 +125,13 @@ func (a *redfishAccessDetails) SupportsSecureBoot() bool {
 	return true
 }
 
+func (a *redfishAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig != nil {
+		return nil, fmt.Errorf("firmware settings for %s are not supported", a.Driver())
+	}
+	return nil, nil
+}
+
 // iDrac Redfish Overrides
 
 func (a *redfishiDracAccessDetails) Driver() string {
@@ -147,3 +157,10 @@ func (a *redfishiDracAccessDetails) RAIDInterface() string {
 func (a *redfishiDracAccessDetails) VendorInterface() string {
 	return "no-vendor"
 }
+
+func (a *redfishiDracAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig != nil {
+		return nil, fmt.Errorf("firmware settings for %s are not supported", a.Driver())
+	}
+	return nil, nil
+}
diff --git a/pkg/bmc/redfish_virtualmedia.go b/pkg/bmc/redfish_virtualmedia.go
index 88e76ac4..10766826 100644
--- a/pkg/bmc/redfish_virtualmedia.go
+++ b/pkg/bmc/redfish_virtualmedia.go
@@ -1,7 +1,10 @@
 package bmc
 
 import (
+	"fmt"
 	"net/url"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -89,3 +92,10 @@ func (a *redfishVirtualMediaAccessDetails) VendorInterface() string {
 func (a *redfishVirtualMediaAccessDetails) SupportsSecureBoot() bool {
 	return true
 }
+
+func (a *redfishVirtualMediaAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	if firmwareConfig != nil {
+		return nil, fmt.Errorf("firmware settings for %s are not supported", a.Driver())
+	}
+	return nil, nil
+}
diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go
index 801c5c19..c5ce111a 100644
--- a/pkg/provisioner/ironic/ironic.go
+++ b/pkg/provisioner/ironic/ironic.go
@@ -1102,6 +1102,24 @@ func (p *ironicProvisioner) buildManualCleaningSteps(bmcAccess bmc.AccessDetails
 		return nil, fmt.Errorf("RAID settings are defined, but the node's driver %s does not support RAID", bmcAccess.Driver())
 	}
 
+	// Build bios clean steps
+	settings, err := bmcAccess.BuildBIOSSettings(data.FirmwareConfig)
+	if err != nil {
+		return nil, err
+	}
+	if len(settings) != 0 {
+		cleanSteps = append(
+			cleanSteps,
+			nodes.CleanStep{
+				Interface: "bios",
+				Step:      "apply_configuration",
+				Args: map[string]interface{}{
+					"settings": settings,
+				},
+			},
+		)
+	}
+
 	// TODO: Add manual cleaning steps for host configuration
 
 	return
diff --git a/pkg/provisioner/provisioner.go b/pkg/provisioner/provisioner.go
index 73625cdb..4765cada 100644
--- a/pkg/provisioner/provisioner.go
+++ b/pkg/provisioner/provisioner.go
@@ -75,6 +75,7 @@ type InspectData struct {
 type PrepareData struct {
 	RAIDConfig      *metal3v1alpha1.RAIDConfig
 	RootDeviceHints *metal3v1alpha1.RootDeviceHints
+	FirmwareConfig  *metal3v1alpha1.FirmwareConfig
 }
 
 type ProvisionData struct {


Test patch:
diff --git a/pkg/bmc/access_test.go b/pkg/bmc/access_test.go
index 3c15e667..2cc06102 100644
--- a/pkg/bmc/access_test.go
+++ b/pkg/bmc/access_test.go
@@ -1,10 +1,13 @@
 package bmc
 
 import (
+	"reflect"
 	"testing"
 
 	logf "sigs.k8s.io/controller-runtime/pkg/log"
 	logz "sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 )
 
 func init() {
@@ -1121,3 +1124,128 @@ func TestUnknownType(t *testing.T) {
 		t.Fatalf("unexpected parse success")
 	}
 }
+
+func TestBuildBIOSCleanSteps(t *testing.T) {
+	var True bool = true
+	var False bool = false
+
+	cases := []struct {
+		name          string
+		address       string
+		firmware      *metal3v1alpha1.FirmwareConfig
+		expected      []map[string]string
+		expectedError bool
+	}{
+		// ilo4
+		{
+			name:    "ilo4",
+			address: "ilo4://192.168.122.1",
+			firmware: &metal3v1alpha1.FirmwareConfig{
+				VirtualizationEnabled:             &True,
+				SimultaneousMultithreadingEnabled: &False,
+			},
+			expected: []map[string]string{
+				{
+					"name":  "ProcVirtualization",
+					"value": "Enabled",
+				},
+				{
+					"name":  "ProcHyperthreading",
+					"value": "Disabled",
+				},
+			},
+		},
+		{
+			name:     "ilo4, firmware is nil",
+			address:  "ilo4://192.168.122.1",
+			firmware: nil,
+			expected: nil,
+		},
+		{
+			name:     "ilo4, firmware is empty",
+			address:  "ilo4://192.168.122.1",
+			firmware: &metal3v1alpha1.FirmwareConfig{},
+			expected: nil,
+		},
+		// ilo5
+		{
+			name:    "ilo5",
+			address: "ilo5://192.168.122.1",
+			firmware: &metal3v1alpha1.FirmwareConfig{
+				VirtualizationEnabled:             &True,
+				SimultaneousMultithreadingEnabled: &False,
+			},
+			expected: []map[string]string{
+				{
+					"name":  "ProcVirtualization",
+					"value": "Enabled",
+				},
+				{
+					"name":  "ProcHyperthreading",
+					"value": "Disabled",
+				},
+			},
+		},
+		{
+			name:     "ilo5, firmware is nil",
+			address:  "ilo5://192.168.122.1",
+			firmware: nil,
+			expected: nil,
+		},
+		{
+			name:     "ilo5, firmware is empty",
+			address:  "ilo5://192.168.122.1",
+			firmware: &metal3v1alpha1.FirmwareConfig{},
+			expected: nil,
+		},
+		// irmc
+		{
+			name:    "irmc",
+			address: "irmc://192.168.122.1",
+			firmware: &metal3v1alpha1.FirmwareConfig{
+				VirtualizationEnabled:             &True,
+				SimultaneousMultithreadingEnabled: &False,
+			},
+			expected: []map[string]string{
+				{
+					"name":  "cpu_vt_enabled",
+					"value": "True",
+				},
+				{
+					"name":  "hyper_threading_enabled",
+					"value": "False",
+				},
+			},
+		},
+		{
+			name:     "irmc, firmware is nil",
+			address:  "irmc://192.168.122.1",
+			firmware: nil,
+			expected: nil,
+		},
+		{
+			name:     "irmc, firmware is empty",
+			address:  "irmc://192.168.122.1",
+			firmware: &metal3v1alpha1.FirmwareConfig{},
+			expected: nil,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			acc, err := NewAccessDetails(c.address, false)
+			if err != nil {
+				t.Fatalf("new AccessDetails failed: %v", err)
+			}
+
+			settings, err := acc.BuildBIOSSettings(c.firmware)
+			if (err != nil) != c.expectedError {
+				t.Fatalf("got unexpected error: %v", err)
+			}
+
+			if !reflect.DeepEqual(c.expected, settings) {
+				t.Errorf("expected settings: %v, got: %v", c.expected, settings)
+			}
+		})
+	}
+}
diff --git a/pkg/provisioner/ironic/testbmc/testbmc.go b/pkg/provisioner/ironic/testbmc/testbmc.go
index 5fab5f55..1cb682bb 100644
--- a/pkg/provisioner/ironic/testbmc/testbmc.go
+++ b/pkg/provisioner/ironic/testbmc/testbmc.go
@@ -3,6 +3,7 @@ package testbmc
 import (
 	"net/url"
 
+	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
 )
 
@@ -85,3 +86,7 @@ func (a *testAccessDetails) VendorInterface() string {
 func (a *testAccessDetails) SupportsSecureBoot() bool {
 	return false
 }
+
+func (a *testAccessDetails) BuildBIOSSettings(firmwareConfig *metal3v1alpha1.FirmwareConfig) (settings []map[string]string, err error) {
+	return nil, nil
+}