diff options
| author | Dominik Brodowski <linux@dominikbrodowski.net> | 2010-01-17 12:13:31 -0500 |
|---|---|---|
| committer | Dominik Brodowski <linux@dominikbrodowski.net> | 2010-02-17 11:48:25 -0500 |
| commit | f971dbd5da4e2fbf756d07b938a9c65a9c75178b (patch) | |
| tree | d21b138ac2ea3ada0e27910425cfd988f72dbede | |
| parent | cfe5d809518eda3d5e2da87c5ccbe8647143573a (diff) | |
pcmcia: use pccardd to handle eject, insert, suspend and resume requests
This avoids any sysfs-related deadlock (or lockdep warning), such
as reported at http://lkml.org/lkml/2010/1/17/88 .
Reported-by: Ming Lei <tom.leiming@gmail.com>
Tested-by: Wolfram Sang <w.sang@pengutronix.de>
Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
| -rw-r--r-- | drivers/pcmcia/cs.c | 177 | ||||
| -rw-r--r-- | drivers/pcmcia/cs_internal.h | 10 | ||||
| -rw-r--r-- | drivers/pcmcia/pcmcia_ioctl.c | 8 | ||||
| -rw-r--r-- | drivers/pcmcia/socket_sysfs.c | 26 | ||||
| -rw-r--r-- | include/pcmcia/ss.h | 3 |
5 files changed, 83 insertions, 141 deletions
diff --git a/drivers/pcmcia/cs.c b/drivers/pcmcia/cs.c index 7ba45b0cca6b..823ecda32216 100644 --- a/drivers/pcmcia/cs.c +++ b/drivers/pcmcia/cs.c | |||
| @@ -505,7 +505,10 @@ static int socket_insert(struct pcmcia_socket *skt) | |||
| 505 | dev_dbg(&skt->dev, "insert\n"); | 505 | dev_dbg(&skt->dev, "insert\n"); |
| 506 | 506 | ||
| 507 | mutex_lock(&skt->ops_mutex); | 507 | mutex_lock(&skt->ops_mutex); |
| 508 | WARN_ON(skt->state & SOCKET_INUSE); | 508 | if (skt->state & SOCKET_INUSE) { |
| 509 | mutex_unlock(&skt->ops_mutex); | ||
| 510 | return -EINVAL; | ||
| 511 | } | ||
| 509 | skt->state |= SOCKET_INUSE; | 512 | skt->state |= SOCKET_INUSE; |
| 510 | 513 | ||
| 511 | ret = socket_setup(skt, setup_delay); | 514 | ret = socket_setup(skt, setup_delay); |
| @@ -682,16 +685,19 @@ static int pccardd(void *__skt) | |||
| 682 | for (;;) { | 685 | for (;;) { |
| 683 | unsigned long flags; | 686 | unsigned long flags; |
| 684 | unsigned int events; | 687 | unsigned int events; |
| 688 | unsigned int sysfs_events; | ||
| 685 | 689 | ||
| 686 | set_current_state(TASK_INTERRUPTIBLE); | 690 | set_current_state(TASK_INTERRUPTIBLE); |
| 687 | 691 | ||
| 688 | spin_lock_irqsave(&skt->thread_lock, flags); | 692 | spin_lock_irqsave(&skt->thread_lock, flags); |
| 689 | events = skt->thread_events; | 693 | events = skt->thread_events; |
| 690 | skt->thread_events = 0; | 694 | skt->thread_events = 0; |
| 695 | sysfs_events = skt->sysfs_events; | ||
| 696 | skt->sysfs_events = 0; | ||
| 691 | spin_unlock_irqrestore(&skt->thread_lock, flags); | 697 | spin_unlock_irqrestore(&skt->thread_lock, flags); |
| 692 | 698 | ||
| 699 | mutex_lock(&skt->skt_mutex); | ||
| 693 | if (events) { | 700 | if (events) { |
| 694 | mutex_lock(&skt->skt_mutex); | ||
| 695 | if (events & SS_DETECT) | 701 | if (events & SS_DETECT) |
| 696 | socket_detect_change(skt); | 702 | socket_detect_change(skt); |
| 697 | if (events & SS_BATDEAD) | 703 | if (events & SS_BATDEAD) |
| @@ -700,10 +706,34 @@ static int pccardd(void *__skt) | |||
| 700 | send_event(skt, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW); | 706 | send_event(skt, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW); |
| 701 | if (events & SS_READY) | 707 | if (events & SS_READY) |
| 702 | send_event(skt, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW); | 708 | send_event(skt, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW); |
| 703 | mutex_unlock(&skt->skt_mutex); | ||
| 704 | continue; | ||
| 705 | } | 709 | } |
| 706 | 710 | ||
| 711 | if (sysfs_events) { | ||
| 712 | if (sysfs_events & PCMCIA_UEVENT_EJECT) | ||
| 713 | socket_remove(skt); | ||
| 714 | if (sysfs_events & PCMCIA_UEVENT_INSERT) | ||
| 715 | socket_insert(skt); | ||
| 716 | if ((sysfs_events & PCMCIA_UEVENT_RESUME) && | ||
| 717 | !(skt->state & SOCKET_CARDBUS)) { | ||
| 718 | ret = socket_resume(skt); | ||
| 719 | if (!ret && skt->callback) | ||
| 720 | skt->callback->resume(skt); | ||
| 721 | } | ||
| 722 | if ((sysfs_events & PCMCIA_UEVENT_SUSPEND) && | ||
| 723 | !(skt->state & SOCKET_CARDBUS)) { | ||
| 724 | if (skt->callback) | ||
| 725 | ret = skt->callback->suspend(skt); | ||
| 726 | else | ||
| 727 | ret = 0; | ||
| 728 | if (!ret) | ||
| 729 | socket_suspend(skt); | ||
| 730 | } | ||
| 731 | } | ||
| 732 | mutex_unlock(&skt->skt_mutex); | ||
| 733 | |||
| 734 | if (events || sysfs_events) | ||
| 735 | continue; | ||
| 736 | |||
| 707 | if (kthread_should_stop()) | 737 | if (kthread_should_stop()) |
| 708 | break; | 738 | break; |
| 709 | 739 | ||
| @@ -745,6 +775,30 @@ void pcmcia_parse_events(struct pcmcia_socket *s, u_int events) | |||
| 745 | } /* pcmcia_parse_events */ | 775 | } /* pcmcia_parse_events */ |
| 746 | EXPORT_SYMBOL(pcmcia_parse_events); | 776 | EXPORT_SYMBOL(pcmcia_parse_events); |
| 747 | 777 | ||
| 778 | /** | ||
| 779 | * pcmcia_parse_uevents() - tell pccardd to issue manual commands | ||
| 780 | * @s: the PCMCIA socket we wan't to command | ||
| 781 | * @events: events to pass to pccardd | ||
| 782 | * | ||
| 783 | * userspace-issued insert, eject, suspend and resume commands must be | ||
| 784 | * handled by pccardd to avoid any sysfs-related deadlocks. Valid events | ||
| 785 | * are PCMCIA_UEVENT_EJECT (for eject), PCMCIA_UEVENT__INSERT (for insert), | ||
| 786 | * PCMCIA_UEVENT_RESUME (for resume) and PCMCIA_UEVENT_SUSPEND (for suspend). | ||
| 787 | */ | ||
| 788 | void pcmcia_parse_uevents(struct pcmcia_socket *s, u_int events) | ||
| 789 | { | ||
| 790 | unsigned long flags; | ||
| 791 | dev_dbg(&s->dev, "parse_uevents: events %08x\n", events); | ||
| 792 | if (s->thread) { | ||
| 793 | spin_lock_irqsave(&s->thread_lock, flags); | ||
| 794 | s->sysfs_events |= events; | ||
| 795 | spin_unlock_irqrestore(&s->thread_lock, flags); | ||
| 796 | |||
| 797 | wake_up_process(s->thread); | ||
| 798 | } | ||
| 799 | } | ||
| 800 | EXPORT_SYMBOL(pcmcia_parse_uevents); | ||
| 801 | |||
| 748 | 802 | ||
| 749 | /* register pcmcia_callback */ | 803 | /* register pcmcia_callback */ |
| 750 | int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c) | 804 | int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c) |
| @@ -828,121 +882,6 @@ int pcmcia_reset_card(struct pcmcia_socket *skt) | |||
| 828 | EXPORT_SYMBOL(pcmcia_reset_card); | 882 | EXPORT_SYMBOL(pcmcia_reset_card); |
| 829 | 883 | ||
| 830 | 884 | ||
| 831 | /* These shut down or wake up a socket. They are sort of user | ||
| 832 | * initiated versions of the APM suspend and resume actions. | ||
| 833 | */ | ||
| 834 | int pcmcia_suspend_card(struct pcmcia_socket *skt) | ||
| 835 | { | ||
| 836 | int ret; | ||
| 837 | |||
| 838 | dev_dbg(&skt->dev, "suspending socket\n"); | ||
| 839 | |||
| 840 | mutex_lock(&skt->skt_mutex); | ||
| 841 | do { | ||
| 842 | if (!(skt->state & SOCKET_PRESENT)) { | ||
| 843 | ret = -ENODEV; | ||
| 844 | break; | ||
| 845 | } | ||
| 846 | if (skt->state & SOCKET_CARDBUS) { | ||
| 847 | ret = -EPERM; | ||
| 848 | break; | ||
| 849 | } | ||
| 850 | if (skt->callback) { | ||
| 851 | ret = skt->callback->suspend(skt); | ||
| 852 | if (ret) | ||
| 853 | break; | ||
| 854 | } | ||
| 855 | ret = socket_suspend(skt); | ||
| 856 | } while (0); | ||
| 857 | mutex_unlock(&skt->skt_mutex); | ||
| 858 | |||
| 859 | return ret; | ||
| 860 | } /* suspend_card */ | ||
| 861 | EXPORT_SYMBOL(pcmcia_suspend_card); | ||
| 862 | |||
| 863 | |||
| 864 | int pcmcia_resume_card(struct pcmcia_socket *skt) | ||
| 865 | { | ||
| 866 | int ret; | ||
| 867 | |||
| 868 | dev_dbg(&skt->dev, "waking up socket\n"); | ||
| 869 | |||
| 870 | mutex_lock(&skt->skt_mutex); | ||
| 871 | do { | ||
| 872 | if (!(skt->state & SOCKET_PRESENT)) { | ||
| 873 | ret = -ENODEV; | ||
| 874 | break; | ||
| 875 | } | ||
| 876 | if (skt->state & SOCKET_CARDBUS) { | ||
| 877 | ret = -EPERM; | ||
| 878 | break; | ||
| 879 | } | ||
| 880 | ret = socket_resume(skt); | ||
| 881 | if (!ret && skt->callback) | ||
| 882 | skt->callback->resume(skt); | ||
| 883 | } while (0); | ||
| 884 | mutex_unlock(&skt->skt_mutex); | ||
| 885 | |||
| 886 | return ret; | ||
| 887 | } /* resume_card */ | ||
| 888 | EXPORT_SYMBOL(pcmcia_resume_card); | ||
| 889 | |||
| 890 | |||
| 891 | /* These handle user requests to eject or insert a card. */ | ||
| 892 | int pcmcia_eject_card(struct pcmcia_socket *skt) | ||
| 893 | { | ||
| 894 | int ret; | ||
| 895 | |||
| 896 | dev_dbg(&skt->dev, "user eject request\n"); | ||
| 897 | |||
| 898 | mutex_lock(&skt->skt_mutex); | ||
| 899 | do { | ||
| 900 | if (!(skt->state & SOCKET_PRESENT)) { | ||
| 901 | ret = -ENODEV; | ||
| 902 | break; | ||
| 903 | } | ||
| 904 | |||
| 905 | ret = send_event(skt, CS_EVENT_EJECTION_REQUEST, CS_EVENT_PRI_LOW); | ||
| 906 | if (ret != 0) { | ||
| 907 | ret = -EINVAL; | ||
| 908 | break; | ||
| 909 | } | ||
| 910 | |||
| 911 | socket_remove(skt); | ||
| 912 | ret = 0; | ||
| 913 | } while (0); | ||
| 914 | mutex_unlock(&skt->skt_mutex); | ||
| 915 | |||
| 916 | return ret; | ||
| 917 | } /* eject_card */ | ||
| 918 | EXPORT_SYMBOL(pcmcia_eject_card); | ||
| 919 | |||
| 920 | |||
| 921 | int pcmcia_insert_card(struct pcmcia_socket *skt) | ||
| 922 | { | ||
| 923 | int ret; | ||
| 924 | |||
| 925 | dev_dbg(&skt->dev, "user insert request\n"); | ||
| 926 | |||
| 927 | mutex_lock(&skt->skt_mutex); | ||
| 928 | do { | ||
| 929 | if (skt->state & SOCKET_PRESENT) { | ||
| 930 | ret = -EBUSY; | ||
| 931 | break; | ||
| 932 | } | ||
| 933 | if (socket_insert(skt) == -ENODEV) { | ||
| 934 | ret = -ENODEV; | ||
| 935 | break; | ||
| 936 | } | ||
| 937 | ret = 0; | ||
| 938 | } while (0); | ||
| 939 | mutex_unlock(&skt->skt_mutex); | ||
| 940 | |||
| 941 | return ret; | ||
| 942 | } /* insert_card */ | ||
| 943 | EXPORT_SYMBOL(pcmcia_insert_card); | ||
| 944 | |||
| 945 | |||
| 946 | static int pcmcia_socket_uevent(struct device *dev, | 885 | static int pcmcia_socket_uevent(struct device *dev, |
| 947 | struct kobj_uevent_env *env) | 886 | struct kobj_uevent_env *env) |
| 948 | { | 887 | { |
diff --git a/drivers/pcmcia/cs_internal.h b/drivers/pcmcia/cs_internal.h index bd386d77845e..127c97acf849 100644 --- a/drivers/pcmcia/cs_internal.h +++ b/drivers/pcmcia/cs_internal.h | |||
| @@ -124,11 +124,11 @@ extern struct class pcmcia_socket_class; | |||
| 124 | int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c); | 124 | int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c); |
| 125 | struct pcmcia_socket *pcmcia_get_socket_by_nr(unsigned int nr); | 125 | struct pcmcia_socket *pcmcia_get_socket_by_nr(unsigned int nr); |
| 126 | 126 | ||
| 127 | int pcmcia_suspend_card(struct pcmcia_socket *skt); | 127 | void pcmcia_parse_uevents(struct pcmcia_socket *socket, unsigned int events); |
| 128 | int pcmcia_resume_card(struct pcmcia_socket *skt); | 128 | #define PCMCIA_UEVENT_EJECT 0x0001 |
| 129 | 129 | #define PCMCIA_UEVENT_INSERT 0x0002 | |
| 130 | int pcmcia_eject_card(struct pcmcia_socket *skt); | 130 | #define PCMCIA_UEVENT_SUSPEND 0x0004 |
| 131 | int pcmcia_insert_card(struct pcmcia_socket *skt); | 131 | #define PCMCIA_UEVENT_RESUME 0x0008 |
| 132 | 132 | ||
| 133 | struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt); | 133 | struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt); |
| 134 | void pcmcia_put_socket(struct pcmcia_socket *skt); | 134 | void pcmcia_put_socket(struct pcmcia_socket *skt); |
diff --git a/drivers/pcmcia/pcmcia_ioctl.c b/drivers/pcmcia/pcmcia_ioctl.c index 96fd236f52a9..13a7132cf688 100644 --- a/drivers/pcmcia/pcmcia_ioctl.c +++ b/drivers/pcmcia/pcmcia_ioctl.c | |||
| @@ -925,16 +925,16 @@ static int ds_ioctl(struct inode *inode, struct file *file, | |||
| 925 | ret = pccard_validate_cis(s, &buf->cisinfo.Chains); | 925 | ret = pccard_validate_cis(s, &buf->cisinfo.Chains); |
| 926 | break; | 926 | break; |
| 927 | case DS_SUSPEND_CARD: | 927 | case DS_SUSPEND_CARD: |
| 928 | ret = pcmcia_suspend_card(s); | 928 | pcmcia_parse_uevents(s, PCMCIA_UEVENT_SUSPEND); |
| 929 | break; | 929 | break; |
| 930 | case DS_RESUME_CARD: | 930 | case DS_RESUME_CARD: |
| 931 | ret = pcmcia_resume_card(s); | 931 | pcmcia_parse_uevents(s, PCMCIA_UEVENT_RESUME); |
| 932 | break; | 932 | break; |
| 933 | case DS_EJECT_CARD: | 933 | case DS_EJECT_CARD: |
| 934 | err = pcmcia_eject_card(s); | 934 | pcmcia_parse_uevents(s, PCMCIA_UEVENT_EJECT); |
| 935 | break; | 935 | break; |
| 936 | case DS_INSERT_CARD: | 936 | case DS_INSERT_CARD: |
| 937 | err = pcmcia_insert_card(s); | 937 | pcmcia_parse_uevents(s, PCMCIA_UEVENT_INSERT); |
| 938 | break; | 938 | break; |
| 939 | case DS_ACCESS_CONFIGURATION_REGISTER: | 939 | case DS_ACCESS_CONFIGURATION_REGISTER: |
| 940 | if ((buf->conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) { | 940 | if ((buf->conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) { |
diff --git a/drivers/pcmcia/socket_sysfs.c b/drivers/pcmcia/socket_sysfs.c index e8826df00a36..fba0e30183f4 100644 --- a/drivers/pcmcia/socket_sysfs.c +++ b/drivers/pcmcia/socket_sysfs.c | |||
| @@ -88,15 +88,14 @@ static DEVICE_ATTR(card_vcc, 0444, pccard_show_vcc, NULL); | |||
| 88 | static ssize_t pccard_store_insert(struct device *dev, struct device_attribute *attr, | 88 | static ssize_t pccard_store_insert(struct device *dev, struct device_attribute *attr, |
| 89 | const char *buf, size_t count) | 89 | const char *buf, size_t count) |
| 90 | { | 90 | { |
| 91 | ssize_t ret; | ||
| 92 | struct pcmcia_socket *s = to_socket(dev); | 91 | struct pcmcia_socket *s = to_socket(dev); |
| 93 | 92 | ||
| 94 | if (!count) | 93 | if (!count) |
| 95 | return -EINVAL; | 94 | return -EINVAL; |
| 96 | 95 | ||
| 97 | ret = pcmcia_insert_card(s); | 96 | pcmcia_parse_uevents(s, PCMCIA_UEVENT_INSERT); |
| 98 | 97 | ||
| 99 | return ret ? ret : count; | 98 | return count; |
| 100 | } | 99 | } |
| 101 | static DEVICE_ATTR(card_insert, 0200, NULL, pccard_store_insert); | 100 | static DEVICE_ATTR(card_insert, 0200, NULL, pccard_store_insert); |
| 102 | 101 | ||
| @@ -113,18 +112,22 @@ static ssize_t pccard_store_card_pm_state(struct device *dev, | |||
| 113 | struct device_attribute *attr, | 112 | struct device_attribute *attr, |
| 114 | const char *buf, size_t count) | 113 | const char *buf, size_t count) |
| 115 | { | 114 | { |
| 116 | ssize_t ret = -EINVAL; | ||
| 117 | struct pcmcia_socket *s = to_socket(dev); | 115 | struct pcmcia_socket *s = to_socket(dev); |
| 116 | ssize_t ret = count; | ||
| 118 | 117 | ||
| 119 | if (!count) | 118 | if (!count) |
| 120 | return -EINVAL; | 119 | return -EINVAL; |
| 121 | 120 | ||
| 122 | if (!(s->state & SOCKET_SUSPEND) && !strncmp(buf, "off", 3)) | 121 | if (!strncmp(buf, "off", 3)) |
| 123 | ret = pcmcia_suspend_card(s); | 122 | pcmcia_parse_uevents(s, PCMCIA_UEVENT_SUSPEND); |
| 124 | else if ((s->state & SOCKET_SUSPEND) && !strncmp(buf, "on", 2)) | 123 | else { |
| 125 | ret = pcmcia_resume_card(s); | 124 | if (!strncmp(buf, "on", 2)) |
| 125 | pcmcia_parse_uevents(s, PCMCIA_UEVENT_RESUME); | ||
| 126 | else | ||
| 127 | ret = -EINVAL; | ||
| 128 | } | ||
| 126 | 129 | ||
| 127 | return ret ? -ENODEV : count; | 130 | return ret; |
| 128 | } | 131 | } |
| 129 | static DEVICE_ATTR(card_pm_state, 0644, pccard_show_card_pm_state, pccard_store_card_pm_state); | 132 | static DEVICE_ATTR(card_pm_state, 0644, pccard_show_card_pm_state, pccard_store_card_pm_state); |
| 130 | 133 | ||
| @@ -132,15 +135,14 @@ static ssize_t pccard_store_eject(struct device *dev, | |||
| 132 | struct device_attribute *attr, | 135 | struct device_attribute *attr, |
| 133 | const char *buf, size_t count) | 136 | const char *buf, size_t count) |
| 134 | { | 137 | { |
| 135 | ssize_t ret; | ||
| 136 | struct pcmcia_socket *s = to_socket(dev); | 138 | struct pcmcia_socket *s = to_socket(dev); |
| 137 | 139 | ||
| 138 | if (!count) | 140 | if (!count) |
| 139 | return -EINVAL; | 141 | return -EINVAL; |
| 140 | 142 | ||
| 141 | ret = pcmcia_eject_card(s); | 143 | pcmcia_parse_uevents(s, PCMCIA_UEVENT_EJECT); |
| 142 | 144 | ||
| 143 | return ret ? ret : count; | 145 | return count; |
| 144 | } | 146 | } |
| 145 | static DEVICE_ATTR(card_eject, 0200, NULL, pccard_store_eject); | 147 | static DEVICE_ATTR(card_eject, 0200, NULL, pccard_store_eject); |
| 146 | 148 | ||
diff --git a/include/pcmcia/ss.h b/include/pcmcia/ss.h index cfaccc224b50..ea5dec8c0d76 100644 --- a/include/pcmcia/ss.h +++ b/include/pcmcia/ss.h | |||
| @@ -200,13 +200,14 @@ struct pcmcia_socket { | |||
| 200 | struct task_struct *thread; | 200 | struct task_struct *thread; |
| 201 | struct completion thread_done; | 201 | struct completion thread_done; |
| 202 | unsigned int thread_events; | 202 | unsigned int thread_events; |
| 203 | unsigned int sysfs_events; | ||
| 203 | 204 | ||
| 204 | /* For the non-trivial interaction between these locks, | 205 | /* For the non-trivial interaction between these locks, |
| 205 | * see Documentation/pcmcia/locking.txt */ | 206 | * see Documentation/pcmcia/locking.txt */ |
| 206 | struct mutex skt_mutex; | 207 | struct mutex skt_mutex; |
| 207 | struct mutex ops_mutex; | 208 | struct mutex ops_mutex; |
| 208 | 209 | ||
| 209 | /* protects thread_events */ | 210 | /* protects thread_events and sysfs_events */ |
| 210 | spinlock_t thread_lock; | 211 | spinlock_t thread_lock; |
| 211 | 212 | ||
| 212 | /* pcmcia (16-bit) */ | 213 | /* pcmcia (16-bit) */ |
