Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 1 | /* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 2 | * |
| 3 | * This program is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License version 2 and |
| 5 | * only version 2 as published by the Free Software Foundation. |
| 6 | * |
| 7 | * This program is distributed in the hope that it will be useful, |
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | * GNU General Public License for more details. |
| 11 | */ |
| 12 | |
| 13 | #include <linux/jiffies.h> |
| 14 | #include <linux/kernel.h> |
| 15 | #include <linux/slab.h> |
| 16 | #include <linux/spinlock.h> |
| 17 | #include <linux/timer.h> |
| 18 | #include <linux/unistd.h> |
| 19 | #include <linux/workqueue.h> |
| 20 | #include <linux/ipa.h> |
| 21 | #include "ipa_rm_i.h" |
| 22 | |
Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 23 | #define MAX_WS_NAME 20 |
| 24 | |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 25 | /** |
| 26 | * struct ipa_rm_it_private - IPA RM Inactivity Timer private |
| 27 | * data |
| 28 | * @initied: indicates if instance was initialized |
| 29 | * @lock - spinlock for mutual exclusion |
| 30 | * @resource_name - resource name |
| 31 | * @work: delayed work object for running delayed releas |
| 32 | * function |
| 33 | * @resource_requested: boolean flag indicates if resource was requested |
| 34 | * @reschedule_work: boolean flag indicates to not release and to |
| 35 | * reschedule the release work. |
| 36 | * @work_in_progress: boolean flag indicates is release work was scheduled. |
| 37 | * @jiffies: number of jiffies for timeout |
| 38 | * |
| 39 | * WWAN private - holds all relevant info about WWAN driver |
| 40 | */ |
| 41 | struct ipa_rm_it_private { |
| 42 | bool initied; |
| 43 | enum ipa_rm_resource_name resource_name; |
| 44 | spinlock_t lock; |
| 45 | struct delayed_work work; |
| 46 | bool resource_requested; |
| 47 | bool reschedule_work; |
| 48 | bool work_in_progress; |
| 49 | unsigned long jiffies; |
Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 50 | struct wakeup_source w_lock; |
| 51 | char w_lock_name[MAX_WS_NAME]; |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 52 | }; |
| 53 | |
| 54 | static struct ipa_rm_it_private ipa_rm_it_handles[IPA_RM_RESOURCE_MAX]; |
| 55 | |
| 56 | /** |
| 57 | * ipa_rm_inactivity_timer_func() - called when timer expired in |
| 58 | * the context of the shared workqueue. Checks internally if |
| 59 | * reschedule_work flag is set. In case it is not set this function calls to |
| 60 | * ipa_rm_release_resource(). In case reschedule_work is set this function |
| 61 | * reschedule the work. This flag is cleared cleared when |
| 62 | * calling to ipa_rm_inactivity_timer_release_resource(). |
| 63 | * |
| 64 | * @work: work object provided by the work queue |
| 65 | * |
| 66 | * Return codes: |
| 67 | * None |
| 68 | */ |
| 69 | static void ipa_rm_inactivity_timer_func(struct work_struct *work) |
| 70 | { |
| 71 | |
| 72 | struct ipa_rm_it_private *me = container_of(to_delayed_work(work), |
| 73 | struct ipa_rm_it_private, |
| 74 | work); |
| 75 | unsigned long flags; |
| 76 | |
| 77 | IPA_RM_DBG_LOW("%s: timer expired for resource %d!\n", __func__, |
| 78 | me->resource_name); |
| 79 | |
| 80 | spin_lock_irqsave( |
| 81 | &ipa_rm_it_handles[me->resource_name].lock, flags); |
| 82 | if (ipa_rm_it_handles[me->resource_name].reschedule_work) { |
| 83 | IPA_RM_DBG_LOW("%s: setting delayed work\n", __func__); |
| 84 | ipa_rm_it_handles[me->resource_name].reschedule_work = false; |
| 85 | queue_delayed_work(system_unbound_wq, |
| 86 | &ipa_rm_it_handles[me->resource_name].work, |
| 87 | ipa_rm_it_handles[me->resource_name].jiffies); |
| 88 | } else if (ipa_rm_it_handles[me->resource_name].resource_requested) { |
| 89 | IPA_RM_DBG_LOW("%s: not calling release\n", __func__); |
| 90 | ipa_rm_it_handles[me->resource_name].work_in_progress = false; |
| 91 | } else { |
| 92 | IPA_RM_DBG_LOW("%s: calling release_resource on resource %d!\n", |
| 93 | __func__, me->resource_name); |
Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 94 | __pm_relax(&ipa_rm_it_handles[me->resource_name].w_lock); |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 95 | ipa_rm_release_resource(me->resource_name); |
| 96 | ipa_rm_it_handles[me->resource_name].work_in_progress = false; |
| 97 | } |
| 98 | spin_unlock_irqrestore( |
| 99 | &ipa_rm_it_handles[me->resource_name].lock, flags); |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * ipa_rm_inactivity_timer_init() - Init function for IPA RM |
| 104 | * inactivity timer. This function shall be called prior calling |
| 105 | * any other API of IPA RM inactivity timer. |
| 106 | * |
| 107 | * @resource_name: Resource name. @see ipa_rm.h |
| 108 | * @msecs: time in miliseccond, that IPA RM inactivity timer |
| 109 | * shall wait prior calling to ipa_rm_release_resource(). |
| 110 | * |
| 111 | * Return codes: |
| 112 | * 0: success |
| 113 | * -EINVAL: invalid parameters |
| 114 | */ |
| 115 | int ipa_rm_inactivity_timer_init(enum ipa_rm_resource_name resource_name, |
| 116 | unsigned long msecs) |
| 117 | { |
Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 118 | struct wakeup_source *pwlock; |
| 119 | char *name; |
| 120 | |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 121 | IPA_RM_DBG_LOW("%s: resource %d\n", __func__, resource_name); |
| 122 | |
| 123 | if (resource_name < 0 || |
| 124 | resource_name >= IPA_RM_RESOURCE_MAX) { |
| 125 | IPA_RM_ERR("%s: Invalid parameter\n", __func__); |
| 126 | return -EINVAL; |
| 127 | } |
| 128 | |
| 129 | if (ipa_rm_it_handles[resource_name].initied) { |
| 130 | IPA_RM_ERR("%s: resource %d already inited\n", |
| 131 | __func__, resource_name); |
| 132 | return -EINVAL; |
| 133 | } |
| 134 | |
| 135 | spin_lock_init(&ipa_rm_it_handles[resource_name].lock); |
| 136 | ipa_rm_it_handles[resource_name].resource_name = resource_name; |
| 137 | ipa_rm_it_handles[resource_name].jiffies = msecs_to_jiffies(msecs); |
| 138 | ipa_rm_it_handles[resource_name].resource_requested = false; |
| 139 | ipa_rm_it_handles[resource_name].reschedule_work = false; |
| 140 | ipa_rm_it_handles[resource_name].work_in_progress = false; |
Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 141 | pwlock = &(ipa_rm_it_handles[resource_name].w_lock); |
| 142 | name = ipa_rm_it_handles[resource_name].w_lock_name; |
| 143 | snprintf(name, MAX_WS_NAME, "IPA_RM%d\n", resource_name); |
| 144 | wakeup_source_init(pwlock, name); |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 145 | INIT_DELAYED_WORK(&ipa_rm_it_handles[resource_name].work, |
| 146 | ipa_rm_inactivity_timer_func); |
| 147 | ipa_rm_it_handles[resource_name].initied = 1; |
| 148 | |
| 149 | return 0; |
| 150 | } |
| 151 | EXPORT_SYMBOL(ipa_rm_inactivity_timer_init); |
| 152 | |
| 153 | /** |
| 154 | * ipa_rm_inactivity_timer_destroy() - De-Init function for IPA |
| 155 | * RM inactivity timer. |
| 156 | * |
| 157 | * @resource_name: Resource name. @see ipa_rm.h |
| 158 | * |
| 159 | * Return codes: |
| 160 | * 0: success |
| 161 | * -EINVAL: invalid parameters |
| 162 | */ |
| 163 | int ipa_rm_inactivity_timer_destroy(enum ipa_rm_resource_name resource_name) |
| 164 | { |
Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 165 | struct wakeup_source *pwlock; |
| 166 | |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 167 | IPA_RM_DBG_LOW("%s: resource %d\n", __func__, resource_name); |
| 168 | |
| 169 | if (resource_name < 0 || |
| 170 | resource_name >= IPA_RM_RESOURCE_MAX) { |
| 171 | IPA_RM_ERR("%s: Invalid parameter\n", __func__); |
| 172 | return -EINVAL; |
| 173 | } |
| 174 | |
| 175 | if (!ipa_rm_it_handles[resource_name].initied) { |
| 176 | IPA_RM_ERR("%s: resource %d already inited\n", |
| 177 | __func__, resource_name); |
| 178 | return -EINVAL; |
| 179 | } |
| 180 | |
| 181 | cancel_delayed_work_sync(&ipa_rm_it_handles[resource_name].work); |
Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 182 | pwlock = &(ipa_rm_it_handles[resource_name].w_lock); |
| 183 | wakeup_source_trash(pwlock); |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 184 | |
| 185 | memset(&ipa_rm_it_handles[resource_name], 0, |
| 186 | sizeof(struct ipa_rm_it_private)); |
| 187 | |
| 188 | return 0; |
| 189 | } |
| 190 | EXPORT_SYMBOL(ipa_rm_inactivity_timer_destroy); |
| 191 | |
| 192 | /** |
| 193 | * ipa_rm_inactivity_timer_request_resource() - Same as |
| 194 | * ipa_rm_request_resource(), with a difference that calling to |
| 195 | * this function will also cancel the inactivity timer, if |
| 196 | * ipa_rm_inactivity_timer_release_resource() was called earlier. |
| 197 | * |
| 198 | * @resource_name: Resource name. @see ipa_rm.h |
| 199 | * |
| 200 | * Return codes: |
| 201 | * 0: success |
| 202 | * -EINVAL: invalid parameters |
| 203 | */ |
| 204 | int ipa_rm_inactivity_timer_request_resource( |
| 205 | enum ipa_rm_resource_name resource_name) |
| 206 | { |
| 207 | int ret; |
| 208 | unsigned long flags; |
| 209 | |
| 210 | IPA_RM_DBG_LOW("%s: resource %d\n", __func__, resource_name); |
| 211 | |
| 212 | if (resource_name < 0 || |
| 213 | resource_name >= IPA_RM_RESOURCE_MAX) { |
| 214 | IPA_RM_ERR("%s: Invalid parameter\n", __func__); |
| 215 | return -EINVAL; |
| 216 | } |
| 217 | |
| 218 | if (!ipa_rm_it_handles[resource_name].initied) { |
| 219 | IPA_RM_ERR("%s: Not initialized\n", __func__); |
| 220 | return -EINVAL; |
| 221 | } |
| 222 | |
| 223 | spin_lock_irqsave(&ipa_rm_it_handles[resource_name].lock, flags); |
| 224 | ipa_rm_it_handles[resource_name].resource_requested = true; |
| 225 | spin_unlock_irqrestore(&ipa_rm_it_handles[resource_name].lock, flags); |
| 226 | ret = ipa_rm_request_resource(resource_name); |
| 227 | IPA_RM_DBG_LOW("%s: resource %d: returning %d\n", __func__, |
| 228 | resource_name, ret); |
| 229 | |
| 230 | return ret; |
| 231 | } |
| 232 | EXPORT_SYMBOL(ipa_rm_inactivity_timer_request_resource); |
| 233 | |
| 234 | /** |
| 235 | * ipa_rm_inactivity_timer_release_resource() - Sets the |
| 236 | * inactivity timer to the timeout set by |
| 237 | * ipa_rm_inactivity_timer_init(). When the timeout expires, IPA |
| 238 | * RM inactivity timer will call to ipa_rm_release_resource(). |
| 239 | * If a call to ipa_rm_inactivity_timer_request_resource() was |
| 240 | * made BEFORE the timout has expired, rge timer will be |
| 241 | * cancelled. |
| 242 | * |
| 243 | * @resource_name: Resource name. @see ipa_rm.h |
| 244 | * |
| 245 | * Return codes: |
| 246 | * 0: success |
| 247 | * -EINVAL: invalid parameters |
| 248 | */ |
| 249 | int ipa_rm_inactivity_timer_release_resource( |
| 250 | enum ipa_rm_resource_name resource_name) |
| 251 | { |
| 252 | unsigned long flags; |
| 253 | |
| 254 | IPA_RM_DBG_LOW("%s: resource %d\n", __func__, resource_name); |
| 255 | |
| 256 | if (resource_name < 0 || |
| 257 | resource_name >= IPA_RM_RESOURCE_MAX) { |
| 258 | IPA_RM_ERR("%s: Invalid parameter\n", __func__); |
| 259 | return -EINVAL; |
| 260 | } |
| 261 | |
| 262 | if (!ipa_rm_it_handles[resource_name].initied) { |
| 263 | IPA_RM_ERR("%s: Not initialized\n", __func__); |
| 264 | return -EINVAL; |
| 265 | } |
| 266 | |
| 267 | spin_lock_irqsave(&ipa_rm_it_handles[resource_name].lock, flags); |
| 268 | ipa_rm_it_handles[resource_name].resource_requested = false; |
| 269 | if (ipa_rm_it_handles[resource_name].work_in_progress) { |
| 270 | IPA_RM_DBG_LOW("%s: Timer already set, no sched again %d\n", |
| 271 | __func__, resource_name); |
| 272 | ipa_rm_it_handles[resource_name].reschedule_work = true; |
| 273 | spin_unlock_irqrestore( |
| 274 | &ipa_rm_it_handles[resource_name].lock, flags); |
| 275 | return 0; |
| 276 | } |
| 277 | ipa_rm_it_handles[resource_name].work_in_progress = true; |
| 278 | ipa_rm_it_handles[resource_name].reschedule_work = false; |
Skylar Chang | 3cf5185 | 2017-11-29 18:13:38 -0800 | [diff] [blame] | 279 | __pm_stay_awake(&ipa_rm_it_handles[resource_name].w_lock); |
Amir Levy | 9659e59 | 2016-10-27 18:08:27 +0300 | [diff] [blame] | 280 | IPA_RM_DBG_LOW("%s: setting delayed work\n", __func__); |
| 281 | queue_delayed_work(system_unbound_wq, |
| 282 | &ipa_rm_it_handles[resource_name].work, |
| 283 | ipa_rm_it_handles[resource_name].jiffies); |
| 284 | spin_unlock_irqrestore(&ipa_rm_it_handles[resource_name].lock, flags); |
| 285 | |
| 286 | return 0; |
| 287 | } |
| 288 | EXPORT_SYMBOL(ipa_rm_inactivity_timer_release_resource); |
| 289 | |