{"id":38,"date":"2024-12-16T23:47:57","date_gmt":"2024-12-16T23:47:57","guid":{"rendered":"https:\/\/gabriel.marcanobrady.family\/blog\/?p=38"},"modified":"2024-12-16T23:51:05","modified_gmt":"2024-12-16T23:51:05","slug":"dell-g5-5505-se-acpi-or-figuring-out-how-to-reset-the-rgb-controller","status":"publish","type":"post","link":"https:\/\/gabriel.marcanobrady.family\/blog\/2024\/12\/16\/dell-g5-5505-se-acpi-or-figuring-out-how-to-reset-the-rgb-controller\/","title":{"rendered":"Dell G5 5505 SE ACPI, or figuring out how to reset the RGB controller"},"content":{"rendered":"\n<p>This is going to be a long one, and it has taken me a while to write up (I started writing this in 2022, and now we&#8217;re almost in 2025!). While this isn&#8217;t as polished as I&#8217;d liked for it to be, it&#8217;s been sitting as a draft for too long, and I almost want to get rid of this laptop, heh.<\/p>\n\n\n\n<p>Four years ago my old 2013 MSI GE40 laptop&#8217;s battery finally kicked the bucket, and after swearing off Nvidia and wanting to try out a CPU from team Red, I acquired the only all-AMD laptop I could find at the time against my better judgement: a Dell G5 5505 Special Edition. Honestly, this laptop has been mostly OK (other than having to replace one of its cooling fans under warranty not even two weeks after getting the laptop, great QC there Dell). One of the first things that caught my attention was the full RGB blacklit keyboard on the laptop, and I immediately wanted to see if I could get it working on Linux. This will probably be the subject of another blog post, later, though. However, in the process of reversing what the RGB controller was doing, I figured out that there was a way to reboot the RGB controller into DFU flashing mode, and that in Windows Dell&#8217;s software did this via ACPI calls.<\/p>\n\n\n\n<p>For almost all commands that follow, one needs the <code><a href=\"https:\/\/github.com\/acpica\/acpica\">acpica<\/a><\/code> or <code>iasl<\/code> package, ideally at least the <code>20240927<\/code> or <code>R09_27_24<\/code>  release. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Dumping and disassembling ACPI tables<\/h2>\n\n\n\n<p>On Linux, <code>acpidump<\/code> and <code>acpixtract<\/code> make quick work of this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# Extracts all ACPI tables into the tables.acpi file\nsudo sh -c &quot;acpidump &gt; tables.acpi&quot;\n# Extracts all tables individually into their own .dat binary file\nacpixtract -a tables.acpi\n<\/pre><\/div>\n\n\n<p>At this point, there should be a <em>ton<\/em> of *.dat files in the working directory. For some reason this laptop has over 20 <code>ssdt<\/code> tables.<\/p>\n\n\n\n<p>To then disassemble the files, I do the following (my shell is fish, so some modification may be required to work with bash):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/fish\nfor I in *.dat\n        echo -----------$I\n        iasl -e $(ls *.dat | grep -v $I) -d $I\nend\n<\/pre><\/div>\n\n\n<p>In essence we want to pass as many tables to the disassembler as possible so that it can find methods and other objects that the table being disassembled may use. We can&#8217;t pass the same table as a reference otherwise <code>iasl<\/code> complains about duplicate objects.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Getting WMI information<\/h2>\n\n\n\n<p>Great, now we can inspect the tables. From some&#8230; poking around Dell files on Windows, I determined that it updates the RGB firmware through some WMI ACPI functions. The Linux kernel has some pretty decent documentation on WMI, <a href=\"https:\/\/docs.kernel.org\/wmi\/devices\/alienware-wmi.html\">including stuff relating to Dell WMI functionality<\/a>.<\/p>\n\n\n\n<p>We need the <code><a href=\"https:\/\/github.com\/pali\/bmfdec\">bmf2mof<\/a><\/code> utility to be able to parse some of the binary blobs exposed by the Linux kernel related to WMI methods. On my system, the right BMOF data for the WMAX function can be extracted as follows:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# As root or with root privileges\nbmf2mof &amp;lt; \/sys\/bus\/wmi\/devices\/05901221-D566-11D1-B2F0-00A0C9062910-1\/bmof\n&#x5B;WMI, Dynamic, Provider(&quot;WmiProv&quot;), Locale(&quot;MS\\\\0x409&quot;), Description(&quot;WMI Function&quot;), guid(&quot;{A70591CE-A997-11DA-B012-B622A1EF5492}&quot;)]\nclass AWCCWmiMethodFunction {\n  &#x5B;key, read] string InstanceName;\n  &#x5B;read] boolean Active;\n\n  &#x5B;WmiMethodId(19), Implemented, read, write, Description(&quot;Get Fan Sensors.&quot;)] void GetFanSensors(&#x5B;in] uint32 arg2, &#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(20), Implemented, read, write, Description(&quot;Thermal Information.&quot;)] void Thermal_Information(&#x5B;in] uint32 arg2, &#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(21), Implemented, read, write, Description(&quot;Thermal Control.&quot;)] void Thermal_Control(&#x5B;in] uint32 arg2, &#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(23), Implemented, read, write, Description(&quot;MemoryOCControl.&quot;)] void MemoryOCControl(&#x5B;in] uint32 arg2, &#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(26), Implemented, read, write, Description(&quot;System Information.&quot;)] void SystemInformation(&#x5B;in] uint32 arg2, &#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(32), Implemented, read, write, Description(&quot;FW Update GPIO toggle.&quot;)] void FWUpdateGPIOtoggle(&#x5B;in] uint32 arg2, &#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(33), Implemented, read, write, Description(&quot;Read Total of GPIOs.&quot;)] void ReadTotalofGPIOs(&#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(34), Implemented, read, write, Description(&quot;Read GPIO pin Status.&quot;)] void ReadGPIOpPinStatus(&#x5B;in] uint32 arg2, &#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(36), Implemented, read, write, Description(&quot;Read Platform Properties.&quot;)] void ReadPlatformProperties(&#x5B;out] uint32 argr);\n  &#x5B;WmiMethodId(37), Implemented, read, write, Description(&quot;Game Shift Status.&quot;)] void GameShiftStatus(&#x5B;in] uint32 arg2, &#x5B;out] uint32 argr);\n};\n\n<\/pre><\/div>\n\n\n<p>Oh, that&#8217;s so nice. From here we know the GUID of the function <code>A70591CE-A997-11DA-B012-B622A1EF5492<\/code> and all of the WMI methods and their IDs. With this information we can go find them in the ACPI tables.<\/p>\n\n\n\n<p>For BIOS version 1.24.0, this is the <code>WMAX<\/code> function in <code>ssdt20.dsl<\/code>. In there there&#8217;s a large switch-case statement with IDs in hexadecimal that match up with the IDs in the <code>BMOF<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reimplementing ACPI GPIO methods to avoid screwing up their output<\/h2>\n\n\n\n<p>The <code>ReadGPIOpPinStatus<\/code> function (ID 34 or 0x22) uses the <code>WISC<\/code> method (defined in <code>dsdt.dsl<\/code>), which changes the direction of the GPIO pin to read it. This&#8230; is not useful if we just want to read the pin without affecting its function (which we can do), as changing its direction makes the GPIO act as thought it was set to output low.<\/p>\n\n\n\n<p>I implemented two new ACPI functions, <code>WISI<\/code> and <code>WISO<\/code>, to refactor <code>WISC<\/code> and to stop screwing up the GPIO output on a GPIO read call. One observation I made is that the &#8220;magic&#8221; address <code>0xFED81500<\/code> is the address of the <code>gpio-amd-fch<\/code> GPIO registers (found in the Linux kernel in <code><a href=\"https:\/\/github.com\/torvalds\/linux\/blob\/f44d154d6e3d633d4c49a5d6aed8a0e4684ae25e\/drivers\/gpio\/gpio-amd-fch.c\">drivers\/gpio\/gpio-amd-fch.c<\/a><\/code>).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n    Method (WISO, 2, Serialized)\n    {   \n        Local0 = (Arg0 &amp;lt;&amp;lt; 0x02)\n        Local0 += 0xFED81500\n        OperationRegion (GREG, SystemMemory, Local0, 0x04)\n        Field (GREG, ByteAcc, NoLock, Preserve)\n        {\n            Offset (0x02),\n                ,   6,\n            OPVL,   1,\n            OPEN,   1\n        } \n        OPEN = One\n        OPVL = Arg1\n    }   \n        \n    Method (WISI, 1, Serialized)\n    {       \n        Local0 = (Arg0 &amp;lt;&amp;lt; 0x02)\n        Local0 += 0xFED81500\n        OperationRegion (GREG, SystemMemory, Local0, 0x04)\n        Field (GREG, ByteAcc, NoLock, Preserve)\n        {   \n            Offset (0x02),\n            PSTS,   1\n        }   \n            \n        Local2 = PSTS \/* \\WISI.PSTS *\/\n        Return (Local2)\n    }\n<\/pre><\/div>\n\n\n<p>I then re-implemented method IDs 0x20 (32) and 0x22 (34) using these new methods. This should let us query the GPIO state without messing with the GPIO direction.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n                    \/\/ Write NRST or BOOT0\n                    \/\/ Name=FWUpdateGPIOtoggle\n                    Case (0x20)\n                    {\n                        AXBF = Arg2\n                        \/\/ BFB0 is pin select (1 == NRST, 0 == BOOT0)\n                        If ((BFB0 == Zero))\n                        {\n                            If ((BFB1 == Zero))\n                            {\n                                WISO(0x05, Zero)\n                            }\n                            Else\n                            {\n                                WISO(0x05, One)\n                            }\n                        }\n                        ElseIf ((BFB0 == One))\n                        {\n                            If ((BFB1 == Zero))\n                            {\n                                WISO(0x0A, Zero)\n                            }\n                            Else\n                            {\n                                WISO(0x0A, One)\n                            }\n                        }\n\n                        Return (Zero)\n                    }\n                    \/\/ Name=ReadTotalofGPIOs\n                    Case (0x21)\n                    {\n                        Return (0x02)\n                    }\n                    \/\/ Name=ReadGPIOpPinStatus\n                    Case (0x22)\n                    {\n                        AXBF = Arg2\n                        Local0 = 0x02\n                        \/\/ BFB0 is pin select (1 == NRST, 0 == BOOT0)\n                        \/\/ WISI does not switch the pin to input\n                        If ((BFB0 == Zero))\n                        {\n                            Local0 = WISI (0x05)\n                        }\n                        ElseIf ((BFB0 == One))\n                        {\n                            Local0 = WISI (0x0A)\n                        }\n\n                        Return (Local0)\n                    }\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Fixing the other ACPI tables (is pain) <\/h2>\n\n\n\n<p>If one tries to just recompile the tables, it won&#8217;t work. Most of the tables have a ton of issues. At a bare minimum, however, <code>dsdt.dsl<\/code> and <code>ssdt20.dsl<\/code> (or whichever <code>ssdt has the WMAX<\/code> function) need to build. Unfortunately the process for fixing these tables is non-trivial and rather tedious. My best recommendation is to scour online for any help in trying to understand the <code>iasl<\/code> error messages.<\/p>\n\n\n\n<p>One critical change that needs to happen is that the version number near the top of the source files needs to be increased, else the Linux ACPI table override system won&#8217;t bother trying to override the table.<\/p>\n\n\n\n<p>Compiling the new table is as simple as doing:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Example rebuilding of the DSDT table\niasl dsdt.dsl\n# If successful, it emits dsdt.aml\n<\/pre><\/div>\n\n\n<p>If there are errors, <code>iasl<\/code> will report them. On success a <code>.aml<\/code> file is generated, and it contains the compiled table.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hacking Linux: Overriding the right tables!<\/h2>\n\n\n\n<p>Alright, we have the tables re-compiled. Now what? The Linux kernel can override ACPI tables, but the mechanism it uses is by table type&#8230; and we&#8217;re overriding at least one SSDT out of a bunch of SDDT ones, so the kernel can&#8217;t tell them apart.<\/p>\n\n\n\n<p>I&#8217;ve come up with what is arguably the worst hack I&#8217;ve implemented by far to work around this issue. My hack checks each table in the kernel against the original table size, if they match the kernel prepares to replace the table. It is nasty, but it works, as none of the SSDT tables I&#8217;ve been overriding have the same original size.<\/p>\n\n\n\n<p>Here&#8217;s the patch, for the 6 <code>SSDT<\/code> tables I&#8217;m overriding for BIOS version 1.24.0:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: diff; title: ; notranslate\" title=\"\">\n-- a\/drivers\/acpi\/tables.c 2022-03-20 13:14:17.000000000 -0700\n+++ b\/drivers\/acpi\/tables.c 2022-04-13 16:37:55.389618306 -0700\n@@ -688,6 +688,10 @@ acpi_table_initrd_override(struct acpi_t\n    struct acpi_table_header *table;\n    u32 table_length;\n \n+   \/\/ FIXME Hacks by Gabriel M. to load a specific SSDT on a Dell G5505 SE\n+   bool is_ssdt;\n+   bool hack;\n+\n    *length = 0;\n    *address = 0;\n    if (!acpi_tables_addr)\n@@ -705,7 +709,9 @@ acpi_table_initrd_override(struct acpi_t\n        table_length = table-&gt;length;\n \n        \/* Only override tables matched *\/\n-       if (memcmp(existing_table-&gt;signature, table-&gt;signature, 4) ||\n+       is_ssdt = !memcmp(existing_table-&gt;signature, &quot;SSDT&quot;, 4);\n+       hack = is_ssdt &amp;&amp; (existing_table-&gt;length == 0x517 || existing_table-&gt;length == 0x53B || existing_table-&gt;length == 0x723C || existing_table-&gt;length == 0x28D || existing_table-&gt;length == 0x30C8 || existing_table-&gt;length == 0xC6C);\n+       if ((is_ssdt &amp;&amp; !hack) || memcmp(existing_table-&gt;signature, table-&gt;signature, 4) ||\n            memcmp(table-&gt;oem_id, existing_table-&gt;oem_id,\n               ACPI_OEM_ID_SIZE) ||\n            memcmp(table-&gt;oem_table_id, existing_table-&gt;oem_table_id,\n@@ -713,6 +719,13 @@ acpi_table_initrd_override(struct acpi_t\n            acpi_os_unmap_memory(table, ACPI_HEADER_SIZE);\n            goto next_table;\n        }\n+\n+       \/\/ FIXME Hack, skip matching all SSDT tables except specific one\n+       if (is_ssdt &amp;&amp; !hack) {\n+           acpi_os_unmap_memory(table, ACPI_HEADER_SIZE);\n+           goto next_table;\n+       }\n+\n        \/*\n         * Mark the table to avoid being used in\n         * acpi_table_initrd_scan() and check the revision.\n<\/pre><\/div>\n\n\n<p>This hack really sucks because it needs to be updated every BIOS update. I wish there were some way to differentiate <code>SSDT<\/code> tables already available, but I haven&#8217;t found one yet.<\/p>\n\n\n\n<p>To actually have the Linux kernel override the ACPI tables, I&#8217;m using <code><a href=\"https:\/\/github.com\/dracutdevs\/dracut\">dracut<\/a><\/code> to generate an initramfs image, and its <code>acpi_override<\/code> option to actually install the <code>*.aml<\/code> files as part of the initramfs. See the man pages for <code>dracut<\/code> for more details.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Implementing a debugfs for these newly found ACPI methods<\/h2>\n\n\n\n<p>OK, finally, we&#8217;re at a point where we can do something interesting. There&#8217;s probably a better way to do this, but I&#8217;ve implemented some code to allow calling these WMI functions through the Linux kernel, and I&#8217;ve exposed them through the <code>debugfs<\/code> interface.<\/p>\n\n\n\n<p>I keep the patches that implement this in the kernel in <a href=\"https:\/\/gitlab.com\/gemarcano\/linux\/-\/tree\/dell_awcc_experiment\">a fork of the kernel on gitlab<\/a>. Once the module is loaded it exposes the following files:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/sys\/kernel\/debug\/dell_awcc\/memory_volt\n\/sys\/kernel\/debug\/dell_awcc\/memory_freq\n\/sys\/kernel\/debug\/dell_awcc\/gameshift\n\/sys\/kernel\/debug\/dell_awcc\/boot0\n\/sys\/kernel\/debug\/dell_awcc\/nrst<\/code><\/pre>\n\n\n\n<p>I can&#8217;t vouch for the complete validity of the <code>memory_*<\/code> stuff, but the <code>boot0<\/code> and <code>nrst<\/code> control the GPIOs connected to the STM32 managing the RGB controller. echo-ing 0 turns the GPIOs low, and echo-ing 1 turns them high. So, to change the RGB controller to its boot DFU mode:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\necho 1 &gt; \/sys\/kernel\/debug\/dell_awcc\/boot0\necho 0 &gt; \/sys\/kernel\/debug\/dell_awcc\/nrst\necho 1 &gt; \/sys\/kernel\/debug\/dell_awcc\/nrst\n<\/pre><\/div>\n\n\n<p>And this is the <code>dmesg<\/code> output of doing the above:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;17039.648454] usb 3-3.2: USB disconnect, device number 5\n&#x5B;17043.154318] usb 3-3.2: new full-speed USB device number 6 using xhci_hcd\n&#x5B;17043.242228] usb 3-3.2: New USB device found, idVendor=0483, idProduct=df11, bcdDevice=22.00\n&#x5B;17043.242235] usb 3-3.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3\n&#x5B;17043.242237] usb 3-3.2: Product: STM32  BOOTLOADER\n&#x5B;17043.242239] usb 3-3.2: Manufacturer: STMicroelectronics\n&#x5B;17043.242241] usb 3-3.2: SerialNumber: FFFFFFFEFFFF\n<\/pre><\/div>\n\n\n<p>Success! We&#8217;ve rebooted the RGB controller to its DFU programming mode and can be dumped using <code>dfu-util<\/code>!<\/p>\n\n\n\n<p>To turn the RGB chip back to its normal self:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\necho 0 &gt; \/sys\/kernel\/debug\/dell_awcc\/boot0\necho 0 &gt; \/sys\/kernel\/debug\/dell_awcc\/nrst\necho 1 &gt; \/sys\/kernel\/debug\/dell_awcc\/nrst\n<\/pre><\/div>\n\n\n<p>And this is my kernel output:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;17144.938660] usb 3-3.2: reset full-speed USB device number 6 using xhci_hcd\n&#x5B;17145.014269] usb 3-3.2: device firmware changed\n&#x5B;17145.014859] usb 3-3.2: USB disconnect, device number 6\n&#x5B;17145.194627] usb 3-3.2: new full-speed USB device number 7 using xhci_hcd\n&#x5B;17145.285273] usb 3-3.2: New USB device found, idVendor=0483, idProduct=df11, bcdDevice= 2.00\n&#x5B;17145.285283] usb 3-3.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3\n&#x5B;17145.285288] usb 3-3.2: Product: DFU in FS Mode\n&#x5B;17145.285292] usb 3-3.2: Manufacturer: STMicroelectronics\n&#x5B;17145.285295] usb 3-3.2: SerialNumber: 206D335B5353\n&#x5B;17146.912500] usb 3-3.2: USB disconnect, device number 7\n&#x5B;17147.080577] usb 3-3.2: new full-speed USB device number 8 using xhci_hcd\n&#x5B;17147.176269] usb 3-3.2: New USB device found, idVendor=187c, idProduct=0550, bcdDevice= 2.00\n&#x5B;17147.176283] usb 3-3.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3\n&#x5B;17147.176289] usb 3-3.2: Product: AW-ELC\n&#x5B;17147.176295] usb 3-3.2: Manufacturer: Alienware\n&#x5B;17147.176300] usb 3-3.2: SerialNumber: 00.01\n&#x5B;17147.269992] hid-generic 0003:187C:0550.0005: hiddev96,hidraw0: USB HID v1.11 Device &#x5B;Alienware AW-ELC] on usb-0000:07:00.4-3.2\/input0\n<\/pre><\/div>\n\n\n<p>I do wonder, now that I know what GPIO pins are connected to the RGB controller, if we should just&#8230; bypass the ACPI table and talk to the controller directly. Maybe that&#8217;s an experiment for another time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is going to be a long one, and it has taken me a while to write up (I started writing this in 2022, and now we&#8217;re almost in 2025!). While this isn&#8217;t as polished as I&#8217;d liked for it to be, it&#8217;s been sitting as a draft for too long, and I almost want [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,4],"tags":[23,24,14,22],"class_list":["post-38","post","type-post","status-publish","format-standard","hentry","category-engineering","category-linux","tag-acpi","tag-dell-g5-5505-se","tag-linux","tag-wmi"],"_links":{"self":[{"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/posts\/38","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/comments?post=38"}],"version-history":[{"count":15,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/posts\/38\/revisions"}],"predecessor-version":[{"id":172,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/posts\/38\/revisions\/172"}],"wp:attachment":[{"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/media?parent=38"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/categories?post=38"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/tags?post=38"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}