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