⬆️ ⬇️

Setting up equipment at an early stage of booting with ACPI (using FreeBSD as an example)

A few years ago, when CardBus and FireWire (IEEE 1394) were still relatively “in use”, many laptop manufacturers used Texas Instruments PCIXX21 and PCIXX11 controllers in their products: one small chip supported not only the interfaces mentioned, but also many popular standards for removable memory cards.



Such a chip (namely, PCI7411) stands in my NEC Versa S950. In my time, this little-known laptop even preferred the ThinkPad-series almost exclusively due to better support of FreeBSD (equipment in general and sleep mode in particular) - I specially tested it in Novosibirsk Tehnositi before buying. For a long time, I did not use the built-in card reader, out of habit by doing it with USB whistles. But I recently discovered that FreeBSD still does not support it. And if five or six years ago it could have been explained by the lack of a normal driver for these controllers (I had to download and build something myself), now I knew for sure that they were supported out of the box in FreeBSD by the sdhci(4) driver, what is directly stated on the manual page (and later confirmed by reading the source code).



I slowly began to google on this topic, and the picture began to emerge gloomy. It turned out that there are quite a few “lucky ones” like me. Many have posted dmesg and pciconf -lv mailing lists and forums, pciconf -lv trackers (for example, OpenBSD PR i386 / 5843), but no one has offered a solution. Moreover, having actually put an end to the question, Alexander Motin, the author of the sdhci(4) driver, wrote on the forum in 2010 that the TI did not give documentation for the chip, which means that if the manufacturer configured the chip incorrectly, and setting it up via BIOS is not provided, do something difficult. In turn, Theo de Raadt closed down i386 / 5843 with the words: “We do what we can. This undoubted bit undocumented bits. We've strugged before to find this information, and failed. If you can find some documentation, we would be thrilled. "



In desperation, I downloaded from the Ubuntu LiveCD. And I was very surprised that the card reader works in Linux. So ...

')

Bad googled



It turns out that back in 2006, Alex Dubov wrote a Linux driver for TI FlashMedia readers. I downloaded the source code and began to study it, hoping to subsequently refine sdhci(4) or even to sport the whole driver. First of all, I looked at the list of supported PCI vendor / device ids in order to compare it with “our” driver. It turned out to be small:



 $ cat linux/pci_ids.h #define PCI_VENDOR_ID_TI 0x104c #define PCI_DEVICE_ID_TI_XX21_XX11_FM 0x8033 #define PCI_DEVICE_ID_TI_XX12_FM 0x803b #define PCI_DEVICE_ID_TI_XX20_FM 0xac8f 


The number 0x8033 is already familiar to me from the output of pciconf -lv on my laptop (chip = 0x8033 104c):



 none3@pci0:6:7:3: class=0x018000 card=0x83191033 chip=0x8033104c rev=0x00 hdr=0x00 vendor = 'Texas Instruments (TI)' device = 'PCIxx11/21 Integrated FlashMedia Controller' class = mass storage 


This is the same card reader that does not work on FreeBSD, but works on Linux. And here is a piece of code from sdhci.c (FreeBSD):



 static const struct sdhci_device { uint32_t model; uint16_t subvendor; char *desc; u_int quirks; } sdhci_devices[] = { { 0x08221180, 0xffff, "RICOH R5C822 SD", SDHCI_QUIRK_FORCE_DMA }, { 0xe8221180, 0xffff, "RICOH SD", SDHCI_QUIRK_FORCE_DMA }, { 0xe8231180, 0xffff, "RICOH R5CE823 SD", SDHCI_QUIRK_LOWER_FREQUENCY }, { 0x8034104c, 0xffff, "TI XX21/XX11 SD", SDHCI_QUIRK_FORCE_DMA }, 


You may notice that the device identifier TI XX21 / XX11 SD (0x803 4 104c) is similar to mine (0x803 3 104c) with one digit accuracy. In addition, I noticed that CardBus (0x8031104c) and FireWire (0x8032104c) controllers not only have similar id's, but also PCI selectors of all devices differ only in the function number, and they all have the same device:



 none1@pci0:6:7:0: class=0x060700 card=0x83191033 chip=0x8031104c rev=0x00 hdr=0x02 vendor = 'Texas Instruments (TI)' device = 'PCIxx21/x515 Cardbus Controller' class = bridge subclass = PCI-CardBus none2@pci0:6:7:2: class=0x0c0010 card=0x83191033 chip=0x8032104c rev=0x00 hdr=0x00 vendor = 'Texas Instruments (TI)' device = 'OHCI Compliant IEEE-1394 FireWire Controller' class = serial bus subclass = FireWire 


Recalling Sasha Motin’s words that the chip actually implements both controllers (SDHCI and FlashMedia), I began to search more purposefully, and soon came across another post , and then a message in the freebsd-mobile @ e- mail about a similar (but a little other) problem on HP NC6220. A working solution was not offered anywhere, but, unlike most discussions, which boiled down to stupid tips like “try the latest driver” or trivial “sorry, but it looks like you're in flight,” now, at least, it became clear that The configuration of the chip is somehow displayed in the PCI function dump (which means it may be possible to change it), and the main thing is that the documentation is available: PCIXXX21 / PCIXXX11 Implementation Guide . And here I was really interested.



Looking ahead, I would say that the most surprising thing is that people, having dug up almost a datasheet for a “naughty” chip, stopped just a step away from solving the problem. I never found any recipe for how to use the documentation correctly (which prompted me to write this post). But first things first.



PCIXXX21 / PCIXXX11 Implementation Guide - a document of 117 pages for hardware designers based on these controllers. It does not make sense to analyze it in detail; the most important thing that I learned from it: the controller actually implements five functions: CardBus, 1394, FlashMedia, SD Host and SmartCard; The initial configuration is usually taken from the EEPROM. The main configuration register, the General Control Register (section 12.4.28, p. 65), is located at 1Eh-1Fh in ROM (we are only interested in the zero byte, because it is the chip functions that are masked) and corresponds to the PCI offset 86h of the zero function devices. Now -



For the cause



First, let's see what the pciconf(8) utility tells us about the PCI configuration space “head” (zero) function of the chip, that is, in the terminology of FreeBSD, the pci0:6:7:0 selector. For the sake of brevity, I will not give a dump of all 256 bytes, and I will confine myself only to those of interest to us, at offset 86h:



 # pciconf -rb pci0:6:7:0 0x86 d3 


Interesting. We look at the tablet on the 65th pdf page, we see that the three in the lower nibble (nibble) is equal to the typical value of the bits responsible for top level arbitration, SmartCard socket power control and OHCI 1394, it interests us a little. But the top nibl just masks (turns on and off) the logic of the other controllers (I don’t include the entire table again, to save space):



scpu022a.pdf section 12.4.28




0xD is 1101, i.e. The DISABLE_SC, DISABLE_SD and DISABLE_SKTB bits are set, and the DISABLE_FM bit is cleared. Therefore, in order to “revive” the SD Host controller, we, logically, need to reset DISABLE_SD (enable), and DISABLE_FM, on the contrary, set (disable). Mask 1011 corresponds to the value 0xB, i.e. in fact, we need to change byte 0xD3 to 0xB3 . The problem, however, is that it must be done strongly in advance, before the chip is initialized, or rather, before it determines which controllers to include. Once the system has booted, changing the configuration is useless: all devices are already “in service”. And here we come to the rescue



ACPI



What ACPI is and why it is needed, I will not explain: it goes beyond the topic, besides, Habré already had a good post on this topic. In this case, the question is important for us: is it possible to patch DSDT before chip initialization so that it turns on the desired controller (SD Host) and turns off the unnecessary one for which we do not have a driver (FlashMedia).



Let's see what we have tools for debugging and outputting information within the interpreter ("virtual machine") ACPI. The ACPI specification (Section 19.5.25, p. 733) mentions a special Debug object that the operating system must “convey” to the user. In FreeBSD, the system variable debug.acpi.enable_debug_objects is responsible for this, which must be set to one:



 # sysctl debug.acpi.enable_debug_objects=1 debug.acpi.enable_debug_objects: 0 -> 1 


Now we can write arbitrary lines in Debug , and the FreeBSD kernel will output them to the console. It remains to figure out how to get ACPI to issue information of interest to us on demand. To begin, let us sdampim and disassemble the DSDT of the laptop, and study it:



 # acpidump -dt > s950.asl 


I decided to find a method that is called through some external influence (or internal, but periodic, such as polling the battery), while practically without affecting the work of the hardware. Studying the DSDT code, I came across a curious piece:



 Method (_Q0C, 0, NotSerialized) { If (\_SB.PCI0.PEGA) { \_SB.PCI0.PEGP.VGA.SWIH () } Else { Store (0x01, TLST) HKDS (0x0A) } } 


Nowhere else is the \_SB.PCI0.PEGP.VGA.SWIH method called, and its name hints that this is some kind of display switching. On the keyboard of many laptops, one of the function keys in combination with the Fn-modifier switches the video output from the internal display to the external one. On my "versa" this is F3. Let's try to modify the method code as follows:



  Method (_Q0C, 0, NotSerialized) { + Store ("Fn-F3 pressed", Debug) If (\_SB.PCI0.PEGA) { 


Rebuild ASL:



 # iasl s950-patched.asl Intel ACPI Component Architecture ASL Optimizing Compiler version 20101013-32 Copyright (c) 2000 - 2010 Intel Corporation ASL Input: s950-patched.asl - 7749 lines, 280987 bytes, 2840 keywords AML Output: /tmp/acpidump.aml - 24863 bytes, 640 named objects, 2200 executable opcodes Compilation complete. 0 Errors, 0 Warnings, 0 Remarks, 958 Optimizations # cp /tmp/acpidump.aml /root/s950-patched.aml 


In order for FreeBSD to use our table when booting, we /boot/loader.conf add /boot/loader.conf following lines to / boot / /boot/loader.conf :



 acpi_dsdt_load="YES" acpi_dsdt_name="/root/s950-patched.aml" 


If everything is done correctly, and our calculation was justified, then when we press Fn-F3, we will see on the console the kernel messages (high brightness) that the Fn-F3 key was pressed. Now that we are more or less able to interact with ACPI, it's time to try



To get to the register 86h



The physical address spaces of various devices (RAM, I / O ports, expansion cards, CMOS, IPMI, etc.) are mapped into the ACPI namespace as so-called. operational regions (OperationRegion), inside of which bit fields (Field) are usually allocated, consisting of one or several named “virtual registers”, or field units (paragraph 19.5.96, p. 782 specifications). An OperationRegion for our controller might look like this:



 OperationRegion (PCIC, PCI_Config, 0x00, 256) Field (PCIC, AnyAcc, NoLock, Preserve) { Offset (0x86), // General Control Register   86h TLA, 2, // Top level arbitration SCSP, 1, // SmartCard socket power OHCI, 1, // Disable OHCI 1394 controller function SKTB, 1, // Disable CardBus socket B FM, 1, // Disable FlashMedia function SD, 1, // Disable SD host controller function SC, 1, // Disable SmartCard function } 


Or even easier, if not all 256 bytes are declared in the OperationRegion , but only the one that interests us, and not to allocate individual bits in the configuration register:



 OperationRegion (PCIC, PCI_Config, 0x86, 0x01) Field (PCIC, AnyAcc, NoLock, Preserve) { GCR0, 8, // General Control Register (Byte 0) } 


It is easy to see that such a definition in itself is useless: it is not tied to any device, being in essence just a structure from one byte at a known offset. Devices in DSDT are specified via the Device keyword (paragraph 19.5.30, p. 735); the aggregate of all devices is a sort of tree. So, all PCI devices are most often inside the \_SB.PCI0 device space, which corresponds to the root PCI bus (usually there is one such bus, but theoretically they can be up to 256 in one PCI domain).



To identify a device on the bus, you must set its address in the form (device << 16) | function. In our case (remember the output of pciconf -lv ?) Function = 0, device = 7, bus = 6. That is, the device, apparently, should look something like this:



 Device (XX11) { Name (_ADR, 0x00070000) // pci0:6:7:0 } 


All right, but where did the sixth tire come from? And where is she in DSDT? Let's look at the kernel boot log ( dmesg ):



 $ dmesg | grep pci6 pci6: <ACPI PCI bus> on pcib4 pci6: <bridge, PCI-CardBus> at device 7.0 (no driver attached) pci6: <serial bus, FireWire> at device 7.2 (no driver attached) pci6: <mass storage> at device 7.3 (no driver attached) pci6: <network> at device 8.0 (no driver attached) $ dmesg | grep pcib4 pcib4: <ACPI PCI-PCI bridge> at device 30.0 on pci0 pci6: <ACPI PCI bus> on pcib4 


It turns out, pci6 is an additional, “virtual” bus on a PCI-PCI bridge. She got number 6 (as well as 4 for the bridge) because FreeBSD distributed the devices that way. Inside the DSDT, of course, there are no six tires and four bridges. Bridge - Device (PCIB) - there is exactly one, as expected. Fully our description should look like this (I give a short version, without decomposing the register into separate bits):



 Scope (\_SB.PCI0.PCIB) { Device (XX11) { Name (_ADR, 0x00070000) // pci0:6:7:0 OperationRegion (PCIC, PCI_Config, 0x86, 0x01) Field (PCIC, AnyAcc, NoLock, Preserve) { GCR0, 8, } } } 


Now we can replace our debugging code in the _Q0C method _Q0C something more meaningful:



  Method (_Q0C, 0, NotSerialized) { + Store (Concatenate("GCR0 = 0x", \_SB.PCI0.PCIB.XX11.GCR0), Debug) If (\_SB.PCI0.PEGA) { 


Reassemble ASL, reboot, click Fn-F3. If we did everything right, then we should see the same value that we previously read through pciconf(8) :



vanilla pciconf -lv




(The implementation of the function to write the register value directly to the video memory is left to the reader as an easy exercise.)



It remains for us to answer the most important question: will it be possible to change the register value and make the chip configure itself as we need?



The ACPI standard defines a special method for initializing devices, _INI (Section 6.5.1, p. 349). Let's add the following code to our device:



 Method (_INI) { Store (0xB3, GCR0) /*     : Store (0x01, FM) Store (0x00, SD) */ } 


We compile ASL, copy the resulting AML file to /root/s950-patched.aml , reboot again. We look at



Result



pciconf -lv after patch




First of all, note that the controller 0x803 3 104c (FlashMedia) disappeared from the pciconf -lv , but 0x803 4 104c (SD Host) appeared. Load the necessary kernel modules, insert the card and try to mount it:



 # kldload sdhci mmc mmcsd # ls /dev/mmcsd0* /dev/mmcsd0 /dev/mmcsd0s1 # mount -t msdosfs /dev/mmcsd0s1 /mnt/tmp # ls /mnt/tmp/DCIM/100CANON IMG_0403.JPG IMG_0424.JPG IMG_0450.JPG IMG_0494.JPG IMG_0515.JPG IMG_0406.JPG IMG_0425.JPG IMG_0451.JPG IMG_0498.JPG IMG_0517.JPG IMG_0407.JPG IMG_0427.JPG IMG_0452.JPG IMG_0499.JPG IMG_0518.JPG IMG_0409.JPG IMG_0429.JPG IMG_0453.JPG IMG_0500.JPG IMG_0520.JPG IMG_0410.JPG IMG_0430.JPG IMG_0467.JPG IMG_0501.JPG IMG_0522.JPG IMG_0412.JPG IMG_0439.JPG IMG_0473.JPG IMG_0506.JPG IMG_0525.JPG IMG_0413.JPG IMG_0440.JPG IMG_0474.JPG IMG_0507.JPG IMG_0526.JPG IMG_0414.JPG IMG_0445.JPG IMG_0475.JPG IMG_0508.JPG IMG_0534.JPG IMG_0415.JPG IMG_0447.JPG IMG_0478.JPG IMG_0510.JPG IMG_0535.JPG IMG_0421.JPG IMG_0448.JPG IMG_0492.JPG IMG_0512.JPG IMG_0537.JPG IMG_0423.JPG IMG_0449.JPG IMG_0493.JPG IMG_0514.JPG IMG_0538.JPG # tar cf /dev/null /mnt/tmp/DCIM/100CANON ; echo $? tar: Removing leading '/' from member names 0 


It seems everything works, well, nice. You can remove the debugging code from DSDT and enjoy life to use the card reader.

Source: https://habr.com/ru/post/150887/



All Articles