[Intel-gfx] [PATCH v4 30/41] drm/i915: Initialize HDCP2.2 and its MEI interface
Daniel Vetter
daniel at ffwll.ch
Tue May 29 08:42:38 UTC 2018
On Tue, May 29, 2018 at 08:53:37AM +0200, Daniel Vetter wrote:
> On Fri, May 25, 2018 at 04:42:52PM +0530, Ramalingam C wrote:
> >
> >
> > On Thursday 24 May 2018 01:36 PM, Daniel Vetter wrote:
> > > On Mon, May 21, 2018 at 06:23:49PM +0530, Ramalingam C wrote:
> > > > Initialize HDCP2.2 support. This includes the mei interface
> > > > initialization along with required notifier registration.
> > > >
> > > > v2:
> > > > mei interface handle is protected with mutex. [Chris Wilson]
> > > > v3:
> > > > Notifiers are used for the mei interface state.
> > > > v4:
> > > > Poll for mei client device state
> > > > Error msg for out of mem [Uma]
> > > > Inline req for init function removed [Uma]
> > > >
> > > > Signed-off-by: Ramalingam C <ramalingam.c at intel.com>
> > > > ---
> > > > drivers/gpu/drm/i915/intel_dp.c | 3 +-
> > > > drivers/gpu/drm/i915/intel_drv.h | 5 +-
> > > > drivers/gpu/drm/i915/intel_hdcp.c | 117 +++++++++++++++++++++++++++++++++++++-
> > > > drivers/gpu/drm/i915/intel_hdmi.c | 2 +-
> > > > 4 files changed, 122 insertions(+), 5 deletions(-)
> > > >
> > > > diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
> > > > index 62f82c4298ac..276eb49113e9 100644
> > > > --- a/drivers/gpu/drm/i915/intel_dp.c
> > > > +++ b/drivers/gpu/drm/i915/intel_dp.c
> > > > @@ -6368,7 +6368,8 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port,
> > > > intel_dp_add_properties(intel_dp, connector);
> > > > if (is_hdcp_supported(dev_priv, port) && !intel_dp_is_edp(intel_dp)) {
> > > > - int ret = intel_hdcp_init(intel_connector, &intel_dp_hdcp_shim);
> > > > + int ret = intel_hdcp_init(intel_connector, &intel_dp_hdcp_shim,
> > > > + false);
> > > > if (ret)
> > > > DRM_DEBUG_KMS("HDCP init failed, skipping.\n");
> > > > }
> > > > diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
> > > > index ac943ec73987..7aaaa50fc19f 100644
> > > > --- a/drivers/gpu/drm/i915/intel_drv.h
> > > > +++ b/drivers/gpu/drm/i915/intel_drv.h
> > > > @@ -442,7 +442,7 @@ struct intel_hdcp {
> > > > /* mei interface related information */
> > > > struct mei_cl_device *cldev;
> > > > struct mei_hdcp_data mei_data;
> > > > -
> > > > + struct notifier_block mei_cldev_nb;
> > > > struct delayed_work hdcp2_check_work;
> > > > };
> > > > @@ -1940,7 +1940,8 @@ void intel_hdcp_atomic_check(struct drm_connector *connector,
> > > > struct drm_connector_state *old_state,
> > > > struct drm_connector_state *new_state);
> > > > int intel_hdcp_init(struct intel_connector *connector,
> > > > - const struct intel_hdcp_shim *hdcp_shim);
> > > > + const struct intel_hdcp_shim *hdcp_shim,
> > > > + bool hdcp2_supported);
> > > > int intel_hdcp_enable(struct intel_connector *connector);
> > > > int intel_hdcp_disable(struct intel_connector *connector);
> > > > int intel_hdcp_check_link(struct intel_connector *connector);
> > > > diff --git a/drivers/gpu/drm/i915/intel_hdcp.c b/drivers/gpu/drm/i915/intel_hdcp.c
> > > > index f3f935046c31..9948e4b4e203 100644
> > > > --- a/drivers/gpu/drm/i915/intel_hdcp.c
> > > > +++ b/drivers/gpu/drm/i915/intel_hdcp.c
> > > > @@ -11,6 +11,7 @@
> > > > #include <linux/i2c.h>
> > > > #include <linux/random.h>
> > > > #include <linux/mei_hdcp.h>
> > > > +#include <linux/notifier.h>
> > > > #include "intel_drv.h"
> > > > #include "i915_reg.h"
> > > > @@ -25,6 +26,7 @@ static int _intel_hdcp2_enable(struct intel_connector *connector);
> > > > static int _intel_hdcp2_disable(struct intel_connector *connector);
> > > > static void intel_hdcp2_check_work(struct work_struct *work);
> > > > static int intel_hdcp2_check_link(struct intel_connector *connector);
> > > > +static int intel_hdcp2_init(struct intel_connector *connector);
> > > > static int intel_hdcp_poll_ksv_fifo(struct intel_digital_port *intel_dig_port,
> > > > const struct intel_hdcp_shim *shim)
> > > > @@ -766,11 +768,15 @@ bool is_hdcp_supported(struct drm_i915_private *dev_priv, enum port port)
> > > > }
> > > > int intel_hdcp_init(struct intel_connector *connector,
> > > > - const struct intel_hdcp_shim *hdcp_shim)
> > > > + const struct intel_hdcp_shim *hdcp_shim,
> > > > + bool hdcp2_supported)
> > > > {
> > > > struct intel_hdcp *hdcp = &connector->hdcp;
> > > > int ret;
> > > > + if (!hdcp_shim)
> > > > + return -EINVAL;
> > > > +
> > > > ret = drm_connector_attach_content_protection_property(
> > > > &connector->base);
> > > > if (ret)
> > > > @@ -779,7 +785,12 @@ int intel_hdcp_init(struct intel_connector *connector,
> > > > hdcp->hdcp_shim = hdcp_shim;
> > > > mutex_init(&hdcp->hdcp_mutex);
> > > > INIT_DELAYED_WORK(&hdcp->hdcp_check_work, intel_hdcp_check_work);
> > > > + INIT_DELAYED_WORK(&hdcp->hdcp2_check_work, intel_hdcp2_check_work);
> > > > INIT_WORK(&hdcp->hdcp_prop_work, intel_hdcp_prop_work);
> > > > +
> > > > + if (hdcp2_supported)
> > > > + intel_hdcp2_init(connector);
> > > > +
> > > > return 0;
> > > > }
> > > > @@ -1637,3 +1648,107 @@ static void intel_hdcp2_check_work(struct work_struct *work)
> > > > schedule_delayed_work(&hdcp->hdcp2_check_work,
> > > > DRM_HDCP2_CHECK_PERIOD_MS);
> > > > }
> > > > +
> > > > +static int initialize_mei_hdcp_data(struct intel_connector *connector)
> > > > +{
> > > > + struct intel_hdcp *hdcp = &connector->hdcp;
> > > > + struct mei_hdcp_data *data = &hdcp->mei_data;
> > > > + enum port port;
> > > > +
> > > > + if (connector->encoder) {
> > > > + port = connector->encoder->port;
> > > > + data->port = GET_MEI_DDI_INDEX(port);
> > > > + }
> > > > +
> > > > + data->port_type = INTEGRATED;
> > > > + data->protocol = hdcp->hdcp_shim->hdcp_protocol();
> > > > +
> > > > + data->k = 1;
> > > > + if (!data->streams)
> > > > + data->streams = kcalloc(data->k,
> > > > + sizeof(struct hdcp2_streamid_type),
> > > > + GFP_KERNEL);
> > > > + if (!data->streams) {
> > > > + DRM_ERROR("Out of Memory\n");
> > > > + return -ENOMEM;
> > > > + }
> > > > +
> > > > + data->streams[0].stream_id = 0;
> > > > + data->streams[0].stream_type = hdcp->content_type;
> > > Ok there's 0 locking here. If there's no locking then of course
> > > CONFIG_PROVE_LOCKING will not spot any issues.
> > >
> > > It also means you're code is racy, e.g. if mei and i915 load at the same
> > > time, things could get corrupted in interesting ways. Same holds if you
> > > have ongoing hdcp2 operations going on in the intel_hdcp code, while the
> > > mei driver is being unloaded.
> > Daniel,
> >
> > Both of these cases are not possible as MEI symbols are used in I915,
> > So I915 can't be loaded before or in parallel to MEI drivers.
> > Similarly we can't unload the MEI before I915 or in parallel. So I guess we
> > are out of above race mentioned
>
> That's not how it works unfortunately. You can unbind drivers while the
> module is still there. And symbol availability doesn't guarantee you that
> the driver itself has loaded already. Same holds for i915.
>
> Yes symbol availability not matching up with driver state is one of the
> neatest pitfalls of the linux driver model. Symbol availability only
> mostly matches driver state, but it can be different.
>
> > MEI related data in intel_connector->hdcp is per connector basis. And per
> > connector data's are protected with mutex in authentication path.
> >
> > In MEI HDCP driver's APIs too, there is no shared variable except mei_cldev
> > that also not modified in those mei_hdcp APIs.
> > So I am not seeing any race conditions as such. So there is no need for any
> > explicit locking between I915 and MEI.
>
> Your notifier callback needs some lock to protect against i915-side
> modeset calls, in case the mei disappears while we try to use them.
>
> I think at least, I need to look at the overall picture from your git tree
> again.
>
> > > Note that you can unload the driver without having to unload the module,
> > > those 2 things aren't coupled.
> > Can you please help me to understand more on this?
> > If you are referring to mei device removal, notification will be sent to
> > I915.
> > Even if a hdcp service is placed for a mei device, that is
> > unbinded/disabled, mei bus will gracefully decline the request.
> >
> > So I don't expect any possible issues. Help me if you otherwise.
> >
> > > Another reason for not using notifiers is that it's another reinvented
> > > wheel to make multi-driver stuff work. We already have the component
> > > framework for this, and we're already using the component framework for
> > > the snd-hda vs. i915 coordination.
> > If we need to we can do with component. Exploring what is needed here.
> > At First glance I915 will be component master and mei will be component
> > client.
>
> I think you can hand-roll it with notifiers, just need to be careful that
> you don't hold locks too much. And that's kinda reinventing component
> framework in that case. Especially since you've used direct function
> calls, making mei a static dependency anyway.
Ok I looked at the overall picture with the git tree and I think:
- You need locking, at least for hdcp->cldev, or some other ordering
guarantees around accessing that pointer.
- Given that you have a link-time dependency (by using the exported
functions from mei_hdcp directly) we already have a strong dependency,
and using the component framework makes sense I think. That should give
you the required ordering guarantees around hdpc->cldev.
Cheers, Daniel
> -Daniel
>
> >
> > Thanks and Regards,
> > Ram
> > >
> > > So here's my recommendation for getting this sorted out:
> > >
> > > - Use the component framework to coordinate the loading of i915 and the
> > > mei_hdcp driver.
> > >
> > > - Bonus points for using device_link to tell the driver core about this,
> > > which optimizes the load sequence (unfortunately we haven't done that
> > > for snd-hda yet, instead have to work around ordering issues in the
> > > suspend/resume handlers a bit).
> > >
> > > - Drop all the EXPORT_SYMBOL and hard links between the two drivers.
> > > Instead have a vtable, like we're using for the audio side.
> > >
> > > - Make sure that any shared state is appropriately protected with a mutex.
> > > Sprinkle lots of assert_lock_held over the code base to make sure you
> > > didn't miss anything, and use CONFIG_PROVE_LOCKING to make sure there's
> > > no deadlocks and oversights.
> > >
> > > - Extend the igt drv_module_reload testcase to make sure you're exercising
> > > the new EPROBE_DEFER point fully (should just need a kernel change to
> > > add that as a potential load failure path).
> > >
> > > I think with that we have a solid solution here which is aligned with how
> > > we handle this in other cases.
> > >
> > > Thanks, Daniel
> > >
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void intel_hdcp2_exit(struct intel_connector *connector)
> > > > +{
> > > > + intel_hdcp_disable(connector);
> > > > + kfree(connector->hdcp.mei_data.streams);
> > > > +}
> > > > +
> > > > +static int mei_cldev_notify(struct notifier_block *nb, unsigned long event,
> > > > + void *cldev)
> > > > +{
> > > > + struct intel_hdcp *hdcp = container_of(nb, struct intel_hdcp,
> > > > + mei_cldev_nb);
> > > > + struct intel_connector *intel_connector = container_of(hdcp,
> > > > + struct intel_connector,
> > > > + hdcp);
> > > > +
> > > > + DRM_DEBUG_KMS("[%s:%d] MEI_HDCP Notification. Interface: %s\n",
> > > > + intel_connector->base.name, intel_connector->base.base.id,
> > > > + cldev ? "UP" : "Down");
> > > > +
> > > > + if (event == MEI_CLDEV_ENABLED) {
> > > > + hdcp->cldev = cldev;
> > > > + initialize_mei_hdcp_data(intel_connector);
> > > > + } else {
> > > > + hdcp->cldev = NULL;
> > > > + intel_hdcp2_exit(intel_connector);
> > > > + }
> > > > +
> > > > + return NOTIFY_OK;
> > > > +}
> > > > +
> > > > +static inline
> > > > +bool is_hdcp2_supported(struct drm_i915_private *dev_priv)
> > > > +{
> > > > + return (INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv) ||
> > > > + IS_KABYLAKE(dev_priv));
> > > > +}
> > > > +
> > > > +static int intel_hdcp2_init(struct intel_connector *connector)
> > > > +{
> > > > + struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
> > > > + struct intel_hdcp *hdcp = &connector->hdcp;
> > > > + int ret;
> > > > +
> > > > + if (!is_hdcp2_supported(dev_priv))
> > > > + return -EINVAL;
> > > > +
> > > > + hdcp->hdcp2_supported = true;
> > > > +
> > > > + hdcp->mei_cldev_nb.notifier_call = mei_cldev_notify;
> > > > + ret = mei_cldev_register_notify(&hdcp->mei_cldev_nb);
> > > > + if (ret) {
> > > > + DRM_ERROR("mei_cldev not available. %d\n", ret);
> > > > + goto exit;
> > > > + }
> > > > +
> > > > + ret = initialize_mei_hdcp_data(connector);
> > > > + if (ret) {
> > > > + mei_cldev_unregister_notify(&hdcp->mei_cldev_nb);
> > > > + goto exit;
> > > > + }
> > > > +
> > > > + /*
> > > > + * Incase notifier is triggered well before this point, request for
> > > > + * notification of the mei client device state.
> > > > + */
> > > > + mei_cldev_poll_notification();
> > > > +
> > > > +exit:
> > > > + if (ret)
> > > > + hdcp->hdcp2_supported = false;
> > > > +
> > > > + return ret;
> > > > +}
> > > > diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c
> > > > index ee929f31f7db..a5cc73101acb 100644
> > > > --- a/drivers/gpu/drm/i915/intel_hdmi.c
> > > > +++ b/drivers/gpu/drm/i915/intel_hdmi.c
> > > > @@ -2334,7 +2334,7 @@ void intel_hdmi_init_connector(struct intel_digital_port *intel_dig_port,
> > > > if (is_hdcp_supported(dev_priv, port)) {
> > > > int ret = intel_hdcp_init(intel_connector,
> > > > - &intel_hdmi_hdcp_shim);
> > > > + &intel_hdmi_hdcp_shim, false);
> > > > if (ret)
> > > > DRM_DEBUG_KMS("HDCP init failed, skipping.\n");
> > > > }
> > > > --
> > > > 2.7.4
> > > >
> >
>
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
--
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
More information about the Intel-gfx
mailing list