最近做的一个项目,系统启动时发现有warning,并打印出了一堆dump stack。
看了下代码,发现是在esai的probe函数中调用request_mem_region时失败代码走到了错误处理的部分。
错误处理中disable了一个clock,warning的内容是说该clock没enable就disable了。
从代码看,存在两个问题:
1、request_mem_region为什么会失败。
2、为什么在disable clock之前没有enable。
先看看request_mem_region为什么会失败。
经大牛指点,request_mem_region失败,应该是因为申请的一块内存,部分/全部已被占用,所以导致申请失败。
为了看看申请的内存是否与其他io申请的内存释放有冲突,将/proc/iomem文件cat出来,发现esai申请的内存与ssi-0申请的内存重叠。
对照cpu的reference manual,发现esai很守规矩,使用的是memory map中给自己指定的memory范围;而ssi-0没使用memory map中指定的内存范围,抢占了esai的内存范围。
找到esai base address和ssi-0(对应的是硬件ssi-1) base address定义,发现定义没问题,与memory map中一致。
在esai和ssi的probe函数中加log,发现首先是ssi-1申请了正确的内存,ssi-2也申请了正确的内存,接下来ssi-0申请的内存范围是esai的内存范围。
因为ssi在esai前面进行的probe,导致esai申请内存时,内存已经被ssi-0申请了去,所以失败。
可疑点是,ssi的顺序为什么是1、2、0,而不是0、1、2。
看了board文件中添加resource的地方,发现是先添加了ssi 1、2、3,然后添加了esai。
跟了下添加ssi resource的代码,发现知道的序号其实用作了数组下标,数组下标都是从0开始的,这儿为什么是1、2、3?
是不是添加ssi 1\2\3的时候,1、2没问题,由于ssi只有0\1\2,添加3的时候,抢占了esai的位置。
将board中的ssi 1\2\3改为0\1\2,果然问题解决。
若只是止步于此,少了些乐趣。
不仅要知其然,也要知其所以然。
先看看request_mem_region的实现。
#define request_mem_region(start, n, name) __request_region(&iomem_resource, (start), (n), (name), 0)
看看iomem_resource的定义:
struct resource iomem_resource = { .name = "PCI mem", .start = 0, .end = -1, .flags = IORESOURCE_MEM, };
resource type有以下几种类型:
#define IORESOURCE_IO 0x00000100 #define IORESOURCE_MEM 0x00000200 #define IORESOURCE_IRQ 0x00000400 #define IORESOURCE_DMA 0x00000800 #define IORESOURCE_BUS 0x00001000
看看__request_region的实现:
/** * __request_region - create a new busy resource region * @parent: parent resource descriptor * @start: resource start address * @n: resource region size * @name: reserving caller's ID string * @flags: IO resource flags */ struct resource * __request_region(struct resource *parent, resource_size_t start, resource_size_t n, const char *name, int flags) { // 定义并初始化一个wait queue /* #define DECLARE_WAITQUEUE(name, tsk) \ wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk) */ /* #define __WAITQUEUE_INITIALIZER(name, tsk) { \ .private = tsk, \ .func = default_wake_function, \ .task_list = { NULL, NULL } } */ DECLARE_WAITQUEUE(wait, current); /* /* * Resources are tree-like, allowing * nesting etc.. */ struct resource { resource_size_t start; resource_size_t end; const char *name; unsigned long flags; struct resource *parent, *sibling, *child; }; */ /* /** * kzalloc - allocate memory. The memory is set to zero. * @size: how many bytes of memory are required. * @flags: the type of memory to allocate (see kmalloc). */ static inline void *kzalloc(size_t size, gfp_t flags) { return kmalloc(size, flags | __GFP_ZERO); } */ struct resource *res = kzalloc(sizeof(*res), GFP_KERNEL); if (!res) return NULL; res->name = name; res->start = start; res->end = start + n - 1; res->flags = IORESOURCE_BUSY; res->flags |= flags; write_lock(&resource_lock); for (;;) { struct resource *conflict; // 检查申请的内存与其他内存是否冲突 /× /* Return the conflict entry if you can't request it */ static struct resource * __request_resource(struct resource *root, struct resource *new) { resource_size_t start = new->start; resource_size_t end = new->end; struct resource *tmp, **p; // 头比尾大,自相矛盾 if (end < start) return root; // 头还在根的头前面,越界了 if (start < root->start) return root; // 尾在根的尾后面,也越界了 if (end > root->end) return root; // 从下面的循环看,root的所有child应该是从低地址到高地址依次排开,并通过他们的sibling进行关联。 // 刚开始,p指向root的最大孩子,也就是地址最低的,并判断其与new是否冲突。 // 若不冲突,通过最大孩子的sibling成员,找到它的下一个兄弟,并判断其与new是否冲突。 // 这样依次判断下去,直到找到了一个空child,或者找到的找到的child的start还在new的end之后,也即完全在new之后。 p = &root->child; for (;;) { tmp = *p; if (!tmp || tmp->start > end) { new->sibling = tmp; *p = new; new->parent = root; return NULL; } p = &tmp->sibling; if (tmp->end < start) continue; return tmp; } } ×/ conflict = __request_resource(parent, res); if (!conflict) break; // 若有冲突,但冲突的不是根,并且conflict的标志并没有说是busy,也就是说并没有被占用,则说明new落在了原来根的一个child的范围内。 // 也就是说conflict其实是new的根,那就将conflict赋值为new的根,再次进行冲突检测。 if (conflict != parent) { parent = conflict; if (!(conflict->flags & IORESOURCE_BUSY)) continue; } // 如果有冲突,并且conflict和new都是可软件多路复用的,则说明暂时是被别人占用了, // 要做到就是把锁释放了,并等待内存被释放 if (conflict->flags & flags & IORESOURCE_MUXED) { /* /* * This is compatibility stuff for IO resources. * * Note how this, unlike the above, knows about * the IO flag meanings (busy etc). * * request_region creates a new busy region. * * check_region returns non-zero if the area is already busy. * * release_region releases a matching busy region. */ static DECLARE_WAIT_QUEUE_HEAD(muxed_resource_wait); void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) { unsigned long flags; wait->flags &= ~WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&q->lock, flags); __add_wait_queue(q, wait); spin_unlock_irqrestore(&q->lock, flags); } static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new) { list_add(&new->task_list, &head->task_list); } */ add_wait_queue(&muxed_resource_wait, &wait); // 释放锁 write_unlock(&resource_lock); // TASK_INTERRUPTIBLE是可以被信号和wake_up()唤醒的,当信号到来时,进程会被设置为可运行。 // TASK_UNINTERRUPTIBLE只能被wake_up()唤醒。 set_current_state(TASK_UNINTERRUPTIBLE); /* asmlinkage void __sched schedule(void) { struct task_struct *tsk = current; sched_submit_work(tsk); __schedule(); } static inline void sched_submit_work(struct task_struct *tsk) { if (!tsk->state) return; /* * If we are going to sleep and we have plugged IO queued, * make sure to submit it to avoid deadlocks. */ if (blk_needs_flush_plug(tsk)) blk_schedule_flush_plug(tsk); } */ // 调度函数,不能简单的一笔带过 schedule(); remove_wait_queue(&muxed_resource_wait, &wait); write_lock(&resource_lock); continue; } /* Uhhuh, that didn't work out.. */ kfree(res); res = NULL; break; } write_unlock(&resource_lock); return res; }
上面有将wait添加到queue,在__release_region中调用了wake_up用来唤醒等待的进程。
/** * __release_region - release a previously reserved resource region * @parent: parent resource descriptor * @start: resource start address * @n: resource region size * * The described resource region must match a currently busy region. */ void __release_region(struct resource *parent, resource_size_t start, resource_size_t n) { struct resource **p; resource_size_t end; p = &parent->child; end = start + n - 1; write_lock(&resource_lock); for (;;) { struct resource *res = *p; if (!res) break; if (res->start <= start && res->end >= end) { if (!(res->flags & IORESOURCE_BUSY)) { p = &res->child; continue; } if (res->start != start || res->end != end) break; *p = res->sibling; write_unlock(&resource_lock); if (res->flags & IORESOURCE_MUXED) wake_up(&muxed_resource_wait); kfree(res); return; } p = &res->sibling; } write_unlock(&resource_lock); printk(KERN_WARNING "Trying to free nonexistent resource " "<%016llx-%016llx>\n", (unsigned long long)start, (unsigned long long)end); }