众所周知我们无法在内核驱动程序中直接使用系统调用接口,但有时项目上会有类似需求,借助系统调用简单明了的接口设计,可以帮助我们在驱动程序中实现各种功能。我们可以利用内核提供的kallsyms_lookup_name机制(编译时需打开相关功能),配合set_fs系列函数实现在内核驱动程序中间接调用系统调用。
以下示例代码来源于一个实际的项目需求,af3840 sfm312子卡驱动程序中在将i2c adapter注册到i2c总线上时,需要为/dev/i2c-?建立一个软链接,公司原有实现方案主要是在用户态脚本里依次建立软链接,需要检测chanid到i2c bus号间的关联关系,有一定局限性。为此我引入了该项技术,直接在驱动程序里完成chanid到i2c节点间的映射。代码如下:
#include <linux/uaccess.h>
#include <linux/kallsyms.h>
... ...
int (*ori_sys_symlink)(const char *oldpath, const char *newpath);
static struct i2c_adapter *sfm312_i2c_add_mux_adapter(struct i2c_adapter *parent,
struct device *mux_dev,
void *mux_priv, u32 force_nr, u32 chan_id,
unsigned int class,
int (*select) (struct i2c_adapter *,
void *, u32),
int (*deselect) (struct i2c_adapter *,
void *, u32))
{
struct sfm312_i2c_mux_priv *priv;
char symlink_name[20], devpath[64], linkpath[64];
int ret;
mm_segment_t old_fs;
g_mux_dev_st = mux_dev;
priv = kzalloc(sizeof(struct sfm312_i2c_mux_priv), GFP_KERNEL);
if (!priv)
return NULL;
/* Set up private adapter data */
priv->parent = parent;
priv->mux_priv = mux_priv;
priv->chan_id = chan_id;
priv->select = select;
priv->deselect = deselect;
/* Need to do algo dynamically because we don't know ahead
* of time what sort of physical adapter we'll be dealing with.
*/
if (parent->algo->master_xfer)
priv->algo.master_xfer = sfm312_i2c_mux_master_xfer;
if (parent->algo->smbus_xfer)
priv->algo.smbus_xfer = sfm312_i2c_mux_smbus_xfer;
priv->algo.functionality = sfm312_i2c_mux_functionality;
/* Now fill out new adapter structure */
snprintf(priv->adap.name, sizeof(priv->adap.name), "i2c-%d-mux (chan_id %d)", i2c_adapter_id(parent), chan_id);
priv->adap.owner = THIS_MODULE;
priv->adap.algo = &priv->algo;
priv->adap.algo_data = priv;
priv->adap.dev.parent = &parent->dev;
priv->adap.retries = parent->retries;
priv->adap.timeout = parent->timeout;
/* Sanity check on class */
if (sfm312_i2c_mux_parent_classes(parent) & class)
dev_err(&parent->dev,
"Segment %d behind mux can't share classes with ancestors\n",
chan_id);
else
priv->adap.class = class;
/*
* Try to populate the mux adapter's of_node, expands to
* nothing if !CONFIG_OF.
*/
if (mux_dev->of_node) {
struct device_node *child;
u32 reg;
for_each_child_of_node(mux_dev->of_node, child) {
ret = of_property_read_u32(child, "reg", ®);
if (ret)
continue;
if (chan_id == reg) {
priv->adap.dev.of_node = child;
break;
}
}
}
if (force_nr) {
priv->adap.nr = force_nr;
ret = i2c_add_numbered_adapter(&priv->adap);
} else {
ret = i2c_add_adapter(&priv->adap);
}
if (ret < 0) {
dev_err(&parent->dev,
"failed to add mux-adapter (error=%d)\n",
ret);
kfree(priv);
return NULL;
}
WARN(sysfs_create_link(&priv->adap.dev.kobj, &mux_dev->kobj, "mux_device"),
"can't create symlink to mux device\n");
snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan_id);
WARN(sysfs_create_link(&mux_dev->kobj, &priv->adap.dev.kobj, symlink_name), "can't create symlink for channel %u\n", chan_id);
dev_info(&parent->dev, "Added multiplexed i2c bus %d\n", i2c_adapter_id(&priv->adap));
snprintf(devpath, sizeof(devpath), "/dev/i2c-%u", priv->adap.nr);
if(chan_id < 12)
snprintf(linkpath, sizeof(linkpath), "/dev/sfm312_sfp-%u", chan_id);
else
snprintf(linkpath, sizeof(linkpath), "/dev/sfm312_present");
ori_sys_symlink = (void *)kallsyms_lookup_name("sys_symlink");
old_fs = get_fs();
set_fs(get_ds());
ori_sys_symlink(devpath, linkpath);
set_fs(old_fs);
return &priv->adap;
}
代码起始处首先给出ori_sys_symlink函数的声明,sfm312_i2c_add_mux_adapter函数的最后一段首先使用kallsyms_lookup_name查找内核中sys_symlink符号的地址,将其赋值到变量ori_sys_symlink,在调用ori_sys_symlink前使用get_fs/get_ds/set_fs 修改进程的地址空间访问限制。原因在于系统调用原本设计用来为用户态进程与内核服务间建立联结,为防止用户空间程序“蓄意” 破坏内核空间,内核会使用access_ok检查用户态进程访问的地址范围,而驱动程序中传给ori_sys_symlink函数的参数地址是位于内核地址空间中的,直接调用会引发访问错误。因此需要在调用接口前使用set_fs将进程的addr_limit设为KERNEL_DS,并在系统调用完成后将其恢复到原先的系统限制值。