diff mbox

drm/i915/opregion: work around buggy firmware that provides 8+ output devices

Message ID 53045DD1.5010406@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Aaron Lu Feb. 19, 2014, 7:31 a.m. UTC
On 02/13/2014 08:03 PM, Daniel Vetter wrote:
> On Thu, Feb 13, 2014 at 11:08 AM, Chris Wilson <chris@chris-wilson.co.uk> wrote:
>> On Thu, Feb 13, 2014 at 05:10:25PM +0800, Aaron Lu wrote:
>>> On 02/12/2014 06:31 PM, Chris Wilson wrote:
>>>> On Wed, Feb 12, 2014 at 11:05:40AM +0800, Aaron Lu wrote:
>>>>> The ACPI table on ASUS UX302LA has more than 8 output devices under the
>>>>> graphics controller device node. The problem is, the real active output
>>>>> device, the LCD panel, is listed the last. The result is, the LCD's
>>>>> device id doesn't get recorded in the active device list CADL array and
>>>>> when the _DCS control method for the LCD device is executed, it returns
>>>>> 0x1d, meaning it is not active. This affects the hotkey delivery ASL
>>>>> code that will not deliver a notification if the output device is not
>>>>> active on backlight hotkey press.
>>>>>
>>>>> I don't see a clean way to solve this problem since the operation region
>>>>> spec doesn't allow more than 8 output devices so we have no way of
>>>>> storing all these output devices. The fact that output devices that have
>>>>> _BCM control method usually means they have a higher possibility of being
>>>>> used than those who don't made me choose a simple way to work around
>>>>> the buggy firmware by replacing the last entry in CADL array with the one
>>>>> that has _BCM control method. There is no specific reason why the last
>>>>> entry is picked instead of others.
>>>>
>>>> Another possibility is that the connector list is in rough priority
>>>> order so might be useful for sorting the CADL array.
>>>>
>>>> Since the CADL should only be a list of currently active devices, we
>>>> could just bite the bullet and repopulate it correctly after every
>>>> setcrtc.
>>>
>>> Thanks for the suggestion. As a first step, does the following un-tested
>>> patch look OK?
>>
>> Yes. Maybe worth putting together the similar routines for blind
>> setting the didl and the cadl, or at least for computing the value from
>> the connector. For instance, the didl logic disagrees with the value of
>> index - is that relevant? I have a suspicion that the CADL entry should
>> match the DIDL entry for the connector, but that is not actually
>> mentioned in the opregion spec afaict.
> 
> I think a problem is that often we have more than one output for a
> given type. So we need to somehow match them up to make sure we put
> the right ones intot didl/cadl lists. The issue here is that our
> connectors don't match up perfectly with the acpi output entries
> (since we have separate dp/hdmi outputs). But I think it would be
> worthwhile trying to match them up and store a link from struct
> intel_connector to the right acpi node acpi node.

Is this possible? I don't see a way to match them up...
The _ADR control method uses a field that seems to be assigned by BIOS
like this:

                Device (LCDD)
                {
                  Method (_ADR, 0, Serialized)  // _ADR: Address
                    {
                        Return (And (0xFFFF, DID2))
                    }
		}
DID2 is in system memory region and has some assigned value like 0x400
when we read it. For this case it is easy since there is only one output
device that is of type LVDS so we can match it to connector of type eDP
or LVDS, suppose there is only one such connector. But for output
devices' whose _ADR has the value of 0x301, 0x302, etc. I have no idea
how to match them up to the connectors of that type as we can't be sure
the probe order we have used in i915 driver is the same as BIOS'.

In the mean time, it seems we can just use driver's information to
populate both DIDL and CADL and forget the _ADR of those devices like
the following patch does:



I've tested the patch locally on some laptops but I've no idea of what
impact it has on others.

Thanks,
Aaron

Comments

Matthew Garrett Feb. 19, 2014, 7:33 a.m. UTC | #1
On Wed, Feb 19, 2014 at 03:31:29PM +0800, Aaron Lu wrote:

> DID2 is in system memory region and has some assigned value like 0x400
> when we read it. For this case it is easy since there is only one output
> device that is of type LVDS so we can match it to connector of type eDP
> or LVDS, suppose there is only one such connector. But for output
> devices' whose _ADR has the value of 0x301, 0x302, etc. I have no idea
> how to match them up to the connectors of that type as we can't be sure
> the probe order we have used in i915 driver is the same as BIOS'.

Non-standard _ADR values are assigend by the GPU vendor, so Intel should 
be able to provide you with the correct interpretations.
Aaron Lu Feb. 19, 2014, 8:59 a.m. UTC | #2
On 02/19/2014 03:33 PM, Matthew Garrett wrote:
> On Wed, Feb 19, 2014 at 03:31:29PM +0800, Aaron Lu wrote:
> 
>> DID2 is in system memory region and has some assigned value like 0x400
>> when we read it. For this case it is easy since there is only one output
>> device that is of type LVDS so we can match it to connector of type eDP
>> or LVDS, suppose there is only one such connector. But for output
>> devices' whose _ADR has the value of 0x301, 0x302, etc. I have no idea
>> how to match them up to the connectors of that type as we can't be sure
>> the probe order we have used in i915 driver is the same as BIOS'.
> 
> Non-standard _ADR values are assigend by the GPU vendor, so Intel should 
> be able to provide you with the correct interpretations.

It doesn't seem the _ADR value has to be the format defined by _DOD, as
the example of the ACPI spec gives:
Method (_ADR, 0) {
    return(0x0100)
}
So that is not the problem here.

The problem is, we don't have any way of matching an ACPI output device
node to a drm connector of the same type when there are more than 1 of
those with the same type, i.e. we don't know how the index value are
assigned by BIOS.
Daniel Vetter March 4, 2014, 2:45 p.m. UTC | #3
On Wed, Feb 19, 2014 at 04:59:06PM +0800, Aaron Lu wrote:
> On 02/19/2014 03:33 PM, Matthew Garrett wrote:
> > On Wed, Feb 19, 2014 at 03:31:29PM +0800, Aaron Lu wrote:
> > 
> >> DID2 is in system memory region and has some assigned value like 0x400
> >> when we read it. For this case it is easy since there is only one output
> >> device that is of type LVDS so we can match it to connector of type eDP
> >> or LVDS, suppose there is only one such connector. But for output
> >> devices' whose _ADR has the value of 0x301, 0x302, etc. I have no idea
> >> how to match them up to the connectors of that type as we can't be sure
> >> the probe order we have used in i915 driver is the same as BIOS'.
> > 
> > Non-standard _ADR values are assigend by the GPU vendor, so Intel should 
> > be able to provide you with the correct interpretations.
> 
> It doesn't seem the _ADR value has to be the format defined by _DOD, as
> the example of the ACPI spec gives:
> Method (_ADR, 0) {
>     return(0x0100)
> }
> So that is not the problem here.
> 
> The problem is, we don't have any way of matching an ACPI output device
> node to a drm connector of the same type when there are more than 1 of
> those with the same type, i.e. we don't know how the index value are
> assigned by BIOS.

I've thought the OpRegion spec has some additional fields in there
indicating the port number, which we could match up at least on modern
platforms (where there's only ever port A-E). But that's very hazy
recollection from a really old OpRegion spec, i.e. I have no bloody clue
at all ;-)

If I misremember this then we need to start on a begging tour again and
ask the windows guys how this is all supposed to work ...
-Daniel
Aaron Lu Dec. 8, 2014, 1:58 a.m. UTC | #4
We have a new bug report that has the same problem:
https://bugzilla.kernel.org/show_bug.cgi?id=88941

The posted patch solves the problem. I know it's not perfect, but it
doesn't seem it would do any harm to existing systems so should be safe.

Better, if someone can shed some light on how this should be properly
handled, that would be great.

Thanks,
Aaron

On 03/04/2014 10:45 PM, Daniel Vetter wrote:
> On Wed, Feb 19, 2014 at 04:59:06PM +0800, Aaron Lu wrote:
>> On 02/19/2014 03:33 PM, Matthew Garrett wrote:
>>> On Wed, Feb 19, 2014 at 03:31:29PM +0800, Aaron Lu wrote:
>>>
>>>> DID2 is in system memory region and has some assigned value like 0x400
>>>> when we read it. For this case it is easy since there is only one output
>>>> device that is of type LVDS so we can match it to connector of type eDP
>>>> or LVDS, suppose there is only one such connector. But for output
>>>> devices' whose _ADR has the value of 0x301, 0x302, etc. I have no idea
>>>> how to match them up to the connectors of that type as we can't be sure
>>>> the probe order we have used in i915 driver is the same as BIOS'.
>>>
>>> Non-standard _ADR values are assigend by the GPU vendor, so Intel should 
>>> be able to provide you with the correct interpretations.
>>
>> It doesn't seem the _ADR value has to be the format defined by _DOD, as
>> the example of the ACPI spec gives:
>> Method (_ADR, 0) {
>>     return(0x0100)
>> }
>> So that is not the problem here.
>>
>> The problem is, we don't have any way of matching an ACPI output device
>> node to a drm connector of the same type when there are more than 1 of
>> those with the same type, i.e. we don't know how the index value are
>> assigned by BIOS.
> 
> I've thought the OpRegion spec has some additional fields in there
> indicating the port number, which we could match up at least on modern
> platforms (where there's only ever port A-E). But that's very hazy
> recollection from a really old OpRegion spec, i.e. I have no bloody clue
> at all ;-)
> 
> If I misremember this then we need to start on a begging tour again and
> ask the windows guys how this is all supposed to work ...
> -Daniel
>
Jani Nikula Dec. 8, 2014, 11 a.m. UTC | #5
On Mon, 08 Dec 2014, Aaron Lu <aaron.lu@intel.com> wrote:
> We have a new bug report that has the same problem:
> https://bugzilla.kernel.org/show_bug.cgi?id=88941
>
> The posted patch solves the problem. I know it's not perfect, but it
> doesn't seem it would do any harm to existing systems so should be safe.
>
> Better, if someone can shed some light on how this should be properly
> handled, that would be great.

There was a bug report that I can't find right now that had a similar
problem. I wrote a few patches, even somewhat polished ones (that I now
pushed to [1] for reference) to handle extended DIDL. Unfortunately this
didn't help the bug reporter because the right one was beyond the
extended DIDL too, so I don't think I even sent these to the list.

Anyway, just one more data point. This might help your reporter, so
worth a try. But it doesn't solve everything.


BR,
Jani.


>
> Thanks,
> Aaron
>
> On 03/04/2014 10:45 PM, Daniel Vetter wrote:
>> On Wed, Feb 19, 2014 at 04:59:06PM +0800, Aaron Lu wrote:
>>> On 02/19/2014 03:33 PM, Matthew Garrett wrote:
>>>> On Wed, Feb 19, 2014 at 03:31:29PM +0800, Aaron Lu wrote:
>>>>
>>>>> DID2 is in system memory region and has some assigned value like 0x400
>>>>> when we read it. For this case it is easy since there is only one output
>>>>> device that is of type LVDS so we can match it to connector of type eDP
>>>>> or LVDS, suppose there is only one such connector. But for output
>>>>> devices' whose _ADR has the value of 0x301, 0x302, etc. I have no idea
>>>>> how to match them up to the connectors of that type as we can't be sure
>>>>> the probe order we have used in i915 driver is the same as BIOS'.
>>>>
>>>> Non-standard _ADR values are assigend by the GPU vendor, so Intel should 
>>>> be able to provide you with the correct interpretations.
>>>
>>> It doesn't seem the _ADR value has to be the format defined by _DOD, as
>>> the example of the ACPI spec gives:
>>> Method (_ADR, 0) {
>>>     return(0x0100)
>>> }
>>> So that is not the problem here.
>>>
>>> The problem is, we don't have any way of matching an ACPI output device
>>> node to a drm connector of the same type when there are more than 1 of
>>> those with the same type, i.e. we don't know how the index value are
>>> assigned by BIOS.
>> 
>> I've thought the OpRegion spec has some additional fields in there
>> indicating the port number, which we could match up at least on modern
>> platforms (where there's only ever port A-E). But that's very hazy
>> recollection from a really old OpRegion spec, i.e. I have no bloody clue
>> at all ;-)
>> 
>> If I misremember this then we need to start on a begging tour again and
>> ask the windows guys how this is all supposed to work ...
>> -Daniel
>> 
>
Jani Nikula Dec. 8, 2014, 11:04 a.m. UTC | #6
On Mon, 08 Dec 2014, Jani Nikula <jani.nikula@linux.intel.com> wrote:
> On Mon, 08 Dec 2014, Aaron Lu <aaron.lu@intel.com> wrote:
>> We have a new bug report that has the same problem:
>> https://bugzilla.kernel.org/show_bug.cgi?id=88941
>>
>> The posted patch solves the problem. I know it's not perfect, but it
>> doesn't seem it would do any harm to existing systems so should be safe.
>>
>> Better, if someone can shed some light on how this should be properly
>> handled, that would be great.
>
> There was a bug report that I can't find right now that had a similar
> problem. I wrote a few patches, even somewhat polished ones (that I now
> pushed to [1] for reference) to handle extended DIDL. Unfortunately this
> didn't help the bug reporter because the right one was beyond the
> extended DIDL too, so I don't think I even sent these to the list.
>
> Anyway, just one more data point. This might help your reporter, so
> worth a try. But it doesn't solve everything.

[1] http://cgit.freedesktop.org/~jani/drm/log/?h=didl
Aaron Lu Dec. 9, 2014, 9:15 a.m. UTC | #7
On 12/08/2014 07:04 PM, Jani Nikula wrote:
> On Mon, 08 Dec 2014, Jani Nikula <jani.nikula@linux.intel.com> wrote:
>> On Mon, 08 Dec 2014, Aaron Lu <aaron.lu@intel.com> wrote:
>>> We have a new bug report that has the same problem:
>>> https://bugzilla.kernel.org/show_bug.cgi?id=88941
>>>
>>> The posted patch solves the problem. I know it's not perfect, but it
>>> doesn't seem it would do any harm to existing systems so should be safe.
>>>
>>> Better, if someone can shed some light on how this should be properly
>>> handled, that would be great.
>>
>> There was a bug report that I can't find right now that had a similar
>> problem. I wrote a few patches, even somewhat polished ones (that I now
>> pushed to [1] for reference) to handle extended DIDL. Unfortunately this
>> didn't help the bug reporter because the right one was beyond the
>> extended DIDL too, so I don't think I even sent these to the list.
>>
>> Anyway, just one more data point. This might help your reporter, so
>> worth a try. But it doesn't solve everything.
> 
> [1] http://cgit.freedesktop.org/~jani/drm/log/?h=didl

Thanks for the info, I've asked Dmitry to give it a try.

Regards,
Aaron
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index a4ffc021c317..55956a517a77 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -204,6 +204,9 @@  struct intel_connector {
 	/* since POLL and HPD connectors may use the same HPD line keep the native
 	   state of connector->polled in case hotplug storm detection changes it */
 	u8 polled;
+
+	/* device id with type and index information */
+	u32 disp_id;
 };
 
 typedef struct dpll {
diff --git a/drivers/gpu/drm/i915/intel_opregion.c b/drivers/gpu/drm/i915/intel_opregion.c
index 68459605bd12..ba08c894ce9a 100644
--- a/drivers/gpu/drm/i915/intel_opregion.c
+++ b/drivers/gpu/drm/i915/intel_opregion.c
@@ -221,11 +221,11 @@  struct opregion_asle {
 #define SWSCI_SBCB_POST_VBE_PM		SWSCI_FUNCTION_CODE(SWSCI_SBCB, 19)
 #define SWSCI_SBCB_ENABLE_DISABLE_AUDIO	SWSCI_FUNCTION_CODE(SWSCI_SBCB, 21)
 
-#define ACPI_OTHER_OUTPUT (0<<8)
-#define ACPI_VGA_OUTPUT (1<<8)
-#define ACPI_TV_OUTPUT (2<<8)
-#define ACPI_DIGITAL_OUTPUT (3<<8)
-#define ACPI_LVDS_OUTPUT (4<<8)
+#define ACPI_OTHER_OUTPUT 0
+#define ACPI_VGA_OUTPUT 1
+#define ACPI_TV_OUTPUT 2
+#define ACPI_DIGITAL_OUTPUT 3
+#define ACPI_LVDS_OUTPUT 4
 
 #define MAX_DSLP	1500
 
@@ -600,78 +600,20 @@  static struct notifier_block intel_opregion_notifier = {
 	.notifier_call = intel_opregion_video_event,
 };
 
-/*
- * Initialise the DIDL field in opregion. This passes a list of devices to
- * the firmware. Values are defined by section B.4.2 of the ACPI specification
- * (version 3)
- */
-
-static void intel_didl_outputs(struct drm_device *dev)
+static void intel_connector_getid(struct drm_device *dev)
 {
-	struct drm_i915_private *dev_priv = dev->dev_private;
-	struct intel_opregion *opregion = &dev_priv->opregion;
-	struct drm_connector *connector;
-	acpi_handle handle;
-	struct acpi_device *acpi_dev, *acpi_cdev, *acpi_video_bus = NULL;
-	unsigned long long device_id;
-	acpi_status status;
-	u32 temp;
 	int i = 0;
+	int index[5] = {0};
+	struct intel_connector *connector;
 
-	handle = ACPI_HANDLE(&dev->pdev->dev);
-	if (!handle || acpi_bus_get_device(handle, &acpi_dev))
-		return;
-
-	if (acpi_is_video_device(handle))
-		acpi_video_bus = acpi_dev;
-	else {
-		list_for_each_entry(acpi_cdev, &acpi_dev->children, node) {
-			if (acpi_is_video_device(acpi_cdev->handle)) {
-				acpi_video_bus = acpi_cdev;
-				break;
-			}
-		}
-	}
-
-	if (!acpi_video_bus) {
-		pr_warn("No ACPI video bus found\n");
-		return;
-	}
-
-	list_for_each_entry(acpi_cdev, &acpi_video_bus->children, node) {
-		if (i >= 8) {
-			dev_dbg(&dev->pdev->dev,
-				"More than 8 outputs detected via ACPI\n");
-			return;
-		}
-		status =
-			acpi_evaluate_integer(acpi_cdev->handle, "_ADR",
-						NULL, &device_id);
-		if (ACPI_SUCCESS(status)) {
-			if (!device_id)
-				goto blind_set;
-			iowrite32((u32)(device_id & 0x0f0f),
-				  &opregion->acpi->didl[i]);
-			i++;
-		}
-	}
-
-end:
-	/* If fewer than 8 outputs, the list must be null terminated */
-	if (i < 8)
-		iowrite32(0, &opregion->acpi->didl[i]);
-	return;
-
-blind_set:
-	i = 0;
-	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+	list_for_each_entry(connector, &dev->mode_config.connector_list, base.head) {
 		int output_type = ACPI_OTHER_OUTPUT;
 		if (i >= 8) {
 			dev_dbg(&dev->pdev->dev,
 				"More than 8 outputs in connector list\n");
 			return;
 		}
-		switch (connector->connector_type) {
+		switch (connector->base.connector_type) {
 		case DRM_MODE_CONNECTOR_VGA:
 		case DRM_MODE_CONNECTOR_DVIA:
 			output_type = ACPI_VGA_OUTPUT;
@@ -690,15 +632,35 @@  blind_set:
 			output_type = ACPI_DIGITAL_OUTPUT;
 			break;
 		case DRM_MODE_CONNECTOR_LVDS:
+		case DRM_MODE_CONNECTOR_eDP:
 			output_type = ACPI_LVDS_OUTPUT;
 			break;
 		}
-		temp = ioread32(&opregion->acpi->didl[i]);
-		iowrite32(temp | (1<<31) | output_type | i,
-			  &opregion->acpi->didl[i]);
+		connector->disp_id = (output_type << 8) | index[output_type]++;
 		i++;
 	}
-	goto end;
+}
+
+/*
+ * Initialise the DIDL field in opregion. This passes a list of devices to
+ * the firmware. Values are defined by section B.4.2 of the ACPI specification
+ * (version 3)
+ */
+
+static void intel_didl_outputs(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct intel_opregion *opregion = &dev_priv->opregion;
+	struct intel_connector *connector;
+	int i = 0;
+
+	list_for_each_entry(connector, &dev->mode_config.connector_list, base.head)
+		iowrite32(connector->disp_id, &opregion->acpi->didl[i++]);
+
+	if (i < 8)
+		iowrite32(0, &opregion->acpi->didl[i]);
+
+	return;
 }
 
 static void intel_setup_cadls(struct drm_device *dev)
@@ -730,6 +692,7 @@  void intel_opregion_init(struct drm_device *dev)
 
 	if (opregion->acpi) {
 		if (drm_core_check_feature(dev, DRIVER_MODESET)) {
+			intel_connector_getid(dev);
 			intel_didl_outputs(dev);
 			intel_setup_cadls(dev);
 		}