diff options
author | Inaky Perez-Gonzalez <inaky@linux.intel.com> | 2009-09-14 17:10:16 -0400 |
---|---|---|
committer | Inaky Perez-Gonzalez <inaky@linux.intel.com> | 2009-10-19 02:56:02 -0400 |
commit | 7b43ca708a767a5f68eeeb732c569c0f11a7d6f7 (patch) | |
tree | 8811c30691a5e4355c56e9b52a7f8fb9fe9108fb /drivers | |
parent | 3ef6129e57b04c116b1907b72c7a20720e6dde75 (diff) |
wimax/i2400m: cache firmware on system suspend
In preparation for a reset_resume implementation, have the firmware
image be cached in memory when the system goes to suspend and released
when out.
This is needed in case the device resets during suspend; the driver
can't load firmware until resume is completed or bad deadlocks
happen.
The modus operandi for this was copied from the Orinoco USB driver.
The caching is done with a kobject to avoid race conditions when
releasing it. The fw loader path is altered only to first check for a
cached image before trying to load from disk. A Power Management event
notifier is register to call i2400m_fw_cache() or i2400m_fw_uncache()
which take care of the actual cache management.
Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/wimax/i2400m/driver.c | 51 | ||||
-rw-r--r-- | drivers/net/wimax/i2400m/fw.c | 159 | ||||
-rw-r--r-- | drivers/net/wimax/i2400m/i2400m.h | 16 |
3 files changed, 213 insertions, 13 deletions
diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c index f07d8527b03b..07d12be0cf81 100644 --- a/drivers/net/wimax/i2400m/driver.c +++ b/drivers/net/wimax/i2400m/driver.c | |||
@@ -66,6 +66,7 @@ | |||
66 | #include <linux/wimax/i2400m.h> | 66 | #include <linux/wimax/i2400m.h> |
67 | #include <linux/module.h> | 67 | #include <linux/module.h> |
68 | #include <linux/moduleparam.h> | 68 | #include <linux/moduleparam.h> |
69 | #include <linux/suspend.h> | ||
69 | 70 | ||
70 | #define D_SUBMODULE driver | 71 | #define D_SUBMODULE driver |
71 | #include "debug-levels.h" | 72 | #include "debug-levels.h" |
@@ -555,6 +556,51 @@ void i2400m_dev_stop(struct i2400m *i2400m) | |||
555 | 556 | ||
556 | 557 | ||
557 | /* | 558 | /* |
559 | * Listen to PM events to cache the firmware before suspend/hibernation | ||
560 | * | ||
561 | * When the device comes out of suspend, it might go into reset and | ||
562 | * firmware has to be uploaded again. At resume, most of the times, we | ||
563 | * can't load firmware images from disk, so we need to cache it. | ||
564 | * | ||
565 | * i2400m_fw_cache() will allocate a kobject and attach the firmware | ||
566 | * to it; that way we don't have to worry too much about the fw loader | ||
567 | * hitting a race condition. | ||
568 | * | ||
569 | * Note: modus operandi stolen from the Orinoco driver; thx. | ||
570 | */ | ||
571 | static | ||
572 | int i2400m_pm_notifier(struct notifier_block *notifier, | ||
573 | unsigned long pm_event, | ||
574 | void *unused) | ||
575 | { | ||
576 | struct i2400m *i2400m = | ||
577 | container_of(notifier, struct i2400m, pm_notifier); | ||
578 | struct device *dev = i2400m_dev(i2400m); | ||
579 | |||
580 | d_fnstart(3, dev, "(i2400m %p pm_event %lx)\n", i2400m, pm_event); | ||
581 | switch (pm_event) { | ||
582 | case PM_HIBERNATION_PREPARE: | ||
583 | case PM_SUSPEND_PREPARE: | ||
584 | i2400m_fw_cache(i2400m); | ||
585 | break; | ||
586 | case PM_POST_RESTORE: | ||
587 | /* Restore from hibernation failed. We need to clean | ||
588 | * up in exactly the same way, so fall through. */ | ||
589 | case PM_POST_HIBERNATION: | ||
590 | case PM_POST_SUSPEND: | ||
591 | i2400m_fw_uncache(i2400m); | ||
592 | break; | ||
593 | |||
594 | case PM_RESTORE_PREPARE: | ||
595 | default: | ||
596 | break; | ||
597 | } | ||
598 | d_fnend(3, dev, "(i2400m %p pm_event %lx) = void\n", i2400m, pm_event); | ||
599 | return NOTIFY_DONE; | ||
600 | } | ||
601 | |||
602 | |||
603 | /* | ||
558 | * The device has rebooted; fix up the device and the driver | 604 | * The device has rebooted; fix up the device and the driver |
559 | * | 605 | * |
560 | * Tear down the driver communication with the device, reload the | 606 | * Tear down the driver communication with the device, reload the |
@@ -738,6 +784,9 @@ int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) | |||
738 | goto error_read_mac_addr; | 784 | goto error_read_mac_addr; |
739 | random_ether_addr(i2400m->src_mac_addr); | 785 | random_ether_addr(i2400m->src_mac_addr); |
740 | 786 | ||
787 | i2400m->pm_notifier.notifier_call = i2400m_pm_notifier; | ||
788 | register_pm_notifier(&i2400m->pm_notifier); | ||
789 | |||
741 | result = register_netdev(net_dev); /* Okey dokey, bring it up */ | 790 | result = register_netdev(net_dev); /* Okey dokey, bring it up */ |
742 | if (result < 0) { | 791 | if (result < 0) { |
743 | dev_err(dev, "cannot register i2400m network device: %d\n", | 792 | dev_err(dev, "cannot register i2400m network device: %d\n", |
@@ -783,6 +832,7 @@ error_wimax_dev_add: | |||
783 | error_dev_start: | 832 | error_dev_start: |
784 | unregister_netdev(net_dev); | 833 | unregister_netdev(net_dev); |
785 | error_register_netdev: | 834 | error_register_netdev: |
835 | unregister_pm_notifier(&i2400m->pm_notifier); | ||
786 | error_read_mac_addr: | 836 | error_read_mac_addr: |
787 | error_bootrom_init: | 837 | error_bootrom_init: |
788 | d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); | 838 | d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); |
@@ -809,6 +859,7 @@ void i2400m_release(struct i2400m *i2400m) | |||
809 | wimax_dev_rm(&i2400m->wimax_dev); | 859 | wimax_dev_rm(&i2400m->wimax_dev); |
810 | i2400m_dev_stop(i2400m); | 860 | i2400m_dev_stop(i2400m); |
811 | unregister_netdev(i2400m->wimax_dev.net_dev); | 861 | unregister_netdev(i2400m->wimax_dev.net_dev); |
862 | unregister_pm_notifier(&i2400m->pm_notifier); | ||
812 | kfree(i2400m->bm_ack_buf); | 863 | kfree(i2400m->bm_ack_buf); |
813 | kfree(i2400m->bm_cmd_buf); | 864 | kfree(i2400m->bm_cmd_buf); |
814 | d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); | 865 | d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); |
diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c index 5719f4a4080f..69f9e45eafbf 100644 --- a/drivers/net/wimax/i2400m/fw.c +++ b/drivers/net/wimax/i2400m/fw.c | |||
@@ -105,6 +105,13 @@ | |||
105 | * read an acknolwedgement from it (or an asynchronous notification) | 105 | * read an acknolwedgement from it (or an asynchronous notification) |
106 | * from it. | 106 | * from it. |
107 | * | 107 | * |
108 | * FIRMWARE LOADING | ||
109 | * | ||
110 | * Note that in some cases, we can't just load a firmware file (for | ||
111 | * example, when resuming). For that, we might cache the firmware | ||
112 | * file. Thus, when doing the bootstrap, if there is a cache firmware | ||
113 | * file, it is used; if not, loading from disk is attempted. | ||
114 | * | ||
108 | * ROADMAP | 115 | * ROADMAP |
109 | * | 116 | * |
110 | * i2400m_barker_db_init Called by i2400m_driver_init() | 117 | * i2400m_barker_db_init Called by i2400m_driver_init() |
@@ -114,9 +121,10 @@ | |||
114 | * | 121 | * |
115 | * i2400m_dev_bootstrap Called by __i2400m_dev_start() | 122 | * i2400m_dev_bootstrap Called by __i2400m_dev_start() |
116 | * request_firmware | 123 | * request_firmware |
117 | * i2400m_fw_check | 124 | * i2400m_fw_bootstrap |
118 | * i2400m_fw_hdr_check | 125 | * i2400m_fw_check |
119 | * i2400m_fw_dnload | 126 | * i2400m_fw_hdr_check |
127 | * i2400m_fw_dnload | ||
120 | * release_firmware | 128 | * release_firmware |
121 | * | 129 | * |
122 | * i2400m_fw_dnload | 130 | * i2400m_fw_dnload |
@@ -141,6 +149,10 @@ | |||
141 | * | 149 | * |
142 | * i2400m_bm_cmd_prepare Used by bus-drivers to prep | 150 | * i2400m_bm_cmd_prepare Used by bus-drivers to prep |
143 | * commands before sending | 151 | * commands before sending |
152 | * | ||
153 | * i2400m_pm_notifier Called on Power Management events | ||
154 | * i2400m_fw_cache | ||
155 | * i2400m_fw_uncache | ||
144 | */ | 156 | */ |
145 | #include <linux/firmware.h> | 157 | #include <linux/firmware.h> |
146 | #include <linux/sched.h> | 158 | #include <linux/sched.h> |
@@ -1459,6 +1471,61 @@ error_dev_rebooted: | |||
1459 | goto hw_reboot; | 1471 | goto hw_reboot; |
1460 | } | 1472 | } |
1461 | 1473 | ||
1474 | static | ||
1475 | int i2400m_fw_bootstrap(struct i2400m *i2400m, const struct firmware *fw, | ||
1476 | enum i2400m_bri flags) | ||
1477 | { | ||
1478 | int ret; | ||
1479 | struct device *dev = i2400m_dev(i2400m); | ||
1480 | const struct i2400m_bcf_hdr *bcf; /* Firmware data */ | ||
1481 | |||
1482 | d_fnstart(5, dev, "(i2400m %p)\n", i2400m); | ||
1483 | bcf = (void *) fw->data; | ||
1484 | ret = i2400m_fw_check(i2400m, bcf, fw->size); | ||
1485 | if (ret >= 0) | ||
1486 | ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); | ||
1487 | if (ret < 0) | ||
1488 | dev_err(dev, "%s: cannot use: %d, skipping\n", | ||
1489 | i2400m->fw_name, ret); | ||
1490 | kfree(i2400m->fw_hdrs); | ||
1491 | i2400m->fw_hdrs = NULL; | ||
1492 | d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); | ||
1493 | return ret; | ||
1494 | } | ||
1495 | |||
1496 | |||
1497 | /* Refcounted container for firmware data */ | ||
1498 | struct i2400m_fw { | ||
1499 | struct kref kref; | ||
1500 | const struct firmware *fw; | ||
1501 | }; | ||
1502 | |||
1503 | |||
1504 | static | ||
1505 | void i2400m_fw_destroy(struct kref *kref) | ||
1506 | { | ||
1507 | struct i2400m_fw *i2400m_fw = | ||
1508 | container_of(kref, struct i2400m_fw, kref); | ||
1509 | release_firmware(i2400m_fw->fw); | ||
1510 | kfree(i2400m_fw); | ||
1511 | } | ||
1512 | |||
1513 | |||
1514 | static | ||
1515 | struct i2400m_fw *i2400m_fw_get(struct i2400m_fw *i2400m_fw) | ||
1516 | { | ||
1517 | if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) | ||
1518 | kref_get(&i2400m_fw->kref); | ||
1519 | return i2400m_fw; | ||
1520 | } | ||
1521 | |||
1522 | |||
1523 | static | ||
1524 | void i2400m_fw_put(struct i2400m_fw *i2400m_fw) | ||
1525 | { | ||
1526 | kref_put(&i2400m_fw->kref, i2400m_fw_destroy); | ||
1527 | } | ||
1528 | |||
1462 | 1529 | ||
1463 | /** | 1530 | /** |
1464 | * i2400m_dev_bootstrap - Bring the device to a known state and upload firmware | 1531 | * i2400m_dev_bootstrap - Bring the device to a known state and upload firmware |
@@ -1479,12 +1546,28 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) | |||
1479 | { | 1546 | { |
1480 | int ret, itr; | 1547 | int ret, itr; |
1481 | struct device *dev = i2400m_dev(i2400m); | 1548 | struct device *dev = i2400m_dev(i2400m); |
1482 | const struct firmware *fw; | 1549 | struct i2400m_fw *i2400m_fw; |
1483 | const struct i2400m_bcf_hdr *bcf; /* Firmware data */ | 1550 | const struct i2400m_bcf_hdr *bcf; /* Firmware data */ |
1551 | const struct firmware *fw; | ||
1484 | const char *fw_name; | 1552 | const char *fw_name; |
1485 | 1553 | ||
1486 | d_fnstart(5, dev, "(i2400m %p)\n", i2400m); | 1554 | d_fnstart(5, dev, "(i2400m %p)\n", i2400m); |
1487 | 1555 | ||
1556 | ret = -ENODEV; | ||
1557 | spin_lock(&i2400m->rx_lock); | ||
1558 | i2400m_fw = i2400m_fw_get(i2400m->fw_cached); | ||
1559 | spin_unlock(&i2400m->rx_lock); | ||
1560 | if (i2400m_fw == (void *) ~0) { | ||
1561 | dev_err(dev, "can't load firmware now!"); | ||
1562 | goto out; | ||
1563 | } else if (i2400m_fw != NULL) { | ||
1564 | dev_info(dev, "firmware %s: loading from cache\n", | ||
1565 | i2400m->fw_name); | ||
1566 | ret = i2400m_fw_bootstrap(i2400m, i2400m_fw->fw, flags); | ||
1567 | i2400m_fw_put(i2400m_fw); | ||
1568 | goto out; | ||
1569 | } | ||
1570 | |||
1488 | /* Load firmware files to memory. */ | 1571 | /* Load firmware files to memory. */ |
1489 | for (itr = 0, bcf = NULL, ret = -ENOENT; ; itr++) { | 1572 | for (itr = 0, bcf = NULL, ret = -ENOENT; ; itr++) { |
1490 | fw_name = i2400m->bus_fw_names[itr]; | 1573 | fw_name = i2400m->bus_fw_names[itr]; |
@@ -1500,21 +1583,71 @@ int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) | |||
1500 | fw_name, ret); | 1583 | fw_name, ret); |
1501 | continue; | 1584 | continue; |
1502 | } | 1585 | } |
1503 | bcf = (void *) fw->data; | ||
1504 | i2400m->fw_name = fw_name; | 1586 | i2400m->fw_name = fw_name; |
1505 | ret = i2400m_fw_check(i2400m, bcf, fw->size); | 1587 | ret = i2400m_fw_bootstrap(i2400m, fw, flags); |
1506 | if (ret >= 0) | ||
1507 | ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); | ||
1508 | if (ret < 0) | ||
1509 | dev_err(dev, "%s: cannot use: %d, skipping\n", | ||
1510 | fw_name, ret); | ||
1511 | kfree(i2400m->fw_hdrs); | ||
1512 | i2400m->fw_hdrs = NULL; | ||
1513 | release_firmware(fw); | 1588 | release_firmware(fw); |
1514 | if (ret >= 0) /* firmware loaded succesfully */ | 1589 | if (ret >= 0) /* firmware loaded succesfully */ |
1515 | break; | 1590 | break; |
1591 | i2400m->fw_name = NULL; | ||
1516 | } | 1592 | } |
1593 | out: | ||
1517 | d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); | 1594 | d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); |
1518 | return ret; | 1595 | return ret; |
1519 | } | 1596 | } |
1520 | EXPORT_SYMBOL_GPL(i2400m_dev_bootstrap); | 1597 | EXPORT_SYMBOL_GPL(i2400m_dev_bootstrap); |
1598 | |||
1599 | |||
1600 | void i2400m_fw_cache(struct i2400m *i2400m) | ||
1601 | { | ||
1602 | int result; | ||
1603 | struct i2400m_fw *i2400m_fw; | ||
1604 | struct device *dev = i2400m_dev(i2400m); | ||
1605 | |||
1606 | /* if there is anything there, free it -- now, this'd be weird */ | ||
1607 | spin_lock(&i2400m->rx_lock); | ||
1608 | i2400m_fw = i2400m->fw_cached; | ||
1609 | spin_unlock(&i2400m->rx_lock); | ||
1610 | if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) { | ||
1611 | i2400m_fw_put(i2400m_fw); | ||
1612 | WARN(1, "%s:%u: still cached fw still present?\n", | ||
1613 | __func__, __LINE__); | ||
1614 | } | ||
1615 | |||
1616 | if (i2400m->fw_name == NULL) { | ||
1617 | dev_err(dev, "firmware n/a: can't cache\n"); | ||
1618 | i2400m_fw = (void *) ~0; | ||
1619 | goto out; | ||
1620 | } | ||
1621 | |||
1622 | i2400m_fw = kzalloc(sizeof(*i2400m_fw), GFP_ATOMIC); | ||
1623 | if (i2400m_fw == NULL) | ||
1624 | goto out; | ||
1625 | kref_init(&i2400m_fw->kref); | ||
1626 | result = request_firmware(&i2400m_fw->fw, i2400m->fw_name, dev); | ||
1627 | if (result < 0) { | ||
1628 | dev_err(dev, "firmware %s: failed to cache: %d\n", | ||
1629 | i2400m->fw_name, result); | ||
1630 | kfree(i2400m_fw); | ||
1631 | i2400m_fw = (void *) ~0; | ||
1632 | } else | ||
1633 | dev_info(dev, "firmware %s: cached\n", i2400m->fw_name); | ||
1634 | out: | ||
1635 | spin_lock(&i2400m->rx_lock); | ||
1636 | i2400m->fw_cached = i2400m_fw; | ||
1637 | spin_unlock(&i2400m->rx_lock); | ||
1638 | } | ||
1639 | |||
1640 | |||
1641 | void i2400m_fw_uncache(struct i2400m *i2400m) | ||
1642 | { | ||
1643 | struct i2400m_fw *i2400m_fw; | ||
1644 | |||
1645 | spin_lock(&i2400m->rx_lock); | ||
1646 | i2400m_fw = i2400m->fw_cached; | ||
1647 | i2400m->fw_cached = NULL; | ||
1648 | spin_unlock(&i2400m->rx_lock); | ||
1649 | |||
1650 | if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) | ||
1651 | i2400m_fw_put(i2400m_fw); | ||
1652 | } | ||
1653 | |||
diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h index 0c165de89b2d..916b1d319299 100644 --- a/drivers/net/wimax/i2400m/i2400m.h +++ b/drivers/net/wimax/i2400m/i2400m.h | |||
@@ -424,11 +424,21 @@ struct i2400m_barker_db; | |||
424 | * @fw_hdrs: NULL terminated array of pointers to the firmware | 424 | * @fw_hdrs: NULL terminated array of pointers to the firmware |
425 | * headers. This is only available during firmware load time. | 425 | * headers. This is only available during firmware load time. |
426 | * | 426 | * |
427 | * @fw_cached: Used to cache firmware when the system goes to | ||
428 | * suspend/standby/hibernation (as on resume we can't read it). If | ||
429 | * NULL, no firmware was cached, read it. If ~0, you can't read | ||
430 | * any firmware files (the system still didn't come out of suspend | ||
431 | * and failed to cache one), so abort; otherwise, a valid cached | ||
432 | * firmware to be used. Access to this variable is protected by | ||
433 | * the spinlock i2400m->rx_lock. | ||
434 | * | ||
427 | * @barker: barker type that the device uses; this is initialized by | 435 | * @barker: barker type that the device uses; this is initialized by |
428 | * i2400m_is_boot_barker() the first time it is called. Then it | 436 | * i2400m_is_boot_barker() the first time it is called. Then it |
429 | * won't change during the life cycle of the device and everytime | 437 | * won't change during the life cycle of the device and everytime |
430 | * a boot barker is received, it is just verified for it being the | 438 | * a boot barker is received, it is just verified for it being the |
431 | * same. | 439 | * same. |
440 | * | ||
441 | * @pm_notifier: used to register for PM events | ||
432 | */ | 442 | */ |
433 | struct i2400m { | 443 | struct i2400m { |
434 | struct wimax_dev wimax_dev; /* FIRST! See doc */ | 444 | struct wimax_dev wimax_dev; /* FIRST! See doc */ |
@@ -495,7 +505,10 @@ struct i2400m { | |||
495 | const char *fw_name; /* name of the current firmware image */ | 505 | const char *fw_name; /* name of the current firmware image */ |
496 | unsigned long fw_version; /* version of the firmware interface */ | 506 | unsigned long fw_version; /* version of the firmware interface */ |
497 | const struct i2400m_bcf_hdr **fw_hdrs; | 507 | const struct i2400m_bcf_hdr **fw_hdrs; |
508 | struct i2400m_fw *fw_cached; /* protected by rx_lock */ | ||
498 | struct i2400m_barker_db *barker; | 509 | struct i2400m_barker_db *barker; |
510 | |||
511 | struct notifier_block pm_notifier; | ||
499 | }; | 512 | }; |
500 | 513 | ||
501 | 514 | ||
@@ -671,6 +684,9 @@ extern void i2400m_tx_release(struct i2400m *); | |||
671 | extern int i2400m_rx_setup(struct i2400m *); | 684 | extern int i2400m_rx_setup(struct i2400m *); |
672 | extern void i2400m_rx_release(struct i2400m *); | 685 | extern void i2400m_rx_release(struct i2400m *); |
673 | 686 | ||
687 | extern void i2400m_fw_cache(struct i2400m *); | ||
688 | extern void i2400m_fw_uncache(struct i2400m *); | ||
689 | |||
674 | extern void i2400m_net_rx(struct i2400m *, struct sk_buff *, unsigned, | 690 | extern void i2400m_net_rx(struct i2400m *, struct sk_buff *, unsigned, |
675 | const void *, int); | 691 | const void *, int); |
676 | extern void i2400m_net_erx(struct i2400m *, struct sk_buff *, | 692 | extern void i2400m_net_erx(struct i2400m *, struct sk_buff *, |