Re: [PATCH v2] platform/x86: ideapad-laptop: Report camera switch as SW_CAMERA_LENS_COVER
From: Marco Giunta
Date: Wed Sep 23 2026 - 07:25:04 EST
Hi Rong & Mark,
thanks a lot for the impressively detailed and informative replies
(as always). Here's an update from me.
1. overall I changed my mind regarding the conditional switch registration.
I no longer really see it as particularly problematic in practice; as Rong
said earlier, in practice it's mostly a theoretical concern given the
typical userspace usage of this switch.
I do agree that if we can find a smart way to filter for switch presence
based on a good criterion, like a suitable wmi method, it would
definitively be an improvement to patch v3. And I also appreciate that
gaining a better understanding of these issues may improve other lenovo
drivers. Finally, in general, I am certainly not trying to stifle the
useful discussion going on here; what I'm trying to say is: as far as the
camera switch patch is concerned, I am perfectly fine with the simple
approach of v3 (at least for now, we can add more complexity later IMO).
2. here https://github.com/marco-giunta/lenovo-acpi-dump you can find
all raw & decompiled ACPI tables and bmof files from both my yoga and my
legion. Feel free to ask for more data.
3. Let me clarify what I meant when I said that the yoga and the lenovo
handle the switch differently (and more generally report some highlights
from the above files).
The yoga's SSDT2 table contains:
Device (WMIU)
{
Name (_HID, "PNP0C14" /* Windows Management Instrumentation Device */) // _HID: Hardware ID
Name (_UID, "LSK20") // _UID: Unique ID
Name (LSKE, 0x02)
Name (_WDG, Buffer (0x3C)
{
/* 0000 */ 0x74, 0x09, 0x6C, 0xCE, 0x07, 0x04, 0x50, 0x4F, // t.l...PO
/* 0008 */ 0x88, 0xBA, 0x4F, 0xC3, 0xB6, 0x55, 0x9A, 0xD8, // ..O..U..
/* 0010 */ 0x53, 0x4B, 0x01, 0x02, 0x21, 0x12, 0x90, 0x05, // SK..!...
/* 0018 */ 0x66, 0xD5, 0xD1, 0x11, 0xB2, 0xF0, 0x00, 0xA0, // f.......
/* 0020 */ 0xC9, 0x06, 0x29, 0x10, 0x44, 0x41, 0x01, 0x00, // ..).DA..
/* 0028 */ 0x0C, 0xDE, 0xC0, 0x8F, 0xE4, 0xB4, 0xFD, 0x43, // .......C
/* 0030 */ 0xB0, 0xF3, 0x88, 0x71, 0x71, 0x1C, 0x12, 0x94, // ...qq...
/* 0038 */ 0xD0, 0x00, 0x01, 0x08 // ....
})
so GUID 8FC0DE0C-B4E4-43FD-B0F3-8871711C1294 on device WMIU, event (0x08)
with notify value 0xD0, no surprises here.
Its _WED method is:
Method (_WED, 1, NotSerialized) // _Wxx: Wake Event, xx=0x00-0xFF
{
If ((Arg0 == 0xD0))
{
Return (PSD1) /* \PSD1 */
}
}
so "notify" = "return PSD1". Then below:
Scope (\_SB.PCI0.LPC0.EC0)
{
...
Method (_Q29, 0, NotSerialized) // _Qxx: EC Query, xx=0x00-0xFF
{
If ((\_SB.PCI0.LPC0.EC0.COSD == 0x02))
{
PSD1 = 0x0C
}
ElseIf ((\_SB.PCI0.LPC0.EC0.COSD == Zero))
{
PSD1 = 0x0D
}
Notify (\_SB.WMIU, 0xD0) // Hardware-Specific
}
}
So the camera on/off 0x0c/0x0d events are sent based on the COSD memory
register in the EC.
This COSD variable is never referred to again anywhere else in the ACPI tables.
On the Legion:
Device (WMIU)
{
Name (_HID, "PNP0C14" /* Windows Management Instrumentation Device */) // _HID: Hardware ID
Name (_UID, "LSK20") // _UID: Unique ID
Name (_WDG, Buffer (0x3C)
{
/* 0000 */ 0x74, 0x09, 0x6C, 0xCE, 0x07, 0x04, 0x50, 0x4F, // t.l...PO
/* 0008 */ 0x88, 0xBA, 0x4F, 0xC3, 0xB6, 0x55, 0x9A, 0xD8, // ..O..U..
/* 0010 */ 0x53, 0x4B, 0x01, 0x02, 0x0C, 0xDE, 0xC0, 0x8F, // SK......
/* 0018 */ 0xE4, 0xB4, 0xFD, 0x43, 0xB0, 0xF3, 0x88, 0x71, // ...C...q
/* 0020 */ 0x71, 0x1C, 0x12, 0x94, 0xD0, 0x00, 0x01, 0x08, // q.......
/* 0028 */ 0x21, 0x12, 0x90, 0x05, 0x66, 0xD5, 0xD1, 0x11, // !...f...
/* 0030 */ 0xB2, 0xF0, 0x00, 0xA0, 0xC9, 0x06, 0x29, 0x10, // ......).
/* 0038 */ 0x44, 0x41, 0x01, 0x00 // DA..
})
Same 8fc0... GUID, same [0xD0, 0x00, 0x01, 0x08] sequence. Then:
Method (_WED, 1, NotSerialized) // _Wxx: Wake Event, xx=0x00-0xFF
{
If ((Arg0 == 0xD0))
{
Return (LSKD) /* External reference */
}
}
But here this LSKD variable is written to with different logic. The relevant
EC query handler is:
Method (_QDF, 0, NotSerialized) // _Qxx: EC Query, xx=0x00-0xFF
{
P80B = 0xDF
Sleep (0x05)
LSKD = Zero
If ((LSKV != Zero))
{
LSKD = LSKV /* \_SB_.PCI0.LPC0.EC0_.LSKV */
If ((LSKD == 0x02)){}
If ((LSKD == 0x03)){}
LSKV = Zero
}
Notify (WMIU, 0xD0) // Hardware-Specific
Notify (GZFD, 0xE8) // Hardware-Specific
}
so there are no assignments of 0x0c/0x0d like on the Yoga: the EC itself
writes the event code into LSKV (a byte at offset 0x1F of the ERAX region,
SystemMemory 0xFEEC2400), and the AML only copies it to LSKD and clears
the mailbox.
The constants therefore appear to live in the EC firmware rather than
in the ACPI tables, and no AML-visible object keeps the switch state,
unlike COSD on the Yoga (which is at least readable in principle, even if
nothing else reads it).
Overall, the firmware appears to work differently internally, and I wasn't
able to find a common interface that a driver could use for this without
resorting to per-model EC details (see below), though I may be missing
something.
4. Unless I'm missing something, the IDs Mark mentioned don't really apply
here.
0x00201000 does not appear anywhere in either machine's tables - consistent
with Mark's point that this ID is new; both of my devices were released in
2025.
0x00200000 appears a few times in SSDT2 (yoga)/SSDT5 (legion) in contexts that
look unrelated (bitmask arithmetic in DYTC on the Yoga, and in the GPS
method on the Legion), plus once in the Yoga's capability data, see below.
LENOVO_OTHER_METHOD is implemented on both machines as \_SB.GZFD.WMAE
(object "AE" in the GZFD _WDG, flags 0x02 = method). Since the MOF
declares GetFeatureValue as WmiMethodId(17), the relevant branch is
Arg1 == 0x11, the same one as in Rong's 83JR snippet. As far as I can
tell neither machine handles the two shutter IDs there: the Yoga has logic
to dispatch based on device/feature bytes (and covers device 0 features
0x02 and 0x08, device 4 feature 0x03, and device 8 feature 0x01); instead
the Legion compares whole IDs and its device-0 list goes from
0x001F0000 straight to 0x00210000. Unhandled IDs return Zero in both
cases, which, if 0 means "closed", would make those IDs report a closed
shutter regardless of the switch position. I would appreciate a second
opinion on this reading, though.
As for the capability data, the Yoga implements LENOVO_CAPABILITY_DATA_00,
defined in the bmof as
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
class LENOVO_CAPABILITY_DATA_00 {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
[WmiDataId(2), read, Description("Capability.")] uint32 Capability;
[WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue;
};
This object is implemented in the acpi tables with GUID
362A3AFE-3D96-4665-8530-96DAD5BB300E, object id "B0", as WQB0 -> GCD0,
which returns the three dwords of each element of the CD00 package.
CD00 enumerates the feature IDs in ascending order, and includes
{ 0x001F0000, Zero, Zero },
{ 0x00200000, Zero, Zero },
{ 0x00210000, Zero, Zero },
while other entries in the same table have capability values such as
0x07, 0x09, 0x05, 0x03 and One. If the capability field is the bitmask
from Mark's table, this would mean the CV DSP Camera Shutter is enumerated
but declared as not supported on this model. (and again there is no
0x00201000 entry)
The Legion's capability data (same 362a... GUID) is built by WQA9 from
hardcoded buffers rather than from a package, and has no entry for
0x00200000 at all.
So while these interfaces look like the right way to expose
SW_CAMERA_LENS_COVER conditionally on firmware that implements them
(like on the 83JR platform), they do not seem usable on these two models.
5. I have basically no experience parsing WMI data and very little with
ACPI tables in general. Please feel free to correct me! And in general
take the above with a pinch of salt.
I hope the linked files will prove more useful than me in this regard.
Thanks again for your work on this, I am learning a lot from this thread.
Best regards,
Marco