目录
2.2.2.1 xgene_hwmon_probe(struct platform_device *pdev)
2.2.2.2 pcc_mbox_request_channel()
2.2.3.1 xgene_hwmon_get_cpu_pwr()
2.2.3.2 xgene_hwmon_get_io_pwr()
2.2.3.3 xgene_hwmon_get_temp()
3.1.2 Device/Driver Binding log
1. Linux Command - Sensors
1.1 Example
1.1.1 x86 desktop
- x86 desktop:
# sensors
coretemp-isa-0000
Adapter: ISA adapter
Package id 0: +61.0°C (high = +84.0°C, crit = +100.0°C)
Core 0: +59.0°C (high = +84.0°C, crit = +100.0°C)
Core 1: +60.0°C (high = +84.0°C, crit = +100.0°C)
Core 2: +61.0°C (high = +84.0°C, crit = +100.0°C)
Core 3: +61.0°C (high = +84.0°C, crit = +100.0°C)
Core 4: +60.0°C (high = +84.0°C, crit = +100.0°C)
Core 5: +60.0°C (high = +84.0°C, crit = +100.0°C)dell_smm-isa-0000
Adapter: ISA adapter
fan1: 2353 RPMacpitz-acpi-0
Adapter: ACPI interface
temp1: +27.8°C (crit = +119.0°C)nvme-pci-0100
Adapter: PCI adapter
Composite: +46.9°C (low = -0.1°C, high = +81.8°C)
(crit = +85.8°C)
1.1.2 ARM server
- ARM server
#sensorsnvme-pci-10300
Adapter: PCI adapter
Composite: +28.9°C (low = -0.1°C, high = +69.8°C)
(crit = +79.8°C)apm_xgene-isa-0000
Adapter: ISA adapter
SoC Temperature: +42.0°C
CPU power: 37.27 W
IO power: 61.75 Wnvme-pci-10100
Adapter: PCI adapter
Composite: +25.9°C (low = -273.1°C, high = +76.8°C)
(crit = +84.8°C)
Sensor 1: +25.9°C (low = -273.1°C, high = +65261.8°C)
Sensor 2: +46.9°C (low = -273.1°C, high = +65261.8°C)apm_xgene-isa-0000
Adapter: ISA adapter
SoC Temperature: +55.0°C
CPU power: 39.50 W
IO power: 62.89 W
1.2 How to install
- Fedora 38
-
sudo dnf install lm_sensors
-
- Ubuntu 20.04
- sudo apt install lm-sensors
2. Source code
hwmon 是用来监控Processor 本身信息, 用来获取 CPU power, CPU temp, IO Power, 通过PCC & mailbox 跟 CPU 内部监控单元交互。
涉及到几个知识点
1. PCCT table, PCC channel - TBD
2. Mailbox 通信 - TBD
2.1 Sensors
TBD
2.2 Linux hwmon driver
2.2.1 Definition
2.2.1.1 struct acpi_pcct_shared_memory {}
struct acpi_pcct_shared_memory {
u32 signature;
u16 command;
u16 status;
// u32 msg[0];
// u32 msg[1];
// u32 msg[2];
// u32 msg[3];
};
2.2.2 xgene_hwmon_driver {}
#ifdef CONFIG_ACPI
static const struct acpi_device_id xgene_hwmon_acpi_match[] = {
{"APMC0D29", XGENE_HWMON_V1},
{"APMC0D8A", XGENE_HWMON_V2},
{},
};
MODULE_DEVICE_TABLE(acpi, xgene_hwmon_acpi_match);
#endif
static const struct of_device_id xgene_hwmon_of_match[] = {
{.compatible = "apm,xgene-slimpro-hwmon"},
{}
};
MODULE_DEVICE_TABLE(of, xgene_hwmon_of_match);
static struct platform_driver xgene_hwmon_driver = {
.probe = xgene_hwmon_probe,
.remove = xgene_hwmon_remove,
.driver = {
.name = "xgene-slimpro-hwmon",
.of_match_table = xgene_hwmon_of_match,
.acpi_match_table = ACPI_PTR(xgene_hwmon_acpi_match),
},
};
module_platform_driver(xgene_hwmon_driver);
MODULE_DESCRIPTION("APM X-Gene SoC hardware monitor");
MODULE_LICENSE("GPL");
2.2.2.1 xgene_hwmon_probe(struct platform_device *pdev)
1. 设备驱动进行绑定
2. 申请 PCC channel & mbox channel 进行通信, PCC shared region 是一块共享内存
映射内存用户读写操作
static int xgene_hwmon_probe(struct platform_device *pdev)
{
struct xgene_hwmon_dev *ctx;
struct mbox_client *cl;
int rc;
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->dev = &pdev->dev;
platform_set_drvdata(pdev, ctx);
cl = &ctx->mbox_client;
spin_lock_init(&ctx->kfifo_lock);
mutex_init(&ctx->rd_mutex);
rc = kfifo_alloc(&ctx->async_msg_fifo, sizeof(struct slimpro_resp_msg) *
SYNC_MSG_FIFO_SIZE, GFP_KERNEL);
if (rc)
return -ENOMEM;
INIT_WORK(&ctx->workq, xgene_hwmon_evt_work);
/* Request mailbox channel */
cl->dev = &pdev->dev;
cl->tx_done = xgene_hwmon_tx_done;
cl->tx_block = false;
cl->tx_tout = MBOX_OP_TIMEOUTMS;
cl->knows_txdone = false;
if (acpi_disabled) {
cl->rx_callback = xgene_hwmon_rx_cb;
ctx->mbox_chan = mbox_request_channel(cl, 0);
} else {
struct pcc_mbox_chan *pcc_chan;
const struct acpi_device_id *acpi_id;
int version;
acpi_id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev);
if (!acpi_id) {
rc = -EINVAL;
goto out_mbox_free;
}
version = (int)acpi_id->driver_data;
if (device_property_read_u32(&pdev->dev, "pcc-channel", &ctx->mbox_idx)) {
dev_err(&pdev->dev, "no pcc-channel property\n");
rc = -ENODEV;
goto out_mbox_free;
}
cl->rx_callback = xgene_hwmon_pcc_rx_cb;
pcc_chan = pcc_mbox_request_channel(cl, ctx->mbox_idx);
ctx->pcc_chan = pcc_chan;
ctx->mbox_chan = pcc_chan->mchan;
...
/*
* This is the shared communication region
* for the OS and Platform to communicate over.
*/
ctx->comm_base_addr = pcc_chan->shmem_base_addr;
if (ctx->comm_base_addr) {
if (version == XGENE_HWMON_V2)
ctx->pcc_comm_addr = (void __force *)devm_ioremap(&pdev->dev,
ctx->comm_base_addr, pcc_chan->shmem_size);
else
ctx->pcc_comm_addr = devm_memremap(&pdev->dev,
ctx->comm_base_addr, pcc_chan->shmem_size, MEMREMAP_WB);
} else {
dev_err(&pdev->dev, "Failed to get PCC comm region\n");
rc = -ENODEV;
goto out;
}
/*
* pcc_chan->latency is just a Nominal value. In reality
* the remote processor could be much slower to reply.
* So add an arbitrary amount of wait on top of Nominal.
*/
ctx->usecs_lat = PCC_NUM_RETRIES * pcc_chan->latency;
}
ctx->hwmon_dev = hwmon_device_register_with_groups(ctx->dev,
"apm_xgene", ctx, xgene_hwmon_groups);
/*
* Schedule the bottom handler if there is a pending message.
*/
schedule_work(&ctx->workq);
dev_info(&pdev->dev, "APM X-Gene SoC HW monitor driver registered\n");
return 0;
out:
if (acpi_disabled)
mbox_free_channel(ctx->mbox_chan);
else
pcc_mbox_free_channel(ctx->pcc_chan);
out_mbox_free:
kfifo_free(&ctx->async_msg_fifo);
return rc;
}
2.2.2.2 pcc_mbox_request_channel()
2.2.3 Operations
//drivers\hwmon\xgene-hwmon.c
#define DBG_SUBTYPE_SENSOR_READ 4
#define SENSOR_RD_MSG 0x04FFE902
#define SENSOR_RD_EN_ADDR(a) ((a) & 0x000FFFFF)
#define PMD_PWR_REG 0x20
#define PMD_PWR_MW_REG 0x26
#define SOC_PWR_REG 0x21
#define SOC_PWR_MW_REG 0x27
#define SOC_TEMP_REG 0x10
2.2.3.1 xgene_hwmon_get_cpu_pwr()
static int xgene_hwmon_get_cpu_pwr(struct xgene_hwmon_dev *ctx, u32 *val)
{
u32 watt, mwatt;
int rc;
rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_REG, &watt);
rc = xgene_hwmon_reg_map_rd(ctx, PMD_PWR_MW_REG, &mwatt);
*val = WATT_TO_mWATT(watt) + mwatt;
return 0;
}
static int xgene_hwmon_reg_map_rd(struct xgene_hwmon_dev *ctx, u32 addr,
u32 *data)
{
u32 msg[3];
int rc;
msg[0] = SENSOR_RD_MSG;
msg[1] = SENSOR_RD_EN_ADDR(addr);
msg[2] = 0;
if (acpi_disabled)
rc = xgene_hwmon_rd(ctx, msg);
else
rc = xgene_hwmon_pcc_rd(ctx, msg);
if (rc < 0)
return rc;
/*
* Check if sensor data is valid.
*/
if (msg[1] & SENSOR_INVALID_DATA)
return -ENODATA;
*data = msg[1];
return rc;
}
2.2.3.2 xgene_hwmon_get_io_pwr()
TBD
2.2.3.3 xgene_hwmon_get_temp()
TBD
2.2.4 xgene_hwmon_pcc_rd()
1. u32 *ptr = (void *)(generic_comm_base + 1): 指向payload(msg)
2. 配置acpi_pcct_shared_memory{ }头结构数据 的状态信息
3. WRITE_ONCE(ptr[i], cpu_to_le32(msg[i])): Copy 操作请求数据
4. mbox_send_message(ctx->mbox_chan, msg): Mailbox 发送数据
msg[0] = SENSOR_RD_MSG;
msg[1] = SENSOR_RD_EN_ADDR(addr);
msg[2] = 0;
5. wait_for_completion_timeout(&ctx->rd_complete:等待数据返回,任务挂起
5.1 但有数据返回的时候,xgene_hwmon_pcc_rx_cb() 会被调用,然后继续处理数据
static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg)
{
struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr;
u32 *ptr = (void *)(generic_comm_base + 1);
int rc, i;
u16 val;
mutex_lock(&ctx->rd_mutex);
init_completion(&ctx->rd_complete);
ctx->resp_pending = true;
/* Write signature for subspace */
WRITE_ONCE(generic_comm_base->signature,
cpu_to_le32(PCC_SIGNATURE_MASK | ctx->mbox_idx));
/* Write to the shared command region */
WRITE_ONCE(generic_comm_base->command,
cpu_to_le16(MSG_TYPE(msg[0]) | PCCC_GENERATE_DB_INT));
/* Flip CMD COMPLETE bit */
val = le16_to_cpu(READ_ONCE(generic_comm_base->status));
val &= ~PCCS_CMD_COMPLETE;
WRITE_ONCE(generic_comm_base->status, cpu_to_le16(val));
/* Copy the message to the PCC comm space */
for (i = 0; i < sizeof(struct slimpro_resp_msg) / 4; i++)
WRITE_ONCE(ptr[i], cpu_to_le32(msg[i]));
/* Ring the doorbell */
rc = mbox_send_message(ctx->mbox_chan, msg);
if (rc < 0) {
dev_err(ctx->dev, "Mailbox send error %d\n", rc);
goto err;
}
if (!wait_for_completion_timeout(&ctx->rd_complete,
usecs_to_jiffies(ctx->usecs_lat))) {
dev_err(ctx->dev, "Mailbox operation timed out\n");
rc = -ETIMEDOUT;
goto err;
}
/* Check for error message */
if (MSG_TYPE(ctx->sync_msg.msg) == MSG_TYPE_ERR) {
rc = -EINVAL;
goto err;
}
msg[0] = ctx->sync_msg.msg;
msg[1] = ctx->sync_msg.param1;
msg[2] = ctx->sync_msg.param2;
err:
mbox_chan_txdone(ctx->mbox_chan, 0);
ctx->resp_pending = false;
mutex_unlock(&ctx->rd_mutex);
return rc;
}
2.2.3 xgene_hwmon_pcc_rx_cb()
等待 mailbox 消息callback, 等有正确消息发来后, 会 complete(&ctx->rd_complete);
这样一个读操作就结束了。
static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg)
{
struct xgene_hwmon_dev *ctx = to_xgene_hwmon_dev(cl);
struct acpi_pcct_shared_memory *generic_comm_base = ctx->pcc_comm_addr;
struct slimpro_resp_msg amsg;
/*
* While the driver registers with the mailbox framework, an interrupt
* can be pending before the probe function completes its
* initialization. If such condition occurs, just queue up the message
* as the driver is not ready for servicing the callback.
*/
if (xgene_hwmon_rx_ready(ctx, &amsg) < 0)
return;
msg = generic_comm_base + 1;
/* Check if platform sends interrupt */
if (!xgene_word_tst_and_clr(&generic_comm_base->status, PCCS_SCI_DOORBEL))
return;
/*
* Response message format:
* msg[0] is the return code of the operation
* msg[1] is the first parameter word
* msg[2] is the second parameter word
* As message only supports dword size, just assign it.
*/
/* Check for sync query */
if (ctx->resp_pending &&
((MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_ERR) ||
(MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_DBG &&
MSG_SUBTYPE(((u32 *)msg)[0]) == DBG_SUBTYPE_SENSOR_READ) ||
(MSG_TYPE(((u32 *)msg)[0]) == MSG_TYPE_PWRMGMT &&
MSG_SUBTYPE(((u32 *)msg)[0]) == PWRMGMT_SUBTYPE_TPC &&
TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) {
/* Check if platform completes command */
if (xgene_word_tst_and_clr(&generic_comm_base->status,
PCCS_CMD_COMPLETE)) {
ctx->sync_msg.msg = ((u32 *)msg)[0];
ctx->sync_msg.param1 = ((u32 *)msg)[1];
ctx->sync_msg.param2 = ((u32 *)msg)[2];
/* Operation waiting for response */
complete(&ctx->rd_complete);
return;
}
}
/*
* Platform notifies interrupt to OSPM.
* OPSM schedules a consumer command to get this information
* in a workqueue. Platform must wait until OSPM has issued
* a consumer command that serves this notification.
*/
/* Enqueue to the FIFO */
kfifo_in_spinlocked(&ctx->async_msg_fifo, &amsg,
sizeof(struct slimpro_resp_msg), &ctx->kfifo_lock);
/* Schedule the bottom handler */
schedule_work(&ctx->workq);
}
2.3 Low level FW Summary
1. 初始化CPPC/PCC, 配置共享内存地址,注册 DB device 侦听请求
2. 收到host mailbox请求,判断是不是 PCC Message
3. 调用 hwmon_cb 处理请求, 根据 sensor_id (Payload[1])处理sensor 数据读取
4. 返回数据到 PCC share memory, 同时通知 host
3. Log
3.1 Boot Phase
3.1.1 Device register log
[ 0.435532] ACPI: Device [HM00] status [0000000f]
[ 0.435535] device: 'APMC0D8A:00': device_add, parent is ACPI0004:00
[ 0.435539] bus: 'acpi': add device APMC0D8A:00
[ 0.435543] PM: Adding info for acpi:APMC0D8A:00
3.1.2 Device/Driver Binding log
[ 126.202906] bus: 'platform': add driver xgene-slimpro-hwmon
[ 126.212860] bus: 'platform': __driver_probe_device: matched device APMC0D8A:00 with driver xgene-slimpro-hwmon
[ 126.222858] bus: 'platform': really_probe: probing driver xgene-slimpro-hwmon with device APMC0D8A:00
[ 126.232090] platform_probe(): APMC0D8A:00