Commit 9037246b authored by Bhawanpreet Lakha's avatar Bhawanpreet Lakha Committed by Alex Deucher

drm/amd/display: Add sysfs interface for set/get srm

[Why]
PSP doesn't have the ability to store SRM in a non-volatile memory.  And since
the kernel cannot write to the storage directly, we need usermode to facilitate
this

As per spec the SRM needs to be persistent so this interface is to be
called by the usermode anytime the system goes down/powers on

*boot/resume: load from storage
*shutdown/suspend: save to storage

[How]
Provide a sysfs interface so that the usermode can set/get srm at the right times

save to storage: call "cat /sys/class/drm/card0/device/hdcp_srm > file" after boot and resume
	-driver calls psp_get_srm() to get the stored srm and outputs it

load from storage: call "cat file > /sys/class/drm/card0/device/hdcp_srm" before shutdown and suspend
	-driver reads the file from sysfs and calls psp_set_srm() to send the SRM to PSP

v2:
-update commit description
-add comment about sysfs file handling in the code

v3:
- squash in use after free fix (Dan Carpenter)
Signed-off-by: default avatarBhawanpreet Lakha <Bhawanpreet.Lakha@amd.com>
Reviewed-by: default avatarRodrigo Siqueira <Rodrigo.Siqueira@amd.com>
Reviewed-by: default avatarHarry Wentland <harry.wentland@amd.com>
Signed-off-by: default avatarAlex Deucher <alexander.deucher@amd.com>
parent 79aad7bd
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
#include "amdgpu_dm.h" #include "amdgpu_dm.h"
#include "dm_helpers.h" #include "dm_helpers.h"
#include <drm/drm_hdcp.h> #include <drm/drm_hdcp.h>
#include "hdcp_psp.h"
static bool static bool
lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size) lp_write_i2c(void *handle, uint32_t address, const uint8_t *data, uint32_t size)
...@@ -67,6 +69,16 @@ lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size) ...@@ -67,6 +69,16 @@ lp_read_dpcd(void *handle, uint32_t address, uint8_t *data, uint32_t size)
return dm_helpers_dp_read_dpcd(link->ctx, link, address, data, size); return dm_helpers_dp_read_dpcd(link->ctx, link, address, data, size);
} }
static uint8_t *psp_get_srm(struct psp_context *psp, uint32_t *srm_version, uint32_t *srm_size)
{
return NULL;
}
static int psp_set_srm(struct psp_context *psp, uint8_t *srm, uint32_t srm_size, uint32_t *srm_version)
{
return 0;
}
static void process_output(struct hdcp_workqueue *hdcp_work) static void process_output(struct hdcp_workqueue *hdcp_work)
{ {
struct mod_hdcp_output output = hdcp_work->output; struct mod_hdcp_output output = hdcp_work->output;
...@@ -88,6 +100,18 @@ static void process_output(struct hdcp_workqueue *hdcp_work) ...@@ -88,6 +100,18 @@ static void process_output(struct hdcp_workqueue *hdcp_work)
schedule_delayed_work(&hdcp_work->property_validate_dwork, msecs_to_jiffies(0)); schedule_delayed_work(&hdcp_work->property_validate_dwork, msecs_to_jiffies(0));
} }
static void link_lock(struct hdcp_workqueue *work, bool lock)
{
int i = 0;
for (i = 0; i < work->max_link; i++) {
if (lock)
mutex_lock(&work[i].mutex);
else
mutex_unlock(&work[i].mutex);
}
}
void hdcp_update_display(struct hdcp_workqueue *hdcp_work, void hdcp_update_display(struct hdcp_workqueue *hdcp_work,
unsigned int link_index, unsigned int link_index,
struct amdgpu_dm_connector *aconnector, struct amdgpu_dm_connector *aconnector,
...@@ -301,8 +325,9 @@ void hdcp_destroy(struct hdcp_workqueue *hdcp_work) ...@@ -301,8 +325,9 @@ void hdcp_destroy(struct hdcp_workqueue *hdcp_work)
cancel_delayed_work_sync(&hdcp_work[i].watchdog_timer_dwork); cancel_delayed_work_sync(&hdcp_work[i].watchdog_timer_dwork);
} }
kfree(hdcp_work->srm);
kfree(hdcp_work->srm_temp);
kfree(hdcp_work); kfree(hdcp_work);
} }
static void update_config(void *handle, struct cp_psp_stream_config *config) static void update_config(void *handle, struct cp_psp_stream_config *config)
...@@ -338,20 +363,163 @@ static void update_config(void *handle, struct cp_psp_stream_config *config) ...@@ -338,20 +363,163 @@ static void update_config(void *handle, struct cp_psp_stream_config *config)
hdcp_update_display(hdcp_work, link_index, aconnector, DRM_MODE_HDCP_CONTENT_TYPE0, false); hdcp_update_display(hdcp_work, link_index, aconnector, DRM_MODE_HDCP_CONTENT_TYPE0, false);
} }
/* NOTE: From the usermodes prospective you only need to call write *ONCE*, the kernel
* will automatically call once or twice depending on the size
*
* call: "cat file > /sys/class/drm/card0/device/hdcp_srm" from usermode no matter what the size is
*
* The kernel can only send PAGE_SIZE at once and since MAX_SRM_FILE(5120) > PAGE_SIZE(4096),
* srm_data_write can be called multiple times.
*
* sysfs interface doesn't tell us the size we will get so we are sending partial SRMs to psp and on
* the last call we will send the full SRM. PSP will fail on every call before the last.
*
* This means we don't know if the SRM is good until the last call. And because of this limitation we
* cannot throw errors early as it will stop the kernel from writing to sysfs
*
* Example 1:
* Good SRM size = 5096
* first call to write 4096 -> PSP fails
* Second call to write 1000 -> PSP Pass -> SRM is set
*
* Example 2:
* Bad SRM size = 4096
* first call to write 4096 -> PSP fails (This is the same as above, but we don't know if this
* is the last call)
*
* Solution?:
* 1: Parse the SRM? -> It is signed so we don't know the EOF
* 2: We can have another sysfs that passes the size before calling set. -> simpler solution
* below
*
* Easy Solution:
* Always call get after Set to verify if set was successful.
* +----------------------+
* | Why it works: |
* +----------------------+
* PSP will only update its srm if its older than the one we are trying to load.
* Always do set first than get.
* -if we try to "1. SET" a older version PSP will reject it and we can "2. GET" the newer
* version and save it
*
* -if we try to "1. SET" a newer version PSP will accept it and we can "2. GET" the
* same(newer) version back and save it
*
* -if we try to "1. SET" a newer version and PSP rejects it. That means the format is
* incorrect/corrupted and we should correct our SRM by getting it from PSP
*/
static ssize_t srm_data_write(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buffer,
loff_t pos, size_t count)
{
struct hdcp_workqueue *work;
uint32_t srm_version = 0;
work = container_of(bin_attr, struct hdcp_workqueue, attr);
link_lock(work, true);
memcpy(work->srm_temp + pos, buffer, count);
if (!psp_set_srm(work->hdcp.config.psp.handle, work->srm_temp, pos + count, &srm_version)) {
DRM_DEBUG_DRIVER("HDCP SRM SET version 0x%X", srm_version);
memcpy(work->srm, work->srm_temp, pos + count);
work->srm_size = pos + count;
work->srm_version = srm_version;
}
link_lock(work, false);
return count;
}
static ssize_t srm_data_read(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buffer,
loff_t pos, size_t count)
{
struct hdcp_workqueue *work;
uint8_t *srm = NULL;
uint32_t srm_version;
uint32_t srm_size;
size_t ret = count;
work = container_of(bin_attr, struct hdcp_workqueue, attr);
link_lock(work, true);
srm = psp_get_srm(work->hdcp.config.psp.handle, &srm_version, &srm_size);
if (!srm)
return -EINVAL;
if (pos >= srm_size)
ret = 0;
if (srm_size - pos < count) {
memcpy(buffer, srm + pos, srm_size - pos);
ret = srm_size - pos;
goto ret;
}
memcpy(buffer, srm + pos, count);
ret:
link_lock(work, false);
return ret;
}
/* From the hdcp spec (5.Renewability) SRM needs to be stored in a non-volatile memory.
*
* For example,
* if Application "A" sets the SRM (ver 2) and we reboot/suspend and later when Application "B"
* needs to use HDCP, the version in PSP should be SRM(ver 2). So SRM should be persistent
* across boot/reboots/suspend/resume/shutdown
*
* Currently when the system goes down (suspend/shutdown) the SRM is cleared from PSP. For HDCP we need
* to make the SRM persistent.
*
* -PSP owns the checking of SRM but doesn't have the ability to store it in a non-volatile memory.
* -The kernel cannot write to the file systems.
* -So we need usermode to do this for us, which is why an interface for usermode is needed
*
*
*
* Usermode can read/write to/from PSP using the sysfs interface
* For example:
* to save SRM from PSP to storage : cat /sys/class/drm/card0/device/hdcp_srm > srmfile
* to load from storage to PSP: cat srmfile > /sys/class/drm/card0/device/hdcp_srm
*/
static const struct bin_attribute data_attr = {
.attr = {.name = "hdcp_srm", .mode = 0664},
.size = PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, /* Limit SRM size */
.write = srm_data_write,
.read = srm_data_read,
};
struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct cp_psp *cp_psp, struct dc *dc) struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct cp_psp *cp_psp, struct dc *dc)
{ {
int max_caps = dc->caps.max_links; int max_caps = dc->caps.max_links;
struct hdcp_workqueue *hdcp_work = kzalloc(max_caps*sizeof(*hdcp_work), GFP_KERNEL); struct hdcp_workqueue *hdcp_work;
int i = 0; int i = 0;
hdcp_work = kcalloc(max_caps, sizeof(*hdcp_work), GFP_KERNEL);
if (hdcp_work == NULL) if (hdcp_work == NULL)
return NULL;
hdcp_work->srm = kcalloc(PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, sizeof(*hdcp_work->srm), GFP_KERNEL);
if (hdcp_work->srm == NULL)
goto fail_alloc_context;
hdcp_work->srm_temp = kcalloc(PSP_HDCP_SRM_FIRST_GEN_MAX_SIZE, sizeof(*hdcp_work->srm_temp), GFP_KERNEL);
if (hdcp_work->srm_temp == NULL)
goto fail_alloc_context; goto fail_alloc_context;
hdcp_work->max_link = max_caps; hdcp_work->max_link = max_caps;
for (i = 0; i < max_caps; i++) { for (i = 0; i < max_caps; i++) {
mutex_init(&hdcp_work[i].mutex); mutex_init(&hdcp_work[i].mutex);
INIT_WORK(&hdcp_work[i].cpirq_work, event_cpirq); INIT_WORK(&hdcp_work[i].cpirq_work, event_cpirq);
...@@ -371,9 +539,17 @@ struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct ...@@ -371,9 +539,17 @@ struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct
cp_psp->funcs.update_stream_config = update_config; cp_psp->funcs.update_stream_config = update_config;
cp_psp->handle = hdcp_work; cp_psp->handle = hdcp_work;
/* File created at /sys/class/drm/card0/device/hdcp_srm*/
hdcp_work[0].attr = data_attr;
if (sysfs_create_bin_file(&adev->dev->kobj, &hdcp_work[0].attr))
DRM_WARN("Failed to create device file hdcp_srm");
return hdcp_work; return hdcp_work;
fail_alloc_context: fail_alloc_context:
kfree(hdcp_work->srm);
kfree(hdcp_work->srm_temp);
kfree(hdcp_work); kfree(hdcp_work);
return NULL; return NULL;
......
...@@ -53,6 +53,12 @@ struct hdcp_workqueue { ...@@ -53,6 +53,12 @@ struct hdcp_workqueue {
enum mod_hdcp_encryption_status encryption_status; enum mod_hdcp_encryption_status encryption_status;
uint8_t max_link; uint8_t max_link;
uint8_t *srm;
uint8_t *srm_temp;
uint32_t srm_version;
uint32_t srm_size;
struct bin_attribute attr;
}; };
void hdcp_update_display(struct hdcp_workqueue *hdcp_work, void hdcp_update_display(struct hdcp_workqueue *hdcp_work,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment