PMC电源管理看门狗:iTCO_wdt

  • 由 b178903294创建, 最后修改于9月 19, 2019   版权所有,谢绝转载

此看门狗相关寄存器位于PMC设备中,用于监控系统电源状态的。PMC (Power Management Controller) 是一个PCI device, pci地址为B/D/F=0/13/1,这个地址是IPC1/GCR/ACPI块的基地址。 关于pci地址如何计算出16进制物理地址请参照:PCI/PCIe基础——配置空间

近日接到需求,在系统log中看到iTCO_wdt在probe的过程中失败:

> [    9.377211] iTCO_wdt: Intel TCO WatchDog Timer Driver v1.11

> [    9.377407] iTCO_wdt: probe of iTCO_wdt failed with error -16

       所以第一时间就去看了是哪里打印出了这一条错误的log,后来经过测试发现是在iTCO_wdt probe的过程中申请相关寄存器没有成功,所以返回错误,具体是下面这段报错:

iTCO_wdt_probe

if (iTCO_wdt_private.iTCO_version >= 2) {

        iTCO_wdt_private.gcs_pmc_res = platform_get_resource(dev,

                            IORESOURCE_MEM,

                            ICH_RES_MEM_GCS_PMC);

 

        if (!iTCO_wdt_private.gcs_pmc_res)

            goto out;

 

        if (!request_mem_region(iTCO_wdt_private.gcs_pmc_res->start,          //这句由于申请没有被批准所以报错返回了

            resource_size(iTCO_wdt_private.gcs_pmc_res), dev->name)) {

            ret = -EBUSY;

            goto out;

        }

        iTCO_wdt_private.gcs_pmc = ioremap(iTCO_wdt_private.gcs_pmc_res->start,

            resource_size(iTCO_wdt_private.gcs_pmc_res));

        if (!iTCO_wdt_private.gcs_pmc) {

            ret = -EIO;

            goto unreg_gcs_pmc;

        }

    }

     request_mem_region的时候直接报错返回。那么肯定是这块内存申请不下来,那么这时候就去查了一下内存和谁冲突了??? 通过cat /proc/iomem 发现:

fe042000-fe043fff : INT34D2:00

   fe043000-fe043fff : iTCO_wdt

     原来是这两个内存段冲突了。那么问题来了我们的内存段是从哪里申请过来的???platform_get_resource这句函数就是申请相关资源用的,但是哪里定义了这些资源呢???经过艰苦卓绝的寻找,发现再intel_pmc_ipc.c中定义了相关资源:

static struct resource tco_res[] = {

    /* ACPI - TCO */

    {

        .flags = IORESOURCE_IO,

    },

    /* ACPI - SMI */

    {

        .flags = IORESOURCE_IO,

    },

    /* GCS */

    {

        .flags = IORESOURCE_MEM,

    },

};

     与iTCO_wdt相关的io和内存资源都在这里面定义了,具体是由下面这段代码添加的资源:

static int ipc_create_tco_device(void)

{

    struct platform_device *pdev;

    struct resource *res;

    int ret;

 

    pdev = platform_device_alloc(TCO_DEVICE_NAME, -1);

    if (!pdev) {

        dev_err(ipcdev.dev, "Failed to alloc tco platform device\n");

        return -ENOMEM;

    }

 

    pdev->dev.parent = ipcdev.dev;

 

    res = tco_res + TCO_RESOURCE_ACPI_IO;

    res->start = ipcdev.acpi_io_base + TCO_BASE_OFFSET;

    res->end = res->start + TCO_REGS_SIZE - 1;

 

    res = tco_res + TCO_RESOURCE_SMI_EN_IO;

    res->start = ipcdev.acpi_io_base + SMI_EN_OFFSET;

    res->end = res->start + SMI_EN_SIZE - 1;

 

    res = tco_res + TCO_RESOURCE_GCR_MEM;        //这里添加内存资源

    res->start = ipcdev.gcr_mem_base;

    res->end = res->start + ipcdev.gcr_size - 1;

 

    ret = platform_device_add_resources(pdev, tco_res, ARRAY_SIZE(tco_res));

    if (ret) {

        dev_err(ipcdev.dev, "Failed to add tco platform resources\n");

        goto err;

    }

 

    ret = platform_device_add_data(pdev, &tco_info, sizeof(tco_info));

    if (ret) {

        dev_err(ipcdev.dev, "Failed to add tco platform data\n");

        goto err;

    }

 

    ret = platform_device_add(pdev);

    if (ret) {

        dev_err(ipcdev.dev, "Failed to add tco platform device\n");

        goto err;

    }

    ipcdev.tco_dev = pdev;

 

    return 0;

err:

    platform_device_put(pdev);

    return ret;

}

     我们可以看到这些资源都是通过一个基地址加上偏移后才赋给tco_res的,那我们上面在iTCO_wdt probe那段代码中获取失败的是一个内存资源IORESOURCE_MEM,在上面这段代码中我们可以看到内存资源是由ipcdev.gcr_base这样的一个基地址获取的,通过寻找发现下面这段代码赋予了这个值;

static int ipc_plat_get_res(struct platform_device *pdev)

{

    struct resource *res, *punit_res;

    void __iomem *addr;

    int size;

 

    res = platform_get_resource(pdev, IORESOURCE_IO,

                    PLAT_RESOURCE_ACPI_IO_INDEX);

    if (!res) {

        dev_err(&pdev->dev, "Failed to get io resource\n");

        return -ENXIO;

    }

    size = resource_size(res);

    ipcdev.acpi_io_base = res->start;

    ipcdev.acpi_io_size = size;

    dev_info(&pdev->dev, "io res: %pR\n", res);

 

    punit_res = punit_res_array;

    /* This is index 0 to cover BIOS data register */

    res = platform_get_resource(pdev, IORESOURCE_MEM,

                    PLAT_RESOURCE_BIOS_DATA_INDEX);

    if (!res) {

        dev_err(&pdev->dev, "Failed to get res of punit BIOS data\n");

        return -ENXIO;

    }

    *punit_res = *res;

    dev_info(&pdev->dev, "punit BIOS data res: %pR\n", res);

 

    /* This is index 1 to cover BIOS interface register */

    res = platform_get_resource(pdev, IORESOURCE_MEM,

                    PLAT_RESOURCE_BIOS_IFACE_INDEX);

    if (!res) {

        dev_err(&pdev->dev, "Failed to get res of punit BIOS iface\n");

        return -ENXIO;

    }

    *++punit_res = *res;

    dev_info(&pdev->dev, "punit BIOS interface res: %pR\n", res);

 

    /* This is index 2 to cover ISP data register, optional */

    res = platform_get_resource(pdev, IORESOURCE_MEM,

                    PLAT_RESOURCE_ISP_DATA_INDEX);

    ++punit_res;

    if (res) {

        *punit_res = *res;

        dev_info(&pdev->dev, "punit ISP data res: %pR\n", res);

    }

 

    /* This is index 3 to cover ISP interface register, optional */

    res = platform_get_resource(pdev, IORESOURCE_MEM,

                    PLAT_RESOURCE_ISP_IFACE_INDEX);

    ++punit_res;

    if (res) {

        *punit_res = *res;

        dev_info(&pdev->dev, "punit ISP interface res: %pR\n", res);

    }

 

    /* This is index 4 to cover GTD data register, optional */

    res = platform_get_resource(pdev, IORESOURCE_MEM,

                    PLAT_RESOURCE_GTD_DATA_INDEX);

    ++punit_res;

    if (res) {

        *punit_res = *res;

        dev_info(&pdev->dev, "punit GTD data res: %pR\n", res);

    }

 

    /* This is index 5 to cover GTD interface register, optional */

    res = platform_get_resource(pdev, IORESOURCE_MEM,

                    PLAT_RESOURCE_GTD_IFACE_INDEX);

    ++punit_res;

    if (res) {

        *punit_res = *res;

        dev_info(&pdev->dev, "punit GTD interface res: %pR\n", res);

    }

 

    res = platform_get_resource(pdev, IORESOURCE_MEM,

                    PLAT_RESOURCE_IPC_INDEX);

    if (!res) {

        dev_err(&pdev->dev, "Failed to get ipc resource\n");

        return -ENXIO;

    }

    size = PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE;     //这里内存分配错误

    res->end = res->start + size - 1;

 

    addr = devm_ioremap_resource(&pdev->dev, res);

    if (IS_ERR(addr))

        return PTR_ERR(addr);

 

    ipcdev.ipc_base = addr;

 

    ipcdev.gcr_mem_base = addr + PLAT_RESOURCE_GCR_OFFSET;     //这里获得内存地址

    dev_info(&pdev->dev, "ipc res: %pR\n", res);

 

    ipcdev.telem_res_inval = 0;

    res = platform_get_resource(pdev, IORESOURCE_MEM,

                    PLAT_RESOURCE_TELEM_SSRAM_INDEX);

    if (!res) {

        dev_err(&pdev->dev, "Failed to get telemetry ssram resource\n");

        ipcdev.telem_res_inval = 1;

    else {

        ipcdev.telem_punit_ssram_base = res->start +

                        TELEM_PUNIT_SSRAM_OFFSET;

        ipcdev.telem_punit_ssram_size = TELEM_SSRAM_SIZE;

        ipcdev.telem_pmc_ssram_base = res->start +

                        TELEM_PMC_SSRAM_OFFSET;

        ipcdev.telem_pmc_ssram_size = TELEM_SSRAM_SIZE;

        dev_info(&pdev->dev, "telemetry ssram res: %pR\n", res);

    }

 

    return 0;

}

     通过阅读代码发现size = PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE; 这个给ipc块的内存给大了,器件手册559811-apl-iafw-vol2of2-bios-spec-rev2p1.pdf(详见附件)上写的这段内存为4k字节。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~先略过这里,最后再看~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  (PS: 如果修改此处size会造成s0ix_residencu_usec异常,因为intel_pmc_s0ix_counter_read()中读写GCR寄存器是从这里申请和ioremap的,如果修改那么会造成gcr_data_readq()、gcr_data_readq()读写溢出。可见相关内核作者们做patch的时候也是考虑不周,挖下了深坑。如果想彻底修改好,应该直接把这里的addr地址加上1008h偏移传给iTCO_wdt.c中iTCO_wdt_probe()的iTCO_wdt_private.gcs_pmc,不需要再经过iTCO_wdt_probe()中的request_mem_region()和ioremap()即可使用。)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

PMC is a PCI device with B/D/F = 0/13/1 and this device represents address space for IPC1/GCR/ACPI block. The PMC function contains three BARs:

  1. BAR0 (8KB) – Lower 4KB for IPC1, upper 4KB for GCR (GCR BAR aka PBASE).
  2. BAR1 (4KB) – PCI configuration space aliased as MMIO in ACPI mode.
  3. BAR2 (I/O 128B) – ACPI configuration. After programming these BARs, IA FW should configure the PMC function as ACPI function.

     宏定义定义了这两个块的大小;

#define PLAT_RESOURCE_IPC_SIZE      0x1000    //4k区域

#define PLAT_RESOURCE_GCR_OFFSET    0x1000

#define PLAT_RESOURCE_GCR_SIZE      0x1000

     所以这就把本应该属于GCR的内存区域占用了,才会导致tco_res获取的内存资源在iTCO_wdt 的probe中request_mem_region时候失败。我们把这里修改后probe过程就能够正常了。但是阅读代码发现iTCO_wdt probe中iTCO_wdt_private.gcs_pmc_res获取的应该是GCR区域的PM CFG - Power Management Configuration (PMC_CFG)—Offset 1008h寄存器(详见附件557556-apl-soc-eds-vol2-rev2p4.pdf) ,但是这里却把整个GCR的内存资源给了他,这之后肯定会由于读写的寄存器错误而导致功能不正常的,所以在static int ipc_create_tco_device(void)原有的地址上加上偏移并修改区域大小再传给iTCO_wdt_private.gcs_pmc_res。

res = tco_res + TCO_RESOURCE_GCR_MEM;

res->start = ipcdev.gcr_base + PMC_GCR_PMC_CFG_REG;       // PMC_GCR_PMC_CFG_REG = 0x08   

res->end = res->start + PMC_GCR_PMC_CFG_REG_SIZE - 1;     //PMC_GCR_PMC_CFG_REG_SIZE = 4

     修改过后,对应寄存器的地址就都能正确对应上了。不仅块地址分配错误,还有一些寄存器的偏移地址也是错误的,所以在修改驱动的时候一定要对照器件手册,把每个寄存器和io都设置正确才能确保驱动正常运行。

      那么肯定有小伙伴好奇了,ipc_plat_get_res(struct platform_device *pdev)中的platform_get_resource又是从哪里获取的资源呢???长春哥给出的建议是dts,在ARM平台中大家第一时间就会想到.dts 也就是设备树,那么在x86平台中不使用.dts我们该去哪里寻找呢??报着这个疑问在听取侠松哥的建议后开始在bios中寻找,又经过了一轮艰苦卓绝的寻找,终于在coreboot中找了定义的位置了。在coreboot/src/soc/intel/apollolake/include/soc/iomap.h中定义了该PCM块的基地址:

iomap.h

/* Accesses to these BARs are hardcoded in FSP */

#define PMC_BAR0                        0xfe042000

#define PMC_BAR1                        0xfe044000

#define PMC_BAR0_SIZE                   (8 * KiB)

 

#define SRAM_BASE_0                     0xfe900000

#define SRAM_SIZE_0                     (8 * KiB)

#define SRAM_BASE_2                     0xfe902000

#define SRAM_SIZE_2                     (4 * KiB)

    并在src/soc/intel/apollolake/pmc.c中使用了这个定义添加了相应资源:

pmc.c

/ * The ACPI IO BAR (offset 0x20) is not PCI compliant. We've observed cases

 * where the BAR reads back as 0, but the IO window is open. This also means

 * that it will not respond to PCI probing. In the event that probing the BAR

 * fails, we still need to create a resource for it.

 */

static void read_resources(device_t dev)

{

        struct resource *res;

        pci_dev_read_resources(dev);

 

        res = new_resource(dev, PCI_BASE_ADDRESS_0);

        res->base = PMC_BAR0;

        res->size = PMC_BAR0_SIZE;

        res->flags = IORESOURCE_MEM | IORESOURCE_ASSIGNED | IORESOURCE_FIXED;

 

        res = new_resource(dev, PCI_BASE_ADDRESS_4);

        res->base = ACPI_BASE_ADDRESS;

        res->size = ACPI_BASE_SIZE;

        res->flags = IORESOURCE_IO | IORESOURCE_ASSIGNED | IORESOURCE_FIXED;

}

ps:所以小伙伴们在找不到相关资源定义的时候不妨去bios中去寻找一下,没准会有什么新发现。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

     经过以上修改再核对iTCO_wdt.c中对对应io和内存资源的使用确认软件逻辑上已经没有什么问题了,我们开开心心的去测试 使用cat > /dev/watchdog 连敲两次回车就会触发我们的狗启动,一旦启动没办法停止,只有不断喂狗才能保障系统不会重启,一旦停止喂狗系统将在设定的heartbeat值计数完成后重启。但是通过测试发现当出发后设定时间内并没有重启,而是有几率的,并且时间不定。那么问题又来了,是软件驱动仍然有问题还是什么???通过加log看相关寄存器的状态发现有一个寄存器并没有如期置1,log如下:

 展开源码

    可以看到当我们的剩余时间快为0的时候计数开始从4到1不断循环了不会到0,并且TCO_STS[17]寄存器读出来的值一致都为0,从器件手册(详见附件557556-apl-soc-eds-vol2-rev2p4.pdf)中的TCO_STS寄存器表中看到:

Second Timeout Status (second_to_sts): PMC sets this bit to 1 to indicate that the TIMEOUT bit had been (or is currently) set and a second timeout occurred before the TCO_RLD register was written. If this bit is set and the NO_REBOOT config bit is 0, then the PMC will reboot the system after the second timeout. The reboot is done by interrupting the Arc and starting a reset flow based on the OS_POLICY. This bit is only cleared by writing a 1 to this bit or by a reset.  On some prior platforms, this field is reset on RSMRST_B, a reset signal based on a RSMRST# pin that indicates the suspend/ resume voltages are stable. This field is reset on RSM_RST_N de-assertion. This field is not reset on cold reset, warm reset, and Sx.

    TCO_STS[17]这一位会被RSM_RST_N脚置零,所以系统不会重启。那么这位一直被置零的话岂不是永远不会重启了???所以就去看看RSM_RST_N脚的变换条件,通过器件手册pentium-celeron-n-series-j-series-datasheet-vol-1.pdf 找到了这个脚的定义:

signal

Dir.

I/O

Voltage

Type

RSM_RST_NIV3P3

Resume Well Reset

Used for resetting the res well. An external RC circuit is required to guarantee that the resume well power is vaild prior to this signal going high.

     既然要用RC电路来保障他稳定没有毛刺,那么就顺便去看看他的电路图吧,我们先看看这个脚的编号,在器件手册pentium-celeron-n-series-j-series-datasheet-vol-1.pdf 的Table 6-1中可以看到此脚编号为AC57。那么我们去开发设备那看看对应的电路;

 震惊震惊震惊!!!器件手册中说好的是输入脚但是在硬件原理图中变成了输出了,所以此脚的电平就是个未知的东西了,那么指望他去决定TCO_STS[17]的状态不就成了奢望了吗?  所以我们的看门狗会启动后不知道什么时候才会重启的原因就是RSM_RST_N的状态有问题!!   至此我们找到了iTCO_wdt 从内核驱动代码问题到硬件配置问题的一系列坑,由于硬件电路我们不能修改,即使我们的软件毫无问题,那么这个狗的工作也是不正常的,所以至此只能放弃这条狗了,不能让他活着时不时不受控的重启系统。

      ps:理论上应该还要把Q80 NMOS拆下然后进行功能测试。

附件:559811-apl-iafw-vol2of2-bios-spec-rev2p1.pdf

557556-apl-soc-eds-vol2-rev2p4.pdf

pentium-celeron-n-series-j-series-datasheet-vol-1.pdf

本文链接:https://my.lmcjl.com/post/8801.html

展开阅读全文

4 评论

留下您的评论.