Re: [PATCH v2] platform/x86: ideapad-laptop: Report camera switch as SW_CAMERA_LENS_COVER

From: Rong Zhang

Date: Wed Sep 23 2026 - 08:50:28 EST


Hi Marco,

Thanks for the detailed information.

On Wed, 2026-09-23 at 13:17 +0200, Marco Giunta wrote:
> 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).

Agreed. More comments below.

>
> 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.

Good job. They are exactly what I want to cross-check with. They'll also
be useful when I develop future patches for lenovo-wmi-*.

>
> 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.

Then we can't query the switch's state via WMI on the Yoga.

>
> 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)

Just checked the SSDT2 table of Yoga, and I found the implementation of
WMIU very similar to my device (ThinkBook 14 G7+ ASP). Mine has no
0x00200000 package though, which makes some sense as it has a passive
shutter that physically covers the camera.

Perhaps the Yoga Pro 7 14ASP10 was developed in the direction of
supporting 0x00200000, but the dev team eventually removed it before
releasing.

>
> 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.

Then we can't query the switch's state via WMI on the Legion.

>
> 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.

Agreed.

>
> 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.

Your interpretation is well done and impressive!

In conclusion, I agreed that we should take the simple approach, i.e.,
exposing KEY_SW unconditionally. If we see a report for a 0x00200000-
capable device, we can add support for it then. For the time being, let's
focus on what we have first.

>
>
> Thanks again for your work on this, I am learning a lot from this thread.

Thanks a lot for your help, too.

I will submit my patch with your C-d-b, S-o-b, and T-b.

Thanks,
Rong

>
> Best regards,
> Marco