1 // Copyright 2016-2017 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/lock.h>
18 #include "esp_pm.h"
19 #include "esp_system.h"
20 #include "sys/queue.h"
21 #include "freertos/FreeRTOS.h"
22 #include "esp_private/pm_impl.h"
23 #include "esp_timer.h"
24 #include "sdkconfig.h"
25 
26 
27 typedef struct esp_pm_lock {
28     esp_pm_lock_type_t type;        /*!< type passed to esp_pm_lock_create */
29     int arg;                        /*!< argument passed to esp_pm_lock_create */
30     pm_mode_t mode;                 /*!< implementation-defined mode for this type of lock*/
31     const char* name;               /*!< used to identify the lock */
32     SLIST_ENTRY(esp_pm_lock) next;  /*!< linked list pointer */
33     size_t count;                   /*!< lock count */
34     portMUX_TYPE spinlock;          /*!< spinlock used when operating on 'count' */
35 #ifdef WITH_PROFILING
36     pm_time_t last_taken;           /*!< time what the lock was taken (valid if count > 0) */
37     pm_time_t time_held;            /*!< total time the lock was taken.
38                                          If count > 0, this doesn't include the time since last_taken */
39     size_t times_taken;             /*!< number of times the lock was ever taken */
40 #endif
41 } esp_pm_lock_t;
42 
43 
44 static const char* s_lock_type_names[] = {
45         "CPU_FREQ_MAX",
46         "APB_FREQ_MAX",
47         "NO_LIGHT_SLEEP"
48 };
49 
50 /* List of all existing locks, used for esp_pm_dump_locks */
51 static SLIST_HEAD(esp_pm_locks_head, esp_pm_lock) s_list =
52         SLIST_HEAD_INITIALIZER(s_head);
53 /* Protects the above list */
54 static _lock_t s_list_lock;
55 
56 
esp_pm_lock_create(esp_pm_lock_type_t lock_type,int arg,const char * name,esp_pm_lock_handle_t * out_handle)57 esp_err_t esp_pm_lock_create(esp_pm_lock_type_t lock_type, int arg,
58         const char* name, esp_pm_lock_handle_t* out_handle)
59 {
60 #ifndef CONFIG_PM_ENABLE
61     return ESP_ERR_NOT_SUPPORTED;
62 #endif
63 
64     if (out_handle == NULL) {
65         return ESP_ERR_INVALID_ARG;
66     }
67     esp_pm_lock_t* new_lock = (esp_pm_lock_t*) calloc(1, sizeof(*new_lock));
68     if (!new_lock) {
69         return ESP_ERR_NO_MEM;
70     }
71     new_lock->type = lock_type;
72     new_lock->arg = arg;
73     new_lock->mode = esp_pm_impl_get_mode(lock_type, arg);
74     new_lock->name = name;
75     new_lock->spinlock = (portMUX_TYPE) portMUX_INITIALIZER_UNLOCKED;
76     *out_handle = new_lock;
77 
78     _lock_acquire(&s_list_lock);
79     SLIST_INSERT_HEAD(&s_list, new_lock, next);
80     _lock_release(&s_list_lock);
81     return ESP_OK;
82 }
83 
esp_pm_lock_delete(esp_pm_lock_handle_t handle)84 esp_err_t esp_pm_lock_delete(esp_pm_lock_handle_t handle)
85 {
86 #ifndef CONFIG_PM_ENABLE
87     return ESP_ERR_NOT_SUPPORTED;
88 #endif
89 
90     if (handle == NULL) {
91         return ESP_ERR_INVALID_ARG;
92     }
93 
94     if (handle->count > 0) {
95         return ESP_ERR_INVALID_STATE;
96     }
97     _lock_acquire(&s_list_lock);
98     SLIST_REMOVE(&s_list, handle, esp_pm_lock, next);
99     _lock_release(&s_list_lock);
100     free(handle);
101     return ESP_OK;
102 }
103 
esp_pm_lock_acquire(esp_pm_lock_handle_t handle)104 esp_err_t IRAM_ATTR esp_pm_lock_acquire(esp_pm_lock_handle_t handle)
105 {
106 #ifndef CONFIG_PM_ENABLE
107     return ESP_ERR_NOT_SUPPORTED;
108 #endif
109 
110     if (handle == NULL) {
111         return ESP_ERR_INVALID_ARG;
112     }
113 
114     portENTER_CRITICAL_SAFE(&handle->spinlock);
115     if (handle->count++ == 0) {
116         pm_time_t now = 0;
117 #ifdef WITH_PROFILING
118         now = pm_get_time();
119 #endif
120         esp_pm_impl_switch_mode(handle->mode, MODE_LOCK, now);
121 #ifdef WITH_PROFILING
122         handle->last_taken = now;
123         handle->times_taken++;
124 #endif
125     }
126     portEXIT_CRITICAL_SAFE(&handle->spinlock);
127     return ESP_OK;
128 }
129 
esp_pm_lock_release(esp_pm_lock_handle_t handle)130 esp_err_t IRAM_ATTR esp_pm_lock_release(esp_pm_lock_handle_t handle)
131 {
132 #ifndef CONFIG_PM_ENABLE
133     return ESP_ERR_NOT_SUPPORTED;
134 #endif
135 
136     if (handle == NULL) {
137         return ESP_ERR_INVALID_ARG;
138     }
139     esp_err_t ret = ESP_OK;
140     portENTER_CRITICAL_SAFE(&handle->spinlock);
141     if (handle->count == 0) {
142         ret = ESP_ERR_INVALID_STATE;
143         goto out;
144     }
145     if (--handle->count == 0) {
146         pm_time_t now = 0;
147 #ifdef WITH_PROFILING
148         now = pm_get_time();
149         handle->time_held += now - handle->last_taken;
150 #endif
151         esp_pm_impl_switch_mode(handle->mode, MODE_UNLOCK, now);
152     }
153 out:
154     portEXIT_CRITICAL_SAFE(&handle->spinlock);
155     return ret;
156 }
157 
esp_pm_dump_locks(FILE * stream)158 esp_err_t esp_pm_dump_locks(FILE* stream)
159 {
160 #ifndef CONFIG_PM_ENABLE
161     return ESP_ERR_NOT_SUPPORTED;
162 #endif
163 
164 #ifdef WITH_PROFILING
165     pm_time_t cur_time = pm_get_time();
166     pm_time_t cur_time_d100 = cur_time / 100;
167 #endif // WITH_PROFILING
168 
169     _lock_acquire(&s_list_lock);
170 #ifdef WITH_PROFILING
171     fprintf(stream, "Time: %lld\n", cur_time);
172 #endif
173 
174     fprintf(stream, "Lock stats:\n");
175     esp_pm_lock_t* it;
176     char line[80];
177     SLIST_FOREACH(it, &s_list, next) {
178         char *buf = line;
179         size_t len = sizeof(line);
180         size_t cb;
181 
182         portENTER_CRITICAL(&it->spinlock);
183         if (it->name == NULL) {
184             cb = snprintf(buf, len, "lock@%p ", it);
185         } else {
186             cb = snprintf(buf, len, "%-15.15s ", it->name);
187         }
188         assert(cb <= len); // above formats should fit into sizeof(line)
189         buf += cb;
190         len -= cb;
191 #ifdef WITH_PROFILING
192         pm_time_t time_held = it->time_held;
193         if (it->count > 0) {
194             time_held += cur_time - it->last_taken;
195         }
196         snprintf(buf, len, "%14s  %3d  %3d  %9d  %12lld  %3lld%%\n",
197                 s_lock_type_names[it->type], it->arg,
198                 it->count, it->times_taken, time_held,
199                 (time_held + cur_time_d100 - 1) / cur_time_d100);
200 #else
201         snprintf(buf, len, "%14s  %3d  %3d\n", s_lock_type_names[it->type], it->arg, it->count);
202 #endif // WITH_PROFILING
203         portEXIT_CRITICAL(&it->spinlock);
204         fputs(line, stream);
205     }
206     _lock_release(&s_list_lock);
207 #ifdef WITH_PROFILING
208     esp_pm_impl_dump_stats(stream);
209 #endif
210     return ESP_OK;
211 }
212