diff options
author | Dan Williams <dan.j.williams@intel.com> | 2011-11-17 20:59:47 -0500 |
---|---|---|
committer | James Bottomley <JBottomley@Parallels.com> | 2012-02-19 14:37:47 -0500 |
commit | 735f7d2fedf57380214221be7bed7f62d729e262 (patch) | |
tree | 067db49c22dcbdf695a6517a6c8664b6bb2c2d32 | |
parent | 6f4e75a49fd07d707995865493b9f452302ae36b (diff) |
[SCSI] libsas: fix domain_device leak
Arrange for the deallocation of a struct domain_device object when it no
longer has:
1/ any children
2/ references by any scsi_targets
3/ references by a lldd
The comment about domain_device lifetime in
Documentation/scsi/libsas.txt is stale as it appears mainline never had
a version of a struct domain_device that was registered as a kobject.
We now manage domain_device reference counts on behalf of external
agents.
Reviewed-by: Jack Wang <jack_wang@usish.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: James Bottomley <JBottomley@Parallels.com>
-rw-r--r-- | Documentation/scsi/libsas.txt | 15 | ||||
-rw-r--r-- | drivers/scsi/libsas/sas_discover.c | 36 | ||||
-rw-r--r-- | drivers/scsi/libsas/sas_expander.c | 10 | ||||
-rw-r--r-- | drivers/scsi/libsas/sas_internal.h | 19 | ||||
-rw-r--r-- | drivers/scsi/libsas/sas_scsi_host.c | 16 | ||||
-rw-r--r-- | include/scsi/libsas.h | 1 |
6 files changed, 56 insertions, 41 deletions
diff --git a/Documentation/scsi/libsas.txt b/Documentation/scsi/libsas.txt index aa54f54c4a50..3cc9c7843e15 100644 --- a/Documentation/scsi/libsas.txt +++ b/Documentation/scsi/libsas.txt | |||
@@ -398,21 +398,6 @@ struct sas_task { | |||
398 | task_done -- callback when the task has finished execution | 398 | task_done -- callback when the task has finished execution |
399 | }; | 399 | }; |
400 | 400 | ||
401 | When an external entity, entity other than the LLDD or the | ||
402 | SAS Layer, wants to work with a struct domain_device, it | ||
403 | _must_ call kobject_get() when getting a handle on the | ||
404 | device and kobject_put() when it is done with the device. | ||
405 | |||
406 | This does two things: | ||
407 | A) implements proper kfree() for the device; | ||
408 | B) increments/decrements the kref for all players: | ||
409 | domain_device | ||
410 | all domain_device's ... (if past an expander) | ||
411 | port | ||
412 | host adapter | ||
413 | pci device | ||
414 | and up the ladder, etc. | ||
415 | |||
416 | DISCOVERY | 401 | DISCOVERY |
417 | --------- | 402 | --------- |
418 | 403 | ||
diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c index 54a5199ceb56..4e649306ef4e 100644 --- a/drivers/scsi/libsas/sas_discover.c +++ b/drivers/scsi/libsas/sas_discover.c | |||
@@ -36,8 +36,6 @@ | |||
36 | 36 | ||
37 | void sas_init_dev(struct domain_device *dev) | 37 | void sas_init_dev(struct domain_device *dev) |
38 | { | 38 | { |
39 | INIT_LIST_HEAD(&dev->siblings); | ||
40 | INIT_LIST_HEAD(&dev->dev_list_node); | ||
41 | switch (dev->dev_type) { | 39 | switch (dev->dev_type) { |
42 | case SAS_END_DEV: | 40 | case SAS_END_DEV: |
43 | break; | 41 | break; |
@@ -73,14 +71,14 @@ static int sas_get_port_device(struct asd_sas_port *port) | |||
73 | struct sas_rphy *rphy; | 71 | struct sas_rphy *rphy; |
74 | struct domain_device *dev; | 72 | struct domain_device *dev; |
75 | 73 | ||
76 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | 74 | dev = sas_alloc_device(); |
77 | if (!dev) | 75 | if (!dev) |
78 | return -ENOMEM; | 76 | return -ENOMEM; |
79 | 77 | ||
80 | spin_lock_irqsave(&port->phy_list_lock, flags); | 78 | spin_lock_irqsave(&port->phy_list_lock, flags); |
81 | if (list_empty(&port->phy_list)) { | 79 | if (list_empty(&port->phy_list)) { |
82 | spin_unlock_irqrestore(&port->phy_list_lock, flags); | 80 | spin_unlock_irqrestore(&port->phy_list_lock, flags); |
83 | kfree(dev); | 81 | sas_put_device(dev); |
84 | return -ENODEV; | 82 | return -ENODEV; |
85 | } | 83 | } |
86 | phy = container_of(port->phy_list.next, struct asd_sas_phy, port_phy_el); | 84 | phy = container_of(port->phy_list.next, struct asd_sas_phy, port_phy_el); |
@@ -130,7 +128,7 @@ static int sas_get_port_device(struct asd_sas_port *port) | |||
130 | } | 128 | } |
131 | 129 | ||
132 | if (!rphy) { | 130 | if (!rphy) { |
133 | kfree(dev); | 131 | sas_put_device(dev); |
134 | return -ENODEV; | 132 | return -ENODEV; |
135 | } | 133 | } |
136 | rphy->identify.phy_identifier = phy->phy->identify.phy_identifier; | 134 | rphy->identify.phy_identifier = phy->phy->identify.phy_identifier; |
@@ -173,6 +171,7 @@ int sas_notify_lldd_dev_found(struct domain_device *dev) | |||
173 | dev_name(sas_ha->dev), | 171 | dev_name(sas_ha->dev), |
174 | SAS_ADDR(dev->sas_addr), res); | 172 | SAS_ADDR(dev->sas_addr), res); |
175 | } | 173 | } |
174 | kref_get(&dev->kref); | ||
176 | } | 175 | } |
177 | return res; | 176 | return res; |
178 | } | 177 | } |
@@ -184,8 +183,10 @@ void sas_notify_lldd_dev_gone(struct domain_device *dev) | |||
184 | struct Scsi_Host *shost = sas_ha->core.shost; | 183 | struct Scsi_Host *shost = sas_ha->core.shost; |
185 | struct sas_internal *i = to_sas_internal(shost->transportt); | 184 | struct sas_internal *i = to_sas_internal(shost->transportt); |
186 | 185 | ||
187 | if (i->dft->lldd_dev_gone) | 186 | if (i->dft->lldd_dev_gone) { |
188 | i->dft->lldd_dev_gone(dev); | 187 | i->dft->lldd_dev_gone(dev); |
188 | sas_put_device(dev); | ||
189 | } | ||
189 | } | 190 | } |
190 | 191 | ||
191 | /* ---------- Common/dispatchers ---------- */ | 192 | /* ---------- Common/dispatchers ---------- */ |
@@ -219,6 +220,20 @@ out_err2: | |||
219 | 220 | ||
220 | /* ---------- Device registration and unregistration ---------- */ | 221 | /* ---------- Device registration and unregistration ---------- */ |
221 | 222 | ||
223 | void sas_free_device(struct kref *kref) | ||
224 | { | ||
225 | struct domain_device *dev = container_of(kref, typeof(*dev), kref); | ||
226 | |||
227 | if (dev->parent) | ||
228 | sas_put_device(dev->parent); | ||
229 | |||
230 | /* remove the phys and ports, everything else should be gone */ | ||
231 | if (dev->dev_type == EDGE_DEV || dev->dev_type == FANOUT_DEV) | ||
232 | kfree(dev->ex_dev.ex_phy); | ||
233 | |||
234 | kfree(dev); | ||
235 | } | ||
236 | |||
222 | static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_device *dev) | 237 | static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_device *dev) |
223 | { | 238 | { |
224 | sas_notify_lldd_dev_gone(dev); | 239 | sas_notify_lldd_dev_gone(dev); |
@@ -230,6 +245,8 @@ static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_d | |||
230 | spin_lock_irq(&port->dev_list_lock); | 245 | spin_lock_irq(&port->dev_list_lock); |
231 | list_del_init(&dev->dev_list_node); | 246 | list_del_init(&dev->dev_list_node); |
232 | spin_unlock_irq(&port->dev_list_lock); | 247 | spin_unlock_irq(&port->dev_list_lock); |
248 | |||
249 | sas_put_device(dev); | ||
233 | } | 250 | } |
234 | 251 | ||
235 | void sas_unregister_dev(struct asd_sas_port *port, struct domain_device *dev) | 252 | void sas_unregister_dev(struct asd_sas_port *port, struct domain_device *dev) |
@@ -239,11 +256,6 @@ void sas_unregister_dev(struct asd_sas_port *port, struct domain_device *dev) | |||
239 | sas_rphy_delete(dev->rphy); | 256 | sas_rphy_delete(dev->rphy); |
240 | dev->rphy = NULL; | 257 | dev->rphy = NULL; |
241 | } | 258 | } |
242 | if (dev->dev_type == EDGE_DEV || dev->dev_type == FANOUT_DEV) { | ||
243 | /* remove the phys and ports, everything else should be gone */ | ||
244 | kfree(dev->ex_dev.ex_phy); | ||
245 | dev->ex_dev.ex_phy = NULL; | ||
246 | } | ||
247 | sas_unregister_common_dev(port, dev); | 259 | sas_unregister_common_dev(port, dev); |
248 | } | 260 | } |
249 | 261 | ||
@@ -322,7 +334,7 @@ static void sas_discover_domain(struct work_struct *work) | |||
322 | list_del_init(&dev->dev_list_node); | 334 | list_del_init(&dev->dev_list_node); |
323 | spin_unlock_irq(&port->dev_list_lock); | 335 | spin_unlock_irq(&port->dev_list_lock); |
324 | 336 | ||
325 | kfree(dev); /* not kobject_register-ed yet */ | 337 | sas_put_device(dev); |
326 | port->port_dev = NULL; | 338 | port->port_dev = NULL; |
327 | } | 339 | } |
328 | 340 | ||
diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 1b831c55ec6e..15d2239a378b 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c | |||
@@ -657,10 +657,11 @@ static struct domain_device *sas_ex_discover_end_dev( | |||
657 | if (phy->attached_sata_host || phy->attached_sata_ps) | 657 | if (phy->attached_sata_host || phy->attached_sata_ps) |
658 | return NULL; | 658 | return NULL; |
659 | 659 | ||
660 | child = kzalloc(sizeof(*child), GFP_KERNEL); | 660 | child = sas_alloc_device(); |
661 | if (!child) | 661 | if (!child) |
662 | return NULL; | 662 | return NULL; |
663 | 663 | ||
664 | kref_get(&parent->kref); | ||
664 | child->parent = parent; | 665 | child->parent = parent; |
665 | child->port = parent->port; | 666 | child->port = parent->port; |
666 | child->iproto = phy->attached_iproto; | 667 | child->iproto = phy->attached_iproto; |
@@ -762,7 +763,7 @@ static struct domain_device *sas_ex_discover_end_dev( | |||
762 | sas_port_delete(phy->port); | 763 | sas_port_delete(phy->port); |
763 | out_err: | 764 | out_err: |
764 | phy->port = NULL; | 765 | phy->port = NULL; |
765 | kfree(child); | 766 | sas_put_device(child); |
766 | return NULL; | 767 | return NULL; |
767 | } | 768 | } |
768 | 769 | ||
@@ -809,7 +810,7 @@ static struct domain_device *sas_ex_discover_expander( | |||
809 | phy->attached_phy_id); | 810 | phy->attached_phy_id); |
810 | return NULL; | 811 | return NULL; |
811 | } | 812 | } |
812 | child = kzalloc(sizeof(*child), GFP_KERNEL); | 813 | child = sas_alloc_device(); |
813 | if (!child) | 814 | if (!child) |
814 | return NULL; | 815 | return NULL; |
815 | 816 | ||
@@ -835,6 +836,7 @@ static struct domain_device *sas_ex_discover_expander( | |||
835 | child->rphy = rphy; | 836 | child->rphy = rphy; |
836 | edev = rphy_to_expander_device(rphy); | 837 | edev = rphy_to_expander_device(rphy); |
837 | child->dev_type = phy->attached_dev_type; | 838 | child->dev_type = phy->attached_dev_type; |
839 | kref_get(&parent->kref); | ||
838 | child->parent = parent; | 840 | child->parent = parent; |
839 | child->port = port; | 841 | child->port = port; |
840 | child->iproto = phy->attached_iproto; | 842 | child->iproto = phy->attached_iproto; |
@@ -858,7 +860,7 @@ static struct domain_device *sas_ex_discover_expander( | |||
858 | spin_lock_irq(&parent->port->dev_list_lock); | 860 | spin_lock_irq(&parent->port->dev_list_lock); |
859 | list_del(&child->dev_list_node); | 861 | list_del(&child->dev_list_node); |
860 | spin_unlock_irq(&parent->port->dev_list_lock); | 862 | spin_unlock_irq(&parent->port->dev_list_lock); |
861 | kfree(child); | 863 | sas_put_device(child); |
862 | return NULL; | 864 | return NULL; |
863 | } | 865 | } |
864 | list_add_tail(&child->siblings, &parent->ex_dev.children); | 866 | list_add_tail(&child->siblings, &parent->ex_dev.children); |
diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 14e21b5fb8ba..0d43408196f9 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h | |||
@@ -76,6 +76,8 @@ struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); | |||
76 | 76 | ||
77 | void sas_hae_reset(struct work_struct *work); | 77 | void sas_hae_reset(struct work_struct *work); |
78 | 78 | ||
79 | void sas_free_device(struct kref *kref); | ||
80 | |||
79 | #ifdef CONFIG_SCSI_SAS_HOST_SMP | 81 | #ifdef CONFIG_SCSI_SAS_HOST_SMP |
80 | extern int sas_smp_host_handler(struct Scsi_Host *shost, struct request *req, | 82 | extern int sas_smp_host_handler(struct Scsi_Host *shost, struct request *req, |
81 | struct request *rsp); | 83 | struct request *rsp); |
@@ -161,4 +163,21 @@ static inline void sas_add_parent_port(struct domain_device *dev, int phy_id) | |||
161 | sas_port_add_phy(ex->parent_port, ex_phy->phy); | 163 | sas_port_add_phy(ex->parent_port, ex_phy->phy); |
162 | } | 164 | } |
163 | 165 | ||
166 | static inline struct domain_device *sas_alloc_device(void) | ||
167 | { | ||
168 | struct domain_device *dev = kzalloc(sizeof(*dev), GFP_KERNEL); | ||
169 | |||
170 | if (dev) { | ||
171 | INIT_LIST_HEAD(&dev->siblings); | ||
172 | INIT_LIST_HEAD(&dev->dev_list_node); | ||
173 | kref_init(&dev->kref); | ||
174 | } | ||
175 | return dev; | ||
176 | } | ||
177 | |||
178 | static inline void sas_put_device(struct domain_device *dev) | ||
179 | { | ||
180 | kref_put(&dev->kref, sas_free_device); | ||
181 | } | ||
182 | |||
164 | #endif /* _SAS_INTERNAL_H_ */ | 183 | #endif /* _SAS_INTERNAL_H_ */ |
diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index e95e5e17bd88..2a163c73fd8b 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c | |||
@@ -737,16 +737,10 @@ struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy) | |||
737 | return found_dev; | 737 | return found_dev; |
738 | } | 738 | } |
739 | 739 | ||
740 | static inline struct domain_device *sas_find_target(struct scsi_target *starget) | ||
741 | { | ||
742 | struct sas_rphy *rphy = dev_to_rphy(starget->dev.parent); | ||
743 | |||
744 | return sas_find_dev_by_rphy(rphy); | ||
745 | } | ||
746 | |||
747 | int sas_target_alloc(struct scsi_target *starget) | 740 | int sas_target_alloc(struct scsi_target *starget) |
748 | { | 741 | { |
749 | struct domain_device *found_dev = sas_find_target(starget); | 742 | struct sas_rphy *rphy = dev_to_rphy(starget->dev.parent); |
743 | struct domain_device *found_dev = sas_find_dev_by_rphy(rphy); | ||
750 | int res; | 744 | int res; |
751 | 745 | ||
752 | if (!found_dev) | 746 | if (!found_dev) |
@@ -758,6 +752,7 @@ int sas_target_alloc(struct scsi_target *starget) | |||
758 | return res; | 752 | return res; |
759 | } | 753 | } |
760 | 754 | ||
755 | kref_get(&found_dev->kref); | ||
761 | starget->hostdata = found_dev; | 756 | starget->hostdata = found_dev; |
762 | return 0; | 757 | return 0; |
763 | } | 758 | } |
@@ -1047,7 +1042,7 @@ int sas_slave_alloc(struct scsi_device *scsi_dev) | |||
1047 | 1042 | ||
1048 | void sas_target_destroy(struct scsi_target *starget) | 1043 | void sas_target_destroy(struct scsi_target *starget) |
1049 | { | 1044 | { |
1050 | struct domain_device *found_dev = sas_find_target(starget); | 1045 | struct domain_device *found_dev = starget->hostdata; |
1051 | 1046 | ||
1052 | if (!found_dev) | 1047 | if (!found_dev) |
1053 | return; | 1048 | return; |
@@ -1055,7 +1050,8 @@ void sas_target_destroy(struct scsi_target *starget) | |||
1055 | if (dev_is_sata(found_dev)) | 1050 | if (dev_is_sata(found_dev)) |
1056 | ata_sas_port_destroy(found_dev->sata_dev.ap); | 1051 | ata_sas_port_destroy(found_dev->sata_dev.ap); |
1057 | 1052 | ||
1058 | return; | 1053 | starget->hostdata = NULL; |
1054 | sas_put_device(found_dev); | ||
1059 | } | 1055 | } |
1060 | 1056 | ||
1061 | static void sas_parse_addr(u8 *sas_addr, const char *p) | 1057 | static void sas_parse_addr(u8 *sas_addr, const char *p) |
diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index 2b14348336d6..7ecb5c1c0851 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h | |||
@@ -206,6 +206,7 @@ struct domain_device { | |||
206 | 206 | ||
207 | void *lldd_dev; | 207 | void *lldd_dev; |
208 | int gone; | 208 | int gone; |
209 | struct kref kref; | ||
209 | }; | 210 | }; |
210 | 211 | ||
211 | struct sas_discovery_event { | 212 | struct sas_discovery_event { |