@@ -14,6 +14,7 @@
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
+#include <linux/ratelimit.h>
#include <linux/set_memory.h>
#include <linux/fs.h>
#include <crypto/aead.h>
@@ -48,12 +49,22 @@ struct snp_guest_dev {
struct snp_req_data input;
u32 *os_area_msg_seqno;
u8 *vmpck;
+
+ struct ratelimit_state rs;
};
static u32 vmpck_id;
module_param(vmpck_id, uint, 0444);
MODULE_PARM_DESC(vmpck_id, "The VMPCK ID to use when communicating with the PSP.");
+static int rate_s = 1;
+module_param(rate_s, int, 0444);
+MODULE_PARM_DESC(rate_s, "The rate limit interval in seconds to limit requests to.");
+
+static int rate_burst = 2;
+module_param(rate_burst, int, 0444);
+MODULE_PARM_DESC(rate_burst, "The rate limit burst amount to limit requests to.");
+
/* Mutex to serialize the shared buffer access and command handling. */
static DEFINE_MUTEX(snp_cmd_mutex);
@@ -324,6 +335,7 @@ static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code, in
u8 type, void *req_buf, size_t req_sz, void *resp_buf,
u32 resp_sz, __u64 *exitinfo2)
{
+ unsigned int vmm_err;
u64 seqno;
int rc;
@@ -339,16 +351,35 @@ static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code, in
if (rc)
return rc;
+retry:
+ /*
+ * Rate limit commands internally since the host can also throttle, and
+ * we don't want to create a tight request spin that could end up
+ * getting this VM throttled more heavily.
+ */
+ if (!__ratelimit(&snp_dev->rs)) {
+ schedule_timeout_interruptible((rate_s * HZ) / 2);
+ goto retry;
+ }
/* Call firmware to process the request */
rc = snp_issue_guest_request(exit_code, &snp_dev->input, exitinfo2);
+ vmm_err = *exitinfo2 >> SNP_GUEST_VMM_ERR_SHIFT;
+ /*
+ * The host may return EBUSY if the request has been throttled.
+ * We retry in the driver to avoid returning and reusing the message
+ * sequence number on a different message.
+ */
+ if (vmm_err == SNP_GUEST_VMM_ERR_BUSY)
+ goto retry;
+
/*
* If the extended guest request fails due to having to small of a
* certificate data buffer retry the same guest request without the
* extended data request.
*/
if (exit_code == SVM_VMGEXIT_EXT_GUEST_REQUEST &&
- *exitinfo2 == SNP_GUEST_REQ_INVALID_LEN) {
+ vmm_err == SNP_GUEST_VMM_ERR_INVALID_LEN) {
const unsigned int certs_npages = snp_dev->input.data_npages;
exit_code = SVM_VMGEXIT_GUEST_REQUEST;
@@ -360,8 +391,8 @@ static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code, in
if (rc) {
dev_alert(snp_dev->dev,
- "Detected error from ASP request. rc: %d, fw_err: %llu\n",
- rc, *fw_err);
+ "Detected error from ASP request. rc: %d, exitinfo2: %llu\n",
+ rc, *exitinfo2);
goto disable_vmpck;
}
@@ -410,7 +441,7 @@ static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_io
rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version,
SNP_MSG_REPORT_REQ, &req, sizeof(req), resp->data,
- resp_len, &arg->fw_err);
+ resp_len, &arg->exitinfo2);
if (rc)
goto e_free;
@@ -450,7 +481,7 @@ static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_reque
rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version,
SNP_MSG_KEY_REQ, &req, sizeof(req), buf, resp_len,
- &arg->fw_err);
+ &arg->exitinfo2);
if (rc)
return rc;
@@ -512,10 +543,10 @@ static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_reques
snp_dev->input.data_npages = npages;
ret = handle_guest_request(snp_dev, SVM_VMGEXIT_EXT_GUEST_REQUEST, arg->msg_version,
SNP_MSG_REPORT_REQ, &req.data,
- sizeof(req.data), resp->data, resp_len, &arg->fw_err);
+ sizeof(req.data), resp->data, resp_len, &arg->exitinfo2);
/* If certs length is invalid then copy the returned length */
- if (arg->fw_err == SNP_GUEST_REQ_INVALID_LEN) {
+ if (arg->vmm_error == SNP_GUEST_VMM_ERR_INVALID_LEN) {
req.certs_len = snp_dev->input.data_npages << PAGE_SHIFT;
if (copy_to_user((void __user *)arg->req_data, &req, sizeof(req)))
@@ -550,7 +581,7 @@ static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long
if (copy_from_user(&input, argp, sizeof(input)))
return -EFAULT;
- input.fw_err = 0xff;
+ input.exitinfo2 = SEV_RET_NO_FW_CALL;
/* Message version must be non-zero */
if (!input.msg_version)
@@ -581,7 +612,7 @@ static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long
mutex_unlock(&snp_cmd_mutex);
- if (input.fw_err && copy_to_user(argp, &input, sizeof(input)))
+ if (input.exitinfo2 && copy_to_user(argp, &input, sizeof(input)))
return -EFAULT;
return ret;
@@ -731,6 +762,8 @@ static int __init sev_guest_probe(struct platform_device *pdev)
if (ret)
goto e_free_cert_data;
+ ratelimit_state_init(&snp_dev->rs, rate_s * HZ, rate_burst);
+
dev_info(dev, "Initialized SEV guest driver (using vmpck_id %d)\n", vmpck_id);
return 0;
@@ -52,8 +52,15 @@ struct snp_guest_request_ioctl {
__u64 req_data;
__u64 resp_data;
- /* firmware error code on failure (see psp-sev.h) */
- __u64 fw_err;
+ /* bits[63:32]: VMM error code, bits[31:0] firmware error code (see psp-sev.h) */
+ union {
+ __u64 exitinfo2;
+ __u64 fw_err; /* Name deprecated in favor of others */
+ struct {
+ __u32 fw_error;
+ __u32 vmm_error;
+ };
+ };
};
struct snp_ext_report_req {
@@ -77,4 +84,11 @@ struct snp_ext_report_req {
/* Get SNP extended report as defined in the GHCB specification version 2. */
#define SNP_GET_EXT_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x2, struct snp_guest_request_ioctl)
+/* Guest message request EXIT_INFO_2 constants */
+#define SNP_GUEST_FW_ERR_MASK GENMASK_ULL(31, 0)
+#define SNP_GUEST_VMM_ERR_SHIFT 32
+
+#define SNP_GUEST_VMM_ERR_INVALID_LEN 1
+#define SNP_GUEST_VMM_ERR_BUSY 2
+
#endif /* __UAPI_LINUX_SEV_GUEST_H_ */