Message ID | 20210915203834.1439-5-sean@poorly.run (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | drm/hdcp: Pull HDCP auth/exchange/check into helpers | expand |
Hi Sean, url: https://github.com/0day-ci/linux/commits/Sean-Paul/drm-hdcp-Pull-HDCP-auth-exchange-check-into-helpers/20210916-044145 base: git://anongit.freedesktop.org/drm-intel for-linux-next config: x86_64-randconfig-m001-20210916 (attached as .config) compiler: gcc-9 (Debian 9.3.0-22) 9.3.0 If you fix the issue, kindly add following tag as appropriate Reported-by: kernel test robot <lkp@intel.com> Reported-by: Dan Carpenter <dan.carpenter@oracle.com> New smatch warnings: drivers/gpu/drm/drm_hdcp.c:1208 drm_hdcp_helper_enable_hdcp() error: uninitialized symbol 'check_link_interval'. Old smatch warnings: drivers/gpu/drm/drm_hdcp.c:514 drm_hdcp_atomic_check() warn: inconsistent indenting vim +/check_link_interval +1208 drivers/gpu/drm/drm_hdcp.c cbc5065be3a652f Sean Paul 2021-09-15 1127 static int drm_hdcp_helper_enable_hdcp(struct drm_hdcp_helper_data *data, cbc5065be3a652f Sean Paul 2021-09-15 1128 struct drm_atomic_state *state, cbc5065be3a652f Sean Paul 2021-09-15 1129 struct mutex *driver_mutex) cbc5065be3a652f Sean Paul 2021-09-15 1130 { cbc5065be3a652f Sean Paul 2021-09-15 1131 struct drm_connector *connector = data->connector; cbc5065be3a652f Sean Paul 2021-09-15 1132 struct drm_connector_state *conn_state; cbc5065be3a652f Sean Paul 2021-09-15 1133 struct drm_device *dev = connector->dev; cbc5065be3a652f Sean Paul 2021-09-15 1134 unsigned long check_link_interval; ^^^^^^^^^^^^^^^^^^^ cbc5065be3a652f Sean Paul 2021-09-15 1135 bool capable; cbc5065be3a652f Sean Paul 2021-09-15 1136 int ret = 0; cbc5065be3a652f Sean Paul 2021-09-15 1137 cbc5065be3a652f Sean Paul 2021-09-15 1138 conn_state = drm_atomic_get_new_connector_state(state, connector); cbc5065be3a652f Sean Paul 2021-09-15 1139 cbc5065be3a652f Sean Paul 2021-09-15 1140 mutex_lock(&data->mutex); cbc5065be3a652f Sean Paul 2021-09-15 1141 cbc5065be3a652f Sean Paul 2021-09-15 1142 if (data->value == DRM_MODE_CONTENT_PROTECTION_ENABLED) { cbc5065be3a652f Sean Paul 2021-09-15 1143 drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_ENABLED, cbc5065be3a652f Sean Paul 2021-09-15 1144 true); cbc5065be3a652f Sean Paul 2021-09-15 1145 goto out_data_mutex; cbc5065be3a652f Sean Paul 2021-09-15 1146 } cbc5065be3a652f Sean Paul 2021-09-15 1147 cbc5065be3a652f Sean Paul 2021-09-15 1148 drm_WARN_ON(dev, data->driver_mutex != NULL); cbc5065be3a652f Sean Paul 2021-09-15 1149 data->driver_mutex = driver_mutex; cbc5065be3a652f Sean Paul 2021-09-15 1150 cbc5065be3a652f Sean Paul 2021-09-15 1151 drm_hdcp_helper_driver_lock(data); cbc5065be3a652f Sean Paul 2021-09-15 1152 cbc5065be3a652f Sean Paul 2021-09-15 1153 if (data->funcs->setup) { cbc5065be3a652f Sean Paul 2021-09-15 1154 ret = data->funcs->setup(connector, state); cbc5065be3a652f Sean Paul 2021-09-15 1155 if (ret) { cbc5065be3a652f Sean Paul 2021-09-15 1156 drm_err(dev, "Failed to setup HDCP %d\n", ret); cbc5065be3a652f Sean Paul 2021-09-15 1157 goto out; cbc5065be3a652f Sean Paul 2021-09-15 1158 } cbc5065be3a652f Sean Paul 2021-09-15 1159 } cbc5065be3a652f Sean Paul 2021-09-15 1160 cbc5065be3a652f Sean Paul 2021-09-15 1161 if (!data->funcs->are_keys_valid || cbc5065be3a652f Sean Paul 2021-09-15 1162 !data->funcs->are_keys_valid(connector)) { cbc5065be3a652f Sean Paul 2021-09-15 1163 if (data->funcs->load_keys) { cbc5065be3a652f Sean Paul 2021-09-15 1164 ret = data->funcs->load_keys(connector); cbc5065be3a652f Sean Paul 2021-09-15 1165 if (ret) { cbc5065be3a652f Sean Paul 2021-09-15 1166 drm_err(dev, "Failed to load HDCP keys %d\n", ret); cbc5065be3a652f Sean Paul 2021-09-15 1167 goto out; cbc5065be3a652f Sean Paul 2021-09-15 1168 } cbc5065be3a652f Sean Paul 2021-09-15 1169 } cbc5065be3a652f Sean Paul 2021-09-15 1170 } cbc5065be3a652f Sean Paul 2021-09-15 1171 cbc5065be3a652f Sean Paul 2021-09-15 1172 /* cbc5065be3a652f Sean Paul 2021-09-15 1173 * Considering that HDCP2.2 is more secure than HDCP1.4, If the setup cbc5065be3a652f Sean Paul 2021-09-15 1174 * is capable of HDCP2.2, it is preferred to use HDCP2.2. cbc5065be3a652f Sean Paul 2021-09-15 1175 */ cbc5065be3a652f Sean Paul 2021-09-15 1176 ret = data->funcs->hdcp2_capable(connector, &capable); cbc5065be3a652f Sean Paul 2021-09-15 1177 if (ret) { cbc5065be3a652f Sean Paul 2021-09-15 1178 drm_err(dev, "HDCP 2.x capability check failed %d\n", ret); cbc5065be3a652f Sean Paul 2021-09-15 1179 goto out; cbc5065be3a652f Sean Paul 2021-09-15 1180 } cbc5065be3a652f Sean Paul 2021-09-15 1181 if (capable) { cbc5065be3a652f Sean Paul 2021-09-15 1182 data->enabled_type = DRM_MODE_HDCP_CONTENT_TYPE1; cbc5065be3a652f Sean Paul 2021-09-15 1183 ret = data->funcs->hdcp2_enable(connector); cbc5065be3a652f Sean Paul 2021-09-15 1184 if (!ret) { cbc5065be3a652f Sean Paul 2021-09-15 1185 check_link_interval = DRM_HDCP2_CHECK_PERIOD_MS; cbc5065be3a652f Sean Paul 2021-09-15 1186 goto out; cbc5065be3a652f Sean Paul 2021-09-15 1187 } cbc5065be3a652f Sean Paul 2021-09-15 1188 } cbc5065be3a652f Sean Paul 2021-09-15 1189 cbc5065be3a652f Sean Paul 2021-09-15 1190 /* cbc5065be3a652f Sean Paul 2021-09-15 1191 * When HDCP2.2 fails and Content Type is not Type1, HDCP1.4 will cbc5065be3a652f Sean Paul 2021-09-15 1192 * be attempted. cbc5065be3a652f Sean Paul 2021-09-15 1193 */ cbc5065be3a652f Sean Paul 2021-09-15 1194 ret = drm_hdcp_helper_hdcp1_capable(data, &capable); cbc5065be3a652f Sean Paul 2021-09-15 1195 if (ret) { cbc5065be3a652f Sean Paul 2021-09-15 1196 drm_err(dev, "HDCP 1.x capability check failed %d\n", ret); cbc5065be3a652f Sean Paul 2021-09-15 1197 goto out; cbc5065be3a652f Sean Paul 2021-09-15 1198 } cbc5065be3a652f Sean Paul 2021-09-15 1199 if (capable && conn_state->content_type != DRM_MODE_HDCP_CONTENT_TYPE1) { cbc5065be3a652f Sean Paul 2021-09-15 1200 data->enabled_type = DRM_MODE_HDCP_CONTENT_TYPE0; cbc5065be3a652f Sean Paul 2021-09-15 1201 ret = drm_hdcp_helper_hdcp1_enable(data); cbc5065be3a652f Sean Paul 2021-09-15 1202 if (!ret) cbc5065be3a652f Sean Paul 2021-09-15 1203 check_link_interval = DRM_HDCP_CHECK_PERIOD_MS; cbc5065be3a652f Sean Paul 2021-09-15 1204 } "ret = 0" and "check_link_interval" is unitialized on else path. cbc5065be3a652f Sean Paul 2021-09-15 1205 cbc5065be3a652f Sean Paul 2021-09-15 1206 out: cbc5065be3a652f Sean Paul 2021-09-15 1207 if (!ret) { cbc5065be3a652f Sean Paul 2021-09-15 @1208 schedule_delayed_work(&data->check_work, check_link_interval); ^^^^^^^^^^^^^^^^^^^ cbc5065be3a652f Sean Paul 2021-09-15 1209 drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_ENABLED, cbc5065be3a652f Sean Paul 2021-09-15 1210 true); cbc5065be3a652f Sean Paul 2021-09-15 1211 } cbc5065be3a652f Sean Paul 2021-09-15 1212 cbc5065be3a652f Sean Paul 2021-09-15 1213 drm_hdcp_helper_driver_unlock(data); cbc5065be3a652f Sean Paul 2021-09-15 1214 if (ret) cbc5065be3a652f Sean Paul 2021-09-15 1215 data->driver_mutex = NULL; cbc5065be3a652f Sean Paul 2021-09-15 1216 cbc5065be3a652f Sean Paul 2021-09-15 1217 out_data_mutex: cbc5065be3a652f Sean Paul 2021-09-15 1218 mutex_unlock(&data->mutex); cbc5065be3a652f Sean Paul 2021-09-15 1219 return ret; cbc5065be3a652f Sean Paul 2021-09-15 1220 } --- 0-DAY CI Kernel Test Service, Intel Corporation https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
On 2021-09-15 13:38, Sean Paul wrote: > From: Sean Paul <seanpaul@chromium.org> > > This patch expands upon the HDCP helper library to manage HDCP > enable, disable, and check. > > Previous to this patch, the majority of the state management and sink > interaction is tucked inside the Intel driver with the understanding > that once a new platform supported HDCP we could make good decisions > about what should be centralized. With the addition of HDCP support > for Qualcomm, it's time to migrate the protocol-specific bits of HDCP > authentication, key exchange, and link checks to the HDCP helper. > > In terms of functionality, this migration is 1:1 with the Intel driver, > however things are laid out a bit differently than with intel_hdcp.c, > which is why this is a separate patch from the i915 transition to the > helper. On i915, the "shim" vtable is used to account for HDMI vs. DP > vs. DP-MST differences whereas the helper library uses a LUT to > account for the register offsets and a remote read function to route > the messages. On i915, storing the sink information in the source is > done inline whereas now we use the new drm_hdcp_helper_funcs vtable > to store and fetch information to/from source hw. Finally, instead of > calling enable/disable directly from the driver, we'll leave that > decision to the helper and by calling drm_hdcp_helper_atomic_commit() > from the driver. All told, this will centralize the protocol and state > handling in the helper, ensuring we collect all of our bugs^Wlogic > in one place. > > Signed-off-by: Sean Paul <seanpaul@chromium.org> > Link: > https://patchwork.freedesktop.org/patch/msgid/20210913175747.47456-5-sean@poorly.run > #v1 > > Changes in v2: > -Fixed set-but-unused variable identified by 0-day > --- > drivers/gpu/drm/drm_hdcp.c | 1103 ++++++++++++++++++++++++++++++++++++ > include/drm/drm_hdcp.h | 191 +++++++ > 2 files changed, 1294 insertions(+) > > diff --git a/drivers/gpu/drm/drm_hdcp.c b/drivers/gpu/drm/drm_hdcp.c > index 742313ce8f6f..47c6e6923a76 100644 > --- a/drivers/gpu/drm/drm_hdcp.c > +++ b/drivers/gpu/drm/drm_hdcp.c > @@ -6,15 +6,20 @@ > * Ramalingam C <ramalingam.c@intel.com> > */ > > +#include <linux/delay.h> > #include <linux/device.h> > #include <linux/err.h> > #include <linux/gfp.h> > +#include <linux/i2c.h> > +#include <linux/iopoll.h> > #include <linux/export.h> > #include <linux/slab.h> > #include <linux/firmware.h> > +#include <linux/workqueue.h> > > #include <drm/drm_atomic.h> > #include <drm/drm_connector.h> > +#include <drm/drm_dp_helper.h> > #include <drm/drm_hdcp.h> > #include <drm/drm_sysfs.h> > #include <drm/drm_print.h> > @@ -513,3 +518,1101 @@ bool drm_hdcp_atomic_check(struct drm_connector > *connector, > return old_hdcp != new_hdcp; > } > EXPORT_SYMBOL(drm_hdcp_atomic_check); > + > +struct drm_hdcp_helper_data { > + struct mutex mutex; > + struct mutex *driver_mutex; > + > + struct drm_connector *connector; > + const struct drm_hdcp_helper_funcs *funcs; > + > + u64 value; > + unsigned int enabled_type; > + > + struct delayed_work check_work; > + struct work_struct prop_work; > + > + struct drm_dp_aux *aux; > + const struct drm_hdcp_hdcp1_receiver_reg_lut *hdcp1_lut; > +}; > + > +struct drm_hdcp_hdcp1_receiver_reg_lut { > + unsigned int bksv; > + unsigned int ri; > + unsigned int aksv; > + unsigned int an; > + unsigned int ainfo; > + unsigned int v[5]; > + unsigned int bcaps; > + unsigned int bcaps_mask_repeater_present; > + unsigned int bstatus; > +}; > + > +static const struct drm_hdcp_hdcp1_receiver_reg_lut > drm_hdcp_hdcp1_ddc_lut = { > + .bksv = DRM_HDCP_DDC_BKSV, > + .ri = DRM_HDCP_DDC_RI_PRIME, > + .aksv = DRM_HDCP_DDC_AKSV, > + .an = DRM_HDCP_DDC_AN, > + .ainfo = DRM_HDCP_DDC_AINFO, > + .v = { DRM_HDCP_DDC_V_PRIME(0), DRM_HDCP_DDC_V_PRIME(1), > + DRM_HDCP_DDC_V_PRIME(2), DRM_HDCP_DDC_V_PRIME(3), > + DRM_HDCP_DDC_V_PRIME(4) }, > + .bcaps = DRM_HDCP_DDC_BCAPS, > + .bcaps_mask_repeater_present = DRM_HDCP_DDC_BCAPS_REPEATER_PRESENT, > + .bstatus = DRM_HDCP_DDC_BSTATUS, > +}; > + > +static const struct drm_hdcp_hdcp1_receiver_reg_lut > drm_hdcp_hdcp1_dpcd_lut = { > + .bksv = DP_AUX_HDCP_BKSV, > + .ri = DP_AUX_HDCP_RI_PRIME, > + .aksv = DP_AUX_HDCP_AKSV, > + .an = DP_AUX_HDCP_AN, > + .ainfo = DP_AUX_HDCP_AINFO, > + .v = { DP_AUX_HDCP_V_PRIME(0), DP_AUX_HDCP_V_PRIME(1), > + DP_AUX_HDCP_V_PRIME(2), DP_AUX_HDCP_V_PRIME(3), > + DP_AUX_HDCP_V_PRIME(4) }, > + .bcaps = DP_AUX_HDCP_BCAPS, > + .bcaps_mask_repeater_present = DP_BCAPS_REPEATER_PRESENT, > + > + /* > + * For some reason the HDMI and DP HDCP specs call this register > + * definition by different names. In the HDMI spec, it's called > BSTATUS, > + * but in DP it's called BINFO. > + */ > + .bstatus = DP_AUX_HDCP_BINFO, > +}; > + > +static int drm_hdcp_remote_ddc_read(struct i2c_adapter *i2c, > + unsigned int offset, u8 *value, size_t len) > +{ > + int ret; > + u8 start = offset & 0xff; > + struct i2c_msg msgs[] = { > + { > + .addr = DRM_HDCP_DDC_ADDR, > + .flags = 0, > + .len = 1, > + .buf = &start, > + }, > + { > + .addr = DRM_HDCP_DDC_ADDR, > + .flags = I2C_M_RD, > + .len = len, > + .buf = value > + } > + }; > + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); > + if (ret == ARRAY_SIZE(msgs)) > + return 0; > + return ret >= 0 ? -EIO : ret; > +} > + > +static int drm_hdcp_remote_dpcd_read(struct drm_dp_aux *aux, > + unsigned int offset, u8 *value, > + size_t len) > +{ > + ssize_t ret; > + > + ret = drm_dp_dpcd_read(aux, offset, value, len); > + if (ret != len) { > + if (ret >= 0) > + return -EIO; > + return ret; > + } > + > + return 0; > +} > + > +static int drm_hdcp_remote_read(struct drm_hdcp_helper_data *data, > + unsigned int offset, u8 *value, u8 len) > +{ > + if (data->aux) > + return drm_hdcp_remote_dpcd_read(data->aux, offset, value, len); > + else > + return drm_hdcp_remote_ddc_read(data->connector->ddc, offset, value, > len); > +} > + > +static int drm_hdcp_remote_ddc_write(struct i2c_adapter *i2c, > + unsigned int offset, u8 *buffer, > + size_t size) > +{ > + int ret; > + u8 *write_buf; > + struct i2c_msg msg; > + > + write_buf = kzalloc(size + 1, GFP_KERNEL); > + if (!write_buf) > + return -ENOMEM; > + > + write_buf[0] = offset & 0xff; > + memcpy(&write_buf[1], buffer, size); > + > + msg.addr = DRM_HDCP_DDC_ADDR; > + msg.flags = 0, > + msg.len = size + 1, > + msg.buf = write_buf; > + > + ret = i2c_transfer(i2c, &msg, 1); > + if (ret == 1) > + ret = 0; > + else if (ret >= 0) > + ret = -EIO; > + > + kfree(write_buf); > + return ret; > +} > + > +static int drm_hdcp_remote_dpcd_write(struct drm_dp_aux *aux, > + unsigned int offset, u8 *value, > + size_t len) > +{ > + ssize_t ret; > + > + ret = drm_dp_dpcd_write(aux, offset, value, len); > + if (ret != len) { > + if (ret >= 0) > + return -EIO; > + return ret; > + } > + > + return 0; > +} > + > +static int drm_hdcp_remote_write(struct drm_hdcp_helper_data *data, > + unsigned int offset, u8 *value, u8 len) > +{ > + if (data->aux) > + return drm_hdcp_remote_dpcd_write(data->aux, offset, value, len); > + else > + return drm_hdcp_remote_ddc_write(data->connector->ddc, offset, > + value, len); > +} > + > +static bool drm_hdcp_is_ksv_valid(struct drm_hdcp_ksv *ksv) > +{ > + /* Valid Ksv has 20 0's and 20 1's */ > + return hweight32(ksv->words[0]) + hweight32(ksv->words[1]) == 20; > +} > + > +static int drm_hdcp_read_valid_bksv(struct drm_hdcp_helper_data *data, > + struct drm_hdcp_ksv *bksv) > +{ > + int ret, i, tries = 2; > + > + /* HDCP spec states that we must retry the bksv if it is invalid */ > + for (i = 0; i < tries; i++) { > + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bksv, > + bksv->bytes, DRM_HDCP_KSV_LEN); > + if (ret) > + return ret; > + > + if (drm_hdcp_is_ksv_valid(bksv)) > + break; > + } > + if (i == tries) { > + drm_dbg_kms(data->connector->dev, "Bksv is invalid %*ph\n", > + DRM_HDCP_KSV_LEN, bksv->bytes); > + return -ENODEV; > + } > + > + return 0; > +} > + > +/** > + * drm_hdcp_helper_hdcp1_capable - Checks if the sink is capable of > HDCP 1.x. > + * > + * @data: pointer to the HDCP helper data. > + * @capable: pointer to a bool which will contain true if the sink is > capable. > + * > + * Returns: > + * -errno if the transacation between source and sink fails. > + */ > +int drm_hdcp_helper_hdcp1_capable(struct drm_hdcp_helper_data *data, > + bool *capable) > +{ > + /* > + * DisplayPort has a dedicated bit for this in DPCD whereas HDMI spec > + * states that transmitters should use bksv to determine capability. > + */ > + if (data->aux) { > + int ret; > + u8 bcaps; > + > + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bcaps, > + &bcaps, 1); > + *capable = !ret && (bcaps & DP_BCAPS_HDCP_CAPABLE); > + } else { > + struct drm_hdcp_ksv bksv; > + > + *capable = drm_hdcp_read_valid_bksv(data, &bksv) == 0; > + } > + > + return 0; > +} > +EXPORT_SYMBOL(drm_hdcp_helper_hdcp1_capable); > + > +static void drm_hdcp_update_value(struct drm_hdcp_helper_data *data, > + u64 value, bool update_property) > +{ > + WARN_ON(!mutex_is_locked(&data->mutex)); > + > + data->value = value; > + if (update_property) { > + drm_connector_get(data->connector); > + schedule_work(&data->prop_work); > + } > +} > + > +static int > +drm_hdcp_helper_hdcp1_ksv_fifo_ready(struct drm_hdcp_helper_data > *data) > +{ > + int ret; > + u8 val, mask; > + > + /* KSV FIFO ready bit is stored in different locations on DP v. HDMI > */ > + if (data->aux) { > + ret = drm_hdcp_remote_dpcd_read(data->aux, DP_AUX_HDCP_BSTATUS, > + &val, 1); > + mask = DP_BSTATUS_READY; > + } else { > + ret = drm_hdcp_remote_ddc_read(data->connector->ddc, > + DRM_HDCP_DDC_BCAPS, &val, 1); > + mask = DRM_HDCP_DDC_BCAPS_KSV_FIFO_READY; > + } > + if (ret) > + return ret; > + if (val & mask) > + return 0; > + > + return -EAGAIN; > +} > + > +static int > +drm_hdcp_helper_hdcp1_read_ksv_fifo(struct drm_hdcp_helper_data > *data, u8 *fifo, > + u8 num_downstream) > +{ > + struct drm_device *dev = data->connector->dev; > + int ret, i; > + > + /* Over HDMI, read the whole thing at once */ > + if (data->connector->ddc) { > + ret = drm_hdcp_remote_ddc_read(data->connector->ddc, > + DRM_HDCP_DDC_KSV_FIFO, fifo, > + num_downstream * DRM_HDCP_KSV_LEN); > + if (ret) > + drm_err(dev, "DDC ksv fifo read failed (%d)\n", ret); > + return ret; > + } > + > + /* Over DP, read via 15 byte window (3 entries @ 5 bytes each) */ > + for (i = 0; i < num_downstream; i += 3) { > + size_t len = min(num_downstream - i, 3) * DRM_HDCP_KSV_LEN; > + ret = drm_hdcp_remote_dpcd_read(data->aux, DP_AUX_HDCP_KSV_FIFO, > + fifo + i * DRM_HDCP_KSV_LEN, > + len); > + if (ret) { > + drm_err(dev, "Read ksv[%d] from DP/AUX failed (%d)\n", > + i, ret); > + return ret; > + } > + } > + > + return 0; > +} > + > +static int drm_hdcp_helper_hdcp1_read_v_prime(struct > drm_hdcp_helper_data *data, > + u32 *v_prime) > +{ > + struct drm_device *dev = data->connector->dev; > + int ret, i; > + > + for (i = 0; i < DRM_HDCP_V_PRIME_NUM_PARTS; i++) { > + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->v[i], > + (u8 *)&v_prime[i], > + DRM_HDCP_V_PRIME_PART_LEN); > + if (ret) { > + drm_dbg_kms(dev, "Read v'[%d] from failed (%d)\n", i, ret); > + return ret >= 0 ? -EIO : ret; > + } > + } > + return 0; > +} > + > +static int > +drm_hdcp_helper_hdcp1_authenticate_downstream(struct > drm_hdcp_helper_data *data) > +{ > + struct drm_connector *connector = data->connector; > + struct drm_device *dev = connector->dev; > + u32 v_prime[DRM_HDCP_V_PRIME_NUM_PARTS]; > + u8 bstatus[DRM_HDCP_BSTATUS_LEN]; > + u8 num_downstream, *ksv_fifo; > + int ret, i, tries = 3; > + > + ret = read_poll_timeout(drm_hdcp_helper_hdcp1_ksv_fifo_ready, ret, > !ret, > + 10 * 1000, 5 * 1000 * 1000, false, data); > + if (ret) { > + drm_err(dev, "Failed to poll ksv ready, %d\n", ret); > + return ret; > + } > + > + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bstatus, > + bstatus, DRM_HDCP_BSTATUS_LEN); > + if (ret) > + return ret; > + > + /* > + * When repeater reports 0 device count, HDCP1.4 spec allows > disabling > + * the HDCP encryption. That implies that repeater can't have its own > + * display. As there is no consumption of encrypted content in the > + * repeater with 0 downstream devices, we are failing the > + * authentication. > + */ > + num_downstream = DRM_HDCP_NUM_DOWNSTREAM(bstatus[0]); > + if (num_downstream == 0) { > + drm_err(dev, "Repeater with zero downstream devices, %*ph\n", > + DRM_HDCP_BSTATUS_LEN, bstatus); > + return -EINVAL; > + } > + > + ksv_fifo = kcalloc(DRM_HDCP_KSV_LEN, num_downstream, GFP_KERNEL); > + if (!ksv_fifo) > + return -ENOMEM; > + > + ret = drm_hdcp_helper_hdcp1_read_ksv_fifo(data, ksv_fifo, > + num_downstream); > + if (ret) { > + drm_err(dev, "Failed to read ksv fifo, %d/%d\n", num_downstream, > + ret); > + goto out; > + } > + > + if (drm_hdcp_check_ksvs_revoked(dev, ksv_fifo, num_downstream)) { > + drm_err(dev, "Revoked Ksv(s) in ksv_fifo\n"); > + ret = -EPERM; > + goto out; > + } > + > + /* > + * When V prime mismatches, DP Spec mandates re-read of > + * V prime atleast twice. > + */ > + for (i = 0; i < tries; i++) { > + ret = drm_hdcp_helper_hdcp1_read_v_prime(data, v_prime); > + if (ret) > + continue; > + > + ret = data->funcs->hdcp1_store_ksv_fifo(connector, ksv_fifo, > + num_downstream, > + bstatus, v_prime); > + if (!ret) > + break; > + } > + if (ret) > + drm_err(dev, "Could not validate KSV FIFO with V' %d\n", ret); > + > +out: > + if (!ret) > + drm_dbg_kms(dev, "HDCP is enabled (%d downstream devices)\n", > + num_downstream); > + > + kfree(ksv_fifo); > + return ret; > +} > + > +static int drm_hdcp_helper_hdcp1_validate_ri(struct > drm_hdcp_helper_data *data) > +{ > + union { > + u32 word; > + u8 bytes[DRM_HDCP_RI_LEN]; > + } ri_prime = { .word = 0 }; > + struct drm_connector *connector = data->connector; > + struct drm_device *dev = connector->dev; > + int ret; > + > + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->ri, ri_prime.bytes, > + DRM_HDCP_RI_LEN); > + if (ret) { > + drm_err(dev, "Failed to read R0' %d\n", ret); > + return ret; > + } > + > + return data->funcs->hdcp1_match_ri(connector, ri_prime.word); > +} > + > +static int drm_hdcp_helper_hdcp1_authenticate(struct > drm_hdcp_helper_data *data) > +{ > + union { > + u32 word; > + u8 bytes[DRM_HDCP_BSTATUS_LEN]; > + } bstatus; > + const struct drm_hdcp_helper_funcs *funcs = data->funcs; > + struct drm_connector *connector = data->connector; > + struct drm_device *dev = connector->dev; > + unsigned long r0_prime_timeout, r0_prime_remaining_us = 0, > tmp_jiffies; > + struct drm_hdcp_ksv aksv; > + struct drm_hdcp_ksv bksv; > + struct drm_hdcp_an an; > + bool repeater_present; > + int ret, i, tries = 3; > + u8 bcaps; > + > + if (funcs->hdcp1_read_an_aksv) { > + ret = funcs->hdcp1_read_an_aksv(connector, an.words, aksv.words); > + if (ret) { > + drm_err(dev, "Failed to read An/Aksv values, %d\n", ret); > + return ret; > + } > + > + ret = drm_hdcp_remote_write(data, data->hdcp1_lut->an, an.bytes, > + DRM_HDCP_AN_LEN); > + if (ret) { > + drm_err(dev, "Failed to write An to receiver, %d\n", ret); > + return ret; > + } > + > + ret = drm_hdcp_remote_write(data, data->hdcp1_lut->aksv, aksv.bytes, > + DRM_HDCP_KSV_LEN); > + if (ret) { > + drm_err(dev, "Failed to write Aksv to receiver, %d\n", ret); > + return ret; > + } > + } else { > + ret = funcs->hdcp1_send_an_aksv(connector); > + if (ret) { > + drm_err(dev, "Failed to read An/Aksv values, %d\n", ret); > + return ret; > + } > + } > + > + /* > + * Timeout for R0' to become available. The spec says 100ms from > Aksv, > + * but some monitors can take longer than this. We'll set the timeout > at > + * 300ms just to be sure. > + */ > + r0_prime_timeout = jiffies + msecs_to_jiffies(300); > + > + memset(&bksv, 0, sizeof(bksv)); > + > + ret = drm_hdcp_read_valid_bksv(data, &bksv); > + if (ret < 0) > + return ret; > + > + if (drm_hdcp_check_ksvs_revoked(dev, bksv.bytes, 1)) { > + drm_err(dev, "BKSV is revoked\n"); > + return -EPERM; > + } > + > + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bcaps, &bcaps, 1); > + if (ret) > + return ret; > + > + memset(&bstatus, 0, sizeof(bstatus)); > + > + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bstatus, > + bstatus.bytes, DRM_HDCP_BSTATUS_LEN); > + if (ret) > + return ret; > + > + if (DRM_HDCP_MAX_DEVICE_EXCEEDED(bstatus.bytes[0]) || > + DRM_HDCP_MAX_CASCADE_EXCEEDED(bstatus.bytes[1])) { > + drm_err(dev, "Max Topology Limit Exceeded, bstatus=%*ph\n", > + DRM_HDCP_BSTATUS_LEN, bstatus.bytes); > + return -EPERM; > + } > + > + repeater_present = bcaps & > data->hdcp1_lut->bcaps_mask_repeater_present; > + > + ret = funcs->hdcp1_store_receiver_info(connector, bksv.words, > + bstatus.word, bcaps, > + repeater_present); > + if (ret) { > + drm_err(dev, "Failed to store bksv, %d\n", ret); > + return ret; > + } > + > + ret = funcs->hdcp1_enable_encryption(connector); > + if (ret) > + return ret; > + > + ret = funcs->hdcp1_wait_for_r0(connector); > + if (ret) > + return ret; > + > + tmp_jiffies = jiffies; > + if (time_before(tmp_jiffies, r0_prime_timeout)) > + r0_prime_remaining_us = jiffies_to_usecs(r0_prime_timeout - > tmp_jiffies); > + > + /* > + * Wait for R0' to become available. > + * > + * On DP, there's an R0_READY bit available but no such bit > + * exists on HDMI. So poll the ready bit for DP and just wait the > + * remainder of the 300 ms timeout for HDMI. > + */ > + if (data->aux) { > + u8 val; > + ret = read_poll_timeout(drm_hdcp_remote_dpcd_read, ret, > + !ret && (val & DP_BSTATUS_R0_PRIME_READY), > + 1000, r0_prime_remaining_us, false, > + data->aux, DP_AUX_HDCP_BSTATUS, &val, 1); > + if (ret) { > + drm_err(dev, "R0' did not become ready %d\n", ret); > + return ret; > + } > + } else { > + usleep_range(r0_prime_remaining_us, > + r0_prime_remaining_us + 1000); > + } > + > + /* > + * DP HDCP Spec mandates the two more reattempt to read R0, incase > + * of R0 mismatch. > + */ > + for (i = 0; i < tries; i++) { > + ret = drm_hdcp_helper_hdcp1_validate_ri(data); > + if (!ret) > + break; > + } > + if (ret) { > + drm_err(dev, "Failed to match R0/R0', aborting HDCP %d\n", ret); > + return ret; > + } > + > + if (repeater_present) > + return drm_hdcp_helper_hdcp1_authenticate_downstream(data); > + > + drm_dbg_kms(dev, "HDCP is enabled (no repeater present)\n"); > + return 0; > +} > + > +static int drm_hdcp_helper_hdcp1_enable(struct drm_hdcp_helper_data > *data) > +{ > + struct drm_connector *connector = data->connector; > + struct drm_device *dev = connector->dev; > + int i, ret, tries = 3; > + > + drm_dbg_kms(dev, "[%s:%d] HDCP is being enabled...\n", > connector->name, > + connector->base.id); > + > + /* Incase of authentication failures, HDCP spec expects reauth. */ > + for (i = 0; i < tries; i++) { > + ret = drm_hdcp_helper_hdcp1_authenticate(data); > + if (!ret) > + return 0; > + > + drm_dbg_kms(dev, "HDCP Auth failure (%d)\n", ret); > + > + /* Ensuring HDCP encryption and signalling are stopped. */ > + data->funcs->hdcp1_disable(data->connector); > + } > + > + drm_err(dev, "HDCP authentication failed (%d tries/%d)\n", tries, > ret); > + return ret; > +} > + > +static inline > +void drm_hdcp_helper_driver_lock(struct drm_hdcp_helper_data *data) > +{ > + if (data->driver_mutex) > + mutex_lock(data->driver_mutex); > +} > + > +static inline > +void drm_hdcp_helper_driver_unlock(struct drm_hdcp_helper_data *data) > +{ > + if (data->driver_mutex) > + mutex_unlock(data->driver_mutex); > +} > + > +static int drm_hdcp_helper_enable_hdcp(struct drm_hdcp_helper_data > *data, > + struct drm_atomic_state *state, > + struct mutex *driver_mutex) > +{ > + struct drm_connector *connector = data->connector; > + struct drm_connector_state *conn_state; > + struct drm_device *dev = connector->dev; > + unsigned long check_link_interval; > + bool capable; > + int ret = 0; > + > + conn_state = drm_atomic_get_new_connector_state(state, connector); > + > + mutex_lock(&data->mutex); > + > + if (data->value == DRM_MODE_CONTENT_PROTECTION_ENABLED) { > + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_ENABLED, > + true); > + goto out_data_mutex; > + } > + > + drm_WARN_ON(dev, data->driver_mutex != NULL); > + data->driver_mutex = driver_mutex; > + > + drm_hdcp_helper_driver_lock(data); > + > + if (data->funcs->setup) { > + ret = data->funcs->setup(connector, state); > + if (ret) { > + drm_err(dev, "Failed to setup HDCP %d\n", ret); > + goto out; > + } > + } > + > + if (!data->funcs->are_keys_valid || > + !data->funcs->are_keys_valid(connector)) { > + if (data->funcs->load_keys) { > + ret = data->funcs->load_keys(connector); > + if (ret) { > + drm_err(dev, "Failed to load HDCP keys %d\n", ret); > + goto out; > + } > + } > + } > + > + /* > + * Considering that HDCP2.2 is more secure than HDCP1.4, If the setup > + * is capable of HDCP2.2, it is preferred to use HDCP2.2. > + */ > + ret = data->funcs->hdcp2_capable(connector, &capable); > + if (ret) { > + drm_err(dev, "HDCP 2.x capability check failed %d\n", ret); > + goto out; > + } > + if (capable) { > + data->enabled_type = DRM_MODE_HDCP_CONTENT_TYPE1; > + ret = data->funcs->hdcp2_enable(connector); > + if (!ret) { > + check_link_interval = DRM_HDCP2_CHECK_PERIOD_MS; > + goto out; > + } > + } > + > + /* > + * When HDCP2.2 fails and Content Type is not Type1, HDCP1.4 will > + * be attempted. > + */ > + ret = drm_hdcp_helper_hdcp1_capable(data, &capable); > + if (ret) { > + drm_err(dev, "HDCP 1.x capability check failed %d\n", ret); > + goto out; > + } > + if (capable && conn_state->content_type != > DRM_MODE_HDCP_CONTENT_TYPE1) { > + data->enabled_type = DRM_MODE_HDCP_CONTENT_TYPE0; > + ret = drm_hdcp_helper_hdcp1_enable(data); > + if (!ret) > + check_link_interval = DRM_HDCP_CHECK_PERIOD_MS; > + } > + > +out: > + if (!ret) { > + schedule_delayed_work(&data->check_work, check_link_interval); > + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_ENABLED, > + true); > + } > + > + drm_hdcp_helper_driver_unlock(data); > + if (ret) > + data->driver_mutex = NULL; > + > +out_data_mutex: > + mutex_unlock(&data->mutex); > + return ret; > +} > + > +static int drm_hdcp_helper_disable_hdcp(struct drm_hdcp_helper_data > *data) > +{ > + int ret = 0; > + > + mutex_lock(&data->mutex); > + drm_hdcp_helper_driver_lock(data); > + > + if (data->value == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) > + goto out; > + > + drm_dbg_kms(data->connector->dev, "[%s:%d] HDCP is being > disabled...\n", > + data->connector->name, data->connector->base.id); > + > + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_UNDESIRED, > true); > + > + if (data->enabled_type == DRM_MODE_HDCP_CONTENT_TYPE1) > + ret = data->funcs->hdcp2_disable(data->connector); > + else > + ret = data->funcs->hdcp1_disable(data->connector); > + > + drm_dbg_kms(data->connector->dev, "HDCP is disabled\n"); > + > +out: > + drm_hdcp_helper_driver_unlock(data); > + data->driver_mutex = NULL; > + mutex_unlock(&data->mutex); > + cancel_delayed_work_sync(&data->check_work); > + return ret; > +} > + > +/** > + * drm_hdcp_helper_atomic_commit - Helper for drivers to call during > commit to > + * enable/disable HDCP > + * > + * @data: pointer to the @drm_hdcp_helper_data for the connector > + * @state: pointer to the atomic state being committed > + * @driver_mutex: driver-provided lock to be used while interacting > with the driver > + * > + * This function can be used by display drivers to determine when > HDCP should be > + * enabled or disabled based on the connector state. It should be > called during > + * steady-state commits as well as connector enable/disable. The > function will > + * handle the HDCP authentication/encryption logic, calling back into > the driver > + * when source operations are necessary. > + * > + * @driver_mutex will be retained and used for the duration of the > HDCP session > + * since it will be needed for link checks and retries. This mutex is > useful if > + * the driver has shared resources across connectors which must be > serialized. > + * For example, driver_mutex can be used for MST connectors sharing a > common > + * encoder which should not be accessed/changed concurrently. When the > + * connector's session is torn down, the mutex will be forgotten by > the helper > + * for this connector until the next session. > + */ > +void drm_hdcp_helper_atomic_commit(struct drm_hdcp_helper_data *data, > + struct drm_atomic_state *state, > + struct mutex *driver_mutex) > +{ > + struct drm_connector *connector = data->connector; > + struct drm_connector_state *conn_state; > + bool type_changed; > + > + conn_state = drm_atomic_get_new_connector_state(state, connector); > + > + type_changed = conn_state->hdcp_content_type != data->enabled_type; > + > + if (conn_state->content_protection == > DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { > + drm_hdcp_helper_disable_hdcp(data); > + return; > + } > + > + if (!conn_state->crtc) { > + drm_hdcp_helper_disable_hdcp(data); > + > + /* Restore property to DESIRED so it's retried later */ > + if (conn_state->content_protection == > DRM_MODE_CONTENT_PROTECTION_ENABLED) { > + mutex_lock(&data->mutex); > + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, > + true); > + mutex_unlock(&data->mutex); > + } > + return; > + } > + > + /* Already enabled */ > + if (conn_state->content_protection == > DRM_MODE_CONTENT_PROTECTION_ENABLED) > + return; > + > + /* Disable and re-enable HDCP on content type change */ > + if (type_changed) > + drm_hdcp_helper_disable_hdcp(data); > + > + drm_hdcp_helper_enable_hdcp(data, state, driver_mutex); > +} > +EXPORT_SYMBOL(drm_hdcp_helper_atomic_commit); > + > +static void drm_hdcp_helper_prop_work(struct work_struct *work) > +{ > + struct drm_hdcp_helper_data *data = container_of(work, > + struct drm_hdcp_helper_data, > + prop_work); > + struct drm_connector *connector = data->connector; > + struct drm_device *dev = connector->dev; > + > + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); > + mutex_lock(&data->mutex); > + > + /* > + * This worker is only used to flip between ENABLED/DESIRED. Either > of > + * those to UNDESIRED is handled by core. If value == UNDESIRED, > + * we're running just after hdcp has been disabled, so just exit > + */ > + if (data->value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) > + drm_hdcp_update_content_protection(connector, data->value); > + > + mutex_unlock(&data->mutex); > + drm_modeset_unlock(&dev->mode_config.connection_mutex); > +} > + > +static int drm_hdcp_hdcp1_check_link(struct drm_hdcp_helper_data > *data) > +{ > + struct drm_connector *connector = data->connector; > + struct drm_device *dev = connector->dev; > + int ret; > + > + if (data->funcs->hdcp1_check_link) { > + ret = data->funcs->hdcp1_check_link(connector); > + if (ret) > + goto retry; > + } > + > + /* The link is checked differently for DP and HDMI */ > + if (data->aux) { > + u8 bstatus; > + ret = drm_hdcp_remote_dpcd_read(data->aux, DP_AUX_HDCP_BSTATUS, > + &bstatus, 1); > + if (ret) { > + drm_err(dev, "Failed to read dpcd bstatus, %d\n", ret); > + return ret; > + } > + if (bstatus & (DP_BSTATUS_LINK_FAILURE | DP_BSTATUS_REAUTH_REQ)) > + ret = -EINVAL; > + } else { > + ret = drm_hdcp_helper_hdcp1_validate_ri(data); > + if (ret) > + drm_err(dev,"Ri' mismatch, check failed (%d)\n", ret); > + } > + if (!ret) > + return 0; > + > +retry: > + drm_err(dev, "[%s:%d] HDCP link failed, retrying authentication\n", > + connector->name, connector->base.id); > + > + ret = data->funcs->hdcp1_disable(connector); > + if (ret) { > + drm_err(dev, "Failed to disable hdcp (%d)\n", ret); > + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, > + true); > + return ret; > + } > + > + ret = drm_hdcp_helper_hdcp1_enable(data); > + if (ret) { > + drm_err(dev, "Failed to enable hdcp (%d)\n", ret); > + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, > + true); > + return ret; > + } > + > + return 0; > +} > + > +static int drm_hdcp_hdcp2_check_link(struct drm_hdcp_helper_data > *data) > +{ > + struct drm_connector *connector = data->connector; > + struct drm_device *dev = connector->dev; > + int ret; > + > + ret = data->funcs->hdcp2_check_link(connector); > + if (!ret) > + return 0; > + > + drm_err(dev, "[%s:%d] HDCP2 link failed, retrying authentication\n", > + connector->name, connector->base.id); > + > + ret = data->funcs->hdcp2_disable(connector); > + if (ret) { > + drm_err(dev, "Failed to disable hdcp2 (%d)\n", ret); > + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, > + true); > + return ret; > + } > + > + ret = data->funcs->hdcp2_enable(connector); > + if (ret) { > + drm_err(dev, "Failed to enable hdcp2 (%d)\n", ret); > + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, > + true); > + return ret; > + } > + > + return 0; > +} > + > +static void drm_hdcp_helper_check_work(struct work_struct *work) > +{ > + struct drm_hdcp_helper_data *data = > container_of(to_delayed_work(work), > + struct drm_hdcp_helper_data, > + check_work); > + unsigned long check_link_interval; > + Does this SW polling for Ri' mismatch need to be done even if the HW is capable of doing it on its own? MSM HDCP 1x HW can periodically check Ri' mismatches and issue an interrupt if there is a mismatch. In that case this SW polling is not needed. So maybe check if the HW supports polling and if so skip this SW polling? > + mutex_lock(&data->mutex); > + if (data->value != DRM_MODE_CONTENT_PROTECTION_ENABLED) > + goto out_data_mutex; > + > + drm_hdcp_helper_driver_lock(data); > + > + if (data->enabled_type == DRM_MODE_HDCP_CONTENT_TYPE1) { > + if (drm_hdcp_hdcp2_check_link(data)) > + goto out; > + check_link_interval = DRM_HDCP2_CHECK_PERIOD_MS; > + } else { > + if (drm_hdcp_hdcp1_check_link(data)) > + goto out; > + check_link_interval = DRM_HDCP_CHECK_PERIOD_MS; > + } > + schedule_delayed_work(&data->check_work, check_link_interval); > + > +out: > + drm_hdcp_helper_driver_unlock(data); > +out_data_mutex: > + mutex_unlock(&data->mutex); > +} > + > +/** > + * drm_hdcp_helper_schedule_hdcp_check - Schedule a check link cycle. > + * > + * @data: Pointer to the HDCP helper data. > + * > + * This function will kick off a check link cycle on behalf of the > caller. This > + * can be used by DP short hpd interrupt handlers, where the driver > must poke > + * the helper to check the link is still valid. > + */ > +void drm_hdcp_helper_schedule_hdcp_check(struct drm_hdcp_helper_data > *data) > +{ > + schedule_delayed_work(&data->check_work, 0); > +} > +EXPORT_SYMBOL(drm_hdcp_helper_schedule_hdcp_check); > + > +static struct drm_hdcp_helper_data * > +drm_hdcp_helper_initialize(struct drm_connector *connector, > + const struct drm_hdcp_helper_funcs *funcs, > + bool attach_content_type_property) > +{ > + struct drm_hdcp_helper_data *out; > + int ret; > + > + out = kzalloc(sizeof(*out), GFP_KERNEL); > + if (!out) > + return ERR_PTR(-ENOMEM); > + > + out->connector = connector; > + out->funcs = funcs; > + > + mutex_init(&out->mutex); > + out->value = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; > + > + INIT_DELAYED_WORK(&out->check_work, drm_hdcp_helper_check_work); > + INIT_WORK(&out->prop_work, drm_hdcp_helper_prop_work); > + > + ret = drm_connector_attach_content_protection_property(connector, > + attach_content_type_property); > + if (ret) { > + drm_hdcp_helper_destroy(out); > + return ERR_PTR(ret); > + } > + > + return out; > +} > + > +/** > + * drm_hdcp_helper_initialize_dp - Initializes the HDCP helpers for a > + * DisplayPort connector > + * > + * @connector: pointer to the DisplayPort connector. > + * @funcs: pointer to the vtable of HDCP helper funcs for this > connector. > + * @attach_content_type_property: True if the content_type property > should be > + * attached. > + * > + * This function intializes the HDCP helper for the given DisplayPort > connector. > + * This involves creating the Content Protection property as well as > the Content > + * Type property (if desired). Upon success, it will return a pointer > to the > + * HDCP helper data. Ownership of the underlaying memory is transfered > to the > + * caller and should be freed using drm_hdcp_helper_destroy(). > + * > + * Returns: > + * Pointer to newly created HDCP helper data. PTR_ERR on failure. > + */ > +struct drm_hdcp_helper_data * > +drm_hdcp_helper_initialize_dp(struct drm_connector *connector, > + struct drm_dp_aux *aux, > + const struct drm_hdcp_helper_funcs *funcs, > + bool attach_content_type_property) > +{ > + struct drm_hdcp_helper_data *out; > + > + out = drm_hdcp_helper_initialize(connector, funcs, > + attach_content_type_property); > + if (IS_ERR(out)) > + return out; > + > + out->aux = aux; > + out->hdcp1_lut = &drm_hdcp_hdcp1_dpcd_lut; > + > + return out; > +} > +EXPORT_SYMBOL(drm_hdcp_helper_initialize_dp); > + > +/** > + * drm_hdcp_helper_initialize_hdmi - Initializes the HDCP helpers for > an HDMI > + * connector > + * > + * @connector: pointer to the HDMI connector. > + * @funcs: pointer to the vtable of HDCP helper funcs for this > connector. > + * @attach_content_type_property: True if the content_type property > should be > + * attached. > + * > + * This function intializes the HDCP helper for the given HDMI > connector. This > + * involves creating the Content Protection property as well as the > Content Type > + * property (if desired). Upon success, it will return a pointer to > the HDCP > + * helper data. Ownership of the underlaying memory is transfered to > the caller > + * and should be freed using drm_hdcp_helper_destroy(). > + * > + * Returns: > + * Pointer to newly created HDCP helper data. PTR_ERR on failure. > + */ > +struct drm_hdcp_helper_data * > +drm_hdcp_helper_initialize_hdmi(struct drm_connector *connector, > + const struct drm_hdcp_helper_funcs *funcs, > + bool attach_content_type_property) > +{ > + struct drm_hdcp_helper_data *out; > + > + out = drm_hdcp_helper_initialize(connector, funcs, > + attach_content_type_property); > + if (IS_ERR(out)) > + return out; > + > + out->hdcp1_lut = &drm_hdcp_hdcp1_ddc_lut; > + > + return out; > +} > +EXPORT_SYMBOL(drm_hdcp_helper_initialize_hdmi); > + > +/** > + * drm_hdcp_helper_destroy - Destroys the given HDCP helper data. > + * > + * @data: Pointer to the HDCP helper data. > + * > + * This function cleans up and destroys the HDCP helper data created > by > + * drm_hdcp_helper_initialize_dp() or > drm_hdcp_helper_initialize_hdmi(). > + */ > +void drm_hdcp_helper_destroy(struct drm_hdcp_helper_data *data) > +{ > + struct drm_connector *connector; > + > + if (!data) > + return; > + > + connector = data->connector; > + > + /* > + * If the connector is registered, it's possible userspace could kick > + * off another HDCP enable, which would re-spawn the workers. > + */ > + drm_WARN_ON(connector->dev, > + connector->registration_state == DRM_CONNECTOR_REGISTERED); > + > + /* > + * Now that the connector is not registered, check_work won't be run, > + * but cancel any outstanding instances of it > + */ > + cancel_delayed_work_sync(&data->check_work); > + > + /* > + * We don't cancel prop_work in the same way as check_work since it > + * requires connection_mutex which could be held while calling this > + * function. Instead, we rely on the connector references grabbed > before > + * scheduling prop_work to ensure the connector is alive when > prop_work > + * is run. So if we're in the destroy path (which is where this > + * function should be called), we're "guaranteed" that prop_work is > not > + * active (tl;dr This Should Never Happen). > + */ > + drm_WARN_ON(connector->dev, work_pending(&data->prop_work)); > + > + kfree(data); > +} > +EXPORT_SYMBOL(drm_hdcp_helper_destroy); > diff --git a/include/drm/drm_hdcp.h b/include/drm/drm_hdcp.h > index e6e3d16bc7d3..69c6405db5d1 100644 > --- a/include/drm/drm_hdcp.h > +++ b/include/drm/drm_hdcp.h > @@ -36,6 +36,7 @@ > #define DRM_HDCP_DDC_BKSV 0x00 > #define DRM_HDCP_DDC_RI_PRIME 0x08 > #define DRM_HDCP_DDC_AKSV 0x10 > +#define DRM_HDCP_DDC_AINFO 0x15 > #define DRM_HDCP_DDC_AN 0x18 > #define DRM_HDCP_DDC_V_PRIME(h) (0x20 + h * 4) > #define DRM_HDCP_DDC_BCAPS 0x40 > @@ -295,6 +296,19 @@ struct drm_atomic_state; > struct drm_device; > struct drm_connector; > > +struct drm_hdcp_ksv { > + union { > + u32 words[2]; > + u8 bytes[DRM_HDCP_KSV_LEN]; > + }; > +}; > +struct drm_hdcp_an { > + union { > + u32 words[2]; > + u8 bytes[DRM_HDCP_AN_LEN]; > + }; > +}; > + > int drm_hdcp_check_ksvs_revoked(struct drm_device *dev, > u8 *ksvs, u32 ksv_count); > int drm_connector_attach_content_protection_property( > @@ -303,9 +317,186 @@ void drm_hdcp_update_content_protection(struct > drm_connector *connector, > u64 val); > bool drm_hdcp_atomic_check(struct drm_connector *connector, > struct drm_atomic_state *state); > +void drm_hdcp_atomic_commit(struct drm_atomic_state *state, > + struct drm_connector *connector); > > /* Content Type classification for HDCP2.2 vs others */ > #define DRM_MODE_HDCP_CONTENT_TYPE0 0 > #define DRM_MODE_HDCP_CONTENT_TYPE1 1 > > +/** > + * struct drm_hdcp_helper_funcs - A vtable of function hooks for the > hdcp helper > + * > + * These hooks are used by the hdcp helper to call into the > driver/connector > + * code to read/write to hw. > + */ > +struct drm_hdcp_helper_funcs { > + /** > + * @setup - Performs driver-specific setup before hdcp is enabled > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*setup)(struct drm_connector *connector, > + struct drm_atomic_state *state); > + > + /** > + * @are_keys_valid - Checks if the HDCP transmitter keys are valid > + * > + * Returns: true if the display controller has valid keys loaded > + */ > + bool (*are_keys_valid)(struct drm_connector *connector); > + > + /** > + * @load_keys - Instructs the driver to load its HDCP transmitter > keys > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*load_keys)(struct drm_connector *connector); > + > + /** > + * @hdcp2_capable - Checks if both source and sink support HDCP 2.x > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp2_capable)(struct drm_connector *connector, bool *capable); > + > + /** > + * @hdcp2_enable - Enables HDCP 2.x on the specified connector > + * > + * Since we don't have multiple examples of HDCP 2.x enablement, we > + * provide the bare minimum support for HDCP 2.x help. Once we have > + * more examples, perhaps we can be more helpful. > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp2_enable)(struct drm_connector *connector); > + > + /** > + * @hdcp2_check_link - Checks the HDCP 2.x link on a specified > connector > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp2_check_link)(struct drm_connector *connector); > + > + /** > + * @hdcp2_disable - Disables HDCP 2.x on the specified connector > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp2_disable)(struct drm_connector *connector); > + > + /** > + * @hdcp1_read_an_aksv - Reads transmitter's An & Aksv from hardware > + * > + * Use this function if hardware allows reading the transmitter's An > and > + * Aksv values from the kernel. If your hardware will not allow this, > + * use hdcp1_send_an_aksv() and implement the transmission in the > + * driver. > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_read_an_aksv)(struct drm_connector *connector, u32 *an, > + u32 *aksv); > + > + /** > + * @hdcp1_send_an_aksv - Sends transmitter's An & Aksv to the > receiver > + * > + * Only implement this on hardware where An or Aksv are not > accessible > + * from the kernel. If these values can be read, use > + * hdcp1_read_an_aksv() instead. > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_send_an_aksv)(struct drm_connector *connector); > + > + /** > + * @hdcp1_store_receiver_info - Stores the receiver's info in the > transmitter > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_store_receiver_info)(struct drm_connector *connector, > + u32 *ksv, u32 status, u8 caps, > + bool repeater_present); > + > + /** > + * @hdcp1_enable_encryption - Enables encryption of the outgoing > signal > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_enable_encryption)(struct drm_connector *connector); > + > + /** > + * @hdcp1_wait_for_r0 - Wait for transmitter to calculate R0 > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_wait_for_r0)(struct drm_connector *connector); > + > + /** > + * @hdcp1_match_ri - Matches the given Ri from the receiver with Ri > in > + * the transmitter > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_match_ri)(struct drm_connector *connector, u32 ri_prime); > + > + /** > + * @hdcp1_post_encryption - Allows the driver to confirm encryption > and > + * perform any post-processing > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_post_encryption)(struct drm_connector *connector); > + > + /** > + * @hdcp1_store_ksv_fifo - Write the receiver's KSV list to > transmitter > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_store_ksv_fifo)(struct drm_connector *connector, > + u8 *ksv_fifo, u8 num_downstream, > + u8 *bstatus, u32 *vprime); > + > + /** > + * @hdcp1_check_link - Allows the driver to check the HDCP 1.x status > + * on a specified connector > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_check_link)(struct drm_connector *connector); > + > + /** > + * @hdcp1_disable - Disables HDCP 1.x on the specified connector > + * > + * Returns: 0 on success, -errno on failure > + */ > + int (*hdcp1_disable)(struct drm_connector *connector); > +}; > + > +struct drm_hdcp_helper_data; > +struct drm_dp_aux; > +struct i2c_adapter; > +struct mutex; > + > +struct drm_hdcp_helper_data * > +drm_hdcp_helper_initialize_dp(struct drm_connector *connector, > + struct drm_dp_aux *aux, > + const struct drm_hdcp_helper_funcs *funcs, > + bool attach_content_type_property); > + > +struct drm_hdcp_helper_data * > +drm_hdcp_helper_initialize_hdmi(struct drm_connector *connector, > + const struct drm_hdcp_helper_funcs *funcs, > + bool attach_content_type_property); > + > +void drm_hdcp_helper_destroy(struct drm_hdcp_helper_data *data); > + > +int drm_hdcp_helper_hdcp1_capable(struct drm_hdcp_helper_data *data, > + bool *capable); > +void drm_hdcp_helper_atomic_commit(struct drm_hdcp_helper_data *data, > + struct drm_atomic_state *state, > + struct mutex *driver_mutex); > + > +void drm_hdcp_helper_schedule_hdcp_check(struct drm_hdcp_helper_data > *data); > + > #endif
On Tue, Sep 21, 2021 at 04:34:59PM -0700, abhinavk@codeaurora.org wrote: > On 2021-09-15 13:38, Sean Paul wrote: > > From: Sean Paul <seanpaul@chromium.org> > > > > This patch expands upon the HDCP helper library to manage HDCP > > enable, disable, and check. > > > > Previous to this patch, the majority of the state management and sink > > interaction is tucked inside the Intel driver with the understanding > > that once a new platform supported HDCP we could make good decisions > > about what should be centralized. With the addition of HDCP support > > for Qualcomm, it's time to migrate the protocol-specific bits of HDCP > > authentication, key exchange, and link checks to the HDCP helper. > > > > In terms of functionality, this migration is 1:1 with the Intel driver, > > however things are laid out a bit differently than with intel_hdcp.c, > > which is why this is a separate patch from the i915 transition to the > > helper. On i915, the "shim" vtable is used to account for HDMI vs. DP > > vs. DP-MST differences whereas the helper library uses a LUT to > > account for the register offsets and a remote read function to route > > the messages. On i915, storing the sink information in the source is > > done inline whereas now we use the new drm_hdcp_helper_funcs vtable > > to store and fetch information to/from source hw. Finally, instead of > > calling enable/disable directly from the driver, we'll leave that > > decision to the helper and by calling drm_hdcp_helper_atomic_commit() > > from the driver. All told, this will centralize the protocol and state > > handling in the helper, ensuring we collect all of our bugs^Wlogic > > in one place. > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org> > > Link: > > https://patchwork.freedesktop.org/patch/msgid/20210913175747.47456-5-sean@poorly.run > > #v1 > > > > Changes in v2: > > -Fixed set-but-unused variable identified by 0-day > > --- > > drivers/gpu/drm/drm_hdcp.c | 1103 ++++++++++++++++++++++++++++++++++++ > > include/drm/drm_hdcp.h | 191 +++++++ > > 2 files changed, 1294 insertions(+) > > > > diff --git a/drivers/gpu/drm/drm_hdcp.c b/drivers/gpu/drm/drm_hdcp.c > > index 742313ce8f6f..47c6e6923a76 100644 > > --- a/drivers/gpu/drm/drm_hdcp.c > > +++ b/drivers/gpu/drm/drm_hdcp.c /snip > > +static void drm_hdcp_helper_check_work(struct work_struct *work) > > +{ > > + struct drm_hdcp_helper_data *data = > > container_of(to_delayed_work(work), > > + struct drm_hdcp_helper_data, > > + check_work); > > + unsigned long check_link_interval; > > + > > Does this SW polling for Ri' mismatch need to be done even if the HW is > capable of doing it > on its own? > MSM HDCP 1x HW can periodically check Ri' mismatches and issue an interrupt > if there is a mismatch. > In that case this SW polling is not needed. So maybe check if the HW > supports polling and if so > skip this SW polling? > One could certainly change this to be HW driven. There is also an interrupt on Intel for DP links which [re]schedules link check in the interrupt handler, something similar could be done for msm. Note that even on these Intel links which support the CP interrupt, the worker still runs on the normal cadence. I haven't considered relying solely on the interrupt since I want to be sure we didn't miss anything. Sean > > + mutex_lock(&data->mutex); > > + if (data->value != DRM_MODE_CONTENT_PROTECTION_ENABLED) > > + goto out_data_mutex; > > + > > + drm_hdcp_helper_driver_lock(data); > > + > > + if (data->enabled_type == DRM_MODE_HDCP_CONTENT_TYPE1) { > > + if (drm_hdcp_hdcp2_check_link(data)) > > + goto out; > > + check_link_interval = DRM_HDCP2_CHECK_PERIOD_MS; > > + } else { > > + if (drm_hdcp_hdcp1_check_link(data)) > > + goto out; > > + check_link_interval = DRM_HDCP_CHECK_PERIOD_MS; > > + } > > + schedule_delayed_work(&data->check_work, check_link_interval); > > + > > +out: > > + drm_hdcp_helper_driver_unlock(data); > > +out_data_mutex: > > + mutex_unlock(&data->mutex); > > +} /snip
Hi Sean On 2021-09-28 10:33, Sean Paul wrote: > On Tue, Sep 21, 2021 at 04:34:59PM -0700, abhinavk@codeaurora.org > wrote: >> On 2021-09-15 13:38, Sean Paul wrote: >> > From: Sean Paul <seanpaul@chromium.org> >> > >> > This patch expands upon the HDCP helper library to manage HDCP >> > enable, disable, and check. >> > >> > Previous to this patch, the majority of the state management and sink >> > interaction is tucked inside the Intel driver with the understanding >> > that once a new platform supported HDCP we could make good decisions >> > about what should be centralized. With the addition of HDCP support >> > for Qualcomm, it's time to migrate the protocol-specific bits of HDCP >> > authentication, key exchange, and link checks to the HDCP helper. >> > >> > In terms of functionality, this migration is 1:1 with the Intel driver, >> > however things are laid out a bit differently than with intel_hdcp.c, >> > which is why this is a separate patch from the i915 transition to the >> > helper. On i915, the "shim" vtable is used to account for HDMI vs. DP >> > vs. DP-MST differences whereas the helper library uses a LUT to >> > account for the register offsets and a remote read function to route >> > the messages. On i915, storing the sink information in the source is >> > done inline whereas now we use the new drm_hdcp_helper_funcs vtable >> > to store and fetch information to/from source hw. Finally, instead of >> > calling enable/disable directly from the driver, we'll leave that >> > decision to the helper and by calling drm_hdcp_helper_atomic_commit() >> > from the driver. All told, this will centralize the protocol and state >> > handling in the helper, ensuring we collect all of our bugs^Wlogic >> > in one place. >> > >> > Signed-off-by: Sean Paul <seanpaul@chromium.org> >> > Link: >> > https://patchwork.freedesktop.org/patch/msgid/20210913175747.47456-5-sean@poorly.run >> > #v1 >> > >> > Changes in v2: >> > -Fixed set-but-unused variable identified by 0-day >> > --- >> > drivers/gpu/drm/drm_hdcp.c | 1103 ++++++++++++++++++++++++++++++++++++ >> > include/drm/drm_hdcp.h | 191 +++++++ >> > 2 files changed, 1294 insertions(+) >> > >> > diff --git a/drivers/gpu/drm/drm_hdcp.c b/drivers/gpu/drm/drm_hdcp.c >> > index 742313ce8f6f..47c6e6923a76 100644 >> > --- a/drivers/gpu/drm/drm_hdcp.c >> > +++ b/drivers/gpu/drm/drm_hdcp.c > > /snip > >> > +static void drm_hdcp_helper_check_work(struct work_struct *work) >> > +{ >> > + struct drm_hdcp_helper_data *data = >> > container_of(to_delayed_work(work), >> > + struct drm_hdcp_helper_data, >> > + check_work); >> > + unsigned long check_link_interval; >> > + >> >> Does this SW polling for Ri' mismatch need to be done even if the HW >> is >> capable of doing it >> on its own? >> MSM HDCP 1x HW can periodically check Ri' mismatches and issue an >> interrupt >> if there is a mismatch. >> In that case this SW polling is not needed. So maybe check if the HW >> supports polling and if so >> skip this SW polling? >> > > One could certainly change this to be HW driven. There is also an > interrupt on > Intel for DP links which [re]schedules link check in the interrupt > handler, > something similar could be done for msm. Note that even on these Intel > links > which support the CP interrupt, the worker still runs on the normal > cadence. I > haven't considered relying solely on the interrupt since I want to be > sure we > didn't miss anything. > > Sean I think we should have the support for HW polling added. From our experience, it has been pretty reliable for us and has a pretty consistent cadence in alignment with the spec. I dont quite understand why we should have both in chipsets capable of HW polling and am bit surprised if SW polling will catch something which HW polling and the subsequent interrupt has missed. > >> > + mutex_lock(&data->mutex); >> > + if (data->value != DRM_MODE_CONTENT_PROTECTION_ENABLED) >> > + goto out_data_mutex; >> > + >> > + drm_hdcp_helper_driver_lock(data); >> > + >> > + if (data->enabled_type == DRM_MODE_HDCP_CONTENT_TYPE1) { >> > + if (drm_hdcp_hdcp2_check_link(data)) >> > + goto out; >> > + check_link_interval = DRM_HDCP2_CHECK_PERIOD_MS; >> > + } else { >> > + if (drm_hdcp_hdcp1_check_link(data)) >> > + goto out; >> > + check_link_interval = DRM_HDCP_CHECK_PERIOD_MS; >> > + } >> > + schedule_delayed_work(&data->check_work, check_link_interval); >> > + >> > +out: >> > + drm_hdcp_helper_driver_unlock(data); >> > +out_data_mutex: >> > + mutex_unlock(&data->mutex); >> > +} > > /snip
diff --git a/drivers/gpu/drm/drm_hdcp.c b/drivers/gpu/drm/drm_hdcp.c index 742313ce8f6f..47c6e6923a76 100644 --- a/drivers/gpu/drm/drm_hdcp.c +++ b/drivers/gpu/drm/drm_hdcp.c @@ -6,15 +6,20 @@ * Ramalingam C <ramalingam.c@intel.com> */ +#include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> #include <linux/gfp.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> #include <linux/export.h> #include <linux/slab.h> #include <linux/firmware.h> +#include <linux/workqueue.h> #include <drm/drm_atomic.h> #include <drm/drm_connector.h> +#include <drm/drm_dp_helper.h> #include <drm/drm_hdcp.h> #include <drm/drm_sysfs.h> #include <drm/drm_print.h> @@ -513,3 +518,1101 @@ bool drm_hdcp_atomic_check(struct drm_connector *connector, return old_hdcp != new_hdcp; } EXPORT_SYMBOL(drm_hdcp_atomic_check); + +struct drm_hdcp_helper_data { + struct mutex mutex; + struct mutex *driver_mutex; + + struct drm_connector *connector; + const struct drm_hdcp_helper_funcs *funcs; + + u64 value; + unsigned int enabled_type; + + struct delayed_work check_work; + struct work_struct prop_work; + + struct drm_dp_aux *aux; + const struct drm_hdcp_hdcp1_receiver_reg_lut *hdcp1_lut; +}; + +struct drm_hdcp_hdcp1_receiver_reg_lut { + unsigned int bksv; + unsigned int ri; + unsigned int aksv; + unsigned int an; + unsigned int ainfo; + unsigned int v[5]; + unsigned int bcaps; + unsigned int bcaps_mask_repeater_present; + unsigned int bstatus; +}; + +static const struct drm_hdcp_hdcp1_receiver_reg_lut drm_hdcp_hdcp1_ddc_lut = { + .bksv = DRM_HDCP_DDC_BKSV, + .ri = DRM_HDCP_DDC_RI_PRIME, + .aksv = DRM_HDCP_DDC_AKSV, + .an = DRM_HDCP_DDC_AN, + .ainfo = DRM_HDCP_DDC_AINFO, + .v = { DRM_HDCP_DDC_V_PRIME(0), DRM_HDCP_DDC_V_PRIME(1), + DRM_HDCP_DDC_V_PRIME(2), DRM_HDCP_DDC_V_PRIME(3), + DRM_HDCP_DDC_V_PRIME(4) }, + .bcaps = DRM_HDCP_DDC_BCAPS, + .bcaps_mask_repeater_present = DRM_HDCP_DDC_BCAPS_REPEATER_PRESENT, + .bstatus = DRM_HDCP_DDC_BSTATUS, +}; + +static const struct drm_hdcp_hdcp1_receiver_reg_lut drm_hdcp_hdcp1_dpcd_lut = { + .bksv = DP_AUX_HDCP_BKSV, + .ri = DP_AUX_HDCP_RI_PRIME, + .aksv = DP_AUX_HDCP_AKSV, + .an = DP_AUX_HDCP_AN, + .ainfo = DP_AUX_HDCP_AINFO, + .v = { DP_AUX_HDCP_V_PRIME(0), DP_AUX_HDCP_V_PRIME(1), + DP_AUX_HDCP_V_PRIME(2), DP_AUX_HDCP_V_PRIME(3), + DP_AUX_HDCP_V_PRIME(4) }, + .bcaps = DP_AUX_HDCP_BCAPS, + .bcaps_mask_repeater_present = DP_BCAPS_REPEATER_PRESENT, + + /* + * For some reason the HDMI and DP HDCP specs call this register + * definition by different names. In the HDMI spec, it's called BSTATUS, + * but in DP it's called BINFO. + */ + .bstatus = DP_AUX_HDCP_BINFO, +}; + +static int drm_hdcp_remote_ddc_read(struct i2c_adapter *i2c, + unsigned int offset, u8 *value, size_t len) +{ + int ret; + u8 start = offset & 0xff; + struct i2c_msg msgs[] = { + { + .addr = DRM_HDCP_DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &start, + }, + { + .addr = DRM_HDCP_DDC_ADDR, + .flags = I2C_M_RD, + .len = len, + .buf = value + } + }; + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret == ARRAY_SIZE(msgs)) + return 0; + return ret >= 0 ? -EIO : ret; +} + +static int drm_hdcp_remote_dpcd_read(struct drm_dp_aux *aux, + unsigned int offset, u8 *value, + size_t len) +{ + ssize_t ret; + + ret = drm_dp_dpcd_read(aux, offset, value, len); + if (ret != len) { + if (ret >= 0) + return -EIO; + return ret; + } + + return 0; +} + +static int drm_hdcp_remote_read(struct drm_hdcp_helper_data *data, + unsigned int offset, u8 *value, u8 len) +{ + if (data->aux) + return drm_hdcp_remote_dpcd_read(data->aux, offset, value, len); + else + return drm_hdcp_remote_ddc_read(data->connector->ddc, offset, value, len); +} + +static int drm_hdcp_remote_ddc_write(struct i2c_adapter *i2c, + unsigned int offset, u8 *buffer, + size_t size) +{ + int ret; + u8 *write_buf; + struct i2c_msg msg; + + write_buf = kzalloc(size + 1, GFP_KERNEL); + if (!write_buf) + return -ENOMEM; + + write_buf[0] = offset & 0xff; + memcpy(&write_buf[1], buffer, size); + + msg.addr = DRM_HDCP_DDC_ADDR; + msg.flags = 0, + msg.len = size + 1, + msg.buf = write_buf; + + ret = i2c_transfer(i2c, &msg, 1); + if (ret == 1) + ret = 0; + else if (ret >= 0) + ret = -EIO; + + kfree(write_buf); + return ret; +} + +static int drm_hdcp_remote_dpcd_write(struct drm_dp_aux *aux, + unsigned int offset, u8 *value, + size_t len) +{ + ssize_t ret; + + ret = drm_dp_dpcd_write(aux, offset, value, len); + if (ret != len) { + if (ret >= 0) + return -EIO; + return ret; + } + + return 0; +} + +static int drm_hdcp_remote_write(struct drm_hdcp_helper_data *data, + unsigned int offset, u8 *value, u8 len) +{ + if (data->aux) + return drm_hdcp_remote_dpcd_write(data->aux, offset, value, len); + else + return drm_hdcp_remote_ddc_write(data->connector->ddc, offset, + value, len); +} + +static bool drm_hdcp_is_ksv_valid(struct drm_hdcp_ksv *ksv) +{ + /* Valid Ksv has 20 0's and 20 1's */ + return hweight32(ksv->words[0]) + hweight32(ksv->words[1]) == 20; +} + +static int drm_hdcp_read_valid_bksv(struct drm_hdcp_helper_data *data, + struct drm_hdcp_ksv *bksv) +{ + int ret, i, tries = 2; + + /* HDCP spec states that we must retry the bksv if it is invalid */ + for (i = 0; i < tries; i++) { + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bksv, + bksv->bytes, DRM_HDCP_KSV_LEN); + if (ret) + return ret; + + if (drm_hdcp_is_ksv_valid(bksv)) + break; + } + if (i == tries) { + drm_dbg_kms(data->connector->dev, "Bksv is invalid %*ph\n", + DRM_HDCP_KSV_LEN, bksv->bytes); + return -ENODEV; + } + + return 0; +} + +/** + * drm_hdcp_helper_hdcp1_capable - Checks if the sink is capable of HDCP 1.x. + * + * @data: pointer to the HDCP helper data. + * @capable: pointer to a bool which will contain true if the sink is capable. + * + * Returns: + * -errno if the transacation between source and sink fails. + */ +int drm_hdcp_helper_hdcp1_capable(struct drm_hdcp_helper_data *data, + bool *capable) +{ + /* + * DisplayPort has a dedicated bit for this in DPCD whereas HDMI spec + * states that transmitters should use bksv to determine capability. + */ + if (data->aux) { + int ret; + u8 bcaps; + + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bcaps, + &bcaps, 1); + *capable = !ret && (bcaps & DP_BCAPS_HDCP_CAPABLE); + } else { + struct drm_hdcp_ksv bksv; + + *capable = drm_hdcp_read_valid_bksv(data, &bksv) == 0; + } + + return 0; +} +EXPORT_SYMBOL(drm_hdcp_helper_hdcp1_capable); + +static void drm_hdcp_update_value(struct drm_hdcp_helper_data *data, + u64 value, bool update_property) +{ + WARN_ON(!mutex_is_locked(&data->mutex)); + + data->value = value; + if (update_property) { + drm_connector_get(data->connector); + schedule_work(&data->prop_work); + } +} + +static int +drm_hdcp_helper_hdcp1_ksv_fifo_ready(struct drm_hdcp_helper_data *data) +{ + int ret; + u8 val, mask; + + /* KSV FIFO ready bit is stored in different locations on DP v. HDMI */ + if (data->aux) { + ret = drm_hdcp_remote_dpcd_read(data->aux, DP_AUX_HDCP_BSTATUS, + &val, 1); + mask = DP_BSTATUS_READY; + } else { + ret = drm_hdcp_remote_ddc_read(data->connector->ddc, + DRM_HDCP_DDC_BCAPS, &val, 1); + mask = DRM_HDCP_DDC_BCAPS_KSV_FIFO_READY; + } + if (ret) + return ret; + if (val & mask) + return 0; + + return -EAGAIN; +} + +static int +drm_hdcp_helper_hdcp1_read_ksv_fifo(struct drm_hdcp_helper_data *data, u8 *fifo, + u8 num_downstream) +{ + struct drm_device *dev = data->connector->dev; + int ret, i; + + /* Over HDMI, read the whole thing at once */ + if (data->connector->ddc) { + ret = drm_hdcp_remote_ddc_read(data->connector->ddc, + DRM_HDCP_DDC_KSV_FIFO, fifo, + num_downstream * DRM_HDCP_KSV_LEN); + if (ret) + drm_err(dev, "DDC ksv fifo read failed (%d)\n", ret); + return ret; + } + + /* Over DP, read via 15 byte window (3 entries @ 5 bytes each) */ + for (i = 0; i < num_downstream; i += 3) { + size_t len = min(num_downstream - i, 3) * DRM_HDCP_KSV_LEN; + ret = drm_hdcp_remote_dpcd_read(data->aux, DP_AUX_HDCP_KSV_FIFO, + fifo + i * DRM_HDCP_KSV_LEN, + len); + if (ret) { + drm_err(dev, "Read ksv[%d] from DP/AUX failed (%d)\n", + i, ret); + return ret; + } + } + + return 0; +} + +static int drm_hdcp_helper_hdcp1_read_v_prime(struct drm_hdcp_helper_data *data, + u32 *v_prime) +{ + struct drm_device *dev = data->connector->dev; + int ret, i; + + for (i = 0; i < DRM_HDCP_V_PRIME_NUM_PARTS; i++) { + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->v[i], + (u8 *)&v_prime[i], + DRM_HDCP_V_PRIME_PART_LEN); + if (ret) { + drm_dbg_kms(dev, "Read v'[%d] from failed (%d)\n", i, ret); + return ret >= 0 ? -EIO : ret; + } + } + return 0; +} + +static int +drm_hdcp_helper_hdcp1_authenticate_downstream(struct drm_hdcp_helper_data *data) +{ + struct drm_connector *connector = data->connector; + struct drm_device *dev = connector->dev; + u32 v_prime[DRM_HDCP_V_PRIME_NUM_PARTS]; + u8 bstatus[DRM_HDCP_BSTATUS_LEN]; + u8 num_downstream, *ksv_fifo; + int ret, i, tries = 3; + + ret = read_poll_timeout(drm_hdcp_helper_hdcp1_ksv_fifo_ready, ret, !ret, + 10 * 1000, 5 * 1000 * 1000, false, data); + if (ret) { + drm_err(dev, "Failed to poll ksv ready, %d\n", ret); + return ret; + } + + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bstatus, + bstatus, DRM_HDCP_BSTATUS_LEN); + if (ret) + return ret; + + /* + * When repeater reports 0 device count, HDCP1.4 spec allows disabling + * the HDCP encryption. That implies that repeater can't have its own + * display. As there is no consumption of encrypted content in the + * repeater with 0 downstream devices, we are failing the + * authentication. + */ + num_downstream = DRM_HDCP_NUM_DOWNSTREAM(bstatus[0]); + if (num_downstream == 0) { + drm_err(dev, "Repeater with zero downstream devices, %*ph\n", + DRM_HDCP_BSTATUS_LEN, bstatus); + return -EINVAL; + } + + ksv_fifo = kcalloc(DRM_HDCP_KSV_LEN, num_downstream, GFP_KERNEL); + if (!ksv_fifo) + return -ENOMEM; + + ret = drm_hdcp_helper_hdcp1_read_ksv_fifo(data, ksv_fifo, + num_downstream); + if (ret) { + drm_err(dev, "Failed to read ksv fifo, %d/%d\n", num_downstream, + ret); + goto out; + } + + if (drm_hdcp_check_ksvs_revoked(dev, ksv_fifo, num_downstream)) { + drm_err(dev, "Revoked Ksv(s) in ksv_fifo\n"); + ret = -EPERM; + goto out; + } + + /* + * When V prime mismatches, DP Spec mandates re-read of + * V prime atleast twice. + */ + for (i = 0; i < tries; i++) { + ret = drm_hdcp_helper_hdcp1_read_v_prime(data, v_prime); + if (ret) + continue; + + ret = data->funcs->hdcp1_store_ksv_fifo(connector, ksv_fifo, + num_downstream, + bstatus, v_prime); + if (!ret) + break; + } + if (ret) + drm_err(dev, "Could not validate KSV FIFO with V' %d\n", ret); + +out: + if (!ret) + drm_dbg_kms(dev, "HDCP is enabled (%d downstream devices)\n", + num_downstream); + + kfree(ksv_fifo); + return ret; +} + +static int drm_hdcp_helper_hdcp1_validate_ri(struct drm_hdcp_helper_data *data) +{ + union { + u32 word; + u8 bytes[DRM_HDCP_RI_LEN]; + } ri_prime = { .word = 0 }; + struct drm_connector *connector = data->connector; + struct drm_device *dev = connector->dev; + int ret; + + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->ri, ri_prime.bytes, + DRM_HDCP_RI_LEN); + if (ret) { + drm_err(dev, "Failed to read R0' %d\n", ret); + return ret; + } + + return data->funcs->hdcp1_match_ri(connector, ri_prime.word); +} + +static int drm_hdcp_helper_hdcp1_authenticate(struct drm_hdcp_helper_data *data) +{ + union { + u32 word; + u8 bytes[DRM_HDCP_BSTATUS_LEN]; + } bstatus; + const struct drm_hdcp_helper_funcs *funcs = data->funcs; + struct drm_connector *connector = data->connector; + struct drm_device *dev = connector->dev; + unsigned long r0_prime_timeout, r0_prime_remaining_us = 0, tmp_jiffies; + struct drm_hdcp_ksv aksv; + struct drm_hdcp_ksv bksv; + struct drm_hdcp_an an; + bool repeater_present; + int ret, i, tries = 3; + u8 bcaps; + + if (funcs->hdcp1_read_an_aksv) { + ret = funcs->hdcp1_read_an_aksv(connector, an.words, aksv.words); + if (ret) { + drm_err(dev, "Failed to read An/Aksv values, %d\n", ret); + return ret; + } + + ret = drm_hdcp_remote_write(data, data->hdcp1_lut->an, an.bytes, + DRM_HDCP_AN_LEN); + if (ret) { + drm_err(dev, "Failed to write An to receiver, %d\n", ret); + return ret; + } + + ret = drm_hdcp_remote_write(data, data->hdcp1_lut->aksv, aksv.bytes, + DRM_HDCP_KSV_LEN); + if (ret) { + drm_err(dev, "Failed to write Aksv to receiver, %d\n", ret); + return ret; + } + } else { + ret = funcs->hdcp1_send_an_aksv(connector); + if (ret) { + drm_err(dev, "Failed to read An/Aksv values, %d\n", ret); + return ret; + } + } + + /* + * Timeout for R0' to become available. The spec says 100ms from Aksv, + * but some monitors can take longer than this. We'll set the timeout at + * 300ms just to be sure. + */ + r0_prime_timeout = jiffies + msecs_to_jiffies(300); + + memset(&bksv, 0, sizeof(bksv)); + + ret = drm_hdcp_read_valid_bksv(data, &bksv); + if (ret < 0) + return ret; + + if (drm_hdcp_check_ksvs_revoked(dev, bksv.bytes, 1)) { + drm_err(dev, "BKSV is revoked\n"); + return -EPERM; + } + + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bcaps, &bcaps, 1); + if (ret) + return ret; + + memset(&bstatus, 0, sizeof(bstatus)); + + ret = drm_hdcp_remote_read(data, data->hdcp1_lut->bstatus, + bstatus.bytes, DRM_HDCP_BSTATUS_LEN); + if (ret) + return ret; + + if (DRM_HDCP_MAX_DEVICE_EXCEEDED(bstatus.bytes[0]) || + DRM_HDCP_MAX_CASCADE_EXCEEDED(bstatus.bytes[1])) { + drm_err(dev, "Max Topology Limit Exceeded, bstatus=%*ph\n", + DRM_HDCP_BSTATUS_LEN, bstatus.bytes); + return -EPERM; + } + + repeater_present = bcaps & data->hdcp1_lut->bcaps_mask_repeater_present; + + ret = funcs->hdcp1_store_receiver_info(connector, bksv.words, + bstatus.word, bcaps, + repeater_present); + if (ret) { + drm_err(dev, "Failed to store bksv, %d\n", ret); + return ret; + } + + ret = funcs->hdcp1_enable_encryption(connector); + if (ret) + return ret; + + ret = funcs->hdcp1_wait_for_r0(connector); + if (ret) + return ret; + + tmp_jiffies = jiffies; + if (time_before(tmp_jiffies, r0_prime_timeout)) + r0_prime_remaining_us = jiffies_to_usecs(r0_prime_timeout - tmp_jiffies); + + /* + * Wait for R0' to become available. + * + * On DP, there's an R0_READY bit available but no such bit + * exists on HDMI. So poll the ready bit for DP and just wait the + * remainder of the 300 ms timeout for HDMI. + */ + if (data->aux) { + u8 val; + ret = read_poll_timeout(drm_hdcp_remote_dpcd_read, ret, + !ret && (val & DP_BSTATUS_R0_PRIME_READY), + 1000, r0_prime_remaining_us, false, + data->aux, DP_AUX_HDCP_BSTATUS, &val, 1); + if (ret) { + drm_err(dev, "R0' did not become ready %d\n", ret); + return ret; + } + } else { + usleep_range(r0_prime_remaining_us, + r0_prime_remaining_us + 1000); + } + + /* + * DP HDCP Spec mandates the two more reattempt to read R0, incase + * of R0 mismatch. + */ + for (i = 0; i < tries; i++) { + ret = drm_hdcp_helper_hdcp1_validate_ri(data); + if (!ret) + break; + } + if (ret) { + drm_err(dev, "Failed to match R0/R0', aborting HDCP %d\n", ret); + return ret; + } + + if (repeater_present) + return drm_hdcp_helper_hdcp1_authenticate_downstream(data); + + drm_dbg_kms(dev, "HDCP is enabled (no repeater present)\n"); + return 0; +} + +static int drm_hdcp_helper_hdcp1_enable(struct drm_hdcp_helper_data *data) +{ + struct drm_connector *connector = data->connector; + struct drm_device *dev = connector->dev; + int i, ret, tries = 3; + + drm_dbg_kms(dev, "[%s:%d] HDCP is being enabled...\n", connector->name, + connector->base.id); + + /* Incase of authentication failures, HDCP spec expects reauth. */ + for (i = 0; i < tries; i++) { + ret = drm_hdcp_helper_hdcp1_authenticate(data); + if (!ret) + return 0; + + drm_dbg_kms(dev, "HDCP Auth failure (%d)\n", ret); + + /* Ensuring HDCP encryption and signalling are stopped. */ + data->funcs->hdcp1_disable(data->connector); + } + + drm_err(dev, "HDCP authentication failed (%d tries/%d)\n", tries, ret); + return ret; +} + +static inline +void drm_hdcp_helper_driver_lock(struct drm_hdcp_helper_data *data) +{ + if (data->driver_mutex) + mutex_lock(data->driver_mutex); +} + +static inline +void drm_hdcp_helper_driver_unlock(struct drm_hdcp_helper_data *data) +{ + if (data->driver_mutex) + mutex_unlock(data->driver_mutex); +} + +static int drm_hdcp_helper_enable_hdcp(struct drm_hdcp_helper_data *data, + struct drm_atomic_state *state, + struct mutex *driver_mutex) +{ + struct drm_connector *connector = data->connector; + struct drm_connector_state *conn_state; + struct drm_device *dev = connector->dev; + unsigned long check_link_interval; + bool capable; + int ret = 0; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + + mutex_lock(&data->mutex); + + if (data->value == DRM_MODE_CONTENT_PROTECTION_ENABLED) { + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_ENABLED, + true); + goto out_data_mutex; + } + + drm_WARN_ON(dev, data->driver_mutex != NULL); + data->driver_mutex = driver_mutex; + + drm_hdcp_helper_driver_lock(data); + + if (data->funcs->setup) { + ret = data->funcs->setup(connector, state); + if (ret) { + drm_err(dev, "Failed to setup HDCP %d\n", ret); + goto out; + } + } + + if (!data->funcs->are_keys_valid || + !data->funcs->are_keys_valid(connector)) { + if (data->funcs->load_keys) { + ret = data->funcs->load_keys(connector); + if (ret) { + drm_err(dev, "Failed to load HDCP keys %d\n", ret); + goto out; + } + } + } + + /* + * Considering that HDCP2.2 is more secure than HDCP1.4, If the setup + * is capable of HDCP2.2, it is preferred to use HDCP2.2. + */ + ret = data->funcs->hdcp2_capable(connector, &capable); + if (ret) { + drm_err(dev, "HDCP 2.x capability check failed %d\n", ret); + goto out; + } + if (capable) { + data->enabled_type = DRM_MODE_HDCP_CONTENT_TYPE1; + ret = data->funcs->hdcp2_enable(connector); + if (!ret) { + check_link_interval = DRM_HDCP2_CHECK_PERIOD_MS; + goto out; + } + } + + /* + * When HDCP2.2 fails and Content Type is not Type1, HDCP1.4 will + * be attempted. + */ + ret = drm_hdcp_helper_hdcp1_capable(data, &capable); + if (ret) { + drm_err(dev, "HDCP 1.x capability check failed %d\n", ret); + goto out; + } + if (capable && conn_state->content_type != DRM_MODE_HDCP_CONTENT_TYPE1) { + data->enabled_type = DRM_MODE_HDCP_CONTENT_TYPE0; + ret = drm_hdcp_helper_hdcp1_enable(data); + if (!ret) + check_link_interval = DRM_HDCP_CHECK_PERIOD_MS; + } + +out: + if (!ret) { + schedule_delayed_work(&data->check_work, check_link_interval); + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_ENABLED, + true); + } + + drm_hdcp_helper_driver_unlock(data); + if (ret) + data->driver_mutex = NULL; + +out_data_mutex: + mutex_unlock(&data->mutex); + return ret; +} + +static int drm_hdcp_helper_disable_hdcp(struct drm_hdcp_helper_data *data) +{ + int ret = 0; + + mutex_lock(&data->mutex); + drm_hdcp_helper_driver_lock(data); + + if (data->value == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) + goto out; + + drm_dbg_kms(data->connector->dev, "[%s:%d] HDCP is being disabled...\n", + data->connector->name, data->connector->base.id); + + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_UNDESIRED, true); + + if (data->enabled_type == DRM_MODE_HDCP_CONTENT_TYPE1) + ret = data->funcs->hdcp2_disable(data->connector); + else + ret = data->funcs->hdcp1_disable(data->connector); + + drm_dbg_kms(data->connector->dev, "HDCP is disabled\n"); + +out: + drm_hdcp_helper_driver_unlock(data); + data->driver_mutex = NULL; + mutex_unlock(&data->mutex); + cancel_delayed_work_sync(&data->check_work); + return ret; +} + +/** + * drm_hdcp_helper_atomic_commit - Helper for drivers to call during commit to + * enable/disable HDCP + * + * @data: pointer to the @drm_hdcp_helper_data for the connector + * @state: pointer to the atomic state being committed + * @driver_mutex: driver-provided lock to be used while interacting with the driver + * + * This function can be used by display drivers to determine when HDCP should be + * enabled or disabled based on the connector state. It should be called during + * steady-state commits as well as connector enable/disable. The function will + * handle the HDCP authentication/encryption logic, calling back into the driver + * when source operations are necessary. + * + * @driver_mutex will be retained and used for the duration of the HDCP session + * since it will be needed for link checks and retries. This mutex is useful if + * the driver has shared resources across connectors which must be serialized. + * For example, driver_mutex can be used for MST connectors sharing a common + * encoder which should not be accessed/changed concurrently. When the + * connector's session is torn down, the mutex will be forgotten by the helper + * for this connector until the next session. + */ +void drm_hdcp_helper_atomic_commit(struct drm_hdcp_helper_data *data, + struct drm_atomic_state *state, + struct mutex *driver_mutex) +{ + struct drm_connector *connector = data->connector; + struct drm_connector_state *conn_state; + bool type_changed; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + + type_changed = conn_state->hdcp_content_type != data->enabled_type; + + if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + drm_hdcp_helper_disable_hdcp(data); + return; + } + + if (!conn_state->crtc) { + drm_hdcp_helper_disable_hdcp(data); + + /* Restore property to DESIRED so it's retried later */ + if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) { + mutex_lock(&data->mutex); + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, + true); + mutex_unlock(&data->mutex); + } + return; + } + + /* Already enabled */ + if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) + return; + + /* Disable and re-enable HDCP on content type change */ + if (type_changed) + drm_hdcp_helper_disable_hdcp(data); + + drm_hdcp_helper_enable_hdcp(data, state, driver_mutex); +} +EXPORT_SYMBOL(drm_hdcp_helper_atomic_commit); + +static void drm_hdcp_helper_prop_work(struct work_struct *work) +{ + struct drm_hdcp_helper_data *data = container_of(work, + struct drm_hdcp_helper_data, + prop_work); + struct drm_connector *connector = data->connector; + struct drm_device *dev = connector->dev; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + mutex_lock(&data->mutex); + + /* + * This worker is only used to flip between ENABLED/DESIRED. Either of + * those to UNDESIRED is handled by core. If value == UNDESIRED, + * we're running just after hdcp has been disabled, so just exit + */ + if (data->value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) + drm_hdcp_update_content_protection(connector, data->value); + + mutex_unlock(&data->mutex); + drm_modeset_unlock(&dev->mode_config.connection_mutex); +} + +static int drm_hdcp_hdcp1_check_link(struct drm_hdcp_helper_data *data) +{ + struct drm_connector *connector = data->connector; + struct drm_device *dev = connector->dev; + int ret; + + if (data->funcs->hdcp1_check_link) { + ret = data->funcs->hdcp1_check_link(connector); + if (ret) + goto retry; + } + + /* The link is checked differently for DP and HDMI */ + if (data->aux) { + u8 bstatus; + ret = drm_hdcp_remote_dpcd_read(data->aux, DP_AUX_HDCP_BSTATUS, + &bstatus, 1); + if (ret) { + drm_err(dev, "Failed to read dpcd bstatus, %d\n", ret); + return ret; + } + if (bstatus & (DP_BSTATUS_LINK_FAILURE | DP_BSTATUS_REAUTH_REQ)) + ret = -EINVAL; + } else { + ret = drm_hdcp_helper_hdcp1_validate_ri(data); + if (ret) + drm_err(dev,"Ri' mismatch, check failed (%d)\n", ret); + } + if (!ret) + return 0; + +retry: + drm_err(dev, "[%s:%d] HDCP link failed, retrying authentication\n", + connector->name, connector->base.id); + + ret = data->funcs->hdcp1_disable(connector); + if (ret) { + drm_err(dev, "Failed to disable hdcp (%d)\n", ret); + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, + true); + return ret; + } + + ret = drm_hdcp_helper_hdcp1_enable(data); + if (ret) { + drm_err(dev, "Failed to enable hdcp (%d)\n", ret); + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, + true); + return ret; + } + + return 0; +} + +static int drm_hdcp_hdcp2_check_link(struct drm_hdcp_helper_data *data) +{ + struct drm_connector *connector = data->connector; + struct drm_device *dev = connector->dev; + int ret; + + ret = data->funcs->hdcp2_check_link(connector); + if (!ret) + return 0; + + drm_err(dev, "[%s:%d] HDCP2 link failed, retrying authentication\n", + connector->name, connector->base.id); + + ret = data->funcs->hdcp2_disable(connector); + if (ret) { + drm_err(dev, "Failed to disable hdcp2 (%d)\n", ret); + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, + true); + return ret; + } + + ret = data->funcs->hdcp2_enable(connector); + if (ret) { + drm_err(dev, "Failed to enable hdcp2 (%d)\n", ret); + drm_hdcp_update_value(data, DRM_MODE_CONTENT_PROTECTION_DESIRED, + true); + return ret; + } + + return 0; +} + +static void drm_hdcp_helper_check_work(struct work_struct *work) +{ + struct drm_hdcp_helper_data *data = container_of(to_delayed_work(work), + struct drm_hdcp_helper_data, + check_work); + unsigned long check_link_interval; + + mutex_lock(&data->mutex); + if (data->value != DRM_MODE_CONTENT_PROTECTION_ENABLED) + goto out_data_mutex; + + drm_hdcp_helper_driver_lock(data); + + if (data->enabled_type == DRM_MODE_HDCP_CONTENT_TYPE1) { + if (drm_hdcp_hdcp2_check_link(data)) + goto out; + check_link_interval = DRM_HDCP2_CHECK_PERIOD_MS; + } else { + if (drm_hdcp_hdcp1_check_link(data)) + goto out; + check_link_interval = DRM_HDCP_CHECK_PERIOD_MS; + } + schedule_delayed_work(&data->check_work, check_link_interval); + +out: + drm_hdcp_helper_driver_unlock(data); +out_data_mutex: + mutex_unlock(&data->mutex); +} + +/** + * drm_hdcp_helper_schedule_hdcp_check - Schedule a check link cycle. + * + * @data: Pointer to the HDCP helper data. + * + * This function will kick off a check link cycle on behalf of the caller. This + * can be used by DP short hpd interrupt handlers, where the driver must poke + * the helper to check the link is still valid. + */ +void drm_hdcp_helper_schedule_hdcp_check(struct drm_hdcp_helper_data *data) +{ + schedule_delayed_work(&data->check_work, 0); +} +EXPORT_SYMBOL(drm_hdcp_helper_schedule_hdcp_check); + +static struct drm_hdcp_helper_data * +drm_hdcp_helper_initialize(struct drm_connector *connector, + const struct drm_hdcp_helper_funcs *funcs, + bool attach_content_type_property) +{ + struct drm_hdcp_helper_data *out; + int ret; + + out = kzalloc(sizeof(*out), GFP_KERNEL); + if (!out) + return ERR_PTR(-ENOMEM); + + out->connector = connector; + out->funcs = funcs; + + mutex_init(&out->mutex); + out->value = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + + INIT_DELAYED_WORK(&out->check_work, drm_hdcp_helper_check_work); + INIT_WORK(&out->prop_work, drm_hdcp_helper_prop_work); + + ret = drm_connector_attach_content_protection_property(connector, + attach_content_type_property); + if (ret) { + drm_hdcp_helper_destroy(out); + return ERR_PTR(ret); + } + + return out; +} + +/** + * drm_hdcp_helper_initialize_dp - Initializes the HDCP helpers for a + * DisplayPort connector + * + * @connector: pointer to the DisplayPort connector. + * @funcs: pointer to the vtable of HDCP helper funcs for this connector. + * @attach_content_type_property: True if the content_type property should be + * attached. + * + * This function intializes the HDCP helper for the given DisplayPort connector. + * This involves creating the Content Protection property as well as the Content + * Type property (if desired). Upon success, it will return a pointer to the + * HDCP helper data. Ownership of the underlaying memory is transfered to the + * caller and should be freed using drm_hdcp_helper_destroy(). + * + * Returns: + * Pointer to newly created HDCP helper data. PTR_ERR on failure. + */ +struct drm_hdcp_helper_data * +drm_hdcp_helper_initialize_dp(struct drm_connector *connector, + struct drm_dp_aux *aux, + const struct drm_hdcp_helper_funcs *funcs, + bool attach_content_type_property) +{ + struct drm_hdcp_helper_data *out; + + out = drm_hdcp_helper_initialize(connector, funcs, + attach_content_type_property); + if (IS_ERR(out)) + return out; + + out->aux = aux; + out->hdcp1_lut = &drm_hdcp_hdcp1_dpcd_lut; + + return out; +} +EXPORT_SYMBOL(drm_hdcp_helper_initialize_dp); + +/** + * drm_hdcp_helper_initialize_hdmi - Initializes the HDCP helpers for an HDMI + * connector + * + * @connector: pointer to the HDMI connector. + * @funcs: pointer to the vtable of HDCP helper funcs for this connector. + * @attach_content_type_property: True if the content_type property should be + * attached. + * + * This function intializes the HDCP helper for the given HDMI connector. This + * involves creating the Content Protection property as well as the Content Type + * property (if desired). Upon success, it will return a pointer to the HDCP + * helper data. Ownership of the underlaying memory is transfered to the caller + * and should be freed using drm_hdcp_helper_destroy(). + * + * Returns: + * Pointer to newly created HDCP helper data. PTR_ERR on failure. + */ +struct drm_hdcp_helper_data * +drm_hdcp_helper_initialize_hdmi(struct drm_connector *connector, + const struct drm_hdcp_helper_funcs *funcs, + bool attach_content_type_property) +{ + struct drm_hdcp_helper_data *out; + + out = drm_hdcp_helper_initialize(connector, funcs, + attach_content_type_property); + if (IS_ERR(out)) + return out; + + out->hdcp1_lut = &drm_hdcp_hdcp1_ddc_lut; + + return out; +} +EXPORT_SYMBOL(drm_hdcp_helper_initialize_hdmi); + +/** + * drm_hdcp_helper_destroy - Destroys the given HDCP helper data. + * + * @data: Pointer to the HDCP helper data. + * + * This function cleans up and destroys the HDCP helper data created by + * drm_hdcp_helper_initialize_dp() or drm_hdcp_helper_initialize_hdmi(). + */ +void drm_hdcp_helper_destroy(struct drm_hdcp_helper_data *data) +{ + struct drm_connector *connector; + + if (!data) + return; + + connector = data->connector; + + /* + * If the connector is registered, it's possible userspace could kick + * off another HDCP enable, which would re-spawn the workers. + */ + drm_WARN_ON(connector->dev, + connector->registration_state == DRM_CONNECTOR_REGISTERED); + + /* + * Now that the connector is not registered, check_work won't be run, + * but cancel any outstanding instances of it + */ + cancel_delayed_work_sync(&data->check_work); + + /* + * We don't cancel prop_work in the same way as check_work since it + * requires connection_mutex which could be held while calling this + * function. Instead, we rely on the connector references grabbed before + * scheduling prop_work to ensure the connector is alive when prop_work + * is run. So if we're in the destroy path (which is where this + * function should be called), we're "guaranteed" that prop_work is not + * active (tl;dr This Should Never Happen). + */ + drm_WARN_ON(connector->dev, work_pending(&data->prop_work)); + + kfree(data); +} +EXPORT_SYMBOL(drm_hdcp_helper_destroy); diff --git a/include/drm/drm_hdcp.h b/include/drm/drm_hdcp.h index e6e3d16bc7d3..69c6405db5d1 100644 --- a/include/drm/drm_hdcp.h +++ b/include/drm/drm_hdcp.h @@ -36,6 +36,7 @@ #define DRM_HDCP_DDC_BKSV 0x00 #define DRM_HDCP_DDC_RI_PRIME 0x08 #define DRM_HDCP_DDC_AKSV 0x10 +#define DRM_HDCP_DDC_AINFO 0x15 #define DRM_HDCP_DDC_AN 0x18 #define DRM_HDCP_DDC_V_PRIME(h) (0x20 + h * 4) #define DRM_HDCP_DDC_BCAPS 0x40 @@ -295,6 +296,19 @@ struct drm_atomic_state; struct drm_device; struct drm_connector; +struct drm_hdcp_ksv { + union { + u32 words[2]; + u8 bytes[DRM_HDCP_KSV_LEN]; + }; +}; +struct drm_hdcp_an { + union { + u32 words[2]; + u8 bytes[DRM_HDCP_AN_LEN]; + }; +}; + int drm_hdcp_check_ksvs_revoked(struct drm_device *dev, u8 *ksvs, u32 ksv_count); int drm_connector_attach_content_protection_property( @@ -303,9 +317,186 @@ void drm_hdcp_update_content_protection(struct drm_connector *connector, u64 val); bool drm_hdcp_atomic_check(struct drm_connector *connector, struct drm_atomic_state *state); +void drm_hdcp_atomic_commit(struct drm_atomic_state *state, + struct drm_connector *connector); /* Content Type classification for HDCP2.2 vs others */ #define DRM_MODE_HDCP_CONTENT_TYPE0 0 #define DRM_MODE_HDCP_CONTENT_TYPE1 1 +/** + * struct drm_hdcp_helper_funcs - A vtable of function hooks for the hdcp helper + * + * These hooks are used by the hdcp helper to call into the driver/connector + * code to read/write to hw. + */ +struct drm_hdcp_helper_funcs { + /** + * @setup - Performs driver-specific setup before hdcp is enabled + * + * Returns: 0 on success, -errno on failure + */ + int (*setup)(struct drm_connector *connector, + struct drm_atomic_state *state); + + /** + * @are_keys_valid - Checks if the HDCP transmitter keys are valid + * + * Returns: true if the display controller has valid keys loaded + */ + bool (*are_keys_valid)(struct drm_connector *connector); + + /** + * @load_keys - Instructs the driver to load its HDCP transmitter keys + * + * Returns: 0 on success, -errno on failure + */ + int (*load_keys)(struct drm_connector *connector); + + /** + * @hdcp2_capable - Checks if both source and sink support HDCP 2.x + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp2_capable)(struct drm_connector *connector, bool *capable); + + /** + * @hdcp2_enable - Enables HDCP 2.x on the specified connector + * + * Since we don't have multiple examples of HDCP 2.x enablement, we + * provide the bare minimum support for HDCP 2.x help. Once we have + * more examples, perhaps we can be more helpful. + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp2_enable)(struct drm_connector *connector); + + /** + * @hdcp2_check_link - Checks the HDCP 2.x link on a specified connector + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp2_check_link)(struct drm_connector *connector); + + /** + * @hdcp2_disable - Disables HDCP 2.x on the specified connector + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp2_disable)(struct drm_connector *connector); + + /** + * @hdcp1_read_an_aksv - Reads transmitter's An & Aksv from hardware + * + * Use this function if hardware allows reading the transmitter's An and + * Aksv values from the kernel. If your hardware will not allow this, + * use hdcp1_send_an_aksv() and implement the transmission in the + * driver. + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_read_an_aksv)(struct drm_connector *connector, u32 *an, + u32 *aksv); + + /** + * @hdcp1_send_an_aksv - Sends transmitter's An & Aksv to the receiver + * + * Only implement this on hardware where An or Aksv are not accessible + * from the kernel. If these values can be read, use + * hdcp1_read_an_aksv() instead. + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_send_an_aksv)(struct drm_connector *connector); + + /** + * @hdcp1_store_receiver_info - Stores the receiver's info in the transmitter + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_store_receiver_info)(struct drm_connector *connector, + u32 *ksv, u32 status, u8 caps, + bool repeater_present); + + /** + * @hdcp1_enable_encryption - Enables encryption of the outgoing signal + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_enable_encryption)(struct drm_connector *connector); + + /** + * @hdcp1_wait_for_r0 - Wait for transmitter to calculate R0 + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_wait_for_r0)(struct drm_connector *connector); + + /** + * @hdcp1_match_ri - Matches the given Ri from the receiver with Ri in + * the transmitter + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_match_ri)(struct drm_connector *connector, u32 ri_prime); + + /** + * @hdcp1_post_encryption - Allows the driver to confirm encryption and + * perform any post-processing + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_post_encryption)(struct drm_connector *connector); + + /** + * @hdcp1_store_ksv_fifo - Write the receiver's KSV list to transmitter + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_store_ksv_fifo)(struct drm_connector *connector, + u8 *ksv_fifo, u8 num_downstream, + u8 *bstatus, u32 *vprime); + + /** + * @hdcp1_check_link - Allows the driver to check the HDCP 1.x status + * on a specified connector + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_check_link)(struct drm_connector *connector); + + /** + * @hdcp1_disable - Disables HDCP 1.x on the specified connector + * + * Returns: 0 on success, -errno on failure + */ + int (*hdcp1_disable)(struct drm_connector *connector); +}; + +struct drm_hdcp_helper_data; +struct drm_dp_aux; +struct i2c_adapter; +struct mutex; + +struct drm_hdcp_helper_data * +drm_hdcp_helper_initialize_dp(struct drm_connector *connector, + struct drm_dp_aux *aux, + const struct drm_hdcp_helper_funcs *funcs, + bool attach_content_type_property); + +struct drm_hdcp_helper_data * +drm_hdcp_helper_initialize_hdmi(struct drm_connector *connector, + const struct drm_hdcp_helper_funcs *funcs, + bool attach_content_type_property); + +void drm_hdcp_helper_destroy(struct drm_hdcp_helper_data *data); + +int drm_hdcp_helper_hdcp1_capable(struct drm_hdcp_helper_data *data, + bool *capable); +void drm_hdcp_helper_atomic_commit(struct drm_hdcp_helper_data *data, + struct drm_atomic_state *state, + struct mutex *driver_mutex); + +void drm_hdcp_helper_schedule_hdcp_check(struct drm_hdcp_helper_data *data); + #endif