Linux v4l2架构学习总链接
handler初始化代码调用如下:
v4l2_ctrl_handler_init(handler, 9);
对应源码:
#define v4l2_ctrl_handler_init(hdl, nr_of_controls_hint) \
v4l2_ctrl_handler_init_class(hdl, nr_of_controls_hint, NULL, NULL)
int v4l2_ctrl_handler_init_class(struct v4l2_ctrl_handler *hdl,
unsigned nr_of_controls_hint,
struct lock_class_key *key, const char *name)
{
mutex_init(&hdl->_lock);
hdl->lock = &hdl->_lock;
lockdep_set_class_and_name(hdl->lock, key, name);
INIT_LIST_HEAD(&hdl->ctrls);
INIT_LIST_HEAD(&hdl->ctrl_refs);
/*
* 这里有个对8求商的操作
* 传入的nr_of_controls_hint值为9
* 所以这里的nr_of_buckets = 1 + 9/8 = 2
*/
hdl->nr_of_buckets = 1 + nr_of_controls_hint / 8;
/*
* 这里注意 buckets的类型是 struct v4l2_ctrl_ref **buckets;
* 所以sizeof(hdl->buckets[0]) 对应的大小是 struct v4l2_ctl_ref *
* 也就是指针的大小,32位系统这个值为4,64位系统这个值为8
* 这里也可以知道 hdl->buckets[0] 用于存储buckets的地址
*/
hdl->buckets = kvmalloc_array(hdl->nr_of_buckets,
sizeof(hdl->buckets[0]),
GFP_KERNEL | __GFP_ZERO);
hdl->error = hdl->buckets ? 0 : -ENOMEM;
return hdl->error;
}
初始化比较简单,接着分析注册相关的代码
static const s64 link_freq_menu_items[] = {
MIPI_FREQ_360M,
MIPI_FREQ_648M,
};
link_freq = v4l2_ctrl_new_int_menu(handler, NULL,
V4L2_CID_LINK_FREQ,
1, 0, link_freq_menu_items);
v4l2_ctrl_new_initn_menu() 代码如下
struct v4l2_ctrl *v4l2_ctrl_new_int_menu(struct v4l2_ctrl_handler *hdl,
const struct v4l2_ctrl_ops *ops,
u32 id, u8 _max, u8 _def, const s64 *qmenu_int)
{
const char *name;
enum v4l2_ctrl_type type;
s64 min;
u64 step;
s64 max = _max;
s64 def = _def;
u32 flags;
/*
* 根据下面的分析可以知道
* name : "Link Frequenncy"
* type : V4L2_CTRL_TYPE_INTEGER_MENU
* flags : 0
* min : 局部变量没有赋值,值未知
* max : 参数传入 1
* step : 局部变量没有赋值,值未知
* def : 参数传入 0
*/
v4l2_ctrl_fill(id, &name, &type, &min, &max, &step, &def, &flags);
if (type != V4L2_CTRL_TYPE_INTEGER_MENU) {
handler_set_err(hdl, -EINVAL);
return NULL;
}
return v4l2_ctrl_new(hdl, ops, NULL, id, name, type,
0, max, 0, def, NULL, 0,
flags, NULL, qmenu_int, NULL);
}
v4l2_ctrl_new_initn_menu() -> v4l2_ctrl_fill()
const char *v4l2_ctrl_get_name(u32 id)
{
switch (id) {
...
case V4L2_CID_LINK_FREQ: return "Link Frequency";
...
}
}
void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
s64 *min, s64 *max, u64 *step, s64 *def, u32 *flags)
{
*name = v4l2_ctrl_get_name(id);
*flags = 0;
switch (id) {
...
case V4L2_CID_LINK_FREQ:
*type = V4L2_CTRL_TYPE_INTEGER_MENU;
break;
...
}
}
v4l2_ctrl_new_initn_menu() -> v4l2_ctrl_new()
static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
const struct v4l2_ctrl_ops *ops,
const struct v4l2_ctrl_type_ops *type_ops,
u32 id, const char *name, enum v4l2_ctrl_type type,
s64 min, s64 max, u64 step, s64 def,
const u32 dims[V4L2_CTRL_MAX_DIMS], u32 elem_size,
u32 flags, const char * const *qmenu,
const s64 *qmenu_int, void *priv)
{
struct v4l2_ctrl *ctrl;
unsigned sz_extra;
unsigned nr_of_dims = 0;
unsigned elems = 1;
bool is_array;
unsigned tot_ctrl_size;
unsigned idx;
void *data;
int err;
if (hdl->error)
return NULL;
/*
* 根据传入的参数分析
* ops : NULL
* type_ops : NULL
* id : V4L2_CID_LINK_FREQ
* name : Link Frequency
* type : V4l2_CTRL_TYPE_INTEGER_MENU
* min : 0
* max : 1
* step : 0
* def : 0
* dims : NULL
* elem_size : 0
* flags : 0
* qmenu : NULL
* qmenu_int : link_freq_menu_items
* priv : NULL
*/
while (dims && dims[nr_of_dims]) {
elems *= dims[nr_of_dims];
nr_of_dims++;
if (nr_of_dims == V4L2_CTRL_MAX_DIMS)
break;
}
is_array = nr_of_dims > 0;
/* Prefill elem_size for all types handled by std_type_ops */
switch (type) {
case V4L2_CTRL_TYPE_INTEGER64:
elem_size = sizeof(s64);
break;
case V4L2_CTRL_TYPE_STRING:
elem_size = max + 1;
break;
case V4L2_CTRL_TYPE_U8:
elem_size = sizeof(u8);
break;
case V4L2_CTRL_TYPE_U16:
elem_size = sizeof(u16);
break;
case V4L2_CTRL_TYPE_U32:
elem_size = sizeof(u32);
break;
default:
if (type < V4L2_CTRL_COMPOUND_TYPES)
elem_size = sizeof(s32);
break;
}
/*
* 对于不同的类型,elem_size值不一样
*/
tot_ctrl_size = elem_size * elems;
/*
* 这里分析的是V4L2_CTRL_TYPE_INTEGER_MENU
* 需要检查qmenu_init是否为NULL
*/
/* Sanity checks */
if (id == 0 || name == NULL || !elem_size ||
id >= V4L2_CID_PRIVATE_BASE ||
(type == V4L2_CTRL_TYPE_MENU && qmenu == NULL) ||
(type == V4L2_CTRL_TYPE_INTEGER_MENU && qmenu_int == NULL)) {
handler_set_err(hdl, -ERANGE);
return NULL;
}
/*
* check_range检查如下
* 对于 V4L2_CTRL_TYPE_INTEGER_MENU
* if (min > max || def < min || def > max)
* return -ERANGE;
* if (step && ((1 << def) & step))
* return -EINVAL;
*/
err = check_range(type, min, max, step, def);
if (err) {
handler_set_err(hdl, err);
return NULL;
}
if (is_array &&
(type == V4L2_CTRL_TYPE_BUTTON ||
type == V4L2_CTRL_TYPE_CTRL_CLASS)) {
handler_set_err(hdl, -EINVAL);
return NULL;
}
sz_extra = 0;
if (type == V4L2_CTRL_TYPE_BUTTON)
flags |= V4L2_CTRL_FLAG_WRITE_ONLY |
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
else if (type == V4L2_CTRL_TYPE_CTRL_CLASS)
flags |= V4L2_CTRL_FLAG_READ_ONLY;
else if (type == V4L2_CTRL_TYPE_INTEGER64 ||
type == V4L2_CTRL_TYPE_STRING ||
type >= V4L2_CTRL_COMPOUND_TYPES ||
is_array)
sz_extra += 2 * tot_ctrl_size;
/*
* 创建一个ctrl
*/
ctrl = kvzalloc(sizeof(*ctrl) + sz_extra, GFP_KERNEL);
if (ctrl == NULL) {
handler_set_err(hdl, -ENOMEM);
return NULL;
}
INIT_LIST_HEAD(&ctrl->node);
INIT_LIST_HEAD(&ctrl->ev_subs);
/*
* 填充ctrl成员
*/
ctrl->handler = hdl;
ctrl->ops = ops;
/*
* 注意type_ops为NULL的话,这里会赋值std_type_ops
*/
ctrl->type_ops = type_ops ? type_ops : &std_type_ops;
ctrl->id = id;
ctrl->name = name;
ctrl->type = type;
ctrl->flags = flags;
ctrl->minimum = min;
ctrl->maximum = max;
ctrl->step = step;
/* 默认值 */
ctrl->default_value = def;
/*
* !is_array == 1
* type != V4L2_CTRL_TYPE_STRING
* is_string == 0
*/
ctrl->is_string = !is_array && type == V4L2_CTRL_TYPE_STRING;
/*
* is_ptr == 0
*/
ctrl->is_ptr = is_array || type >= V4L2_CTRL_COMPOUND_TYPES || ctrl->is_string;
/*
* !ctrl->is_ptr == 1
* type != V4L2_CTRL_TYPE_INTEGER64
* is_int == 1
*/
ctrl->is_int = !ctrl->is_ptr && type != V4L2_CTRL_TYPE_INTEGER64;
ctrl->is_array = is_array;
ctrl->elems = elems;
ctrl->nr_of_dims = nr_of_dims;
if (nr_of_dims)
memcpy(ctrl->dims, dims, nr_of_dims * sizeof(dims[0]));
ctrl->elem_size = elem_size;
if (type == V4L2_CTRL_TYPE_MENU)
ctrl->qmenu = qmenu;
else if (type == V4L2_CTRL_TYPE_INTEGER_MENU)
/*
* ctrl->qmenu_int = link_freq_menu_items
*/
ctrl->qmenu_int = qmenu_int;
ctrl->priv = priv;
/*
* ctrl->cur.val = 0
* ctrl->val = 0
*/
ctrl->cur.val = ctrl->val = def;
/*
* 因为sz_extra = 0
* 所以data没有指向的地址 为NULL
*/
data = &ctrl[1];
if (!ctrl->is_int) {
ctrl->p_new.p = data;
ctrl->p_cur.p = data + tot_ctrl_size;
} else {
/*
* @p_cur:通过联合表示的控件的当前值,该联合提供了一种通过指针访问控件类型的标准方法。
* @p_new:通过联合表示控件的新值,该联合提供了一种通过指针访问控件类型的标准方法。
*/
ctrl->p_new.p = &ctrl->val;
ctrl->p_cur.p = &ctrl->cur.val;
}
/*
* elems值为1
* type_ops->init
* std_init()
* 对于type为V4L2_CTRL_TYPE_INTEGER_MENU
* ptr.p_s32[idx] = ctrl->default_value
*/
for (idx = 0; idx < elems; idx++) {
ctrl->type_ops->init(ctrl, idx, ctrl->p_cur);
ctrl->type_ops->init(ctrl, idx, ctrl->p_new);
}
if (handler_new_ref(hdl, ctrl)) {
kvfree(ctrl);
return NULL;
}
mutex_lock(hdl->lock);
/*
* 将ctrl通过node挂载到hdl底ctrls链表上
*/
list_add_tail(&ctrl->node, &hdl->ctrls);
mutex_unlock(hdl->lock);
return ctrl;
}
v4l2_ctrl_new_initn_menu() -> v4l2_ctrl_new() -> handler_new_ref()
static int handler_new_ref(struct v4l2_ctrl_handler *hdl,
struct v4l2_ctrl *ctrl)
{
struct v4l2_ctrl_ref *ref;
struct v4l2_ctrl_ref *new_ref;
u32 id = ctrl->id;
/*
* 这里获取ctrl所属的类
* 比如当前分析的V4L2_CID_LINK_FREQ
* 所属的类为 V4L2_CTRL_CLASS_IMAGE_PROC
*/
u32 class_ctrl = V4L2_CTRL_ID2WHICH(id) | 1;
/*
* 这里将不同的id放入多个buckets中,方便快速查找
*/
int bucket = id % hdl->nr_of_buckets; /* which bucket to use */
/*
* Automatically add the control class if it is not yet present and
* the new control is not a compound control.
*/
/*
* 如果尚不存在控件类并且新控件不是复合控件,则自动添加该控件类。
* 对于控件类这里先不分析,等分析完V4L2_CID_LINK_FREQ
* 再回来看这个
*/
if (ctrl->type < V4L2_CTRL_COMPOUND_TYPES &&
id != class_ctrl && find_ref_lock(hdl, class_ctrl) == NULL)
if (!v4l2_ctrl_new_std(hdl, NULL, class_ctrl, 0, 0, 0, 0))
return hdl->error;
if (hdl->error)
return hdl->error;
/*
* ref是reference的缩写,参考的意思
*/
new_ref = kzalloc(sizeof(*new_ref), GFP_KERNEL);
if (!new_ref)
return handler_set_err(hdl, -ENOMEM);
/*
* 填充ctrl
*/
new_ref->ctrl = ctrl;
/*
* 对于v4l2_ctrl_add_handler调用当前函数的时候
* ctrl->handler != hdl
* 所以这里加上判断
* 但是if中的代码暂时没有看明白....
*/
if (ctrl->handler == hdl) {
/* By default each control starts in a cluster of its own.
new_ref->ctrl is basically a cluster array with one
element, so that's perfect to use as the cluster pointer.
But only do this for the handler that owns the control. */
ctrl->cluster = &new_ref->ctrl;
ctrl->ncontrols = 1;
}
INIT_LIST_HEAD(&new_ref->node);
mutex_lock(hdl->lock);
/* Add immediately at the end of the list if the list is empty, or if
the last element in the list has a lower ID.
This ensures that when elements are added in ascending order the
insertion is an O(1) operation. */
/*
* new_ref是根据id排列挂载到hdl->ctrl_refs上的
* 1. hdl->ctrl_refs链表是空的,直接添加就可以
* 2. 链表上最后一个成员ID比当前的ID小
* 链表上有数据,说明已经排列过了,所以这里也可以直接添加
*/
if (list_empty(&hdl->ctrl_refs) || id > node2id(hdl->ctrl_refs.prev)) {
list_add_tail(&new_ref->node, &hdl->ctrl_refs);
goto insert_in_hash;
}
/*
* 上面的条件都不符合,这里要找到合适的位置添加
*/
/* Find insert position in sorted list */
list_for_each_entry(ref, &hdl->ctrl_refs, node) {
if (ref->ctrl->id < id)
continue;
/* Don't add duplicates */
if (ref->ctrl->id == id) {
kfree(new_ref);
goto unlock;
}
/*
* 找到合适的位置,添加
*/
list_add(&new_ref->node, ref->node.prev);
break;
}
insert_in_hash:
/*
* 之前说过buckets是为了实现快速查找
* 这里直接通过hdl->buckets[bucket]指向new_ref
*/
/* Insert the control node in the hash */
new_ref->next = hdl->buckets[bucket];
hdl->buckets[bucket] = new_ref;
unlock:
mutex_unlock(hdl->lock);
return 0;
}
以上就完成了handler ctrl的注册
假设11个ctrl,ID为1~11,这里先不考虑ctrl class,注册顺序 1 3 4 6 7 8 10 11 9 2 5

2025.7.4更新
上图的理解是有问题的
关键代码
int bucket = id % hdl->nr_of_buckets; /* which bucket to use */
所以buctkets[0] 里面是2 4 6 8 10
buctkets[1] 里面是1 3 5 7 9 11
该博客是Linux v4l2架构学习总链接,介绍了handler初始化代码调用及对应源码,分析了注册相关代码,如v4l2_ctrl_new_initn_menu()及其调用关系,完成了handler ctrl的注册,还假设了11个ctrl的注册顺序。
1024





