1.说明
本节以x86-power-control/src/power_control.cpp
为基础,分析整个GPIO
的调用流程,实现简单的ipmitool power on/off
,ipmitool power status
的管理。
- 1.资源:
x86-power-control
:https://github.com/openbmc/x86-power-control - 2.相关文件:
meta-phosphor/recipes-x86/chassis/x86-power-control_git.bb
- 3.本节涉及到的代码可以参考: https://gitee.com/wit_yuan/sdk_v09.01_ast2600/blob/yuan_support_power_status
备注:
为了加速编译过程,在文件build/ast2600-default/conf/local.conf
中添加参数:
BB_NUMBER_THREADS ='16'
PARALLEL_MAKE ='-j 4'
BB_NO_NETWORK="1"
2.分析x86-power-control
在文件:power_control.cpp
中可以看到调用流程:
build/ast2600-default/workspace/sources/x86-power-control/src/power_control.cpp
main()
---> loadConfigValues()
---> 读取文件: "/usr/share/x86-power-control/power-config-host" + power_control::node + ".json"
---> 读取 tempGpioData->lineName = gpioConfig["LineName"];
---> // Request PS_PWROK GPIO events
---> requestGPIOEvents(powerOkConfig.lineName, psPowerOKHandler,psPowerOKLine, psPowerOKEvent)
---> gpioLine = gpiod::find_line(name);
---> gpioLine.request({appName, gpiod::line_request::EVENT_BOTH_EDGES, {}});
因此,需要关注2个函数:
gpioLine = gpiod::find_line(name)
gpioLine.request(...)
3.分析libgpiod
下载好libgpiod
库,实际跟踪文件:build/ast2600-default/workspace/sources/libgpiod/bindings/cxx/line.cpp
中的函数:
line find_line(const ::std::string& name)
{
line ret;
for (auto& it: make_chip_iter()) {
ret = it.find_line(name);
if (ret)
break;
}
return ret;
}
调用流程为:
build/ast2600-default/workspace/sources/libgpiod/bindings/cxx/line.cpp
line find_line(const ::std::string& name)
---> for (auto& it: make_chip_iter()) {
---> ret = it.find_line(name); // = line chip::find_line(const ::std::string& name) const
---> ::gpiod_line* handle = ::gpiod_chip_find_line(this->_m_chip.get(), name.c_str());
---> return handle ? line(handle, *this) : line();
build/ast2600-default/workspace/sources/libgpiod/bindings/cxx/iter.cpp
chip_iter make_chip_iter(void)
---> ::gpiod_chip_iter* iter = ::gpiod_chip_iter_new();
---> return chip_iter(iter);
chip_iter::chip_iter(::gpiod_chip_iter *iter) : _m_iter(iter, chip_iter_deleter)
---> ::gpiod_chip* first = ::gpiod_chip_iter_next_noclose(this->_m_iter.get());
---> this->_m_current = chip(first);
build/ast2600-default/workspace/sources/libgpiod/lib/iter.c
struct gpiod_chip_iter *gpiod_chip_iter_new(void)
---> num_chips = scandir("/dev", &dirs, dir_filter, alphasort);
---> iter->num_chips = num_chips;
---> iter->offset = 0;
---> iter->chips[i] = gpiod_chip_open_by_name(dirs[i]->d_name);
根据上面的内容,实际上C++
调用到C
,有几个函数:
build/ast2600-default/workspace/sources/libgpiod/lib/helpers.c
struct gpiod_line *gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
---> iter = gpiod_line_iter_new(chip);
---> iter->num_lines = gpiod_chip_num_lines(chip);
---> iter->lines[i] = gpiod_chip_get_line(chip, i);
build/ast2600-default/workspace/sources/libgpiod/lib/core.c
---> gpiod_line_update(line); //!!!!重要!!!
---> struct gpioline_info info;
---> rv = ioctl(line->chip->fd, GPIO_GET_LINEINFO_IOCTL, &info);
---> strncpy(line->name, info.name, sizeof(line->name));
---> strncpy(line->consumer, info.consumer, sizeof(line->consumer));
---> gpiod_foreach_line(iter, line) {
---> tmp = gpiod_line_name(line);
---> if (tmp && strcmp(tmp, name) == 0) { //找到了 line.
...
---> ...
注意留意函数:gpiod_line_update()
里面使用了内核结构体:
build/ast2600-default/workspace/sources/linux-aspeed/include/uapi/linux/gpio.h
struct gpioline_info {
__u32 line_offset;
__u32 flags;
char name[GPIO_MAX_NAME_SIZE];
char consumer[GPIO_MAX_NAME_SIZE];
};
应用层用到了line->name
. 这个line_name
又是从驱动/内核传递而来。应用层在build/ast2600-default/workspace/sources/x86-power-control/config/power-config-host0.json
中定义了一个!!LineName!!
.
函数gpiod_chip_open_by_name()
:
build/ast2600-default/workspace/sources/libgpiod/lib/helpers.c
struct gpiod_chip *gpiod_chip_open_by_name(const char *name)
---> rv = asprintf(&path, "/dev/%s", name);
---> chip = gpiod_chip_open(path);
函数gpiod_chip_open()
调用的是:
build/ast2600-default/workspace/sources/libgpiod/lib/core.c
struct gpiod_chip *gpiod_chip_open(const char *path)
---> fd = open(path, O_RDWR | O_CLOEXEC);
---> if (!is_gpiochip_cdev(path)) goto err_close_fd;
---> rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
---> chip->fd = fd;
---> chip->num_lines = info.lines;
---> strncpy(chip->name, info.name, sizeof(chip->name));
---> ...
根据ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info)
需要找到内核层。
4.分析内核层
4.1 分析dts
当前文件build/ast2600-default/workspace/sources/linux-aspeed/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
中写的gpio
为:
gpio0: gpio@1e780000 {
#gpio-cells = <2>;
gpio-controller;
compatible = "aspeed,ast2600-gpio";
reg = <0x1e780000 0x400>;
interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
gpio-ranges = <&pinctrl 0 0 208>;
ngpios = <208>;
clocks = <&syscon ASPEED_CLK_APB2>;
interrupt-controller;
#interrupt-cells = <2>;
};
根据aspeed,ast2600-gpio
可以定位到文件build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpio-aspeed.c
.
4.2 gpio
驱动
4.2.1 gpiolib.c
文件:build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib.c
作为实际的入口调用,可以看到函数gpiolib_dev_init()
:
static int __init gpiolib_dev_init(void)
{
...
---> ret = bus_register(&gpio_bus_type);
---> ret = driver_register(&gpio_stub_drv);
---> ret = alloc_chrdev_region(&gpio_devt, 0, GPIO_DEV_MAX, GPIOCHIP_NAME);
---> gpiochip_setup_devs();
}
core_initcall(gpiolib_dev_init);
以及函数static void gpiochip_setup_devs(void)
static void gpiochip_setup_devs(void)
---> ret = gpiochip_setup_dev(gdev);
函数gpiochip_setup_dev()
:
static int gpiochip_setup_dev(struct gpio_device *gdev)
---> ret = gcdev_register(gdev, gpio_devt);
---> ret = gpiochip_sysfs_register(gdev);
比较重要的是一个宏定义:
#ifdef CONFIG_GPIO_CDEV
#define gcdev_register(gdev, devt) gpiolib_cdev_register((gdev), (devt))
#define gcdev_unregister(gdev) gpiolib_cdev_unregister((gdev))
#else
/*
* gpiolib_cdev_register() indirectly calls device_add(), which is still
* required even when cdev is not selected.
*/
#define gcdev_register(gdev, devt) device_add(&(gdev)->dev)
#define gcdev_unregister(gdev) device_del(&(gdev)->dev)
#endif
根据该宏定义定义的函数gpiolib_cdev_register()
,实际就跳到文件:build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib-cdev.c
中。
4.2.2 gpiolib-cdev.c
文件build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib-cdev.c
作为整个gpio
字符驱动的核心入口.
函数gpiolib_cdev_register()
:
int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt)
---> cdev_init(&gdev->chrdev, &gpio_fileops);
结构体gpio_fileops
定义如下:
static const struct file_operations gpio_fileops = {
.release = gpio_chrdev_release,
.open = gpio_chrdev_open,
.poll = lineinfo_watch_poll,
.read = lineinfo_watch_read,
.owner = THIS_MODULE,
.llseek = no_llseek,
.unlocked_ioctl = gpio_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = gpio_ioctl_compat,
#endif
};
对于gpio_ioctl
:
static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
---> call_ioctl_locked(file, cmd, arg, cdev->gdev,gpio_ioctl_unlocked);
实际gpio_ioctl_unlocked()
:
static long gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
---> case GPIO_GET_CHIPINFO_IOCTL:
---> return chipinfo_get(cdev, ip);
这就是前面函数struct gpiod_chip *gpiod_chip_open(const char *path)
实际调用的内核函数。
另外,函数chipinfo_get()
内容如:
static int chipinfo_get(struct gpio_chardev_data *cdev, void __user *ip)
---> strscpy(chipinfo.name, dev_name(&gdev->dev), sizeof(chipinfo.name));
---> strscpy(chipinfo.label, gdev->label, sizeof(chipinfo.label));
---> chipinfo.lines = gdev->ngpio;
---> copy_to_user(ip, &chipinfo, sizeof(chipinfo))
如此,可以获取到gpio chip
包含的gpio line
数量。
4.2.3 gpio-aspeed.c
在dts
中配置了:compatible = "aspeed,ast2600-gpio";
之后,会经过一系列流程之后,调用到文件:
build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpio-aspeed.c
中的static int __init aspeed_gpio_probe(struct platform_device *pdev)
:
static int __init aspeed_gpio_probe(struct platform_device *pdev)
---> err = of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpio);
---> rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
注意,devm_gpiochip_add_data()
在文件:build/ast2600-default/workspace/sources/linux-aspeed/include/linux/gpio/driver.h
中定义,定义如:
#ifdef CONFIG_LOCKDEP
#define gpiochip_add_data(gc, data) ({ \
static struct lock_class_key lock_key; \
static struct lock_class_key request_key; \
gpiochip_add_data_with_key(gc, data, &lock_key, \
&request_key); \
})
#define devm_gpiochip_add_data(dev, gc, data) ({ \
static struct lock_class_key lock_key; \
static struct lock_class_key request_key; \
devm_gpiochip_add_data_with_key(dev, gc, data, &lock_key, \
&request_key); \
})
#else
#define gpiochip_add_data(gc, data) gpiochip_add_data_with_key(gc, data, NULL, NULL)
#define devm_gpiochip_add_data(dev, gc, data) \
devm_gpiochip_add_data_with_key(dev, gc, data, NULL, NULL)
#endif /* CONFIG_LOCKDEP */
只需要关注函数devm_gpiochip_add_data_with_key()
:
build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib-devres.c
devm_gpiochip_add_data_with_key()
build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib.c
---> gpiochip_add_data_with_key(gc, data, lock_key, request_key);
---> ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id);
---> ret = gpiochip_get_ngpios(gc, &gdev->dev);
---> ret = gpiochip_set_desc_names(gc);
---> ret = gpiochip_set_names(gc);
---> count = device_property_string_array_count(dev, "gpio-line-names");
---> ret = device_property_read_string_array(dev, "gpio-line-names",...
---> gdev->descs[i].name = names[chip->offset + i];
---> devm_add_action_or_reset(dev, devm_gpio_chip_release, gc);
对于应用层调用ioctl(line->chip->fd, GPIO_GET_LINEINFO_IOCTL, &info);
,流程如:
build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib-cdev.c
static long gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
---> case GPIO_GET_LINEINFO_IOCTL:
---> return lineinfo_get_v1(cdev, ip, false);
---> struct gpio_desc *desc;
---> desc = gpiochip_get_desc(cdev->gdev->chip, lineinfo.line_offset);
---> gpio_desc_to_lineinfo(desc, &lineinfo_v2);
//struct gpio_desc *desc
---> info->offset = gpio_chip_hwgpio(desc);
---> if (desc->name) strscpy(info->name, desc->name, sizeof(info->name));
---> gpio_v2_line_info_to_v1(&lineinfo_v2, &lineinfo);
---> memcpy(info_v1->name, info_v2->name, sizeof(info_v1->name));
---> memcpy(info_v1->consumer, info_v2->consumer, sizeof(info_v1->consumer));
---> copy_to_user(ip, &lineinfo, sizeof(lineinfo)
从以上代码可以看出,应用层用的是LineName
, 驱动用的是:gpio-line-names
。因此,适配的时候,注意将这2个名字匹配好。
因此,文件build/ast2600-default/workspace/sources/x86-power-control/config/power-config-host0.json
中定义如下:
"gpio_configs": [
{
"Name": "IdButton",
"LineName": "ID_BUTTON",
"Type": "GPIO",
"Polarity": "ActiveLow"
},
{
"Name": "PowerOk",
"LineName": "PS_PWROK",
"Type": "GPIO",
"Polarity": "ActiveLow"
},
{
"Name": "PowerOut",
"LineName": "POWER_BUTTON",
"Type": "GPIO",
"Polarity": "ActiveLow"
}
],
对应,文件build/ast2600-default/workspace/sources/linux-aspeed/arch/arm/boot/dts/aspeed/aspeed-ast2600-evb.dts
内容定义如下:
&gpio0 {
status = "okay";
gpio-line-names =
/*A0~A7*/"A0","A1","A2","","","","ID_BUTTON","",
/*B0~B7*/"POWER_BUTTON","B1","B2","","","","","",
/*C0~C7*/"C0","C1","C2","","","","","",
/*D0~D7*/"D0","D1","D2","","","","","",
/*E0~E7*/"E0","E1","E2","","","","","",
/*F0~F7*/"F0","F1","PS_PWROK","","","","","",
/*G0~G7*/"G0","G1","G2","","","","","",
/*H0~H7*/"H0","","","","","","","",
/*I0~7*/"I0","","I2","","","","","",
/*J0~7*/"J0","J1","","","","","","",
/*K0~7*/"K0","K1","","","","","","",
/*L0~7*/"","","","","","","","",
/*M0~7*/"","","","","","","","",
/*N0~7*/"","","","","","","","",
/*O0~7*/"","","","","","","","",
/*P0~7*/"","","","","","","","",
/*Q0~7*/"","","","","","","","",
/*R0~7*/"","","","","","","","",
/*S0~7*/"","","","","","","","",
/*T0~7*/"","","","","","","","",
/*U0~7*/"","","","","","","","",
/*V0~7*/"","","","","","","","",
/*W0~7*/"","","","","","","","",
/*X0~7*/"","","","","","","","",
/*Y0~7*/"","","","","","","","",
/*Z0~7*/"","","","","","","","";
};
注意,将gpio-line-names
的数量控制在208
或者以内即可。
5.适配power status,power on/off
5.1 修改dts
在适配中,发现在文件:build/ast2600-default/workspace/sources/x86-power-control/src/power_control.cpp
中报错,报错点为:gpioLine.request({appName, gpiod::line_request::EVENT_BOTH_EDGES, {}});
,报错内容为:
# journalctl | grep power
Failed to request events for PS_PWROK: error requesting GPIO lines: Invalid argument
实际追踪下来,发现具体是在文件build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib-cdev.c
中的函数lineevent_create()
报错,具体点为函数gpiod_request_user()
:
build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib-cdev.c
static int lineevent_create(struct gpio_device *gdev, void __user *ip)
---> copy_from_user(&eventreq, ip, sizeof(eventreq))
---> desc = gpiochip_get_desc(gdev->chip, offset);
---> ret = gpiod_request_user(desc, le->label);
其中,函数gpiod_request_user()
定义如下:
build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib.h
static inline int gpiod_request_user(struct gpio_desc *desc, const char *label)
---> ret = gpiod_request(desc, label);
其中,函数gpiod_request()
定义如下:
build/ast2600-default/workspace/sources/linux-aspeed/drivers/gpio/gpiolib.c
int gpiod_request(struct gpio_desc *desc, const char *label)
---> int ret = -EPROBE_DEFER;
---> if (try_module_get(desc->gdev->owner)) {
---> ret = gpiod_request_commit(desc, label);
---> if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) {
---> desc_set_label(desc, label ? : "?");
else
---> ret = -EBUSY;
---> goto out_free_unlock;
---> }
---> if (gc->request) {
---> if (gpiochip_line_is_valid(gc, offset))
---> ret = gc->request(gc, offset); //aspeed_gpio_request
---> else
----> ...
--->}
---> }
再次查看BMC
日志,发现如下内容:
[ 72.729136] aspeed-g6-pinctrl 1e6e2000.syscon:pinctrl: pin D23 already requested by 1e740100.sdhci; cannot claim for 1e780000.gpio:554
[ 72.742714] aspeed-g6-pinctrl 1e6e2000.syscon:pinctrl: pin-42 (1e780000.gpio:554) status -22
对比build/ast2600-default/workspace/sources/linux-aspeed/arch/arm/boot/dts/aspeed/aspeed-ast2600-evb.dts
文件,发现是GPIO
被占用了。
以上,设置完成之后,通过ipmitool power status
查看系统状态:
# ipmitool power status
Chassis Power is on