Re: cardbus pirq conflict

Andrew Morton
2000-12-09T02:26:22Z

Matthew Galgoci wrote:
>
> Hi Folks,
>
> I am running the 2.4.0test12pre7 kernel on my laptop computer, and
> I'm having some rather interesting problems.
>
> For the longest time, usb never worked on this machine. As of the
> happy patch that enabled bus mastering for usb controllers, it
> magically started working. I am really happy that it does work
> now.
>
> The usb controller and the pcmcia bridge both share the same
> irq, irq 10.
>
> Now, my cardbus cards have stopped working. When I insert a cardbus
> nic, I get the following message: "IRQ routing conflict in pirq
> table! Try 'pci=autoirq'"
>
> The card fails to initialize, and upon issuing the halt command, the
> system generates a kernel Oops. I tend to think that the Oops is a
> symptom of having a half initialized device. If anyone is interested,
> I'll catch the Oops, run it though ksymoops, and send it to them.

test12-pre7 has a number of hotplug problems. I think what you're
seeing is a deadlock where keventd is waiting on itself in
call_usermodehelper() :(

And yes, when you shutdown the system in this state it oopses. I'm
not sure why it was doing that - it shouldn't have. hmmm...

Could you please test this stuff?

--- linux-2.4.0-test12-pre7/include/linux/sched.h Thu Dec 7 22:05:21 2000
+++ linux-akpm/include/linux/sched.h Sat Dec 9 01:36:19 2000
@@ -152,6 +152,7 @@
extern int schedule_task(struct tq_struct *task);
extern void run_schedule_tasks(void);
extern int start_context_thread(void);
+extern int current_is_keventd(void);

/*
* The default fd array needs to be at least BITS_PER_LONG,
--- linux-2.4.0-test12-pre7/include/linux/kernel.h Thu Dec 7 22:05:21 2000
+++ linux-akpm/include/linux/kernel.h Sat Dec 9 01:22:18 2000
@@ -63,6 +63,8 @@
extern int get_option(char **str, int *pint);
extern char *get_options(char *str, int nints, int *ints);
extern unsigned long memparse(char *ptr, char **retptr);
+extern void dev_probe_lock(void);
+extern void dev_probe_unlock(void);

extern int session_of_pgrp(int pgrp);

--- linux-2.4.0-test12-pre7/drivers/pci/pci.c Thu Dec 7 22:05:20 2000
+++ linux-akpm/drivers/pci/pci.c Sat Dec 9 01:24:46 2000
@@ -300,18 +300,25 @@
pci_announce_device(struct pci_driver *drv, struct pci_dev *dev)
{
const struct pci_device_id *id;
+ int ret = 0;

if (drv->id_table) {
id = pci_match_device(drv->id_table, dev);
- if (!id)
- return 0;
+ if (!id) {
+ ret = 0;
+ goto out;
+ }
} else
id = NULL;
+
+ dev_probe_lock();
if (drv->probe(dev, id) >= 0) {
dev->driver = drv;
- return 1;
+ ret = 1;
}
- return 0;
+ dev_probe_unlock();
+out:
+ return ret;
}

int
@@ -360,9 +367,9 @@
if (!hotplug_path[0])
return;

- sprintf(class_id, "PCI_CLASS=%X", pdev->class);
- sprintf(id, "PCI_ID=%X/%X", pdev->vendor, pdev->device);
- sprintf(sub_id, "PCI_SUBSYS_ID=%X/%X", pdev->subsystem_vendor, pdev->subsystem_device);
+ sprintf(class_id, "PCI_CLASS=%04X", pdev->class);
+ sprintf(id, "PCI_ID=%04X:%04X", pdev->vendor, pdev->device);
+ sprintf(sub_id, "PCI_SUBSYS_ID=%04X:%04X", pdev->subsystem_vendor, pdev->subsystem_device);
sprintf(bus_id, "PCI_SLOT_NAME=%s", pdev->slot_name);

i = 0;
--- linux-2.4.0-test12-pre7/kernel/exit.c Thu Dec 7 22:05:21 2000
+++ linux-akpm/kernel/exit.c Fri Dec 8 22:38:30 2000
@@ -302,9 +302,9 @@
{
struct mm_struct * mm = tsk->mm;

+ mm_release();
if (mm) {
atomic_inc(&mm->mm_count);
- mm_release();
if (mm != tsk->active_mm) BUG();
/* more a memory barrier than a real lock */
task_lock(tsk);
--- linux-2.4.0-test12-pre7/kernel/kmod.c Thu Dec 7 22:05:21 2000
+++ linux-akpm/kernel/kmod.c Sat Dec 9 11:53:32 2000
@@ -256,21 +256,6 @@

#endif /* CONFIG_HOTPLUG */

-
-static int exec_helper (void *arg)
-{
- long ret;
- void **params = (void **) arg;
- char *path = (char *) params [0];
- char **argv = (char **) params [1];
- char **envp = (char **) params [2];
-
- ret = exec_usermodehelper (path, argv, envp);
- if (ret < 0)
- ret = -ret;
- do_exit(ret);
-}
-
struct subprocess_info {
struct semaphore *sem;
char *path;
@@ -279,73 +264,36 @@
int retval;
};

-/*
- * This is a standalone child of keventd. It forks off another thread which
- * is the desired usermode helper and then waits for the child to exit.
- * We return the usermode process's exit code, or some -ve error code.
- */
static int ____call_usermodehelper(void *data)
{
struct subprocess_info *sub_info = data;
- struct task_struct *curtask = current;
- void *params [3] = { sub_info->path, sub_info->argv, sub_info->envp };
- pid_t pid, pid2;
- mm_segment_t fs;
- int retval = 0;
+ int retval;

- if (!curtask->fs->root) {
- printk(KERN_ERR "call_usermodehelper[%s]: no root fs\n", sub_info->path);
- retval = -EPERM;
- goto up_and_out;
- }
- if ((pid = kernel_thread(exec_helper, (void *) params, 0)) < 0) {
- printk(KERN_ERR "failed fork2 %s, errno = %d", sub_info->argv[0], -pid);
- retval = pid;
- goto up_and_out;
- }
+ retval = -EPERM;
+ if (current->fs->root)
+ retval = exec_usermodehelper(sub_info->path, sub_info->argv, sub_info->envp);

- if (retval >= 0) {
- /* Block everything but SIGKILL/SIGSTOP */
- spin_lock_irq(&curtask->sigmask_lock);
- siginitsetinv(&curtask->blocked, sigmask(SIGKILL) | sigmask(SIGSTOP));
- recalc_sigpending(curtask);
- spin_unlock_irq(&curtask->sigmask_lock);
-
- /* Allow the system call to access kernel memory */
- fs = get_fs();
- set_fs(KERNEL_DS);
- pid2 = waitpid(pid, &retval, __WCLONE);
- if (pid2 == -1 && errno < 0)
- pid2 = errno;
- set_fs(fs);
-
- if (pid2 != pid) {
- printk(KERN_ERR "waitpid(%d) failed, %d\n", pid, pid2);
- retval = (pid2 < 0) ? pid2 : -1;
- }
- }
-
-up_and_out:
+ /* Exec failed? */
sub_info->retval = retval;
- curtask->exit_signal = SIGCHLD; /* Wake up parent */
- up_and_exit(sub_info->sem, retval);
+ do_exit(0);
}

/*
- * This is a schedule_task function, so we must not sleep for very long at all.
- * But the exec'ed process could do anything at all. So we launch another
- * kernel thread.
+ * This is run by keventd.
*/
static void __call_usermodehelper(void *data)
{
struct subprocess_info *sub_info = data;
pid_t pid;

- if ((pid = kernel_thread (____call_usermodehelper, (void *)sub_info, 0)) < 0) {
- printk(KERN_ERR "failed fork1 %s, errno = %d", sub_info->argv[0], -pid);
+ /*
+ * CLONE_VFORK: wait until the usermode helper has execve'd successfully
+ * We need the data structures to stay around until that is done.
+ */
+ pid = kernel_thread (____call_usermodehelper, sub_info, CLONE_VFORK | SIGCHLD);
+ if (pid < 0)
sub_info->retval = pid;
- up(sub_info->sem);
- }
+ up(sub_info->sem);
}

/*
@@ -358,22 +306,50 @@
{
DECLARE_MUTEX_LOCKED(sem);
struct subprocess_info sub_info = {
- sem: &sem,
- path: path,
- argv: argv,
- envp: envp,
- retval: 0,
- };
- struct tq_struct tqs = {
- next: 0,
- sync: 0,
- routine: __call_usermodehelper,
- data: &sub_info,
+ sem: &sem,
+ path: path,
+ argv: argv,
+ envp: envp,
+ retval: 0,
};
+ int retval = 0;
+
+ if (path[0] == '\0')
+ goto out;

- schedule_task(&tqs);
- down(&sem); /* Wait for an error or completion */
- return sub_info.retval;
+ if (current_is_keventd()) {
+ /* We can't wait on keventd! */
+ __call_usermodehelper(&sub_info);
+ } else {
+ struct tq_struct tqs = {
+ next: 0,
+ sync: 0,
+ routine: __call_usermodehelper,
+ data: &sub_info,
+ };
+
+ schedule_task(&tqs);
+ down(&sem); /* Wait for an error or completion */
+ }
+ retval = sub_info.retval;
+out:
+ return retval;
+}
+
+/*
+ * This is for the serialisation of device probe() functions
+ * against device open() functions
+ */
+static DECLARE_MUTEX(dev_probe_sem);
+
+void dev_probe_lock(void)
+{
+ down(&dev_probe_sem);
+}
+
+void dev_probe_unlock(void)
+{
+ up(&dev_probe_sem);
}

EXPORT_SYMBOL(exec_usermodehelper);
--- linux-2.4.0-test12-pre7/kernel/context.c Thu Dec 7 22:05:21 2000
+++ linux-akpm/kernel/context.c Fri Dec 8 22:38:30 2000
@@ -18,6 +18,17 @@
static DECLARE_TASK_QUEUE(tq_context);
static DECLARE_WAIT_QUEUE_HEAD(context_task_wq);
static int keventd_running;
+static struct task_struct *keventd_task;
+
+int current_is_keventd(void)
+{
+ int ret = 0;
+ if (keventd_running == 0)
+ printk(KERN_ERR "current_is_keventd(): keventd has not started\n");
+ else
+ ret = (current == keventd_task);
+ return ret;
+}

int schedule_task(struct tq_struct *task)
{
@@ -38,6 +49,7 @@
daemonize();
strcpy(curtask->comm, "keventd");
keventd_running = 1;
+ keventd_task = curtask;

spin_lock_irq(&curtask->sigmask_lock);
siginitsetinv(&curtask->blocked, sigmask(SIGCHLD));
--- linux-2.4.0-test12-pre7/net/core/dev.c Thu Dec 7 22:05:21 2000
+++ linux-akpm/net/core/dev.c Sat Dec 9 02:11:10 2000
@@ -154,6 +154,12 @@
static struct timer_list samp_timer = { function: sample_queue };
#endif

+#ifdef CONFIG_HOTPLUG
+static int net_run_sbin_hotplug(struct net_device *dev, char *action);
+#else
+#define net_run_sbin_hotplug(dev, action) ({ 0; })
+#endif
+
/*
* Our notifier list
*/
@@ -2196,9 +2202,11 @@
if (!capable(CAP_NET_ADMIN))
return -EPERM;
dev_load(ifr.ifr_name);
+ dev_probe_lock();
rtnl_lock();
ret = dev_ifsioc(&ifr, cmd);
rtnl_unlock();
+ dev_probe_unlock();
return ret;

case SIOCGIFMEM:
@@ -2217,9 +2225,11 @@
if (cmd >= SIOCDEVPRIVATE &&
cmd <= SIOCDEVPRIVATE + 15) {
dev_load(ifr.ifr_name);
+ dev_probe_lock();
rtnl_lock();
ret = dev_ifsioc(&ifr, cmd);
rtnl_unlock();
+ dev_probe_unlock();
if (!ret && copy_to_user(arg, &ifr, sizeof(struct ifreq)))
return -EFAULT;
return ret;
@@ -2388,10 +2398,12 @@
if (ret)
return ret;
#endif /* CONFIG_NET_DIVERT */
-
+
/* Notify protocols, that a new device appeared. */
notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);

+ net_run_sbin_hotplug(dev, "register");
+
return 0;
}

@@ -2475,6 +2487,8 @@
/* Shutdown queueing discipline. */
dev_shutdown(dev);

+ net_run_sbin_hotplug(dev, "unregister");
+
/* Notify protocols, that we are about to destroy
this device. They should clean all the things.
*/
@@ -2714,29 +2728,15 @@
/* Notify userspace when a netdevice event occurs,
* by running '/sbin/hotplug net' with certain
* environment variables set.
- *
- * Currently reported events are listed in netdev_event_names[].
*/

-/* /sbin/hotplug ONLY executes for events named here */
-static char *netdev_event_names[] = {
- [NETDEV_REGISTER] = "register",
- [NETDEV_UNREGISTER] = "unregister",
-};
-
-static int run_sbin_hotplug(struct notifier_block *this,
- unsigned long event, void *ptr)
+static int net_run_sbin_hotplug(struct net_device *dev, char *action)
{
- struct net_device *dev = (struct net_device *) ptr;
- char *argv[3], *envp[5], ifname[12 + IFNAMSIZ], action[32];
+ char *argv[3], *envp[5], ifname[12 + IFNAMSIZ], action_str[32];
int i;

- if ((event >= ARRAY_SIZE(netdev_event_names)) ||
- !netdev_event_names[event])
- return NOTIFY_DONE;
-
sprintf(ifname, "INTERFACE=%s", dev->name);
- sprintf(action, "ACTION=%s", netdev_event_names[event]);
+ sprintf(action_str, "ACTION=%s", action);

i = 0;
argv[i++] = hotplug_path;
@@ -2748,27 +2748,11 @@
envp [i++] = "HOME=/";
envp [i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
envp [i++] = ifname;
- envp [i++] = action;
+ envp [i++] = action_str;
envp [i] = 0;

call_usermodehelper (argv [0], argv, envp);

return NOTIFY_DONE;
-}
-
-static struct notifier_block sbin_hotplug = {
- notifier_call: run_sbin_hotplug,
-};
-
-/*
- * called from init/main.c, -after- all the initcalls are complete.
- * Registers a hook that calls /sbin/hotplug on every netdev
- * addition and removal.
- */
-void __init net_notifier_init (void)
-{
- if (register_netdevice_notifier(&sbin_hotplug))
- printk (KERN_WARNING "unable to register netdev notifier\n"
- KERN_WARNING "/sbin/hotplug will not be run.\n");
}
#endif
--- linux-2.4.0-test12-pre7/net/ipv4/devinet.c Thu Aug 24 21:07:25 2000
+++ linux-akpm/net/ipv4/devinet.c Sat Dec 9 11:14:03 2000
@@ -519,6 +519,7 @@
return -EINVAL;
}

+ dev_probe_lock();
rtnl_lock();

if ((dev = __dev_get_by_name(ifr.ifr_name)) == NULL) {
@@ -649,10 +650,12 @@
}
done:
rtnl_unlock();
+ dev_probe_unlock();
return ret;

rarok:
rtnl_unlock();
+ dev_probe_unlock();
if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
return -EFAULT;
return 0;
--- linux-2.4.0-test12-pre7/init/main.c Thu Dec 7 22:05:21 2000
+++ linux-akpm/init/main.c Sat Dec 9 00:47:46 2000
@@ -716,14 +716,6 @@
/* Mount the root filesystem.. */
mount_root();

-#if defined(CONFIG_HOTPLUG) && defined(CONFIG_NET)
- /* do this after other 'do this last' stuff, because we want
- * to minimize spurious executions of /sbin/hotplug
- * during boot-up
- */
- net_notifier_init();
-#endif
-
mount_devfs_fs ();

#ifdef CONFIG_BLK_DEV_INITRD