Denník „kernel developera“

69 minút

Tento príbeh je o riešení jednej nepríjemnej vlastnosti trackpointu a o zdĺhavej premene jedného obyčajného web developera na kernel developera. V článku sú opísané čiastkové kroky, ale aj slepé uličky, ktorými som sa nakoniec dostal k funkčnému riešeniu.

Opis problému

Frek­ven­cia po­sie­la­nia po­lo­hy kur­zo­ru sa vý­raz­ne zni­žu­je, keď po­u­ží­vam pri­blí­žim ru­ku k touch­pa­du, ale­bo keď po­u­ží­vam touch­pad ako opier­ku dla­ne. V na­sle­du­jú­com vý­pi­se je vý­stup prog­ra­mu evhz:

TPPS/2 Elan TrackPoint: Latest    26Hz, Average    36Hz
TPPS/2 Elan TrackPoint: Latest    47Hz, Average    36Hz
TPPS/2 Elan TrackPoint: Latest    43Hz, Average    36Hz
TPPS/2 Elan TrackPoint: Latest    47Hz, Average    36Hz
TPPS/2 Elan TrackPoint: Latest    33Hz, Average    36Hz
TPPS/2 Elan TrackPoint: Latest    34Hz, Average    36Hz
TPPS/2 Elan TrackPoint: Latest    28Hz, Average    36Hz
TPPS/2 Elan TrackPoint: Latest    51Hz, Average    36Hz

Pri tak níz­kej frek­ven­cii kur­zor pri po­hy­be vý­raz­ne se­ká. Za­sta­ve­nie kur­zo­ra na pres­nej po­zí­cii je vý­raz­ne ťaž­šie, pre­to­že po­hyb je ne­rov­no­mer­ný.

Hardvér, alebo softvér

Skôr než som sa pus­til do prá­ce som mu­sel zis­tiť, či je prob­lém hard­vé­ro­vý, ale­bo soft­vé­ro­vý. Na­šťas­tie som ne­chal du­al­bo­ot s Win­do­wsom, tak­že sta­či­lo re­štar­to­vať a vy­skú­šať. Vo win­do­wse sa kur­zor po­hy­bu­je ply­nu­lo bez ohľa­du na to, či sa opie­ram dla­ňou o touch­pad, ale­bo nie.

S tým­to prob­lé­mom som sa stre­tol aj u star­ších think­pa­dov, me­no­vi­te na­prí­klad T420. Tam sa dal prob­lém vy­rie­šiť jed­no­du­cho vy­pnu­tím touch­pa­du v BIO­Se. Môj no­vý think­pad má sí­ce tú­to mož­nosť v BIO­Se, ale tá ne­fun­gu­je. Star­šie mo­de­ly sku­toč­ne vy­pí­na­li touch­pad, ale v no­vých mo­de­loch sa iba ulo­ží prí­znak do NV­RAM a je na ope­rač­nom sys­té­me, aby sa s tým vy­spo­ria­dal.

Čo fy­zic­ké od­po­je­nie touch­pa­du? Pri prá­ci po­u­ží­vam iba track­po­int, tak­že fy­zic­ké od­po­je­nie touch­pa­du by mi ne­va­di­lo. Ani to­to rie­še­nie nie je schod­né. Po od­po­je­ní flex káb­la touch­pa­du to­tiž pre­stal fun­go­vať aj track­po­int. Track­po­int nie je pri­po­je­ný pria­mo na ma­tič­nú do­sku, ale pu­tu­je naj­skôr do touch­pa­du, kde sa mul­tip­le­xu­je. Ten­to di­zajn je ne­vy­hnut­ný na­prí­klad pre touch­pa­dy, kto­ré ne­ma­jú fy­zic­ké tla­čid­lá (T440). Tu sí­ce má­me fy­zic­ké tla­čid­lá, ale za­po­je­nie je rov­na­ké.

Konektor
Ob­rá­zok 1: Ko­nek­tor touch­pa­du (ľa­vý mod­rý)

Hľadanie príčiny

Naj­skôr je vhod­né iden­ti­fi­ko­vať mo­dul jad­ra, kto­rý by mal ob­slu­ho­vať za­ria­de­nie. Väč­ši­na za­ria­de­ní sa dá iden­ti­fi­ko­vať ná­stro­jom lshw, kde sú pre­hľad­ne zo­bra­ze­né za­ria­de­nia. Za­ria­de­nie je buď roz­poz­na­né a lshw zo­bra­zí mo­dul jad­ra, kto­rý ho ob­slu­hu­je, ale­bo nie je roz­poz­na­né a je po­treb­né zis­tiť pod­po­ru po­mo­cou za­da­nia ID za­ria­de­nia do in­ter­ne­to­vé­ho vy­hľa­dá­va­ča. Pri track­po­in­te viem, že nie­kto­rý mo­dul ho ob­slu­hu­je. Tak­to vy­ze­rá vý­pis lshw:

  *-input:2
       product: TPPS/2 Elan TrackPoint
       physical id: 4
       logical name: input13
       logical name: /dev/input/event12
       capabilities: i8042

V tom­to vý­pi­se sí­ce nie je pria­mo zo­bra­ze­ný mo­dul jad­ra, ale i8042 je kon­tro­lér PS/2. Let­mý po­hľad na za­ve­de­né mo­du­ly (lsmod) pre­zra­dí, že sa tam na­chá­dza mo­dul psmouse. Po od­strá­ne­ní mo­du­lu (modprobe -r psmouse) pre­sta­ne track­po­int re­a­go­vať. Po opä­tov­nom za­ve­de­ní (modprobe psmouse) opäť fun­gu­je, čo zna­me­ná, že som na­šiel správ­ny mo­dul.

Vo vý­pi­se dmesg pri­bud­li na­sle­du­jú­ce riad­ky:

psmouse serio1: synaptics: queried max coordinates: x [..5678], y [..4694]
psmouse serio1: synaptics: queried min coordinates: x [1266..], y [1162..]
psmouse serio1: synaptics: Your touchpad (PNP: LEN2073 PNP0f13) says it can support a different bus. If i2c-hid and hid-rmi are not used, you might want to try setting psmouse.synaptics_intertouch to 1 and report this to linux-input@vger.kernel.org.
psmouse serio1: synaptics: Touchpad model: 1, fw: 10.32, id: 0x1e2a1, caps: 0xf01ea3/0x940300/0x12e800/0x500000, board id: 3471, fw id: 3418235
psmouse serio1: synaptics: serio: Synaptics pass-through port at isa0060/serio1/input0
input: SynPS/2 Synaptics TouchPad as /devices/platform/i8042/serio1/input/input20
psmouse serio3: trackpoint: Elan TrackPoint firmware: 0x12, buttons: 3/3
input: TPPS/2 Elan TrackPoint as /devices/platform/i8042/serio1/serio3/input/input21

Pod­ľa všet­ké­ho je touch­pad pri­po­je­ný k 2 zber­ni­ciam sú­čas­ne. Ak­tu­ál­ne sa po­u­ží­va ro­z­hra­nie PS/2, cez kto­ré sa pre­ná­ša­jú pa­ke­ty ma­xi­mál­nou frek­ven­ci­ou 80Hz, ale­bo 40Hz pri pri strie­da­ní kaž­dé­ho dru­hé­ho pac­ke­tu z touch­pa­du / track­po­in­tu. Vý­pis zá­ro­veň po­nú­ka jed­no­du­ché rie­še­nie, kto­rým by sa ce­lý ten­to prí­beh mo­hol skon­čiť. Ovlá­dač kon­tro­lu­je, či touch­pad pod­po­ru­je al­ter­na­tív­ny pro­to­kol, ale zá­ro­veň má whi­te­list pod­po­ro­va­ných touch­pa­dov.

Je tu veľ­ká ná­dej, že prob­lém sa po­da­rí vy­rie­šiť po­mo­cou po­ky­nov z vý­pi­su. Pod­ľa ne­ho by ma­lo sta­čiť skon­tro­lo­vať, či ná­ho­dou nie sú za­ve­de­né mo­du­ly i2c-hid a hid-rmi. Ak áno, ma­li by byť od­strá­ne­né (modprobe -r i2c-hid hid-rmi). Ná­sled­ne sta­čí za­viesť psmouse s pa­ra­met­rom synaptics_intertouch=1. V prí­pa­de, že to fun­gu­je, je dob­ré to na­hlá­siť na ad­re­su li­nux-in­put@vger.ker­nel.org, aby sa za­ria­de­nie pri­da­lo do whi­te­lis­tu.

modprobe -r psmouse
modprobe psmouse synaptics_intertouch=1

Na mo­jom no­te­bo­oku to ne­po­moh­lo. Vý­pis dmesg zo­stal rov­na­ký až na tie­to 2 riad­ky:

psmouse serio1: synaptics: Trying to set up SMBus access
psmouse serio1: synaptics: SMbus companion is not ready yet

Hľa­da­nie prí­či­ny za­čí­nam prí­ka­zom grep -r "SMbus companion is not ready yet" /usr/src/linux v zdro­jo­vých kó­doch jad­ra. Tým­to spô­so­bom som na­šiel ria­dok ria­dok s vy­pi­som. Vý­pis sa zo­bra­zí, ak zly­há fun­kcia synaptics_create_intertouch:

static int synaptics_create_intertouch(struct psmouse *psmouse,
                                       struct synaptics_device_info *info,
                                       bool leave_breadcrumbs)
{
        // ...
        const struct i2c_board_info intertouch_board = {
                I2C_BOARD_INFO("rmi4_smbus", 0x2c),
                .flags = I2C_CLIENT_HOST_NOTIFY,
        };

        return psmouse_smbus_init(psmouse, &intertouch_board,
                                  &pdata, sizeof(pdata), true,
                                  leave_breadcrumbs);
}

Fun­kcia ps­mou­se­_sm­bu­s_i­nit skon­čí ne­ús­pe­chom, pre­to­že i2c_for_each_dev(smbdev, psmouse_smbus_create_companion) ne­náj­de žiad­ne za­ria­de­nie. Úlo­hou tej­to fun­kcie je nájsť za­ria­de­nie s ad­re­sou 0x2c (to­to je ad­re­sa touch­pa­du znač­ky sy­nap­tics) vy­ho­vu­jú­ce fun­kcii ps­mou­se­_sm­bu­s_c­re­a­te­_com­pa­ni­on.

Debugger

Aby som zis­til, čo sa de­je vo fun­kcii psmouse_smbus_create_companion po­u­ži­jem je­den z najp­ri­mi­tív­nej­ších spô­so­bov la­de­nia - vlo­že­nie vý­pi­sov do kon­zo­ly. V C sa na vý­pis po­u­ží­va fun­kcia printf. V ker­ne­li sa po­u­ží­va veľ­mi po­dob­ná fun­kcia printk. Drob­ný roz­diel je hlav­ne v ur­če­ní úrov­ne zá­važ­nos­ti vý­pi­su (KERN_INFO, KERN_WARNING…). V na­sle­du­jú­com kó­de je upra­ve­ná fun­kcia psmouse_smbus_create_companion s pri­da­ný­mi vý­pis­mi.

static int psmouse_smbus_create_companion(struct device *dev, void *data)
{
        struct psmouse_smbus_dev *smbdev = data;
        unsigned short addr_list[] = { smbdev->board.addr, I2C_CLIENT_END };
        struct i2c_adapter *adapter;
        struct i2c_client *client;

        adapter = i2c_verify_adapter(dev);
        if (!adapter) {
                printk(KERN_INFO "%s Adapter error\n", dev_name(dev));
                return 0;
        }

        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_HOST_NOTIFY)) {
                printk(KERN_INFO "%s No host notify\n", dev_name(dev));
                return 0;
        }

        client = i2c_new_scanned_device(adapter, &smbdev->board,
                                        addr_list, NULL);
        if (IS_ERR(client)) {
                printk(KERN_INFO "%s New device error\n", dev_name(dev));
                return 0;
        }

        /* We have our(?) device, stop iterating i2c bus. */
        smbdev->client = client;
        return 1;
}

Po za­ve­de­ní mo­du­lu sa vo vý­stu­pe dmesg ob­ja­vi­lo:

i2c-0 No host notify
i2c-1 No host notify
i2c-2 No host notify
i2c-3 No host notify
i2c-4 No host notify
i2c-5 No host notify
i2c-6 No host notify
i2c-7 No host notify
i2c-8 No host notify
i2c-9 No host notify
i2c-10 No host notify
i2c-11 No host notify

Žia­den I2C / SM­Bus adap­tér v mo­jom sys­té­me ne­má pod­po­ru I2C_FUNC_SMBUS_HOST_NOTIFY. To je dô­vod, pre­čo sa ne­ini­cia­li­zu­je touch­pad cez I2C / SM­Bus.

I2C / SMBus

Pred ďal­ším po­kra­čo­va­ním je vhod­né vy­svet­liť si poj­my I2C, SM­Bus, aký vzťah je me­dzi ni­mi a pre­čo sa čas­to za­mie­ňa­jú.

Zber­ni­ca I2C slú­ži na pre­nos dát s po­mo­cou 2 vo­di­čov. Kaž­dá sprá­va za­čí­na naj­skôr 7-bi­to­vou ad­re­sou na­sle­do­va­nou prí­zna­kom čí­ta­nia / zá­pi­su. Pre po­kra­čo­va­nie ko­mu­ni­ká­cie je oslo­ve­né za­ria­de­nie po­vin­né od­po­ve­dať prí­zna­kom ACK. Na­sle­du­je ľu­bo­voľ­ne dl­há sek­ven­cia čí­ta­ní a zá­pi­sov 8-bi­to­vých slov.

Rov­na­ko fun­gu­je aj SM­Bus, len s jed­ným roz­die­lom - na­mies­to ľu­bo­voľ­nej štruk­tú­ry sprá­vy má SM­Bus po­vo­le­né len nie­kto­ré ty­py a dĺž­ky. SM­Bus je te­da pod­mno­ži­nou I2C a pod­po­ru­je len na­sle­du­jú­ce sprá­vy:

Prí­kaz quick je sprá­va nu­lo­vej dĺž­ky. Na zber­ni­cu sa po­šle len ad­re­sa, R/W prí­znak a pre­čí­ta sa prí­znak ACK. To je pres­ne tá is­tá sek­ven­cia, kto­rou za­čí­na kaž­dá ko­mu­ni­ká­cia na I2C zber­ni­ci. Vďa­ka to­mu sa dá po­mo­cou quick prí­ka­zu zis­tiť, či je za­ria­de­nie pri­po­je­né na zber­ni­cu. Po­stup­ným odo­sie­la­ním quick prí­ka­zu na ad­re­sy 0-127 sa dá ske­no­vať zo­znam za­ria­de­ní pri­po­je­ných na zber­ni­ci.

Zo­stá­va eš­te zis­tiť, čo je SMBUS_HOST_NOTIFY.

Host notify prerušenia

Do­vo­lím si spra­viť ma­lú od­boč­ku k pre­ru­še­niam. Uda­los­ti od ex­ter­né­ho za­ria­de­nia sa da­jú spra­co­vať dvo­ma spô­sob­mi - pe­ri­odic­kým do­ta­zo­va­ním a pre­ru­še­ním.

Pri pe­ri­odic­kom do­ta­zo­va­ní sa ope­rač­ný sys­tém pra­vi­del­ne pý­ta za­ria­de­nia, či na­sta­la ne­ja­ká uda­losť. Ten­to spô­sob má vyš­šiu la­ten­ciu, pre­to­že za­ria­de­nie mu­sí ča­kať na na mo­ment, keď sa ho ope­rač­ný sys­tém spý­ta na stav. Zá­ro­veň je čas­té pre­bú­dza­nie CPU zo šet­ria­ce­ho re­ži­mu ener­ge­tic­ky ná­roč­né a ne­vhod­né pre no­te­bo­oky.

Rie­še­ním je pre­ru­še­nie. Pri uda­los­ti za­ria­de­nie po­žia­da pro­ce­sor o ob­slu­hu len keď na­sta­la uda­losť (po­hyb my­ši, stla­če­nie klá­ve­sy …). Uda­losť sa spra­cu­je bez ča­ka­nia na na­sle­du­jú­ci in­ter­val. Pro­ce­sor mô­že byť po­čas ce­lej do­by me­dzi uda­los­ťa­mi v šet­ria­com re­ži­me.

Pro­to­kol SM­Bus host no­ti­fy je špe­ciál­ny for­mát sprá­vy, kto­rú po­sie­la za­ria­de­nie žia­da­jú­ce o ob­slu­hu pre­ru­še­nia. Je opí­sa­ný v špe­ci­fi­ká­cii SM­Bus v ka­pi­to­le 6.5.9 (stra­na 44). Sprá­va za­čí­na ad­re­sou SM­Bus Host, čo je špe­ciál­na ad­re­sa de­fi­no­va­ná v ta­buľ­ke 17 ap­pen­di­xu C (stra­na 79). Po host ad­re­se na­sle­du­je ad­re­sa za­ria­de­nia žia­da­jú­ce­ho o pre­ru­še­nie a vo­li­teľ­ne 2 by­ty s dá­ta­mi. Ak chce sy­nap­tics touch­pad (s ad­re­sou 0x2c) po­žia­dať o ob­slu­hu pre­ru­še­nia, po­šle naj­skôr host ad­re­su (0x08) a po­tom vlast­nú ad­re­su (0x2c).

ACPI

Na no­te­bo­oku mám nie­koľ­ko I2C / SM­Bus zber­níc. Po­tre­bo­val som pres­ne zis­tiť, ku kto­rej je pri­po­je­ný track­po­int. Pod­ľa vý­pi­su z dmesgu sa track­po­int iden­ti­fi­ku­je ako Elan Track­Po­int.

In­for­má­cie o hard­vé­ri sa na x86 da­jú zvy­čaj­ne zís­kať z AC­PI. Štan­dard AC­PI nie je len jed­no­du­chá ta­buľ­ka ob­sa­hu­jú­ca zo­znam hard­vé­ru. Je to veľ­mi kom­plex­ný štan­dard. Zo­znam za­ria­de­ní ne­mu­sí byť sta­tic­ký, ale mô­že ob­sa­ho­vať aj spus­ti­teľ­ný kód - AC­PI Ma­chi­ne Lan­gu­age - AML.

AC­PI ta­buľ­ky sú do­stup­né v ad­re­sá­ri /sys/firmware/acpi/tables/. Na­sle­du­jú­ce prí­ka­zy sko­pí­ru­jú AC­PI ta­buľ­ky do pra­cov­né­ho ad­re­sá­ra a de­kom­pi­lu­je ich do .dsl zdro­jo­vé­ho kó­du:

cp -R /sys/firmware/acpi/tables/* .
for file in *; do iasl -d $file; done

Ce­lý ob­sah AC­PI ta­bu­liek a de­kom­pi­lo­va­né zdro­jo­vé kó­dy sú pri­lo­že­né k blo­gu v sú­bo­re p14s_ge­n2_am­d_ac­pi­_tab­les.tar.xz.

Hlav­ná ta­buľ­ka je DSDT.dsl. Zau­jí­ma­vo vy­ze­rá zber­ni­ca _SB.I2CB. Na­chá­dza sa tam nie­koľ­ko za­ria­de­ní s náz­vom za­čí­na­jú­cim sa na ELAN. V AML kó­de je de­fi­no­va­ná me­tó­da _INI, kto­rá na zá­kla­de prí­zna­kov z BIOS-u vy­be­rie kon­krét­ny mo­del ELAN* za­ria­de­nia. Je tu de­fi­no­va­ná aj me­tó­da _STA, kto­rej úlo­hou je vrá­tiť stav za­ria­de­nia. Pre funkč­né za­ria­de­nie by ma­la vrá­tiť mi­ni­mál­ne bi­to­vú mas­ku 0xB (väč­ši­nou to bu­de 0xF). Vý­znam jed­not­li­vých bi­tov je na­sle­dov­ný:

Scope (_SB.I2CB)
{
    Device (TPNL)
    {
        Name (_HID, "XXXX0000")  // _HID: Hardware ID
        Name (_CID, "PNP0C50" /* HID Protocol Device (I2C bus) */)  // _CID: Compatible ID
        Name (_S0W, 0x03)  // _S0W: S0 Device Wake State
        Name (HID2, 0x00)
        Name (POIO, 0x00)
        Name (SBFB, ResourceTemplate ()
        {
            I2cSerialBusV2 (0x0000, ControllerInitiated, 0x00061A80,
                AddressingMode7Bit, "\\_SB.I2CB",
                0x00, ResourceConsumer, _Y0C, Exclusive,
                )
        })
        Name (SBFG, ResourceTemplate ()
        {
            GpioInt (Level, ActiveLow, ExclusiveAndWake, PullNone, 0x0000,
                "\\_SB.GPIO", 0x00, ResourceConsumer, ,
                )
                {   // Pin list
                    0x0005
                }
        })
        CreateWordField (SBFB, \_SB.I2CB.TPNL._Y0C._ADR, BADR)  // _ADR: Address
        CreateDWordField (SBFB, \_SB.I2CB.TPNL._Y0C._SPE, SPED)  // _SPE: Speed
        Name (ITML, Package (0x0A)
        {
            Package (0x07)
            {
                0x04F3,
                0x2A3B,
                0x10,
                0x01,
                0x01,
                "ELAN901C",
                0x01
            },
            // ...
        })

        // ...

        Method (_STA, 0, NotSerialized)  // _STA: Status
        {
            If (((PNVD == 0x00) || (PNPD == 0x00)))
            {
                Return (0x00)
            }

            If ((TPOS >= 0x60))
            {
                Return (0x0F)
            }
            Else
            {
                Return (Zero)
            }
        }
    }
}

Je dob­ré skon­tro­lo­vať si, AC­PI vrá­ti, že za­ria­de­nie je pri­po­je­né. Ak má ker­nel skom­pi­lo­va­nú pod­po­ru ACPI_DEBUGGER a ACPI_DEBUGGER_USER je mož­né po­u­žiť uti­lit­ku acpidbg. Kiež by som o nej ve­del dáv­nej­šie … V kaž­dom prí­pa­de acpidbg som na­šiel ne­skôr, než som sa ce­lý deň trá­pil s mo­du­lom acpi_call. Uti­lit­ka acpidbg je cel­kom prí­jem­né com­mand li­ne ro­z­hra­nie k AC­PI. Po­moc sa dá vy­pí­sať prí­ka­zom help. Ja po­tre­bu­jem spus­tiť me­tó­du _STA. Prí­ka­zom find náj­dem všet­ky ob­jek­ty s tou­to me­tó­dou:

- find _STA
…
     \_SB.I2CA._STA Method       0000000069360f17 001 Args 0 Len 0012 Aml 0000000038f2dff5
\_SB.I2CA.NFC1._STA Method       00000000faf2813c 001 Args 0 Len 0023 Aml 000000009fb7f0d9
     \_SB.I2CB._STA Method       00000000d6066f3a 001 Args 0 Len 001D Aml 00000000f303d8db
\_SB.I2CB.TPNL._STA Method       000000004272e64a 001 Args 0 Len 0025 Aml 00000000fa4f29fe
…

Me­tó­da sa spúš­ťa prí­ka­zom execute:

Evaluating \_SB.I2CB.TPNL._STA
Evaluation of \_SB.I2CB.TPNL._STA returned object 000000007b765997, external buffer length 18
 [Integer] = 0000000000000000

Tak­že za­ria­de­nie pod­ľa me­tó­dy _STA vô­bec nie je v sys­té­me na­in­šta­lo­va­né. Buď je to chy­ba v AC­PI, ale­bo je po­treb­né nie­čo uro­biť pre za­pnu­tie za­ria­de­nia (na­prí­klad ak­ti­vo­vať GPIO), ale­bo po­ze­rám na zlú zber­ni­cu. Pre is­to­tu som si po­zrel, čo by moh­lo byť za­ria­de­nie ELAN901C. Go­og­le ho­vo­rí, že je to kon­tro­lér do­ty­ko­vej vrstvy. Z toh­to zis­te­nia pred­po­kla­dám, že k por­tu I2CB bý­va na nie­kto­rých no­te­bo­okoch pri­po­je­ný kon­tro­lér do­ty­ko­vej vrstvy. Ja do­ty­ko­vú vrstvu ne­mám, pre­to je lo­gic­ké že vý­sled­kom vo­la­nia _STA je 0x0.

Pre­hľa­da­nie AC­PI ta­bu­liek ma k náj­de­niu zber­ni­ce touch­pa­du ne­pri­vied­lo. Mô­žem skú­siť opač­ný po­stup - vy­hľa­dám SM­Bus a zis­tím, čo je k ne­mu pri­po­je­né. V AC­PI pre­to vy­hľa­dá­vam text 'SMB' a na­chá­dzam na­sle­du­jú­ci zá­znam:

Scope (_SB.PCI0)
{
    Device (SMB1)
    {
        Name (_HID, "SMB0001")  // _HID: Hardware ID
        Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
        {
            IO (Decode16,
                0x0B20,             // Range Minimum
                0x0B20,             // Range Maximum
                0x20,               // Alignment
                0x20,               // Length
                )
            IRQ (Level, ActiveLow, Shared, )
                {7}
        })
        Method (_STA, 0, NotSerialized)  // _STA: Status
        {
            Return (0x0F)
        }
    }
}

Pri­po­je­né za­ria­de­nie tu ne­vi­dím, ale sek­cia ob­sa­hu­je iné zau­jí­ma­vé in­for­má­cie. V pr­vom ra­de je to Har­dwa­re ID SMB0001. K to­mu sa o chví­ľu do­sta­ne­me. Ďa­lej tu má­me ad­re­su za­ria­de­nia 0x0B20 a in­for­má­ciu, že za­ria­de­nie po­u­ží­va pre­ru­še­nie 7.

PIIX4

Pod­ľa lshw je v sys­té­me je­di­né SM­Bus za­ria­de­nie ob­slu­ho­va­né ovlá­da­čom piix4_smbus:

*-serial
     description: SMBus
     product: FCH SMBus Controller
     vendor: Advanced Micro Devices, Inc. [AMD]
     physical id: 14
     bus info: pci@0000:00:14.0
     version: 51
     width: 32 bits
     clock: 66MHz
     configuration: driver=piix4_smbus latency=0

Zo­znam I2C / SM­Bus por­tov sa dá zo­bra­ziť prí­ka­zom i2cdetect -l:

i2cdetect -l|sort
i2c-0   i2c             Synopsys DesignWare I2C adapter         I2C adapter
i2c-1   i2c             Synopsys DesignWare I2C adapter         I2C adapter
i2c-2   i2c             AMDGPU DM i2c hw bus 0                  I2C adapter
i2c-3   i2c             AMDGPU DM i2c hw bus 1                  I2C adapter
i2c-4   i2c             AMDGPU DM i2c hw bus 2                  I2C adapter
i2c-5   i2c             AMDGPU DM i2c hw bus 3                  I2C adapter
i2c-6   i2c             AMDGPU DM aux hw bus 0                  I2C adapter
i2c-7   i2c             AMDGPU DM aux hw bus 2                  I2C adapter
i2c-8   i2c             AMDGPU DM aux hw bus 3                  I2C adapter
i2c-9   smbus           SMBus PIIX4 adapter port 0 at ff00      SMBus adapter
i2c-10  smbus           SMBus PIIX4 adapter port 2 at ff00      SMBus adapter
i2c-11  smbus           SMBus PIIX4 adapter port 1 at ff20      SMBus adapter

Zber­ni­ce I2C ob­slu­ho­va­né ovlá­da­čom amdgpu mô­žem po­koj­ne ig­no­ro­vať, slú­žia to­tiž na ko­mu­ni­ká­ciu s mo­ni­to­rom cez ro­z­hra­nie DDC/CI (ov­lá­da­nie ja­su, kon­tras­tu, pre­pí­na­nie vý­stu­pov …). Tak­tiež ig­no­ru­jem zvyš­né I2C adap­té­ry, pre­to­že ne­pod­po­ru­jú pre­ru­še­nia. Zo­stá­va­jú SM­Bus adap­té­ry 9-11. Zo­znam pri­po­je­ných za­ria­de­ní sa dá zís­kať po­mo­cou i2cdetect, kto­rý po­šle quick prí­kaz na všet­ky re­le­vant­né ad­re­sy:

i2cdetect -y -q 9
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

To is­té pla­tí aj pre por­ty 10 a 11. Vý­pis dmesg po­tom ob­sa­hu­je mno­ho ta­kých­to riad­kov:

i2c i2c-9: Failed! (ff)
i2c i2c-9: Failed! (ff)
…

Chy­ba sa dá nájsť po­mer­ne ľah­ko. V zo­zna­me adap­té­rov je zo­bra­ze­ná zá­klad­ná I/O ad­re­sa adap­té­ra:

i2c-9   smbus           SMBus PIIX4 adapter port 0 at ff00      SMBus adapter
i2c-10  smbus           SMBus PIIX4 adapter port 2 at ff00      SMBus adapter
i2c-11  smbus           SMBus PIIX4 adapter port 1 at ff20      SMBus adapter

Pod­ľa AC­PI je zá­klad­ná ad­re­sa 0x0b20, mo­dul však po­u­ží­va chyb­nú ad­re­su 0xff00/ff20. Na­pra­viť by sa to ma­lo dať za­ve­de­ním mo­du­lu s vo­li­teľ­ným pa­ra­met­rom force_addr:

modinfo i2c-piix4|grep addr
parm:           force_addr:Forcibly enable the PIIX4 at the given address. EXTREMELY DANGEROUS! (int)

Od­strá­nim mo­dul z jad­ra a na­čí­tam ho zno­vu so správ­nou ad­re­sou.

modprobe -r i2c-piix4
modprobe i2c-piix4 force_add=0x0b20

Vý­pis dmesgu vy­ze­rá tak­to:

piix4_smbus 0000:00:14.0: SMBus does not support forcing address!
piix4_smbus: probe of 0000:00:14.0 failed with error -22

Sí­ce pa­ra­me­ter je uve­de­ný vo vý­pi­se modinfo, ale ne­dá sa po­u­žiť. Pred hľa­da­ním prí­či­ny si do­vo­lím ma­lú od­boč­ku k PCI.

PCI

Pod­ľa zdro­jo­vé­ho kó­du i2c-pi­i­x4.c je SM­Bus zber­ni­ca pri­po­oje­ná na PCI. Sken PCI to po­tvr­dí:

lspci -nn|grep SMBus
00:14.0 SMBus [0c05]: Advanced Micro Devices, Inc. [AMD] FCH SMBus Controller [1022:790b] (rev 51)

lspci -vvv -b -x -xxx -xxxx -nn -s 00:14.0
00:14.0 SMBus [0c05]: Advanced Micro Devices, Inc. [AMD] FCH SMBus Controller [1022:790b] (rev 51)
	Subsystem: Lenovo FCH SMBus Controller [17aa:5094]
	Control: I/O- Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
	Status: Cap- 66MHz+ UDF- FastB2B- ParErr- DEVSEL=medium >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
	IOMMU group: 9
	Kernel modules: i2c_piix4, sp5100_tco
00: 22 10 0b 79 00 04 20 02 51 00 05 0c 00 00 80 00
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 aa 17 94 50
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
f0: a7 df 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Čís­lo 1022:790b ozna­ču­je ID vý­rob­cu a ID za­ria­de­nia. Kon­krét­ne 1022 je AMD a 790b ozna­ču­je SM­Bus na chip­se­te AMD kerncz.

Pri tom­to PCI ID sa vo­lá fun­kcia pi­i­x4_se­tu­p_s­b800. Hneď na za­čiat­ku sú­bo­ru je kon­tro­la pa­ra­met­ra force_addr. Pri AMD sa mo­dul spo­lie­ha vý­luč­ne na au­to­ma­tic­kú de­tek­ciu ad­re­sy a nie je mož­né ju vy­nú­tiť pa­ra­met­rom.

SB800

Od­bo­čím tro­chu k SB800. Pod­ľa všet­ké­ho je SB800 south brid­ge chip­set od AMD. Do­ku­men­tá­cia k ne­mu sa dá nájsť na strán­ke AMD. Zvý­še­nú po­zor­nosť si za­slú­ži do­ku­ment AMD SB8xx Re­gis­ter Re­fe­ren­ce Gu­ide.

Kód de­tek­cie ad­re­sy v i2c-piix4.c vy­ze­rá tak­to:

#define SB800_PIIX4_SMB_IDX 0xcd6

// …

outb_p(smb_en, SB800_PIIX4_SMB_IDX);
smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1);
outb_p(smb_en + 1, SB800_PIIX4_SMB_IDX);
smba_en_hi = inb_p(SB800_PIIX4_SMB_IDX + 1);

Na­šťas­tie je tu ma­gic­ká kon­štan­ta cd6, kto­rá zjed­no­du­ší vy­hľa­dá­va­nie v re­fe­renč­nej prí­ruč­ke AMD SB8xx. Pod­ľa sek­cie Po­wer Ma­na­ge­ment (PM) Re­gis­ters (stra­na 146) je 0xcd6 PM in­dex re­gis­ter a 0xcd7 da­ta re­gis­ter. Po zá­pi­se čís­la PM re­gis­tra na IO ad­re­su 0xcd6 je mož­né čí­ta­ním z ad­re­sy 0xcd7 zis­tiť hod­no­tu PM re­gis­tra.

Štruk­tú­ra PM re­gis­trov je na stra­ne 147. PM re­gis­ter Smbus0En je zdo­ku­men­to­va­ný na stra­ne 151. Tu sa na­chá­dza ad­re­sa SM­Bu­su a stav za­ria­de­nia (za­pnu­té / vy­pnu­té). Kód vy­ze­rá te­da správ­ne, naj­skôr za­pí­še do PM in­dex re­gis­tra 0xcd6 čís­lo PM re­gis­tra 0x2c, pre­čí­ta spod­né bi­ty ad­re­sy, po­tom za­pí­še 0x2c+1 do 0xcd6 a z 0xcd7 pre­čí­ta hor­né bi­ty.

Všet­ko vy­ze­rá byť správ­ne, ale de­tek­cia ad­re­sy evi­den­tne zly­há­va. Skú­šam te­da za­pí­sať do en_hi pev­nú ad­re­su 0xb (spod­ná ad­re­sa as­poň pri AUX por­te vy­ze­rá správ­na).

outb_p(smb_en, SB800_PIIX4_SMB_IDX);
smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1);
outb_p(smb_en + 1, SB800_PIIX4_SMB_IDX);
smba_en_hi = inb_p(SB800_PIIX4_SMB_IDX + 1);

smba_en_hi = 0xb;

Vý­pis z dmesgu vy­ze­rá správ­ne:

piix4_smbus 0000:00:14.0: SMBus Host Controller at 0xb00, revision 0
piix4_smbus 0000:00:14.0: Using register 0x02 for SMBus port selection
piix4_smbus 0000:00:14.0: Auxiliary SMBus Host Controller at 0xb20

Te­raz môž­me spus­tiť de­tek­ciu za­ria­de­ní. Ak je všet­ko správ­ne, po­tom na AUX (0xb20) by ma­lo byť za­ria­de­nie 0x2c (čo je ad­re­sa pri­de­le­ná touch­pa­du sy­nap­tics). V mo­jom sys­té­me je to port 11, ale pre is­to­tu uvá­dzam aj vý­pis z 9 a 10 na ad­re­se 0xb00.

i2cdetect -q -y 9
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- 36 37 -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- 58 -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

i2cdetect -q -y 10
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- 36 37 -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- 58 -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

i2cdetect -q -y 11
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- 1c -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- 2c 2d 2e 2f
30: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
40: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
50: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f
60: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f
70: 70 71 72 73 74 75 76 77

Pr­vé 2 vý­pi­sy sú iden­tic­ké. To nie je až ta­ké prek­va­pe­nie, pre­to­že vý­ber por­tu fun­gu­je cez PM re­gis­ter, kto­rý som prá­ve obi­šiel. Na pr­vých vý­pi­soch aj tak nič zau­jí­ma­vé nie je, ale po­sled­ný vý­pis má za­ria­de­nie 0x2c a prá­ve qu­ick wri­te na tú­to ad­re­su do­stal zber­ni­cu do ne­kon­zis­tent­né­ho sta­vu, po kto­rom od­po­ve­dá na všet­ky ad­re­sy. Dru­hý sken je už ab­so­lút­ne ne­pou­ži­teľ­ný:

i2cdetect -q -y 11
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         08 09 0a 0b 0c 0d 0e 0f
10: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
20: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f
30: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
40: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
50: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f
60: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f
70: 70 71 72 73 74 75 76 77

Pod­ľa pr­vé­ho vý­pi­su há­dam, že som na­šiel správ­nu zber­ni­cu, na kto­rej je touch­pad. Do fun­kcie pi­i­x4_func pri­dá­vam flag I2C_FUNC_SMBUS_HOST_NOTIFY. Tým dekla­ru­jem pod­po­ru host no­ti­fy pro­to­ko­lu, aj keď v ov­lá­da­či za­tiaľ nie je im­ple­men­to­va­ná.

Po na­čí­ta­ní mo­du­lu i2c-piix4 zá­ro­veň s mo­du­lom psmouse vy­ze­rá vý­pis dmesgu na­sle­dov­ne:

psmouse serio1: synaptics: queried max coordinates: x [..5678], y [..4694]
psmouse serio1: synaptics: queried min coordinates: x [1266..], y [1162..]
psmouse serio1: synaptics: Trying to set up SMBus access
rmi4_smbus 11-002c: registering SMbus-connected sensor
rmi4_physical rmi4-00: rmi_driver_probe: Starting probe.
rmi4_physical rmi4-00: rmi_probe_interrupts: Counting IRQs.
rmi4_physical rmi4-00: rmi_init_functions: Creating functions.
rmi4_physical rmi4-00: Initializing F34.
rmi4_physical rmi4-00: Registered F34.
rmi4_physical rmi4-00: Initializing F01.
rmi4_f01 rmi4-00.fn01: found RMI device, manufacturer: Synaptics, product: TM3471-030, fw id: 3418235
rmi4_physical rmi4-00: Registered F01.
rmi4_physical rmi4-00: Initializing F12.
rmi4_f12 rmi4-00.fn12: rmi_f12_probe
rmi4_physical rmi4-00: rmi_read_register_desc: reg: 0 reg size: 1 subpackets: 1
…
rmi4_f12 rmi4-00.fn12: rmi_f12_probe: data packet size: 79
rmi4_f12 rmi4-00.fn12: rmi_f12_read_sensor_tuning: max_x: 1162 max_y: 778
rmi4_f12 rmi4-00.fn12: rmi_f12_read_sensor_tuning: Inactive Border xlo:0 xhi:0 ylo:0 yhi:0
rmi4_f12 rmi4-00.fn12: rmi_f12_read_sensor_tuning: x_mm: 96 y_mm: 64
rmi4_physical rmi4-00: Registered F12.
rmi4_physical rmi4-00: Initializing F54.
rmi4_physical rmi4-00: Registered F54.
rmi4_physical rmi4-00: Initializing F3A.
rmi4_physical rmi4-00: Registered F3A.
rmi4_physical rmi4-00: Initializing F03.
rmi4_physical rmi4-00: Registered F03.
rmi4_physical rmi4-00: Initializing F55.
rmi4_physical rmi4-00: Registered F55.
input: Synaptics TM3471-030 as /devices/rmi4-00/input/input56
serio: RMI4 PS/2 pass-through port at rmi4-00.fn03
rmi4_smbus 11-002c: rmi_register_transport_device: Registered 11-002c as rmi4-00.
rmi4_f03 rmi4-00.fn03: rmi_f03_pt_open: Consumed 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (14) from PS2 guest
rmi4_f03 rmi4-00.fn03: rmi_f03_pt_write: Wrote f2 to PS/2 passthrough address

To je úspeš­ne ini­cia­li­zo­va­ný touch­pad! Sí­ce ne­re­a­gu­je na uda­los­ti, pre­to­že nie je im­ple­men­to­va­ný host no­ti­fy pro­to­kol, ale aj to je úspech. Od te­raz viem, ku kto­rej zber­ni­ci je touch­pad pri­po­je­ný, aj to, že ko­mu­ni­ká­cia s nim vy­ze­rá byť v po­riad­ku.

I/O adresa

Eš­te sa tro­chu vrá­tim k ad­re­se. Sa­moz­rej­me ta­ká­to zá­pla­ta ne­mô­že pu­to­vať do ker­ne­lu. Naj­skôr je po­treb­né zis­tiť, pre­čo čí­ta­nie z PM re­gis­tra vra­cia ne­správ­nu hod­no­tu. Do­ku­men­tá­cia k 10-roč­né­mu chip­se­tu, kto­rý v mo­jom no­te­bo­oku ani nie je asi ne­bu­de tým naj­lep­ším mies­tom pre zis­te­nie prí­či­ny.

Sú­čas­né pro­ce­so­ry sú na­vr­hnu­té skôr ako SoC (sys­tem on chip). Rôz­ne zber­ni­ce sa sta­li pria­mo sú­čas­ťou CPU, tak­že na strán­kach AMD hľa­dám pria­mo bios ker­nel de­ve­lo­per gu­ide k CPU. Naj­nov­ší do­ku­ment v ča­se pí­sa­nia je k fa­mi­ly 15h, mo­de­lom 70h-7fh. Ja mám sí­ce fa­mi­ly 19h, ale uspo­ko­jím sa aj s tým­to. V na­sle­du­jú­com tex­te bu­dem ten­to do­ku­ment vo­lať jed­no­du­cho BKDG.

Pod­ľa no­vej do­ku­men­tá­cie sa ad­re­sa SM­Bus zber­ni­ce zis­ťu­je … úpl­ne rov­na­ko. Pred­po­kla­dám, že zme­na na­sta­la nie­kde v nov­ších pro­ce­so­roch a pred­po­kla­dám, že ad­re­sa bu­de ulo­že­ná v nie­kto­rom inom z 256 mož­ných re­gis­trov. Nie je to tak veľ­ké čís­lo, aby som si ne­mo­hol pre­čí­tať kaž­dý re­gis­ter a vy­hľa­dať s v nich ad­re­su. Pri­dá­vam do piix4_setup_sb800 na­sle­du­jú­ci kód:

for (reg = 0; reg < 256; ++reg) {
        outb_p(reg, SB800_PIIX4_SMB_IDX);
        reg_val = inb_p(SB800_PIIX4_SMB_IDX + 1);
        printk(KERN_INFO "register=%02x value=%02x\n", reg, reg_val);
}

Všet­ky čí­ta­nia re­gis­tra však vra­ca­jú hod­no­tu 0xff.

register=00 value=ff
register=01 value=ff
register=02 value=ff
register=03 value=ff
register=04 value=ff
…

Pod­ľa BKDG (stra­na 984) sú PM re­gis­tre do­stup­né cez ne­pria­my IO prí­stup na ad­re­sách 0xcd6/7, ale­bo cez MMIO na od ad­re­sy FED8_0000h+300h.

Na­ko­niec som na­šiel v ker­nel mai­ling lis­te patch s pod­po­rou prí­stu­pu k PM re­gi­strom cez MMIO. Nov­šie pro­ce­so­ry ma­jú to­tiž štan­dard­ne vy­pnu­tý prí­stup cez ne­pria­me I/O ad­re­so­va­nie. Ak je prí­stup cez I/O vy­pnu­tý, vrá­tia všet­ky po­ku­sy o čí­ta­nie hod­no­tu 0xff, čo pres­ne zod­po­ve­dá vý­pi­su. Po ap­li­ká­cii pat­chu už pre­pí­na­nie me­dzi port­mi fun­gu­je a i2cdetect 9/10 má roz­diel­ny vý­stup.

Naivná implementácia prerušení

Touch­pad je ob­slu­ho­va­ný ovlá­da­čom pre pro­to­kol RMI4. Z AC­PI už viem, že SM­Bus po­u­ží­va pre­ru­še­nie 7. Ne­mám sí­ce eš­te na­prog­ra­mo­va­ný host no­ti­fy pro­to­kol, ale bu­dem tro­chu opti­mis­tic­ký a ho­vo­rím si, že keď v ovlá­da­či RMI4 po­u­ži­jem pria­mo pre­ru­še­nie 7, bu­de to fun­go­vať. Na­mies­to pdata->irq som za­pí­sal kon­štan­tu 7, zno­vu na­čí­tal psmouse a trak­po­int fun­gu­je! Fun­gu­je bez se­ka­nia!

No dob­re, tro­chu pre­há­ňam s tým „fun­gu­je“. Nie­ke­dy po na­čí­ta­ní ovlá­da­ča fun­gu­je, nie­ke­dy fun­gu­je pár se­kúnd, nie­ke­dy vô­bec. Uspá­va­nie no­te­bo­oku pre­sta­lo fun­go­vať. Ovlá­dač per­ma­nen­tne spot­re­bu­je 10% ča­su CPU. Pod­ľa sú­bo­ru /proc/interrupts sa vy­ko­ná­va pre­ru­še­nie 2 000x za se­kun­du. Nie úpl­ne dob­rý vý­sle­dok, ale som na správ­nej ces­te.

Do­stať sa do toh­to bo­du ma stá­lo nie­koľ­ko ví­ken­dov. Ve­ľa ča­su som strá­vil hra­ba­ním sa v do­ku­men­tá­cií, uče­ním sa C a pí­sa­ním otras­né­ho kó­du, aby som zis­til, či som vô­bec na správ­nej ces­te. Ne­bo­lo to zlo­ži­té, ale po­tre­bo­val som pre­skú­mať ve­ľa sle­pých uli­čiek. Za­tiaľ som prak­tic­ky ne­pot­re­bo­val žiad­ne špe­ciál­ne ve­do­mos­ti. Všet­ko, čo som po­tre­bo­val som si na­štu­do­val prie­bež­ne. Te­raz, keď už mám sko­ro funkč­nú pod­po­ru touch­pa­du cez SM­Bus zo­stá­va há­dam len im­ple­men­to­vať host no­ti­fy pr­to­kol.

Host notify

Do­te­raz to bo­lo len ta­ké hra­nie sa s vy­pi­so­va­ním pre­men­ných. Te­raz na­stu­pu­je sku­toč­ná prá­ca pre sku­toč­ných hr­di­nov. Žia­den Bru­ce Wil­lis na as­te­ro­ide, ani ďal­šia mňam­ka. Te­raz idem re­ál­ne im­ple­men­to­vať no­vú fun­kci­ona­li­tu do jad­ra.

Na za­čia­tok je dob­rý ná­pad po­zrieť si, ako tú­to fun­kciu im­ple­men­tu­jú iné ovlá­da­če. Prí­kaz grep -r I2C_FUNC_SMBUS_HOST_NOTIFY . nad kó­dom ker­ne­lu náj­de 3 re­le­vant­né vý­sled­ky. Ak vy­ra­dí­me rôz­ne mik­ro­kon­tro­lé­ry a za­me­ria­me sa na x86, zo­sta­ne je­di­ný vý­sle­dok - i2c-i801.c. Ce­lá ob­slu­ha je ex­trém­ne jed­no­du­chá, sta­čí pre­čí­ta­nie jed­né­ho re­gis­tra, za­vo­la­nie i2c_handle_smbus_host_notify, vy­čis­te­nie sta­vo­vé­ho re­gis­tra a vrá­te­nie sta­vu IRQ_HANDLED.

static irqreturn_t i801_host_notify_isr(struct i801_priv *priv)
{
        unsigned short addr;

        addr = inb_p(SMBNTFDADD(priv)) >> 1;
        2c_handle_smbus_host_notify(&priv->adapter, addr);

        /* clear Host Notify bit and return */
        outb_p(SMBSLVSTS_HST_NTFY_STS, SMBSLVSTS(priv));
        return IRQ_HANDLED;
}

Zo­stá­va už len re­gis­tro­vať pre­ru­še­nie, nájsť re­le­vant­né re­gis­tre v BGKD a na­pí­sať ob­slu­hu pre­ru­še­nia.

Za­čí­na­me pre­ru­še­ním. Do piix4_probe pri­dá­vam na­sle­du­jú­ci kód:

static irqreturn_t piix4_isr(int irq, void *dev_id)
{
        printk(KERN_INFO "isr\n");
        return IRQ_HANDLED;
}

// … piix4_probe
        retval = devm_request_irq(&dev->dev, dev->irq, piix4_isr, IRQF_SHARED, "piix4_smbus", piix4_aux_adapter);
        if (!retval) {
                printk(KERN_INFO "smbus Using irq %d\n", dev->irq);
        }
        else {
                printk(KERN_INFO "smbus No irq %d\n", dev->irq);
        }

Po za­ve­de­ní mo­du­lu sa v dmesgu ob­ja­ví: No irq -1. Po­le irq štruk­tú­ry pci_dev je na­sta­ve­né pod­ľa PCI kon­fi­gu­rač­né­ho pries­to­ru. Hod­no­ta in­ter­rupt li­ne bý­va štan­dard­ne v kon­fi­gu­rač­nom pries­to­re na ad­re­se 0x3c. Pri­po­me­niem vý­pis lspci:

00: 22 10 0b 79 00 04 20 02 51 00 05 0c 00 00 80 00
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 aa 17 94 50
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Pod­ľa BKDG, stra­na 866, re­gis­ter D14F0x3C In­ter­rupt Li­ne bi­ty 7:0 je hod­no­ta toh­to re­gis­tra vždy 0:

In­ter­rupt­Li­ne: In­ter­rupt Li­ne. Va­lue: 0. 0=This mo­du­le do­es not ge­ne­ra­te in­ter­rupts.

Kon­fi­gu­rá­cia pre­ru­še­nia by sa ma­la dať na­čí­tať z acpi_device vo­la­ním ACPI_COMPANION, ale to ne­fun­gu­je. K rie­še­niu sa vrá­tim ne­skôr. Za­tiaľ si vy­sta­čím s pev­ne na­sta­ve­ným čís­lom pre­ru­še­nia. Asi naj­čis­tej­šie rie­še­nie je pri­dať na­sle­du­jú­ci kód do sú­bo­ru drivers/pci/quirks.c:

static void quirk_piix4_amd(struct pci_dev *dev) {
        printk(KERN_INFO "piix4 fixing interrupt line");
        pci_write_config_byte(dev, PCI_INTERRUPT_LINE, 7);
        dev->irq = 7;
}
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, quirk_piix4_amd);

Pre­ru­še­nie sa te­raz už re­gis­tru­je bez prob­lé­mov. Vy­ze­rá to tak, že sa spúš­ťa pri kaž­dom pre­no­se dát (mož­no aj pri host no­ti­fy, ale tým som si ne­bol is­tý). Je čas pre­skú­mať po­riad­ne re­gis­tre v BKDG. Do­ku­men­tá­cia sa za­čí­na po­pi­som PCI kon­fi­gu­rač­né­ho pries­to­ru na stra­ne 863 a po­kra­ču­je ASF (Alert Stan­dard For­mat) na­sle­do­va­né SM­Bu­som. Tro­chu sa za­sta­vím u ASF. Pod­ľa do­ku­men­tá­cie by na 0x0b20 ma­lo byť za­ria­de­nie ASF:

Sm­bu­sAs­fI­oBa­se. Re­ad-wri­te. Re­set: 0Bh. Spe­ci­fi­ies SM­Bus and ASF IO ba­se add­ress. By de­fault SM­Bus IO ba­se is B00h and ASF IO ba­se is B20h.

V ker­ne­li sa ne­na­chá­dza žiad­na pod­po­ra ASF. Pod­ľa štan­dar­du ide o ro­z­hra­nie pre vzdia­le­nú sprá­vu po­čí­ta­ča.

The term "sys­tem ma­na­ge­a­bi­li­ty" re­pre­sents a wi­de ran­ge of tech­no­lo­gies that enab­le re­mo­te sys­tem ac­cess and con­trol in both OS-pre­sent and OS-ab­sent en­vi­ron­ments.

Je do­sť ne­prav­de­po­dob­né, že by bol touch­pad pri­po­je­ný na ro­z­hra­nie pre vzdia­le­nú sprá­vu po­čí­ta­ča. Bu­dem pred­po­kla­dať, že mám sta­rú, ale­bo chyb­nú do­ku­men­tá­ciu a na nov­ších pro­ce­so­roch je na ad­re­se 0x0b20 tiež SM­Bus.

Pri­pra­vil som si fun­kciu kto­rá vy­pí­še všet­ky hod­no­ty re­gis­trov ok­rem 0x02 a 0x07, aby som sa do­zve­del, kto­ré sta­vo­vé re­gis­tre sa na­sta­via po pre­ru­še­ní. Spo­me­nu­té re­gis­tre vy­ne­chá­vam, pre­to­že pri ich čí­ta­ní pre­sta­ne fun­go­vať ko­mu­ni­ká­cia s touch­pa­dom. Pri re­gis­tri 0x07 je to lo­gic­ké, pre­to­že je to SMBusBlockData - špe­ciál­ny re­gis­ter so vsta­va­ným po­in­te­rom, kto­rý sa in­kre­men­tu­je kaž­dým čí­ta­ním a zá­pi­som. V do­ku­men­tá­cii nie je na­pí­sa­né, pre­čo má si­de efekt aj čí­ta­nie re­gis­tra SMBusControl, ale em­pi­ric­ky som zis­til, že čí­ta­ním sa re­se­tu­je in­ter­ný po­in­ter SMBusBlockData re­gis­tra. Kód vý­pi­su vy­ze­rá na­sle­dov­ne:

static void piix4_dump_registers(struct i2c_adapter *piix4_adapter, char *label)
{
        struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(piix4_adapter);
        unsigned short piix4_smba = adapdata->smba;
        int i;
        u8 d[0x17];

        for (i = 0; i < 0x17; ++i) {
                if (i == 2 || i == 7) {
                        d[i] = 0;
                }
                else {
                        d[i] = inb_p(i + piix4_smba);
                }
        }

        printk(KERN_INFO "%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x    %02x%02x %02x%02x %02x%02x %02x%02x\n", label, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15], d[16], d[17], d[18], d[19], d[20], d[21], d[22], d[23]);
}

Sa­mot­ný vý­pis vy­ze­rá tak­to:

┌------------------------------------------------------------ SMBusStatus
| ┌---------------------------------------------------------- SMBusSlaveStatus
| |  ┌------------------------------------------------------- SMBusControl
| |  | ┌----------------------------------------------------- SMBusHostCmd
| |  | |  ┌-------------------------------------------------- SMBusAddress
| |  | |  | ┌------------------------------------------------ SMBusData0
| |  | |  | |  ┌--------------------------------------------- SMBusData1
| |  | |  | |  | ┌------------------------------------------- SMBusBlockData
| |  | |  | |  | |  ┌---------------------------------------- SMBusSlaveControl
| |  | |  | |  | |  | ┌-------------------------------------- SMBusShadowCmd
| |  | |  | |  | |  | |  ┌----------------------------------- SMBusSlaveEvent
| |  | |  | |  | |  | |  |    ┌------------------------------ SlaveData
| |  | |  | |  | |  | |  |    |    ┌------------------------- SMBusTiming
| |  | |  | |  | |  | |  |    |    |       ┌----------------- I2CbusConfig
| |  | |  | |  | |  | |  |    |    |       | ┌--------------- I2CCommand
| |  | |  | |  | |  | |  |    |    |       | |  ┌------------ I2CShadow1
| |  | |  | |  | |  | |  |    |    |       | |  | ┌---------- I2Cshadow2
| |  | |  | |  | |  | |  |    |    |       | |  | |  ┌------- SMBusAutoPoll
| |  | |  | |  | |  | |  |    |    |       | |  | |  | ┌----- SMBusCounter
| |  | |  | |  | |  | |  |    |    |       | |  | |  | |  ┌-- SMBusStop
| |  | |  | |  | |  | |  |    |    |       | |  | |  | |  | ┌ SMBusHostCmd2
0000 0006 5801 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0087 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0007 5801 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0080 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0000 5801 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0081 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0001 5802 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0001 5802 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0082 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0002 5902 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0083 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0003 590e 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0001 5802 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0084 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0004 5801 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0001 5802 0000 0f59 00ff ff00 a8aa    0000 008d 0002 0400

Po­sled­ným prí­ka­zom sa skon­či­la ini­cia­li­zá­cia. Z do­ku­men­tá­ciu touch­pa­du (sy­nap­tics) som zis­til, že vždy po ini­cia­li­zá­ci­ii po­šle no­ti­fi­ká­ciu, tak­že po­sled­ný ria­dok by sa mal v nie­čom lí­šiť od pred­chá­dza­jú­cich. V sku­toč­nos­ti sa aj lí­ši v re­gis­tri I2CShaow2. Vo vý­pi­se je ok­rem to­ho nie­koľ­ko ano­má­lii, kto­rým som ne­ro­zu­mel.

V pr­vom ra­de som ča­kal, že SMBusStatus bu­de mať na­sta­ve­ný bit SMBusInterrupt in­di­ku­jú­ci ukon­če­nie ope­rá­cie (prí­pad­ne aj iné bi­ty pod­ľa to­ho, či doš­lo na­prí­klad ku ko­lí­zii). Sta­tus je však vždy nu­lo­vý.

Ďal­šou ano­má­li­ou je SMBusSlaveEvent. Pri ini­cia­li­zá­cii som ex­pli­cit­ne do oboch čas­tí re­gis­tra za­pí­sal 0xff, aby som pri­jí­mal všet­ky uda­los­ti. Na­priek to­mu je hor­ná po­lo­vi­ca re­gis­tra prázd­na.

outb_p(0xff, (0x0a + piix4_smba));
outb_p(0xff, (0x0b + piix4_smba));

Po­tom tu má­me SMBusStop, kto­rý ob­sa­hu­je ne­prí­pust­nú hod­no­tu, SMBusCounter, kto­rý z ne­ja­ké­ho zá­had­né­ho dô­vo­du ob­sa­hu­je 0x02 a pre­čo sa me­ní iba I2CShadow2?

Ne­zrov­na­los­ti za­tiaľ ig­no­ru­jem a skú­šam za­pnúť pre­ru­še­nie pri prí­cho­de host no­ti­fy sprá­vy. Pod­ľa do­ku­men­tá­cie by sa to moh­lo do­siah­nuť zá­pi­som buď ad­re­sy 0x2c, 0x08, ale­bo ich ek­vi­va­len­tov s bi­to­vým po­su­nom o 1 do­ľa­va do re­gis­tra I2CCommand. Zá­ro­veň by mal mať bit I2CbusInterrupt re­gis­tra I2CbusConfig hod­no­tu 1. Po­kú­sil som sa na­sta­viť všet­ky uda­los­ti v re­gis­tri SMBusSlaveEvent na 1 a tak­tiež SlaveEnable a SMBusAlertEnable bi­ty SMBusSlaveControl re­gis­tra. Skú­šal som všet­ky mož­né kom­bi­ná­cie vy­še týžd­ňa, ab­so­lút­ne bez úspe­chu. Pre­ru­še­nia bo­li ge­ne­ro­va­né len pre pre­no­sy dát, ale nie pre no­ti­fi­ká­cie od za­ria­de­nia.

Keď už som ne­ve­del, ako ďa­lej, skú­sil som sa po­zrieť na GPIO. Pod­ľa RMI4 In­tr­fa­cing Gu­ide vie touch­pad ge­ne­ro­vať buď host no­ti­fy sprá­vy, ale­bo vie žia­dať o pre­ru­še­nie na ne­ja­kom vý­stup­nom pi­ne. Čo ak pre­ru­še­nie 7 slú­ži len na in­di­ká­ciu ukon­če­nia pre­no­su a touch­pad je pri­po­je­ný na pin GPIO či­pu?

Pod­ľa /sys/kernel/debug/pinctrl/AMDI0030:00/pins má môj no­te­bo­ok 183 pi­nov. Po­do­bá sa to tro­chu hľa­da­niu ih­ly v ko­pe se­na. Na­šťas­tie v tom is­tom ad­re­sá­ri exis­tu­je sú­bor pingroups s na­sle­du­jú­cim ob­sa­hom:

registered pin groups:
…
group: i2c3
pin 19 (GPIO_19)
pin 20 (GPIO_20)

group: uart0
…

Zau­jí­ma­vý je zá­znam i2c3. Nie, že by os­tat­né I2C por­ty ne­bo­li zau­jí­ma­vé, ale keď som dal mo­ni­to­ro­vať prá­ve ten­to prí­ka­zom gpiomon --num-events=5000 gpiochip0 19 20 za­zna­me­nal som ak­ti­vi­tu na zber­ni­ci hneď po na­čí­ta­ní psmouse a po­tom na­sta­lo ti­cho. Keď som sa dot­kol touch­pa­du, okam­ži­te sa ob­no­vi­la ak­ti­vi­ta na zber­ni­ci a pre­sta­la až 5 mi­nút po po­sled­nom do­ty­ku. Prav­de­po­dob­ne po tej­to zber­ni­ci ko­mu­ni­ku­je touch­pad a je cel­kom sluš­ná šan­ca, že ob­ja­vím in­ter­rupt pin. Bez po­pi­su GPIO pi­nov to však ne­pôj­de a AMD za­tiaľ ne­vy­da­lo do­ku­men­tá­ciu k môj­mu CPU.

Zo­znam GPIO som na­šiel v zdro­jo­vom kó­de co­re­bo­otu. Eš­te zau­jí­ma­vej­ší je sú­bor gpio.c, v kto­rom sa da­jú nájsť pi­ny pri­ra­de­né k uda­los­tiam. Ne­bu­dem dl­ho na­pí­nať a po­viem, že ani je­den ne­vy­ze­ral ako pre­ru­še­nie touch­pa­du. Pri po­ku­se o pre­čí­ta­nie väč­ši­ny GPIO doš­lo k zho­de­niu sys­té­mu. Za­se sle­pá ulič­ka, ale do­zve­del som sa as­poň, že na I2C3 je ak­ti­vi­ta zod­po­ve­da­jú­ca do­ty­ku. Rých­losť čí­ta­nia GPIO je sí­ce ne­dos­ta­toč­ná na to, aby som do­ká­zal de­kó­do­vať, čo sa tam po­sie­la, ale aj tak skve­lá in­dí­cia.

Po­kra­ču­jem ďa­lej v štu­do­va­ní do­ku­men­tá­cie k chip­se­tu PI­I­X4. Pod­ľa AMD sa pri po­u­ži­tí SlaveEnable sa mu­sí ad­re­sa, na kto­rej po­čú­va za­pí­sať do sla­ve con­trol re­gis­tra.

Sla­ve­E­nab­le. Re­ad-wri­te. Re­set: 0. Enab­le the ge­ne­ra­ti­on of an in­ter­rupt or re­su­me event upon an ex­ter­nal SM­Bus mas­ter ge­ne­ra­ting a trans­ac­ti­on with an add­ress that mat­ches the host con­trol­ler sla­ve port of 10h, a com­mand field that mat­ches the SM­Bus sla­ve con­trol re­gis­ter, and a match of cor­res­pon­ding enab­led events.

Do­ku­men­tá­cia AMD ni­kde in­de ne­spo­mí­na sla­ve con­trol re­gis­ter. Prá­ve tu pri­chá­dza na scé­nu do­ku­men­tá­cia k PI­I­X4, kto­rá de­fi­nu­je con­trol re­gis­ter ako 0xD4 re­gis­ter PCI kon­fi­gu­rač­né­ho pries­to­ru. Sí­ce fajn, ale zá­pis na mo­jom za­ria­de­ní ne­fun­gu­je. Po zvá­že­ní od­ha­du­jem, že ta­diaľ ces­ta tiež ne­ve­die. AMD si zrej­me ne­jak im­ple­men­to­va­lo host no­ti­fy, ale do­ku­men­tá­ciu sko­pí­ro­va­lo od In­te­lu. Pre­to sa v nej spo­mí­na­jú ne­exis­tu­jú­ce re­gis­tre.

Vra­ciam sa späť k SM­Bus re­gi­strom. Do očí mi ten­to­raz ud­re­li 2 ve­ci. Zá­pis do I2CCommand ne­me­ní hod­no­tu re­gis­tra (ako ke­by bol re­ad on­ly, aj keď pod­ľa do­ku­men­tá­cie je re­ad/wri­te). Ďa­lej je tu zá­had­ná hod­no­ta a8 aa re­gis­tra SMBusTiming. Ak by to bo­lo ASF, po­tom by ad­re­sa SMBusTiming re­gis­tra zod­po­ve­da­la re­gi­strom RemoteCtrlAdr a SensorAdr. Pr­vá má štan­dard­ne hod­no­tu 0x54 po­su­nu­tú o 1 bit do­ľa­va, čo zod­po­ve­dá 0xa8. Dru­há 0x55, čo zod­po­ve­dá 0xaa. Zá­ro­veň re­gis­ter I2CCommand zod­po­ve­dá DataReadPointer v ASF a ten je re­ad on­ly. Za­čí­nam mať tu­še­nie, že sa k ASF sna­žím pri­stu­po­vať, ako ke­by to bol SM­Bus.

S tým­to po­doz­re­ním som sa roz­ho­dol po­zrieť na ovlá­dač pre win­do­ws. Me­dzi sú­bor­mi vi­dím Smb_driver_AMDASF.sys a Smb_driver_Intel.sys. Ono to je váž­ne na AMD pri­po­je­né k ASF!

ASF

Pus­til som sa hneď do štú­dia ASF čí­ta­ním špe­ci­fi­ká­cie. Skú­šal som pod­ľa špe­ci­fi­ká­cie im­ple­men­to­vať Get Event Da­ta, ale ab­so­lút­ne bez úspe­chu. Skú­šal som za­pnúť kon­trol­né súč­ty pac­ke­tov (pre­to­že tie sú pre ASF po­vin­né) a nič. Skú­šal som na­pí­sať vlast­nú im­ple­men­tá­ciu kon­trol­ných súč­tov, bez úspe­chu. Za­ria­de­nie uve­de­né v SensorAdr vô­bec ne­od­po­ve­da­lo. Hra­bal som sa pod­rob­nej­šie aj v špe­ci­fi­ká­cii SM­Bu­su, aby som cez ARP zis­til pri­po­je­né za­ria­de­nie, ale vy­ze­rá to tak, že AMD ne­im­ple­men­tu­je ani ARP. Za­se raz ví­kend, pri kto­rom som sa ni­kam ne­po­mo­hol.

Ne­skôr som len tak ná­ho­dou za­pí­sal ad­re­su 0x08 do ListenAdr re­gis­tra. Zra­zu sa za­ča­li spon­tán­ne ge­ne­ro­vať pre­ru­še­nia. Nie sí­ce úpl­ne pra­vi­del­ne a ne­bol som si is­tý, či re­a­gu­jú na do­tyk touch­pa­du (vi­deo), ale roz­hod­ne zau­jí­ma­vé zis­te­nie. Os­tat­né ad­re­sy ne­fun­go­va­li, ale 0x08 áno. Vlast­ne je to úpl­ne lo­gic­ké, pre­to­že na tú­to ad­re­su má za­ria­de­nie po­sie­lať po­žia­dav­ku na ob­slu­hu pre­ru­še­nia. Te­raz už len zis­tiť, či pre­ru­še­nie bo­lo ge­ne­ro­va­né pre­no­som, ale­bo sla­ve za­ria­de­ním, pre­čí­tať ad­re­su sla­ve a za­vo­lať i2c_handle_smbus_host_notify. Ma­lou kom­pli­ká­ci­ou je, že ad­re­su 0x2c ne­vi­dím v žiad­nom re­gis­tri a ne­vi­dím žiad­ny zá­sad­ný roz­diel me­dzi pre­ru­še­ním z pre­no­su a pre­ru­še­ním od sla­ve.

Eš­te raz zo­pa­ku­jem vý­pis re­gis­trov, ale ten­to­raz so správ­ny­mi náz­va­mi:

┌------------------------------------------------------------ HostStatus
|    ┌------------------------------------------------------- HostControl
|    | ┌----------------------------------------------------- HostCommand
|    | |  ┌-------------------------------------------------- SlaveAddress
|    | |  | ┌------------------------------------------------ Data0
|    | |  | |  ┌--------------------------------------------- Data1
|    | |  | |  | ┌------------------------------------------- DataIndex
|    | |  | |  | |  ┌---------------------------------------- PEC
|    | |  | |  | |  | ┌-------------------------------------- ListenAdr
|    | |  | |  | |  | |  ┌----------------------------------- ASFStatus
|    | |  | |  | |  | |  | ┌--------------------------------- StatusMask0
|    | |  | |  | |  | |  | |  ┌------------------------------ StatusMask1
|    | |  | |  | |  | |  | |  | ┌---------------------------- SlaveStatus
|    | |  | |  | |  | |  | |  | |  ┌------------------------- RemoteCtrlAdr
|    | |  | |  | |  | |  | |  | |  | ┌----------------------- SensorAdr
|    | |  | |  | |  | |  | |  | |  | |     ┌----------------- DataReadPointer
|    | |  | |  | |  | |  | |  | |  | |     | ┌--------------- DataWritePointer
|    | |  | |  | |  | |  | |  | |  | |     | |  ┌------------ SetDataReadPointer
|    | |  | |  | |  | |  | |  | |  | |     | |  | ┌---------- DataBankSel
|    | |  | |  | |  | |  | |  | |  | |     | |  | |  ┌------- Semaphore
|    | |  | |  | |  | |  | |  | |  | |     | |  | |  | ┌----- SlaveEn
|    | |  | |  | |  | |  | |  | |  | |     | |  | |  | |  ┌-- DelayMasterTimer
0000 0006 5801 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0087 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0007 5801 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0080 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0000 5801 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0081 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0001 5802 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0001 5802 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0082 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0002 5902 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0083 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0003 590e 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0001 5802 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0084 5804 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0004 5801 0000 0f59 00ff ff00 a8aa    0000 0081 0002 0400
0000 0001 5802 0000 0f59 00ff ff00 a8aa    0000 008d 0002 0400

Na kon­ci ini­cia­li­zač­nej sek­ven­cie re­gis­ter sa me­ní re­gis­ter DataBankSel. Pri­bud­li prí­zna­ky DatabankXFull.

Keď­že som sa ne­ve­del ďa­lej moc po­hnúť, skú­sil som de­kom­pi­lo­vať sta­rý SM­Bus ovlá­dač a tiež no­vý (tro­chu zlo­ži­tej­ší) ovlá­dač (ako de­kom­pi­lá­tor po­u­ží­vam ghid­ra).

V zdro­jo­vých kó­doch som ex­pli­cit­ne hľa­dal IO vo­la­nia in a out. Na­sle­du­jú­ci kód som iden­ti­fi­ko­val ako ob­slu­hu host no­ti­fy pre­ru­še­nia. V ko­men­tá­ri je ta­buľ­ka, kto­rú som si pri­pra­vil pre hod­no­ty re­gis­tra DataBankSel & 0x0c. Pr­vý stĺpec je hod­no­ta re­gis­tra, na­sle­du­jú 2 stĺp­ce re­pre­zen­tu­jú­ce čí­ta­nie (pr­vá čís­li­ca ur­ču­je ban­ku z kto­rej sa čí­ta a dru­há čís­li­ca ban­ku, do kto­rej sa na­čí­ta­ná hod­no­ta za­pí­še). Po­sled­ným stĺp­com je vý­stup­ná hod­no­ta.

/*

0 00x0       ret0
1 00x1       ret0
4 01x0    00 ret0
5 01x1       ret0
8 10x0       ret0
9 10x1    10 ret1
c 11x0 10 01 ret1
d 11x1 00 11 ret1

*/

ulonglong FUN_1400044c4(longlong param_1)

{
  byte bVar1;
  ulonglong uVar2;
  ulonglong uVar3;
  longlong lVar4;
  longlong lVar6;

  bVar1 = in((short)*(undefined4 *)(param_1 + 0x14) + 0x13);
  bVar1 = bVar1 & 0xd;
  uVar2 = 0;
  if (bVar1 < 2) {
LAB_1400045d8:
    return uVar2 & 0xffffffffffffff00;
  }
  if (bVar1 == 4) {
    *(undefined8 *)(param_1 + 0xb0) = 0;
    lVar6 = 0;
  }
  else {
    if (bVar1 == 9) {
      *(undefined8 *)(param_1 + 0xb0) = 0;
      lVar6 = 0;
      lVar4 = 1;
      goto LAB_14000458b;
    }
    if (bVar1 != 0xc) {
      if (bVar1 != 0xd) {
        out((ulonglong)*(uint *)(param_1 + 0x14) + 0x13,bVar1 & 0xc);
        return 0;
      }
      *(undefined8 *)(param_1 + 0xb0) = 0;
      read_notify_address(param_1,0,0);
      lVar6 = 1;
      lVar4 = 1;
      goto LAB_14000458b;
    }
    *(undefined8 *)(param_1 + 0xb0) = 0;
    read_notify_address(param_1,1,0);
    lVar6 = 1;
  }
  lVar4 = 0;
LAB_14000458b:
  uVar2 = read_notify_address(param_1,lVar4,lVar6);
  uVar2 = uVar2 & 0xffffffffffffff00;
  out((short)*(undefined4 *)(param_1 + 0x14) + 0x13,bVar1 & 0xc);
  return uVar2 & 0xffffffffffffff00 | 1;
}

void read_notify_address(longlong param_1,longlong param_2,longlong param_3)
{
  byte bVar1;
  undefined uVar2;
  undefined uVar3;

  *(int *)(param_1 + 0xa0) = *(int *)(param_1 + 0xa0) + 1;
  out((short)*(undefined4 *)(param_1 + 0x14) + 0x13,(&DAT_1400051d0)[param_2]);
  in((short)*(undefined4 *)(param_1 + 0x14) + 2);
  in((short)*(undefined4 *)(param_1 + 0x14) + 7);
  bVar1 = in((short)*(undefined4 *)(param_1 + 0x14) + 7);
  uVar2 = in((short)*(undefined4 *)(param_1 + 0x14) + 7);
  uVar3 = in((short)*(undefined4 *)(param_1 + 0x14) + 7);
  *(byte *)(param_1 + 0xc0 + param_3 * 4) = bVar1 >> 1;
  *(ushort *)(param_1 + 0xc2 + param_3 * 4) = CONCAT11(uVar3,uVar2);
  *(longlong *)(param_1 + 0xb8) = param_3;
  return;
}

Stá­le mi uni­kal bit SetReadHostDataBank re­gis­tra DataBankSel. Ak je ten­to bit 1, po­tom čí­ta­nie z DataIndex po­stup­ne pre­čí­ta dá­ta z po­sled­né­ho blo­ko­vé­ho pre­no­su, len­že ak je 0 pre­čí­ta ad­re­su, kto­ré žia­da­lo o pre­ru­še­nie. Pres­ne to­to som po­tre­bo­val.

Re­gis­ter DataBankSel má tro­chu zlo­ži­tej­šiu štruk­tú­ru. V do­be pí­sa­nia jej cel­kom ne­ro­zu­miem, ale mám akú-ta­kú im­ple­men­tá­ciu, kto­rú po­va­žu­jem za do­sta­toč­ne spo­ľah­li­vú. Te­raz skú­sim opí­sať, čo o tom­to re­gis­tri viem.

Za­ria­de­nie má 2 da­ta ban­ky. Pre­pí­nať sa da­jú zá­pi­som 0/1 do SetReadRevDataBank. Dá­ta na SM­Bu­se mô­žu mať rôz­nu dĺž­ku a ne­exis­tu­je sek­ven­cia, kto­rá by jed­no­znač­ne od­de­ľo­va­la sprá­vy. Pre­to od­ha­du­jem, že bi­ty Databank0Full a Databank1Full zna­me­na­jú, že do ban­ky bo­la za­pí­sa­ná prá­ve 1 sprá­va. Vý­znam DataBank[1] ne­po­znám. Pred­po­kla­dám, že bit DataBank[0] (0=Da­ta Bank 0 is the la­test tou­ched da­ta bank) by mal slú­žiť len pre jed­no­znač­né ur­če­nie, do kto­rej ban­ky priš­la sprá­va skôr. Win­do­wso­vý ovlá­dač tam ro­bí eš­te ne­ja­kú má­giu oko­lo toh­to re­gis­tra. Mo­ja im­ple­men­tá­cia vy­ze­rá byť lo­gic­ky správ­na, ale s nie­kto­rý­mi kom­bi­ná­cia­mi bi­tov mi ne­pre­čí­ta ce­lú ad­re­su, ale­bo pre­čí­ta ad­re­sy v opač­nom po­ra­dí, ale stá­va sa to tak zried­ka­vo, že za­tiaľ to ig­no­ru­jem. Mo­ja im­ple­men­tá­cia vy­ze­rá tak­to:

static u8 read_asf_data_bank(struct i2c_adapter *piix4_adapter, u8 bank_number)
{
        struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(piix4_adapter);
        unsigned short piix4_smba = adapdata->smba;
        u8 host_addr, addr, bank_sel;

        outb_p(bank_number << ASF_SET_READ_DATA_BANK_OFFSET, ASF_DATA_BANK_SEL);
        bank_sel = inb_p(ASF_DATA_BANK_SEL);
        inb_p(SMBHSTCNT); // reset DataIndex
        host_addr = inb_p(SMBBLKDAT);
        addr = inb_p(SMBBLKDAT);

        //dev_dbg(&piix4_adapter->dev, "BankSel=%02x Data=%02x %02x\n", bank_sel, host_addr, addr);

        if (host_addr != 0x10) {
                return 0;
        }

        return addr;
}


static irqreturn_t piix4_isr(int irq, void *dev_id)
{
        struct i2c_adapter *piix4_adapter = (struct i2c_adapter *)dev_id;
        struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(piix4_adapter);
        unsigned short piix4_smba = adapdata->smba;

        u8 bank_sel;
        u8 asf_status;
        u8 address[2] = {0x00, 0x00};
        u8 *current_address;

        current_address = &address[0];

        bank_sel = inb_p(ASF_DATA_BANK_SEL); // DataBankSel

        if ((bank_sel & ASF_DATA_BANK_LAST_TOUCH) == 0) { // Last touched bank is 0
                if (bank_sel & ASF_DATA_BANK_1_FULL) {
                        *current_address = read_asf_data_bank(piix4_adapter, 1);
                        current_address++;
                }
                if (bank_sel & ASF_DATA_BANK_0_FULL) {
                        *current_address = read_asf_data_bank(piix4_adapter, 0);
                }
        }
        else { // Last touched bank is 1
                if (bank_sel & ASF_DATA_BANK_0_FULL) {
                        *current_address = read_asf_data_bank(piix4_adapter, 0);
                        current_address++;
                }
                if (bank_sel & ASF_DATA_BANK_1_FULL) {
                        *current_address = read_asf_data_bank(piix4_adapter, 1);
                }
        }

        outb_p(bank_sel & (ASF_DATA_BANK_0_FULL | ASF_DATA_BANK_1_FULL), ASF_DATA_BANK_SEL); // Clear DataBankxFull

        // Trigger notifications
        if (address[0] != 0x00) {
                i2c_handle_smbus_host_notify(piix4_aux_adapter, address[0] >> 1);
        }
        if (address[1] != 0x00) {
                i2c_handle_smbus_host_notify(piix4_aux_adapter, address[1] >> 1);
        }

        // Clean ASFStatus SlaveIntr
        asf_status = inb_p(ASF_STATUS);
        if (asf_status & ASF_SLAVE_INTR) {
                outb_p(ASF_SLAVE_INTR, (ASF_STATUS)); // ASFStatus SlaveIntr? (in doc 0x20)
        }

        return IRQ_HANDLED;
}

Tak­to to viac-me­nej fun­gu­je, ale hroz­ne se­ká. Vlast­ne ani to nie je prek­va­pe­nie keď vi­dím, ako ka­ta­stro­fál­ne je to im­ple­men­to­va­né v li­nu­xe. Na­mies­to jed­no­du­ché­ho ča­ka­nia na pre­ru­še­nie je tu ak­tív­ne ča­ka­nie v sluč­ke, kým ne­bu­de na­sta­ve­ný prí­znak do­kon­če­nia ope­rá­cie. Ukáž­ka li­nu­xo­vé­ho kó­du:

while ((++timeout < MAX_TIMEOUT) &&
       ((temp = inb_p(SMBHSTSTS)) & 0x01))
        usleep_range(250, 500);

Roz­ho­dol som sa ak­tív­ne ča­ka­nie na­hra­diť pre­ru­še­ním. Pod­ľa ker­nel do­ku­men­tá­cie som alo­ko­val a ini­cia­li­zo­val completion ob­jekt a ča­kal na do­kon­če­nie ope­rá­cie s li­mi­tom ča­ka­nia 100ms.

timeout = wait_for_completion_timeout(adapdata-&gt;completion, msecs_to_jiffies(100));
if (timeout == 0) {
        dev_err(&piix4_adapter-&gt;dev, "SMBus Timeout!\n");
        result = -ETIMEDOUT;
}

V ob­slu­he pre­ru­še­nia sta­čí za­vo­lať complete:

host_status = inb_p(SMBHSTSTS);

// Clear HostStatus Intr and complete waiting
if (host_status & ASF_HOST_INTR) {
        outb_p(ASF_HOST_INTR, SMBHSTSTS);

        if (adapdata->completion) {
                complete(adapdata->completion); // Notify caller
        }
}

Te­raz som skú­sil po­slať je­den quick command a nič. Žiad­ne pre­ru­še­nie. Zno­vu som po­slal prí­kaz a ten­to­raz som do­stal pre­ru­še­nie. Zno­vu a ďal­šie pre­ru­še­nie. Zau­jí­ma­vý fakt, že pr­vé vo­la­nie ni­kdy ne­vy­vo­lá pre­ru­še­nie. Zá­ro­veň v pre­ru­še­ní mám stá­le prázd­ny HostStatus.

Ďa­lej som sa po­zrel na ča­so­va­nie. Pre­ru­še­nie sa mi spúš­ťa 7μs po spus­te­ní pre­no­su. No po­čkať. Pod­ľa PM re­gis­tra (prá­ve som ho kon­tro­lo­val) je frek­ven­cia zber­ni­ce na­sta­ve­ná na 100kHz. Te­raz po­čí­taj­me. Prí­kaz quick command sa skla­dá zo štart bi­tu, 7-bi­to­vej ad­re­sy, wri­te bi­tu a po­tvr­de­nia. Spo­lu 10 bi­tov. Pri frek­ven­cii 100kHz je­den tak tr­vá 10μs. Pre­nos 10 bi­tov ne­mô­že byť krat­ší než 100μs. Nie­čo tu váž­ne ne­se­dí …

Tak si daj­me do­ko­py fak­ty. Pre­ru­še­nie sa spus­tí po dru­hom pre­no­se. Pre­ru­še­nie sa spus­tí skôr než sa mô­že odo­slať čo i len je­den bit. Pre­ru­še­nie sa ne­spus­tí keď zber­ni­cu ini­cia­li­zu­jem a re­se­tu­jem. Pre­ru­še­nie sa spus­tí ak uro­bím 1 pre­nos a po­tom re­se­tu­jem zber­ni­cu. Te­raz si do­vo­lím vy­slo­viť ka­cír­sku myš­lien­ku, že pre­ru­še­nie sa spúš­ťa v ne­správ­nom mo­men­te. Pri­po­meň­me si na­sta­ve­nie pre­ru­še­ní z AC­PI:

IRQ (Level, ActiveLow, Shared, )
    {7}

Pre­ru­še­nie je cit­li­vé na úro­veň a ak­tív­na hod­no­ta je lo­gic­ká 0. Do­vo­lím si jed­nu ma­lú od­boč­ku k pre­ru­še­niam.

Typy prerušení

Exis­tu­jú 2 ty­py pre­ru­še­ní - cit­li­vé na hra­nu a na úro­veň. K obom exis­tu­jú 2 po­la­ri­ty:

  Typ
Po­la­ri­ta Ná­bež­ná hra­na Ak­tív­na 1
Do­bež­ná hra­na Ak­tív­na 0

Pre­ru­še­nia cit­li­vé na hra­nu sa spúš­ťa­jú iba pri pre­cho­de. Oby­čaj­ne sa po­u­ží­va­jú pri jed­no­du­chých za­ria­de­niach, kto­ré ma­jú je­di­ný zdroj pre­ru­še­nia. Ná­bež­nú hra­nu po­u­ží­va na­prí­klad ISA zber­ni­ca. Na­opak PCI zber­ni­ca po­u­ží­va ak­tív­nu 0. Dô­vo­dom cit­li­vos­ti na úro­veň je to, že PCI má zvy­čaj­ne via­cej zdro­jov pre­ru­še­nia, ale iba jed­nú in­ter­rupt li­ne. Ukáž­me si mo­de­lo­vú si­tu­áciu za­ria­de­nia, kto­ré má 2 sa­mos­tat­né por­ty a jed­nú in­ter­rupt li­ne, kto­rá in­di­ku­je, že nie­čo sa sta­lo.

Port 1 - pre­ru­še­nie
Port 2 - pre­ru­še­nie
Spúš­ťa sa ob­slu­ha pre­ru­še­nia
Ob­slu­ha por­tu 1
Port 1 ob­slú­že­ný
Ob­slu­ha por­tu 2
Port 1 - pre­ru­še­nie
Port 2 ob­slú­že­ný

Ak by bo­lo pre­ru­še­nie cit­li­vé na hra­nu, pre­chod do ak­tív­ne­ho sta­vu by na­stal hneď pri pr­vom riad­ku. Ob­slu­ha pre­ru­še­nia po­stup­ne kon­tro­lu­je všet­ky po­ten­ciál­ne prí­či­ny pre­ru­še­nia (na­pr. port 1, 2 atď), ob­slú­ži ich a vy­nu­lu­je prí­znak pre­ru­še­nia pre jed­not­li­vé por­ty. Ak by na por­te 1 po­čas ob­slu­hy por­tu 2 doš­lo zno­vu k pre­ru­še­niu, ni­kdy by ne­na­stal pre­chod do ne­ak­tív­ne­ho sta­vu. Za­ria­de­nie by zo­sta­lo v ak­tív­nom sta­ve aj po ob­slu­he pre­ru­še­nia, ale keď­že ne­doš­lo k pre­cho­du, ďal­šia ob­slu­ha by sa už ni­kdy ne­spus­ti­la.

Pri cit­li­vos­ti na úro­veň sa to­to ne­sta­ne. Ak by sa zno­vu pre­ru­šil port 1 po­čas ob­slu­hy por­tu 2, zo­stal by stá­le ak­tív­ny stav. Po do­kon­če­ní ob­slu­hy pre­ru­še­nia by sa zno­vu spus­ti­la ob­slu­ha a tá by sa spúš­ťa­la až do­vte­dy, kým by ne­bo­li vy­čis­te­né / ob­slú­že­né všet­ky zdro­je pre­ru­še­nia.

Prerušenia

Pod­ľa prí­zna­kov bu­dem há­dať, že mám ne­správ­nu po­la­ri­tu aj typ pre­ru­še­nia. Po­zri­me sa na mo­de­lo­vú si­tu­áciu, kde bo­dy r-1 a r-2 sú bo­dy, kde vy­čis­tím HostStatus. Bo­dy s-1 a s-2 sú re­ál­ne za­čiat­ky pre­no­su na zber­ni­ci. Bo­dy e-1 a e-2 sú kon­ce pre­no­su na zber­ni­ci. Tak­to zrej­me vy­ze­rá prie­beh sig­ná­lu.

Priebeh signálu
Ob­rá­zok 2: Prie­beh sig­ná­lu

Ak by to bo­lo tak­to, pres­ne by to vy­svet­ľo­va­lo, pre­čo pr­vý pre­nos ne­spus­tí pre­ru­še­nie, pre­čo je ob­slu­ha spus­te­ná v tak krát­kom ča­se, pre­čo sa spus­tí pri re­se­te za­ria­de­nia ak pred­tým bol ini­cia­li­zo­va­ný pre­nos. Tak­to vy­ze­rá môj /proc/interrupts:

IR-IO-APIC    2-edge     timer
IR-IO-APIC    1-edge     i8042
IR-IO-APIC    7-edge     piix4_smbus
IR-IO-APIC    8-edge     rtc0
IR-IO-APIC    9-fasteoi  acpi, pinctrl_amd
IR-IO-APIC   10-edge     AMDI0010:00
IR-IO-APIC   11-edge     AMDI0010:01

Pre­ru­še­nie je cit­li­vé na hra­nu, pres­ne ako som oča­ká­val. Na na­sta­ve­nie ty­pu pre­ru­še­nia sa pod­ľa do­ku­men­tá­cie po­u­ží­va fun­kcia irq_set_irq_type. Pred, ale­bo po re­gis­trá­ciu pre­ru­še­nia (devm_request_irq) pre­to vkla­dám na­sle­du­jú­ci kód:

irq_set_irq_type(dev->irq, IRQ_TYPE_LEVEL_LOW);

Tak­to vy­ze­rá te­raz vý­pis /proc/interrupts.

IR-IO-APIC    7-edge     piix4_smbus

Pre­ru­še­nie je stá­le cit­li­vé na hra­nu, na­mies­to úrov­ne. Pod­ľa kó­du ty­pu pre­ru­še­nia je za na­sta­ve­nie zod­po­ved­ná in­štan­cia irq_chipu. Štruk­tú­ra IR-IO-APIC ne­má na­sta­ve­nú ope­rá­ciu irq_set_type, tak­že nie je mož­né po­čas be­hu zme­niť typ pre­ru­še­nia.

Pod­ľa všet­ké­ho to vy­ze­rá tak, že chy­ba je nie­kde v io-apic. Zau­jí­ma­vé je, že pre­ru­še­nie 9 vy­uží­va tiež io-apic, ale má správ­ny typ. V zo­zna­me pa­ra­met­rov ker­ne­lu sa na­chá­dza apic=debug. Po na­bo­oto­va­ní ker­ne­lu s tým­to pa­ra­met­rom je v dmesgu ten­to zau­jí­ma­vý vý­pis:

ACPI: INT_SRC_OVR (bus 0 bus_irq 0 global_irq 2 dfl dfl)
Int: type 0, pol 0, trig 0, bus 00, IRQ 00, APIC ID 20, APIC INT 02
ACPI: INT_SRC_OVR (bus 0 bus_irq 9 global_irq 9 low level)
Int: type 0, pol 3, trig 3, bus 00, IRQ 09, APIC ID 20, APIC INT 09

Vy­hľa­dá­va­nie re­ťaz­ca INT_SRC_OVR gre­pom náj­de fun­kciu acpi_table_print_madt_entry. Ta­buľ­ka MADT (Mul­tip­le APIC Desc­rip­ti­on Tab­le) je sú­čas­ťou AC­PI. Na­sle­du­jú­ci vý­pis je časť de­kom­pi­lo­va­nej APIC AC­PI ta­buľ­ky:

[0C4h 0196   1]                Subtable Type : 02 [Interrupt Source Override]
[0C5h 0197   1]                       Length : 0A
[0C6h 0198   1]                          Bus : 00
[0C7h 0199   1]                       Source : 00
[0C8h 0200   4]                    Interrupt : 00000002
[0CCh 0204   2]        Flags (decoded below) : 0000
                                    Polarity : 0
                                Trigger Mode : 0

[0CEh 0206   1]                Subtable Type : 02 [Interrupt Source Override]
[0CFh 0207   1]                       Length : 0A
[0D0h 0208   1]                          Bus : 00
[0D1h 0209   1]                       Source : 09
[0D2h 0210   4]                    Interrupt : 00000009
[0D6h 0214   2]        Flags (decoded below) : 000F
                                    Polarity : 3
                                Trigger Mode : 3

Pres­ne pod­ľa pred­chá­dza­jú­ce­ho vý­pi­su je v tej­to ta­buľ­ke na­sta­ve­nie pre­ru­še­nia 2 a 9. Pre­ru­še­nie čís­lo 7 chý­ba. Pod­ľa kó­du ini­cia­li­zá­cie APIC sa po­la­ri­ta a typ pre­ru­še­nia zis­ťu­jú vo fun­kcii ac­pi­_ge­t_o­ver­ri­de­_i­rq. Pri bliž­šom po­hľa­de tá­to fun­kcia len ske­nu­je APIC ta­buľ­ku AC­PI, v kto­rej chý­ba pre­ru­še­nie IRQ. Pod­ľa AC­PI špe­ci­fi­ká­cie, sek­cia 5.2.12.5 In­ter­rupt Sour­ce Over­ri­de Struc­tu­re má byť pr­vých 16 pre­ru­še­ní cit­li­vých na ná­bež­nú hra­nu ak to nie je de­fi­no­va­né inak v APIC/MADT ta­buľ­ke. Ak to správ­ne chá­pem, ten­to zá­znam je po­vin­ný bez ohľa­du na to, či je pre­ru­še­nie de­fi­no­va­né v nie­kto­rej z iných AC­PI ta­bu­liek. Win­do­ws buď ske­nu­je aj _CRS re­sour­ce zá­zna­my, ale­bo do­vo­ľu­je zme­niť typ pre­ru­še­nia po­čas be­hu, čo asi nie­je v sú­la­de so špe­ci­fi­ká­ci­ou.

Z to­ho dô­vo­du som kon­tak­to­val Le­no­vo a na­hlá­sil chý­ba­jú­ci zá­znam pre pre­ru­še­nie 7. Zá­ro­veň som do mp_sa­ve­_i­rq pri­dal ta­kú­to prí­šer­nosť, aby som ne­bol blo­ko­va­ný, kým Le­no­vo vy­dá ak­tu­ali­zá­ciu BIO­Su.

if (mp_irq_entries == 7) {
        m->irqflag = MP_IRQPOL_ACTIVE_LOW | MP_IRQTRIG_LEVEL;
}

Po tej­to zme­ne už pre­ru­še­nia fun­gu­jú v správ­ny mo­ment. Ob­slu­ha pre­ru­še­nia te­raz mu­sí ko­rekt­ne vy­čis­tiť bit Intr re­gis­tra HostStatus a SlaveIntr re­gis­tra ASFStatus, inak sa bu­de ob­slu­ha spúš­ťať do­ne­ko­neč­na.

Pred ďal­ším po­kra­čo­va­ním tro­chu zhr­niem fak­ty. Ovlá­dač i2c-pi­i­x4 má im­ple­men­to­va­ný pro­to­kol host no­ti­fy. Pre­ru­še­nia či už pri trans­ak­ciách, ale­bo aj pri pre­ru­še­niach od za­ria­de­nia fun­gu­jú bez prob­lé­mov. Touch­pad / track­po­int by mal fun­go­vať …

Stav

Synaptics

Ak je touch­pad sy­nap­tics pri­po­je­ný k I2C, ale­bo SPI ro­z­hra­niu, po­u­ží­va na ko­mu­ni­ká­ciu pro­to­kol RMI4. Pred ďal­ším po­kra­čo­va­ním vy­svet­lím zá­kla­dy RMI4 pro­to­ko­lu.

Pri ko­mu­ni­ká­cii cez I2C/SM­Bus sa vy­sie­la naj­skôr ad­re­sa touch­pa­du (0x2c) na­sle­do­va­ná prí­zna­kom re­ad / wri­te a 16-bi­to­vou ad­re­sou re­gis­tra RMI4. Re­gis­tre sa de­lia na 4 ty­py - da­ta (na čí­ta­nie / zá­pis dát), con­trol (ovlá­da­nie za­ria­de­nia a čí­ta­nie vý­sled­ku), com­mand (ovlá­da­nie za­ria­de­nia) a qu­e­ry (zís­ka­nie in­for­má­cie). Ďa­lej sú re­gis­tre čle­ne­né pod­ľa fun­kcie, na­pr F12 je touch­pad, F03 je track­po­int (ale­bo pres­nej­šie po­ve­da­né PS/2 pass-though, F34 je ak­tu­ali­zá­cia firm­vé­ru atď. Špe­ciál­nu úlo­hu má F01 - po­vo­le­nie iných fun­kcií, na­sta­ve­nie re­ži­mu šet­re­nia ener­gie, po­vo­le­nie pre­ru­še­ní, čí­ta­nie sta­vu pre­ru­še­nia …

Ad­re­sy re­gis­trov nie sú sta­tic­ké. Sta­tic­kú ad­re­su má len PDT (pa­ge desc­rip­ti­on tab­le) ta­buľ­ka. Pre­čí­ta­ním PDT ta­buľ­ky je mož­né zis­tiť, aké fun­kcie má touch­pad im­ple­men­to­va­né, aké sú zá­klad­né ad­re­sy re­gis­trov a kto­ré pre­ru­še­nia sú pri­ra­de­né jed­not­li­vým fun­kciám.

Page description talbe
Ob­rá­zok 3: Pa­ge desc­rip­ti­on tab­le

Pri ini­cia­li­zá­cii touch­pa­du cez RMI4 pro­to­kol sa naj­skôr mu­sí pre­čí­tať PDT ta­buľ­ka. Ná­sled­ne sa pod­ľa nej re­gis­tru­jú pod­po­ro­va­né fun­kcie a po­vo­lia sa ich pre­ru­še­nia. Po do­kon­če­ní ini­cia­li­zá­cie za­šle touch­pad 1 host no­ti­fy po­žia­dav­ku.

Za­tiaľ to vy­ze­rá v po­riad­ku. Touch­pad nor­mál­ne re­a­gu­je na po­hyb a re­por­ting ra­te je zhru­ba 80 Hz, čo zod­po­ve­dá ma­xi­mál­nej pod­po­ro­va­nej rých­los­ti sy­nap­tic­su. Track­po­int hlá­si ra­te sta­bil­ných 100 Hz. Tu je vi­deo. Sú tu však 2 prob­lé­my. Pr­vým je stra­ta pri­bliž­ne 10% pac­ke­tov v dô­sled­ku ko­lí­zií. Dru­hým prob­lé­mom je, že ob­slu­ha pre­ru­še­nia sa vo­lá pri­bliž­ne 1000x za se­kun­du, čo vý­raz­ne zvy­šu­je spot­re­bu no­te­bo­oku (v mo­jom prí­pa­de z 3W na 4W).

Pre­ru­še­nia sa ne­zač­nú ge­ne­ro­vať hneď po ini­cia­li­zá­cii, ale až po pr­vom do­ty­ku. Ge­ne­ro­va­nie sa za­sta­ví asi 5 mi­nút po po­sled­nom do­ty­ku.

Po­dob­ne by sa sprá­va­lo pre­ru­še­nie, ak by som v ob­slu­he ne­vy­čis­til všet­ky prí­zna­ky pre­ru­še­nia. Ak by to tak aj bo­lo, ni­kdy by sa ge­ne­ro­va­nie ne­za­sta­vi­lo, ale tu sa za­sta­ví asi po 5 mi­nú­tach po po­sled­nom do­ty­ku.

Môj dru­hý typ je, že touch­pad má vlast­ný prí­znak pre­ru­še­nia, kto­rý sa v je­ho ob­slu­he ne­vy­čis­tí a pre­to sa stá­le vy­sie­la host no­ti­fy sig­nál, kým sa za­ria­de­nie ne­prep­ne do šet­ria­ce­ho re­ži­mu. Ako te­ória dob­ré. Čo ho­vo­rí do­ku­men­tá­cia?

The at­ten­ti­on sig­nal is de-as­ser­ted by re­a­ding all of the In­ter­rupt Sta­tus re­gis­ters in a de­vi­ce. This me­ans that a host dri­ver should pro­cess the in­ter­rupt hand­lers for all in­ter­rupt sour­ces that are re­por­ting ‘1’ when the In­ter­rupt Sta­tus is re­ad.

Do­ku­men­tá­cia ho­vo­rí jas­ne, že sta­čí pre­čí­tať všet­ky in­ter­rupt sta­tus re­gis­tre. Žiad­na do­da­toč­ná ak­cia nie je po­treb­ná. Čí­ta­nie všet­kých in­ter­rupt sta­tus re­gis­trov sa de­je vo fun­kcii rmi­_p­ro­ces­s_in­ter­rup­t_re­qu­ests. Fun­kcia sa sku­toč­ne spúš­ťa pri kaž­dej pri­ja­tej no­ti­fi­ká­cii. Pri­dá­vam pre­to vý­pis in­ter­rupt sta­tus re­gis­tra.

printk(KERN_INFO "IRQ 0x%lx\n", data->irq_status[0]);

Vý­pis pod­ľa čin­nos­ti vy­ze­rá na­sle­dov­ne:

IRQ 0x0 // Žiaden dotyk
IRQ 0x80 // pohyb trackpointom
IRQ 0x18 // pohyb touchpadom

Pod­ľa do­ku­men­tá­cie by sa vô­bec ne­mal ge­ne­ro­vať host no­ti­fy sig­nál ak je in­ter­rupt sta­tus nu­lo­vý. U mňa sa však ge­ne­ru­je aj keď čí­tam 0x00. Pre is­to­tu kon­tro­lu­jem, ku kto­rej fun­kcii pat­rí pre­ru­še­nie. Naj­skôr však po­tre­bu­jem vý­pis fun­kcií a prí­sluš­ných pre­ru­še­ní. Do fun­kcie rmi_init_functions pri­dá­vam ten­to ria­dok rmi_scan_pdt(rmi_dev, &irq_count, rmi_debug_function), kto­rý spus­tí rmi_debug_function pre kaž­dý zá­znam PDT ta­buľ­ky. Fun­kcia pre vý­pis vy­ze­rá tak­to:

static int rmi_debug_function(struct rmi_device *rmi_dev,
                               void *ctx, const struct pdt_entry *pdt)
{
        int *current_irq_count = ctx;
        printk(KERN_INFO "PDT %02x: start=%04x cmd=%02x ctrl=%02x data=%02x, IRQ=%d+%d\n", pdt->function_number, pdt->page_start, pdt->command_base_addr, pdt->control_base_addr, pdt->data_base_addr, *current_irq_count, pdt->interrupt_source_count);
        *current_irq_count += pdt->interrupt_source_count;
        return 0;
}

Vý­pis vy­ze­rá tak­to:

PDT 34: start=0000 cmd=00 ctrl=13 data=00, IRQ=0+1
PDT 01: start=0000 cmd=28 ctrl=14 data=06, IRQ=1+2
PDT 12: start=0000 cmd=00 ctrl=1d data=0c, IRQ=3+2
PDT 54: start=0100 cmd=3d ctrl=0f data=00, IRQ=5+1
PDT 3a: start=0200 cmd=00 ctrl=11 data=00, IRQ=6+1
PDT 03: start=0200 cmd=00 ctrl=00 data=01, IRQ=7+1
PDT 55: start=0300 cmd=03 ctrl=00 data=00, IRQ=8+1

Pod­ľa vý­pi­su bi­to­vá mas­ka 0x18 zod­po­ve­dá touch­pa­du a 0x80 track­po­in­tu. Te­raz pár po­ku­sov s rmi_enable_irq. Ak ni­kdy ne­po­vo­lím pre­ru­še­nia, pre­ru­še­nia nie sú ge­ne­ro­va­né. Ak po­vo­lím pre­ru­še­nia iba pre touch­pad, pre­ru­še­nia sú ge­ne­ro­va­né po pr­vom do­ty­ku s hod­no­tou in­ter­rupt sta­tus re­gis­tra 0x18 kým mám prst na touch­pa­de, po­tom 0x00 až kým po pár mi­nú­tach ne­pres­ta­nú. Track­po­int ne­ovp­lyv­ňu­je in­ter­rupt sta­tus re­gis­ter. Ak po­vo­lím len track­po­int, po­tom pri po­hy­be má in­ter­rupt sta­tus hod­no­tu 0x80, ale zá­ro­veň pri do­ty­ku touch­pa­du sa na­sta­vu­je 0x18. Pod­ľa prí­zna­kov ti­pu­jem, že je chy­ba nie­kde vo firm­vé­ri, ale do­ká­zať to ne­viem. Pre ne­prie­strel­ný dô­kaz by som po­tre­bo­val pres­ne vi­dieť, ako vy­ze­ra­jú odo­sie­la­né bi­ty na zber­ni­ci.

Logický analyzátor

Po­čas pred­chá­dza­jú­cich po­ku­sov som zis­til, že po ak­ti­vá­cií touch­pa­du / track­po­in­tu sa za­čnú po­sie­lať dá­ta na GPIO pi­noch 19 a 20. Pod­ľa zdro­jo­vých kó­dov co­re­bo­otu tie­to pi­ny pat­ria I2C ro­z­hra­niu. Je cel­kom sluš­ná šan­ca, že prá­ve ak­ti­vi­ta na tých­to pi­noch mi de­fi­ni­tív­ne zod­po­vie otáz­ku, či touch­pad po­si­la dá­ta sám, ale­bo je vy­vo­la­né ne­vhod­nou ko­mu­ni­ká­ci­ou zo stra­ny SM­Bus ov­lá­da­ča.

Na sle­do­va­nie ak­ti­vi­ty na zber­ni­ci sa po­u­ží­va­jú špe­ciál­ne za­ria­de­nia - lo­gic­ké ana­ly­zá­to­ry. Väč­ši­nou ide o za­ria­de­nie s mno­hý­mi vstup­mi, veľ­mi rých­lou pa­mä­ťou a FP­GA, ale­bo ASIC ob­vo­dom na spra­co­va­nie sig­ná­lov. No­te­bo­ok som už sí­ce mal ot­vo­re­ný, ale aj tak sa vy­hý­bam in­va­zív­nym me­tó­dam od­po­čú­va­nia zber­ni­ce. Na­mies­to to­ho sa spo­lie­ham na to, že do­ká­žem do­sta­toč­ne rých­lo čí­tať hod­no­ty GPIO pi­nov pria­mo z li­nu­xu.

Libgpiod

Môj pr­vý po­kus bol s kniž­ni­cou libg­pi­od. Hos­ting na ker­nel.org naz­na­ču­je, že by moh­lo ísť o po­mer­ne kva­lit­nú kniž­ni­cu. Pod­ľa zdro­jo­vých kó­dov (pre­to­že do­ku­men­tá­cia je rov­na­ko ako v prí­pa­de ker­ne­lu veľ­mi stro­há) pod­po­ru­je ope­rá­cie nad via­ce­rý­mi vstup­mi / vý­stup­mi sú­čas­ne, čo sa bu­de ho­diť.

Kom­plet­ný zdro­jo­vý kód sa dá skom­pi­lo­vať prí­ka­zom gcc -O3 -lgpiod dump_gpio_gpiod.c -o dump_gpio_gpiod. V na­sle­du­jú­com vý­pi­se je ko­men­to­va­ná časť kó­du, kto­rý pri­stu­pu­je k GPIO:

struct gpiod_chip *chip;
struct gpiod_line_bulk lines;

unsigned int offsets[2] = {19, 20}; // Piny 19/20
unsigned int values[2]; // Buffer pre načítané hodnoty
int ret;

chip = gpiod_chip_open("/dev/gpiochip0");
if (!chip) {
        printf("GPIO not opened\n");
        return -1;
}

ret = gpiod_chip_get_lines(chip, &offsets[0], 2, &lines);
if (ret < 0) {
        printf("Lines not opened\n");
        return -1;
}

gpiod_line_request_input(lines.lines[0], NAME);
gpiod_line_request_input(lines.lines[1], NAME);
// Nastavenie open drain vstupu
gpiod_line_set_flags(lines.lines[0], GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW | GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);
gpiod_line_set_flags(lines.lines[1], GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW | GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);

while (true) {
        // súčasné načítanie vstupov
        gpiod_line_get_value_bulk(&lines, values);
        // uloženie poľa values
}

Te­raz tro­cha vý­poč­tov. Frek­ven­cia SM­Bus zber­ni­ce je 100kHz. Na kaž­dý bit sú po­treb­ne 2 pre­cho­dy ho­dín (pre­chod z 1 do 0 a na­s­päť z 0 do 1). Čí­tať hod­no­tu je po­treb­né s mi­ni­mál­nou frek­ven­ci­ou 200kHz, čo zna­me­ná, že na jed­no čí­ta­nie má­me ma­xi­mál­ne 5µs. Ma­xi­mál­na frek­ven­cia plá­no­va­ča jad­ra je 1kHz, čo je ab­so­lút­ne ne­dos­ta­toč­né. Pri tak vy­so­kej cit­li­vos­ti na ča­so­va­nie je po­treb­né za­bez­pe­čiť, aby pro­ces ne­bol pre­ru­še­ný plá­no­va­čom.

Ker­nel by mal mať vy­hra­de­né 1 jad­ro CPU na 1 ne­pre­ru­ši­teľ­nú úlo­hu. Pres­ne pre ten­to účel exis­tu­je pa­ra­me­ter jad­ra isolc­pus. Po na­štar­to­va­ní s pa­ra­met­rom isolcpus=1 ne­bu­de ker­nel vô­bec po­u­ží­vať, ani plá­no­vať úlo­hy pre jad­ro 1. Úlo­ha sa dá spus­tiť na tom­to jad­re len pri ex­pli­cit­nom ur­če­ní afi­ni­ty. Prog­ram spúš­ťam cez uti­li­tu schedtool na­sle­du­jú­cim prí­ka­zom:

schedtool -a 0x01 -F -p 99 -n -20 -e ./dump_gpio_gpiod

Pa­ra­me­ter -a ur­ču­je zo­znam CPU, na kto­rých mô­že byť pro­ces spus­te­ný. Zo­znam je im­ple­men­to­va­ný ako bi­to­vá mas­ka, tak­že 0x01 zna­me­ná pr­vý CPU, 0x03 pr­vý a dru­hý atď. Pre fi­fo plá­no­vač je ur­če­ný pa­ra­me­ter -F. Pa­ra­me­ter -p je pri­ori­ta fi­fo plá­no­va­ča v roz­sa­hu 1-99 (naj­vyš­šia je 99) a -n je hod­no­ta ni­ce.

Na pa­pie­ri to fun­gu­je, v re­a­li­te sa­moz­rej­me, že nie. V zá­sa­de čí­ta­nie hod­no­ty GPIO tr­vá oko­lo 2µs +/- 20µs. La­ten­cia mu­sí byť však pod 5µs za kaž­dých okol­nos­tí. Ok­rem to­ho, čí­ta­nie via­ce­rých pi­nov na mo­jom stro­ji vô­bec ne­fun­go­va­lo. Pr­vý áno, dru­hý vrá­ti vždy 0. Ak ich čí­tam sek­venč­ne, tak áno, pre­čí­ta­jú sa oba s ča­som 2µs +/- 20µs, čo je ne­pou­ži­teľ­né.

Ioctl

V dru­hom po­ku­se som vy­ne­chal aké­koľ­vek kniž­ni­ce a hra­bol som rov­no svo­ji­mi ioctl vo­la­nia­mi po /dev/gpiochip0. V na­sle­du­jú­com vý­pi­se sú zau­jí­ma­vé čas­ti kó­du:

int fd, ret;
struct gpiohandle_request rq;
struct gpiohandle_data data;

fd = open(DEV_NAME, O_RDONLY);
if (fd < 0) {;
        printf("Device not opened\n");
        return -1;
}

rq.lineoffsets[0] = 19;
rq.lineoffsets[1] = 20;
rq.flags = GPIOHANDLE_REQUEST_INPUT;
rq.lines = 2;
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &rq);
close(fd);
if (ret == -1) {
        printf("Cant get handle\n");
        return -1;
}

while(true) {
        ioctl(rq.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
        // práca s data.values[0,1]
}

Ten­to kód sí­ce na­čí­ta oba pi­ny, ale ré­žia pre­pí­na­nia kon­tex­tu ker­ne­lu je stá­le prí­liš veľ­ká. Vý­sle­dok je za­tiaľ ne­pou­ži­teľ­ný. Aby som sa vy­hol ré­žii pri pre­pí­na­ní kon­tex­tu, pre­su­nul som kód do ker­ne­lu.

Kernel

Ku GPIO sa dá pri­stu­po­vať len cez MMIO. Ad­re­sa re­gis­tra je v BKDG prí­ruč­ke, časť 3.26.11.1 GPIO Re­gis­ters. Na­sle­du­jú­ci kód ma­pu­je MMIO do ad­res­né­ho pries­to­ru ker­ne­lu na ad­re­su gpi_addr. Od toh­to mo­men­tu sa dá pri­stu­po­vať k I/O za­ria­de­niu po­mo­cou pria­me­ho čí­ta­nia, ale­bo zá­pi­su do re­gi­ó­nu pa­mä­te gpio_addr.

#define GPIO_ADDR   0xFED81500
#define GPIO_SIZE   0x00000400

struct resource *gpio_res;
void __iomem *gpio_addr;

gpio_res = request_mem_region(GPIO_ADDR, GPIO_SIZE, "amd-pinctrl");
if (!gpio_res) {
        printk(KERN_INFO "GPIO resource not acquired\n");
}
else {
        gpio_addr = ioremap(GPIO_ADDR, GPIO_SIZE);
        if (!gpio_addr) {
                printk(KERN_INFO "Failed to map GPIO\n");
        }
}

Ker­nel po­tre­bu­je sprí­stup­niť za­zna­me­na­né dá­ta. Na ten­to účel sa dá veľ­mi jed­no­du­cho zne­užiť debugfs.

#define GPIO_RECORDING_SIZE (1024 * 1024 * 8)

u8 gpio_recording[GPIO_RECORDING_SIZE];
struct dentry *gpio_dfs;
struct debugfs_blob_wrapper gpio_blob;

gpio_blob.data = &gpio_recording[0];
gpio_blob.size = GPIO_RECORDING_SIZE;
gpio_dfs = debugfs_create_blob("piix4_bus_dump", 0644, NULL, &gpio_blob);

Ten­to krát­ky kód sprí­stup­ní za­zna­me­na­né dá­ta ako sú­bor /sys/kernel/debug/piix4_bus_dump. Je­ho ob­sah sa dá štan­dard­ný­mi ná­stroj­mi sko­pí­ro­vať na disk. Hod­no­ty pi­nov sa bu­dú za­zna­me­ná­vať v sa­mos­tat­nom vlák­ne spus­te­nom na izo­lo­va­nom CPU jad­re. Pod­ľa ta­buľ­ky 278 v BKDG má kaž­dý pin šír­ku 32 re­gis­trov (4 by­ty). Re­le­vant­né I2C pi­ny za­čí­na­jú na ad­re­se 0x4c (19 * 4). Z kaž­dej sa­dy re­gis­trov je zau­jí­ma­vý len re­gis­ter 16 (PinSts). Keď­že sú pi­ny hneď ved­ľa se­ba, da­jú sa pre­čí­tať je­di­nou in­štruk­ci­ou reqdq pre na­čí­ta­nie 64-bi­to­vej hod­no­ty. Po tro­che má­gie s bit­mi sa za­pí­še do buf­fe­ru 1 by­te s hod­no­tou ho­di­no­vé­ho sig­ná­lu na naj­niž­šej po­zí­cii a hod­no­tou dá­to­vé­ho sig­ná­lu na dru­hej naj­niž­šej po­zí­cii.

static int gpio_thread_func(void *data) {
        u64 gpio_val;
        size_t pos;
        for (pos = 0; pos < GPIO_RECORDING_SIZE; pos++) {
                if (kthread_should_stop()) {
                        break;
                }
                gpio_val = readq(gpio_addr + 19*4);
                // bit 16 z prvého registra + bit 16 z druhého registra (16+32) posunuý o 1 pozíciu doľava
                gpio_recording[pos] = ((gpio_val & BIT(16)) >> 16) | ((gpio_val & BIT(48)) >> 47);
        }
        return 0;
}

gpio_thread = kthread_create(gpio_thread_func, NULL, "piix4_bus_dump-work");
kthread_bind(gpio_thread, 0);

V kó­de trans­ak­cie sta­čí už len pri ske­no­va­ní na­štar­to­vať vlák­no, kto­ré bu­de za­zna­me­ná­vať ak­ti­vi­tu:

if (size == I2C_SMBUS_QUICK) {
        wake_up_process(gpio_thread);
}

Te­raz krát­ka kon­tro­la. Fun­gu­je to? No po­ve­dz­me, že lep­šie než pred­chá­dza­jú­ci kód, ale hor­šie než som ča­kal. Jed­na ite­rá­cia tr­vá pri­bliž­ne 1µs +/- 15µs. Pri­po­mí­nam, že do­ba mu­sí byť pod 5. Na dru­hej stra­ne, čas vy­ska­ku­je pri­bliž­ne pri kaž­dých 100 bi­toch. Nie je to do­sť dob­ré, aby som kom­plet­ne de­kó­do­val, čo sa de­je na zber­ni­ci, ale je to do­sť dob­ré, aby som si uro­bil pred­sta­vu, čo sa tam de­je.

Čo keď o nestačí

Ak by to ne­sta­či­lo, po­kra­čo­val by som po­u­ži­tím DMA (di­rect me­mo­ry ac­cess). Prin­cíp čin­nos­ti DMA je po­mer­ne jed­no­du­chý. Ope­rač­ný sys­tém na­kon­fi­gu­ru­je, čo má DMA kon­tro­lér uro­biť, spus­tí pre­nos a vo­li­teľ­ne po­čká na pre­ru­še­nie, kto­ré ozná­mi ukon­če­nie pre­no­su. DMA pre­no­sy ma­jú oby­čaj­ne vy­so­kú pri­ori­tu a je tu veľ­ká šan­ca, že by to­to rie­še­nie fun­go­va­lo sta­bil­ne bez stra­ty je­di­né­ho bi­tu. Pre ini­cia­li­zá­ciu DMA pre­no­su je po­treb­né:

Ako zdroj pre­ru­še­nia by bol po­u­ži­tý nie­kto­rý z ča­so­va­čov. Do toh­to rie­še­nia som ne­šiel, pre­to­že mi sta­čí zá­klad­ný pre­hľad o dia­ní na zber­ni­ci.

Analýza

Vý­sled­ný sú­bor sa dá na­čí­tať na­prí­klad po­mo­cou pulseview. Po im­por­te raw bi­nár­ne­ho sú­bo­ru a pri­da­ní I2C de­kó­de­ra je pek­ne vi­di­teľ­ná ko­mu­ni­ká­cia.

I2C
Ob­rá­zok 4: Ak­ti­vi­ta zdiaľ­ky

Po mier­nom pri­blí­že­ní je vi­di­teľ­né pra­vi­del­né opa­ko­va­nie.

I2C
Ob­rá­zok 5: Mier­ne pri­blí­že­nie

Pri pri­blí­že­ní na 1 blok je vi­di­teľ­ný naj­skôr zá­pis 0x2c << 1 (0x58) na ad­re­su 0x08. Tým­to spô­so­bom po­ža­du­je za­ria­de­nie 0x2c host 0x08 o ob­slu­hu pre­ru­še­nia. Na zá­kla­de tej­to po­žia­dav­ky host oslo­ví za­ria­de­nie 0x2c a vy­žia­da si hod­no­tu in­ter­rupt sta­tus re­gis­tra 0x00. Touch­pad od­po­vie svo­jou vlast­nou ad­re­sou 0x2c, dĺž­kou blo­ku dát 0x02 a hod­no­tou in­ter­rupt sta­tus re­gis­tra 0x00,0x00. To­to je ko­neč­ný dô­kaz, že touch­pad po­sie­la no­ti­fi­ká­cie, aj keď na to ne­má dô­vod.

I3C
Ob­rá­zok 6: Pri­blí­že­nie na 1 blok

Ok­rem opí­sa­ných blo­kov sa nie­ke­dy ob­ja­vu­jú aj krát­ke jed­no­by­to­vé zá­pi­sy. Tie sú však len dô­sled­kom fak­tu, že pred trans­ak­ci­ou vy­pí­nam host fun­kciu, tak­že zá­pis na ad­re­su 0x08 nie je úspeš­ný. Ak ne­vyp­nem host fun­kciu, host bu­de na ad­re­su 0x08 od­po­ve­dať vždy. Mier­ne sa vte­dy zvý­ši frek­ven­cia ko­lí­zií.

Vyhodnotenie

Po­mo­cou zá­zna­mu ak­ti­vi­ty na zber­ni­ci som zis­til, že touch­pad sám odo­sie­la po­žia­dav­ky na pre­ru­še­nie. V ovlá­da­či SM­Bu­su nie je žiad­na zá­sad­ná chy­ba, kto­rá by pre­ká­ža­la v po­u­ží­va­ní touch­pa­du. Ovlá­dač touch­pa­du sa sprá­va ko­rekt­ne pod­ľa sta­rej do­ku­men­tá­cie, kto­rú som na­šiel na we­be. K nov­šej do­ku­men­tá­cii ne­mám prí­stup.

Ako by som mo­hol po­kra­čo­vať? Na­prí­klad vy­hľa­da­ním prob­lé­mov sy­nap­tic­su. Vy­ze­rá to tak, že prob­lém sa pre­ja­vu­je nie­ke­dy aj vo win­do­ws. Je­den ko­men­tár z le­no­vo fó­ra ho­vo­rí za všet­ko:

I'll for­ward tho­se on to Sy­nap­tics as well in ca­se it's use­ful - though it sounds li­ke it mat­ches what they're lo­oking at but the­re are so­me in­te­res­ting pie­ces in the­re with the in­ter­rupts be­ing so bad. Sy­nap­tics ha­ve as­ked me not to dis­cuss the­ir in­ves­ti­ga­ti­on de­tails pub­lic­ly and I'm res­pec­ting that.

I will no­te that my un­ders­tan­ding is that this is­sue is im­pac­ting Win­do­ws too.

I don't ha­ve an ETA on the so­lu­ti­on yet I'm af­raid, but it is be­ing ac­ti­ve­ly wor­ked on and I've be­en chec­king in on it.

Te­raz by som mo­hol po­kra­čo­vať skú­ma­ním win­do­ws ovlá­da­ča. Ak za­zna­me­nám ve­ľa pre­ru­še­ní aj vo win­do­wse, je to jed­no­znač­ne vad­ný firm­vér. Ak nie, prav­de­po­dob­ne win­do­ws ovlá­dač ne­ja­kým spô­so­bom ex­pli­cit­ne vy­čis­tí in­ter­rupt sta­tus re­gis­ter.

Vo win­do­wse pod­ľa všet­ké­ho fun­gu­je ovlá­dač správ­ne. V tom­to mo­men­te by som sa mo­hol ďa­lej ba­brať s de­bug­ge­rom a di­sas­sem­ble­rom, ale úp­rim­ne, nech­ce sa mi. Nie­kto s do­ku­men­tá­ci­ou by to mož­no zvlá­dol za 5 mi­nút, mne sa nech­ce rie­šiť to­to ce­lý deň, tak­že som len po­slal e-mail za­mest­nan­co­vi sy­nap­tic­su, kto­rý pra­cu­je na li­nu­xu­ovom ovlá­da­či.

Detekcia hardvéru

Ovlá­dač touch­pa­du za­tiaľ ne­chám tak a vra­ciam sa k SM­Bu­su. Ten je sí­ce po mo­jich úp­ra­vách funkč­ný, ale ne­chal som tam na­prí­klad na­pev­no čís­lo pre­ru­še­nia 7. In­for­má­cia o čís­le pre­ru­še­nia však mu­sí byť od­nie­kiaľ na­čí­ta­ná, pre­to­že nie na kaž­dom po­čí­ta­či bu­de SM­Bu­su pri­ra­de­né to is­té pre­ru­še­nie.

Rozdiel medzi pci_driver a platform_driver

V ak­tu­ál­nej mas­ter ver­zii ker­ne­lu je 410 vý­sky­tov module_pci_driver a 2846 vý­sky­tov module_platform_driver. Čo je pci_driver je cel­kom jas­né z náz­vu.. Čo je však platform_driver, pre­čo je ich omno­ho viac než PCI ovlá­da­čov a pre­čo sa vlast­ne pri PCI za­ria­de­ní za­obe­rám ne­ja­kým platform_driverom?

Do­vo­lím si ma­lú ci­tá­ciu z do­ku­men­tá­cie ker­ne­lu.

This pse­udo-bus is used to con­nect de­vi­ces on bus­ses with mi­ni­mal in­fras­truc­tu­re, li­ke tho­se used to in­teg­ra­te pe­rip­he­rals on ma­ny sys­tem-on-chip pro­ces­sors, or so­me "le­ga­cy" PC in­ter­con­nects; as oppo­sed to lar­ge for­mal­ly spe­ci­fied ones li­ke PCI or USB.
Plat­form de­vi­ces are de­vi­ces that ty­pi­cal­ly ap­pe­ar as au­to­no­mous en­ti­ties in the sys­tem. This inc­lu­des le­ga­cy port-ba­sed de­vi­ces and host brid­ges to pe­rip­he­ral bu­ses, and most con­trol­lers in­teg­ra­ted in­to sys­tem-on-chip plat­forms.

Z uve­de­nej čas­ti do­ku­men­tá­cie vy­plý­va, že platform_driver mám za­ho­diť za hla­vu a pl­ne sa sú­stre­diť na pci_driver.

Registrácia PCI ovládača

Ope­rač­ný sys­tém do­ká­že zís­kať zo­znam za­ria­de­ní pri­po­je­ných na zber­ni­ci. Kaž­dé PCI za­ria­de­nie má 256 kon­fi­gu­rač­ných re­gis­trov.

Štruktúra registrov PCI konfiguračného priestoru
Ob­rá­zok 7: Štruk­tú­ra re­gis­trov PCI kon­fi­gu­rač­né­ho pries­to­ru

Pri­po­me­niem eš­te vý­pis z lspci s pa­ra­met­rom -xxx pre vý­pis 256 re­gis­trov PCI kon­fi­gu­rač­né­ho pries­to­ru:

lspci -vvv -b -x -xxx -xxxx -nn -s 00:14.0
00: 22 10 0b 79 00 04 20 02 51 00 05 0c 00 00 80 00
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 aa 17 94 50
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
…

Naj­dô­le­ži­tej­ší­mi re­gis­tra­mi sú dvo­j­ica Ven­dor ID a De­vi­ce ID. Tie­to 2 hod­no­ty tvo­ria tzv. PCI ID. Ide o jed­no­znač­ný iden­ti­fi­ká­tor za­ria­de­nia, pod­ľa kto­ré­ho sa na­čí­ta­va prí­sluš­ný ov­lá­dač. Li­nu­xo­vé ov­lá­da­če PCI za­ria­de­ní ma­jú v se­be me­ta­dá­ta so zo­zna­mom pod­po­ro­va­ných PCI ID. Na­prí­klad ov­lá­dač i2c-piix4 pod­po­ru­je tie­to PCI ID.

V PCI kon­fi­gu­rač­nom pries­to­re sa zvy­čaj­ne na­chá­dza aj zá­klad­ná ad­re­sa za­ria­de­nia (tá, kto­rá sa hor­ko-ťaž­ko do­lo­va­la z PM re­gis­tra) a tiež in­ter­rupt li­ne. Ker­nel na zá­kla­de PCI za­ria­de­nia au­to­ma­tic­ky na­sta­ví po­le irq štruk­tú­ry pci_dev. Správ­ne by mal na­sle­du­jú­ci kód vy­pí­sať čís­lo 7.

None

Vo vý­pi­se sa však ob­ja­ví 0. Dô­vod pre­zra­dí BKDG do­ku­ment, časť 3.26.6 SM­Bus Host Con­trol­ler. Pod­ľa ne­ho re­gis­ter 0x3c vra­cia vždy 0. To is­té vrá­ti aj zá­klad­ná ad­re­sa 0x10. Ce­lý kon­fi­gu­rač­ný pries­to­re je viac-me­nej ne­pou­ži­teľ­ný ok­rem PCI ID a čís­la re­ví­zie.

Ovládače platform_driver

Pre správ­ne fun­go­va­nie mu­sí ope­rač­ný sys­tém ve­dieť, kto­ré za­ria­de­nia má pri­po­je­né, cez aké ad­re­sy s ni­mi mô­že ko­mu­ni­ko­vať a aké pre­ru­še­nia mô­že oča­ká­vať.

V prí­pa­de ARM do­siek sa to rie­ši väč­ši­nou cez de­vi­cet­ree. Zo­znam za­ria­de­ní sa za­pí­še do sú­bo­ru, ten sa in­teg­ru­je do ker­ne­lu (buď sta­tic­ky, ale­bo cez initrd) a na zá­kla­de ne­ho OS ini­cia­li­zu­je ovlá­da­če. Dô­sled­kom toh­to prí­stu­pu je, že ker­nel z jed­nej ARM do­sky (na­pr. rasp­ber­ry pi) nie je mož­né pre­niesť na inú do­sku.

Ovlá­da­če platform_driver sú prá­ve ovlá­da­če ini­cia­li­zo­va­né po­mo­cou ta­buľ­ky za­ria­de­ní. Aby som to zhr­nul pci_driver vy­uží­va vý­sle­dok ske­no­va­nia PCI zber­ni­ce a platform_driver vy­uží­va kon­fi­gu­rá­ciu z ta­buľ­ky, na zá­kla­de kto­rej ini­cia­li­zu­je ovlá­da­če.

Kon­fi­gu­rá­cia v ta­buľ­ke … čo mi to len pri­po­mí­na? AC­PI ta­buľ­ky! AC­PI po­sky­tu­je na x86 plat­for­me pres­ne to, čo de­vi­cet­ree na rôz­nych ARM do­skách a iných em­bed­ded za­ria­de­niach. Li­nux do­ká­že au­to­ma­tic­ky vy­tvo­riť platform_device štruk­tú­ry z AC­PI zá­zna­mov. Ovlá­da­če plat­for­m_d­ri­ver nie sú vô­bec za­sta­ra­lé a vô­bec ne­pla­tí, že by ma­la byť pre­fe­ro­va­ná kon­fi­gu­rá­cia cez PCI kon­fi­gu­rač­ný pries­tor. Na­opak kon­fi­gu­rá­cia ker­ne­lu je v tom­to bo­de za­sta­ra­lá.

Po­môcť by moh­lo, ke­by sa ovlá­dač ne­via­zal len na PCI ID, ale aj na _HID zá­znam AC­PI ta­buľ­ky. Ovlá­dač však mô­že byť len je­den, te­da pci_driver, ale­bo platform_driver. Kto­rý by mal byť te­da po­u­ži­tý? Pre osvie­že­nie pa­mä­te pri­po­me­niem časť DSDT ta­buľ­ky z AC­PI:

Name (_HID, "SMB0001")  // _HID: Hardware ID
Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
{
    IO (Decode16,
        0x0B20,             // Range Minimum
        0x0B20,             // Range Maximum
        0x20,               // Alignment
        0x20,               // Length
        )
    IRQ (Level, ActiveLow, Shared, )
        {7}
})

Je tu aj zá­klad­ná ad­re­sa, aj čís­lo pre­ru­še­nia. Prob­lé­mom je _HID SMB0001, kto­rý ozna­ču­je vir­tu­ál­ne SM­Bus za­ria­de­nie. Pre prí­stup k tým­to dá­tam mu­sí byť ovlá­dač im­ple­men­to­va­ný ako platform_driver, čo nie je mož­né, pre­to­že v AC­PI je de­fi­no­va­ný len ako vir­tu­ál­ne za­ria­de­nie, tak­že by ob­slu­ho­val aj ne­kom­pa­ti­bil­né SM­Bus za­ria­de­nia.

Zme­niť ovlá­dač na platform_driver sí­ce ne­mô­žem, ale mô­žem skú­siť zís­kať prí­stup k AC­PI z pci_device. To som sa­moz­rej­me aj skú­sil, ale mo­ja na­iv­ná pred­sta­va, že by na­sle­du­jú­ci kód mo­hol fun­go­vať skon­či­la NULL de­ref­ren­ci­ou.

struct platform_device *pdev = NULL;
struct resource *res;

pdev = to_platform_device(&dev->dev);
if (pdev) {
        res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
        if (res) {
                printk(KERN_INFO "IRQ: %llu\n", res->start);
        }
}
BUG: kernel NULL pointer dereference, address: 0000000000000018
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page

Ker­nel by mal au­to­ma­tic­ky kon­ver­to­vať AC­PI zá­zna­my na platform_device. Aby som vi­del čo sa de­je, roz­ho­dol som sa ísť o úro­veň niž­šie a po­u­žiť AC­PI ro­z­hra­nie pria­mo.

Pre zís­ka­nie AC­PI zá­zna­mu má ker­nel mak­ro ACPI_COMPANION. Prí­stup k IRQ z acpi_device nie je úpl­ne pria­mo­čia­ry, ale na­šiel som v ker­ne­li fun­kciu acpi_dev_gpio_irq_get, kto­rú po­u­ží­va­jú nie­kto­ré ovlá­da­če. Na­sle­du­jú­ci kód by te­ore­tic­ky mo­hol fun­go­vať:

int irq;
struct acpi_device *adev;

adev = ACPI_COMPANION(&dev->dev);
irq = acpi_dev_gpio_irq_get(adev, 0);
printk(KERN_INFO "irq %d\n", irq);

// výstup irq -2

Ani ten­to kód ne­fun­gu­je. Za­čí­nam mať po­cit, že ACPI_COMPANION vra­cia ne­správ­ne za­ria­de­nie. Pri­dám vý­pis pár me­ta­dát:

char *hid = NULL;

acpi_device = ACPI_COMPANION(&dev->dev);
hid = acpi_device_hid(acpi_device);
if (hid) {
        printk(KERN_INFO "hid '%s'\n", hid);
}
printk(KERN_INFO "pnp bus_id '%s'\n", acpi_device->pnp.bus_id);
printk(KERN_INFO "pnp device_name '%s'\n", acpi_device->pnp.device_name);
printk(KERN_INFO "pnp unique_id '%s'\n", acpi_device->pnp.unique_id);
hid 'device'
pnp bus_id 'SMB'
pnp device_name ''
pnp unique_id '(null)'

Pod­ľa vý­stu­pu vrá­ti ACPI_COMPANION len dum­my zá­znam. Zá­znam v AC­PI ta­buľ­ke to­tiž nie je pri­ra­de­ný k PCI za­ria­de­niu. Viem, že v AC­PI má zá­znam _HID hod­no­tu SMB0001a mô­žem ex­pli­cit­ne vy­žia­dať zá­znam pod­ľa je­ho náz­vu a ná­sled­ne za­se skú­siť zís­kať IRQ re­sour­ce z platform_device pri­ra­de­né­ho k AC­PI zá­zna­mu. Vra­ciam sa k po­u­ži­tiu platform_device zís­ka­né­ho zo správ­ne­ho AC­PI zá­zna­mu po­mo­cou ac­pi­_­plat­for­m_de­vi­ce­_fin­d_by­_com­pa­ni­on.

struct acpi_device *adev;
struct resource *res;
struct device *dev;

adev = acpi_dev_get_first_match_dev("SMB0001", NULL, -1);
if (adev) {
        dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev);
        if (dev) {
                pdev = to_platform_device(dev);
                if (pdev) {
                        res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
                        if (res) {
                                printk(KERN_INFO "IRQ %llu\n", res->start);
                        }
                }
        }
}

Ten­to kód je ko­neč­ne správ­ny. Len­že ne­fun­gu­je. Vlast­ne fun­gu­je ak sa za­ho­dí ten­to com­mit. Je­ho úlo­hou je za­brá­niť vy­tvo­re­niu in­štan­cie platform_device z AC­PI za­ria­de­nia, kto­ré má _HID SMB0001. Od­strá­ne­nie toh­to kó­du by moh­lo mať za ná­sle­dok prob­lé­my na inom hard­vé­ri, tak­že idem na pria­my prí­stup k AC­PI bez in­štan­cie platform_device.

struct acpi_device *adev;
acpi_status status;
int irq;

static acpi_status acpi_device_find_irq(struct acpi_resource *res, void *data)
{
        int *irq = data;

        switch (res->type) {
        case ACPI_RESOURCE_TYPE_IRQ:
                *irq = res->data.irq.interrupts[0];
                return AE_OK;
        case ACPI_RESOURCE_TYPE_END_TAG:
                if (*irq)
                        return AE_OK;
                else
                        return AE_NOT_FOUND;
        default:
                return AE_OK;
        }
}

acpi_device = acpi_dev_get_first_match_dev("SMB0001", NULL, -1);
status = acpi_walk_resources(acpi_device->handle, METHOD_NAME__CRS, acpi_device_find_irq, &irq);
if (ACPI_FAILURE(status)) {
        printk(KERN_WARNING "IRQ not found\n");
        return;
}

printk(KERN_INFO "IRQ %d\n", irq);

Ten­to kód ko­neč­ne náj­de čís­lo pre­ru­še­nia bez roz­bi­tia pod­po­ry li­nu­xu na iných sys­té­moch. Te­raz už zo­stá­va len za­čle­niť kód do ker­ne­lu.

Mo­je úp­ra­vy sú väč­ši­ne než sa­mot­ný ovlá­dač i2c-piix4. Ďa­lej bu­dem po­kra­čo­vať po­ku­som o za­čle­ne­nie do jad­ra, ale prav­de­po­dob­ne ako sa­mos­tat­ný ovlá­dač. Ak­tu­ál­ne sa po­u­ží­va po­ma­lý IO prí­stup, ale ak by som išiel ces­tou sa­mos­tat­né­ho ovlá­da­ča, na­hra­dil by som kom­plet­ne všet­ky IO vo­la­nia rých­lej­ším MMIO prí­stu­pom.

Pokus z opačnej strany

Pô­vod­ne som chcel len opra­viť se­ka­nie track­po­in­tu. Na­ko­niec som do­pl­nil i2c-piix4 o ne­vy­hnut­ný kód, ale rie­še­nie pô­vod­né­ho prob­lé­mu som vzdal. Prob­lém sa ob­ja­vu­je len keď sa pri­blí­žim k touch­pa­du. Pre­to som na za­čiat­ku skú­šal fy­zic­ky od­po­jiť touch­pad. Vô­bec by mi ne­va­di­lo, ak by bol kom­plet­ne vy­pnu­tý.

Me­dzi­tým som si len tak pre zau­jí­ma­vosť pre­chá­dzal roz­ší­re­nia PS/2 pro­to­ko­lu, kto­ré má im­ple­men­to­va­né sy­nap­tics. Do oka mi pa­dol re­žim SLE­E­P_MO­DE. Keď­že od za­čiat­ku mám prob­lém iba keď je touch­pad ak­tív­ny, ten­to re­žim vy­ze­ral po­mer­ne ná­dej­ne.

Po na­sta­ve­ní SLEEP_MODE sa sí­ce nič ne­sta­lo, ale vši­mol som si me­dzi na­sta­ve­ním re­ži­mov chý­ba­jú­ce bi­ty 4 a 5. Na­sta­ve­nie bi­tu 4 ne­zna­me­na­lo žiad­nu zme­nu, ale po na­sta­ve­ní bi­tu 5 pre­stal touch­pad / track­po­int úpl­ne fun­go­vať. Skú­sil som te­da zno­vu na­čí­tať mo­dul ps­mou­se (modprobe -r psmouse; modprobe psmouse) a touch­pad vô­bec ne­bol roz­poz­na­ný. Zo­stal len track­po­int. Skú­šam te­da uti­lit­ku evhz a vi­dím sta­bil­ných 100Hz na­mies­to pô­vod­ných ani nie 40.

TPPS/2 Elan TrackPoint: Latest   114Hz, Average    99Hz
TPPS/2 Elan TrackPoint: Latest    99Hz, Average    99Hz
TPPS/2 Elan TrackPoint: Latest    99Hz, Average    99Hz
TPPS/2 Elan TrackPoint: Latest    99Hz, Average   100Hz
TPPS/2 Elan TrackPoint: Latest   108Hz, Average   100Hz
TPPS/2 Elan TrackPoint: Latest    99Hz, Average   100Hz
TPPS/2 Elan TrackPoint: Latest   100Hz, Average   100Hz
TPPS/2 Elan TrackPoint: Latest   100Hz, Average   100Hz
TPPS/2 Elan TrackPoint: Latest   101Hz, Average   100Hz
TPPS/2 Elan TrackPoint: Latest   100Hz, Average   100Hz
TPPS/2 Elan TrackPoint: Latest    99Hz, Average   100Hz

Frek­ven­cia sa dr­ží sta­bil­ne bez ohľa­du na to, či sa touch­pa­du do­tý­kam, ale­bo nie. Od­po­je­nie touch­pa­du je čias­toč­ne per­zis­tent­né. Te­da čias­toč­ne zna­me­ná, že ne­bu­de roz­poz­na­ný ani po re­štar­te. Touch­pad ne­náj­de ani diag­nos­tic­ký ná­stroj pre tes­to­va­nie hard­vé­ru v UE­FI. V gra­fic­kej nad­stav­be UE­FI tak is­to ne­fun­gu­je. Zno­vu sa za­pne až po úpl­nom vy­pnu­tí no­te­bo­oku.

Tak­že svo­je rie­še­nie by som mal, te­raz zo­stá­va zis­tiť pre­čo to fun­gu­je. Môj pô­vod­ný od­had bol, že som touch­pad do­stal do ne­ja­ké­ho re­ži­mu pre uplo­ad firm­vé­ru. Za­čal som hľa­dať na in­ter­ne­te do­ku­men­tá­ciu, až som sa do­pát­ral k sta­ré­mu ma­nu­álu k PS/2 pro­to­ko­lu pre sy­nap­tics.

Bit 5 je zdo­ku­men­to­va­ný na stra­ne 39 ako Trans­pa­rent Mo­de. Pod­ľa do­ku­men­tá­cie je to re­žim, v kto­rom sú PS/2 pac­ke­ty z dru­hé­ho za­ria­de­nia (v tom­to prí­pa­de track­po­in­tu) po­sie­la­né pria­mo ope­rač­né­mu sys­té­mu. Bež­ne sa tie­to pac­ke­ty za­ba­lia do väč­ších pac­ke­tov, aby sa da­li mi­xo­vať sig­ná­ly z oboch za­ria­de­ní. Za­ria­de­nie sa dá vrá­tiť do pô­vod­né­ho re­ži­mu odo­sla­ním prí­ka­zov 0xe7, 0xe6 v pres­ne tom­to po­ra­dí.

Te­raz fun­go­val track­po­int pres­ne tak, ako som chcel, ale bo­lo tu eš­te pár ma­lých prob­lé­mov, kto­ré bo­lo po­treb­né vy­rie­šiť. V pr­vom ra­de po pr­vom na­čí­ta­ní mo­du­lu psmouse bo­lo po­treb­né od­strá­niť mo­dul a zno­vu ho na­čí­tať, pre­to­že pred pre­pnu­tím ak­cep­to­val len za­ba­le­né PS2 pac­ke­ty.

Svoj ker­nel patch som mo­hol im­ple­men­to­vať 2 spô­sob­mi. V tom jed­no­duch­šom by som na­sta­vil TRANSPARENT_MODE, od­strá­nil za­ria­de­nie a spus­til no­vú de­tek­ciu. To má však nie­koľ­ko ne­vý­hod. V pr­vom ra­de nie je mož­né za­ria­de­nie vrá­tiť späť do pô­vod­né­ho re­ži­mu bez úpl­né­ho vy­pnu­tia no­te­bo­oku. Dru­hým prob­lé­mom je, že touch­pad sa mô­že sa­mo­voľ­ne re­štar­to­vať (na­prí­klad v dô­sled­ku elek­tro­sta­tic­ké­ho vý­bo­ja) a po re­štar­te by zo­stal v zlom re­ži­me.

Pri zlo­ži­tej­šom spô­so­be by som pac­ke­ty ne­spra­co­vá­val na úrov­ni hlav­né­ho za­ria­de­nia, ale jed­no­du­cho by som po­sie­lal kaž­dý pri­ja­tý bit pria­mo passthrough za­ria­de­niu. Spra­co­va­nie pri­ja­té­ho by­tu vy­ze­rá tak­to:

static psmouse_ret_t transparent_process_byte(struct psmouse *psmouse)
{
    struct synaptics_data *priv = psmouse->private;

    if (!priv->pt_port)
        return PSMOUSE_BAD_DATA;

    serio_interrupt(priv->pt_port, psmouse->packet[psmouse->pktcnt - 1], 0);
    return PSMOUSE_FULL_PACKET;
}

Ďa­lej som mu­sel ak­tu­ali­zo­vať na­sta­ve­nie re­ži­mu, aby sa po ak­ti­vá­cii TRANSPARENT_MODE za­čal po­u­ží­vať transparent_process_byte na­mies­to synaptics_process_byte:

static void synaptics_update_protocol_handler(struct psmouse *psmouse)
{
    struct synaptics_data *priv = psmouse->private;
    struct serio *pt_port = priv->pt_port;

    bool absolute_mode = priv->absolute_mode;
    bool transparent_mode = priv->transparent_mode;

    if (transparent_mode && pt_port) {
        psmouse->protocol_handler = transparent_process_byte;
    }
    else {
        if (absolute_mode) {
            psmouse->protocol_handler = synaptics_process_byte;
            psmouse->pktsize = 6;
        } else {
            /* Relative mode follows standard PS/2 mouse protocol */
            psmouse->protocol_handler = psmouse_process_byte;
            psmouse->pktsize = 3;
        }
    }
}

Ten­to prí­stup mi umož­ňu­je do­kon­ca vy­tvo­riť sú­bor transparent_mode v /sys/devices/platform/i8042/serio1 a pre­pí­nať re­žim bez to­ho, aby som mu­sel re­lo­ado­vať mo­dul psmouse.

static ssize_t synaptics_show_transparent_mode(struct psmouse *psmouse,
                                               void *data, char *buf)
{
        struct synaptics_data *priv = psmouse->private;

        return sprintf(buf, "%c\n", priv->transparent_mode ? '1' : '0');
}

static ssize_t synaptics_set_transparent_mode(struct psmouse *psmouse,
                                              void *data, const char *buf,
                                              size_t len)
{
        struct synaptics_data *priv = psmouse->private;
        unsigned int value;
        int err;

        err = kstrtouint(buf, 10, &value);
        if (err)
                return err;

        if (value > 1)
                return -EINVAL;

        if (value == priv->transparent_mode)
                return len;

        priv->transparent_mode = value;

        synaptics_update_protocol_handler(psmouse);

        if (value) {
                if (synaptics_enter_transparent_mode(psmouse))
                        return -EIO;
        }
        else {
                if (synaptics_exit_transparent_mode(psmouse))
                        return -EIO;
        }

        return len;
}

PSMOUSE_DEFINE_ATTR(transparent_mode, S_IWUSR | S_IRUGO, NULL,
                    synaptics_show_transparent_mode,
                    synaptics_set_transparent_mode);

Ce­lý patch je o kú­sok zlo­ži­tej­ší a je zve­rej­ne­ný v ke­re­nel mai­ling lis­te.

Pár slov na záver

Ten­to prí­beh by som rád za­kon­čil slo­va­mi: kto­koľ­vek sa mô­že za­po­jiť do vý­vo­ja ker­ne­lu. Ve­do­mos­ti neh­ra­jú prak­tic­ky žiad­nu úlo­hu. Sám som na za­čiat­ku ne­ve­del o vý­vo­ji ker­ne­lu ab­so­lút­ne nič.

Jed­no­du­cho som za­čal rie­šiť prob­lém krok za kro­kom. Od jed­nej chy­by, k dru­hej. Do­ku­men­tá­ciu som čí­tal prie­bež­ne, pod­ľa to­ho, čo som prá­ve po­tre­bo­val ve­dieť. Do­kon­ca som si ne­pa­mä­tal nič z prog­ra­mo­va­nia v C, ale to, čo som po­tre­bo­val sa da­lo na­štu­do­vať za ne­ce­lý deň.

Do­pre­du som ne­ve­del, aká hl­bo­ká bu­de mo­ja za­ja­čia no­ra, ale na­ko­niec som sa po veľ­mi dl­hej ces­te s rôz­ny­mi od­boč­ka­mi do­stal k 2 do­sť dob­rým rie­še­niam.

Dokumenty