blob: ae619d3b99046b36e12b6eb172f7c89275619b16 [file] [log] [blame]
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +01001/* si_cache.c
2 *
3 * Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
4 * Author: Pau Espin Pedrol <pespin@sysmocom.de>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21#include <string.h>
22#include <talloc.h>
23#include <inttypes.h>
24
25#include <osmocom/core/utils.h>
26
27#include <neigh_cache.h>
28#include <gprs_debug.h>
29
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +010030static inline bool neigh_cache_entry_key_eq(const struct neigh_cache_entry_key *a,
31 const struct neigh_cache_entry_key *b)
32{
33 return a->local_lac == b->local_lac &&
34 a->local_ci == b->local_ci &&
35 a->tgt_arfcn == b->tgt_arfcn &&
36 a->tgt_bsic == b->tgt_bsic;
37}
38
39static void neigh_cache_schedule_cleanup(struct neigh_cache *cache);
40static void neigh_cache_cleanup_cb(void *data)
41{
42 struct timespec now, threshold;
43 struct neigh_cache *cache = (struct neigh_cache *)data;
44 struct neigh_cache_entry *it, *tmp;
45
46 osmo_clock_gettime(CLOCK_MONOTONIC, &now);
47
48 /* Instead of adding keep_time_intval to each, substract it from now once */
49 timespecsub(&now, &cache->keep_time_intval, &threshold);
50
51 llist_for_each_entry_safe(it, tmp, &cache->list, list) {
52 if (timespeccmp(&threshold, &it->update_ts, <))
53 break;
54 LOGP(DNACC, LOGL_DEBUG,
55 "neigh_cache: Removing entry " NEIGH_CACHE_ENTRY_KEY_FMT " => %s\n",
56 NEIGH_CACHE_ENTRY_KEY_ARGS(&it->key), osmo_cgi_ps_name(&it->value));
57 llist_del(&it->list);
58 talloc_free(it);
59 }
60
61 neigh_cache_schedule_cleanup(cache);
62}
63
64static void neigh_cache_schedule_cleanup(struct neigh_cache *cache)
65{
66 struct neigh_cache_entry *it;
67 struct timespec now, threshold, result;
68
69 /* First item is the one with oldest update_ts */
70 it = llist_first_entry_or_null(&cache->list, struct neigh_cache_entry, list);
71 if (!it)
72 return;
73
74 osmo_clock_gettime(CLOCK_MONOTONIC, &now);
75
76 timespecadd(&it->update_ts, &cache->keep_time_intval, &threshold);
77
78 if (timespeccmp(&now, &threshold, >=)) {
79 /* Too late, let's flush asynchonously so newly added isn't
80 * immediatelly freed before return. */
81 result = (struct timespec){ .tv_sec = 0, .tv_nsec = 0 };
82 } else {
83 timespecsub(&threshold, &now, &result);
84 }
85 osmo_timer_schedule(&cache->cleanup_timer, result.tv_sec, result.tv_nsec*1000);
86}
87
Pau Espin Pedrolab7159f2021-01-26 17:51:44 +010088struct neigh_cache *neigh_cache_alloc(void *ctx, unsigned int keep_time_sec)
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +010089{
90 struct neigh_cache *cache = talloc_zero(ctx, struct neigh_cache);
91 OSMO_ASSERT(cache);
92 INIT_LLIST_HEAD(&cache->list);
93 osmo_timer_setup(&cache->cleanup_timer, neigh_cache_cleanup_cb, cache);
Pau Espin Pedrolab7159f2021-01-26 17:51:44 +010094 cache->keep_time_intval = (struct timespec){ .tv_sec = keep_time_sec, .tv_nsec = 0};
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +010095 return cache;
96
97}
Pau Espin Pedrolab7159f2021-01-26 17:51:44 +010098
99void neigh_cache_set_keep_time_interval(struct neigh_cache *cache, unsigned int keep_time_sec)
100{
101 cache->keep_time_intval = (struct timespec){ .tv_sec = keep_time_sec, .tv_nsec = 0};
102 neigh_cache_schedule_cleanup(cache);
103}
104
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +0100105struct neigh_cache_entry *neigh_cache_add(struct neigh_cache *cache,
106 const struct neigh_cache_entry_key *key,
107 const struct osmo_cell_global_id_ps *value)
108{
109 struct neigh_cache_entry *it;
110
111 /* First check if it already exists. If so, simply update timer+value */
112 it = neigh_cache_lookup_entry(cache, key);
113 if (!it) {
114 LOGP(DNACC, LOGL_DEBUG,
115 "neigh_cache: Inserting new entry " NEIGH_CACHE_ENTRY_KEY_FMT " => %s\n",
116 NEIGH_CACHE_ENTRY_KEY_ARGS(key), osmo_cgi_ps_name(value));
117 it = talloc_zero(cache, struct neigh_cache_entry);
118 OSMO_ASSERT(it);
119 memcpy(&it->key, key, sizeof(it->key));
120 } else {
121 LOGP(DNACC, LOGL_DEBUG,
122 "neigh_cache: Updating entry " NEIGH_CACHE_ENTRY_KEY_FMT " => (%s -> %s)\n",
123 NEIGH_CACHE_ENTRY_KEY_ARGS(key), osmo_cgi_ps_name(&it->value), osmo_cgi_ps_name2(value));
124 /* remove item, we'll add it to the end to have them sorted by last update */
125 llist_del(&it->list);
126 }
127
128 memcpy(&it->value, value, sizeof(it->value));
129 OSMO_ASSERT(osmo_clock_gettime(CLOCK_MONOTONIC, &it->update_ts) == 0);
130 llist_add_tail(&it->list, &cache->list);
131 neigh_cache_schedule_cleanup(cache);
132 return it;
133}
134
135struct neigh_cache_entry *neigh_cache_lookup_entry(struct neigh_cache *cache,
136 const struct neigh_cache_entry_key *key)
137{
138 struct neigh_cache_entry *tmp;
139 llist_for_each_entry(tmp, &cache->list, list) {
140 if (neigh_cache_entry_key_eq(&tmp->key, key))
141 return tmp;
142 }
143 return NULL;
144}
145
146const struct osmo_cell_global_id_ps *neigh_cache_lookup_value(struct neigh_cache *cache,
147 const struct neigh_cache_entry_key *key)
148{
149 struct neigh_cache_entry *it = neigh_cache_lookup_entry(cache, key);
150 if (it)
151 return &it->value;
152 return NULL;
153}
154
155void neigh_cache_free(struct neigh_cache *cache)
156{
157 struct neigh_cache_entry *it, *tmp;
158 if (!cache)
159 return;
160
161 llist_for_each_entry_safe(it, tmp, &cache->list, list) {
162 llist_del(&it->list);
163 talloc_free(it);
164 }
165 osmo_timer_del(&cache->cleanup_timer);
166 talloc_free(cache);
167}
168
169
170////////////////////
171// SI CACHE
172///////////////////
173
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +0100174static void si_cache_schedule_cleanup(struct si_cache *cache);
175static void si_cache_cleanup_cb(void *data)
176{
177 struct timespec now, threshold;
178 struct si_cache *cache = (struct si_cache *)data;
179 struct si_cache_entry *it, *tmp;
180
181 osmo_clock_gettime(CLOCK_MONOTONIC, &now);
182
183 /* Instead of adding keep_time_intval to each, substract it from now once */
184 timespecsub(&now, &cache->keep_time_intval, &threshold);
185
186 llist_for_each_entry_safe(it, tmp, &cache->list, list) {
187 if (timespeccmp(&threshold, &it->update_ts, <))
188 break;
189 LOGP(DNACC, LOGL_DEBUG, "si_cache: Removing entry %s\n",
190 osmo_cgi_ps_name(&it->key));
191 llist_del(&it->list);
192 talloc_free(it);
193 }
194
195 si_cache_schedule_cleanup(cache);
196}
197
198static void si_cache_schedule_cleanup(struct si_cache *cache)
199{
200 struct si_cache_entry *it;
201 struct timespec now, threshold, result;
202
203 /* First item is the one with oldest update_ts */
204 it = llist_first_entry_or_null(&cache->list, struct si_cache_entry, list);
205 if (!it)
206 return;
207
208 osmo_clock_gettime(CLOCK_MONOTONIC, &now);
209
210 timespecadd(&it->update_ts, &cache->keep_time_intval, &threshold);
211
212 if (timespeccmp(&now, &threshold, >=)) {
213 /* Too late, let's flush asynchonously so newly added isn't
214 * immediatelly freed before return. */
215 result = (struct timespec){ .tv_sec = 0, .tv_nsec = 0 };
216 } else {
217 timespecsub(&threshold, &now, &result);
218 }
219 osmo_timer_schedule(&cache->cleanup_timer, result.tv_sec, result.tv_nsec*1000);
220}
221
Pau Espin Pedrolab7159f2021-01-26 17:51:44 +0100222struct si_cache *si_cache_alloc(void *ctx, unsigned int keep_time_sec)
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +0100223{
224 struct si_cache *cache = talloc_zero(ctx, struct si_cache);
225 OSMO_ASSERT(cache);
226 INIT_LLIST_HEAD(&cache->list);
227 osmo_timer_setup(&cache->cleanup_timer, si_cache_cleanup_cb, cache);
Pau Espin Pedrolab7159f2021-01-26 17:51:44 +0100228 cache->keep_time_intval = (struct timespec){ .tv_sec = keep_time_sec, .tv_nsec = 0};
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +0100229 return cache;
230}
Pau Espin Pedrolab7159f2021-01-26 17:51:44 +0100231
232void si_cache_set_keep_time_interval(struct si_cache *cache, unsigned int keep_time_sec)
233{
234 cache->keep_time_intval = (struct timespec){ .tv_sec = keep_time_sec, .tv_nsec = 0};
235 si_cache_schedule_cleanup(cache);
236}
237
Pau Espin Pedrolc0a250d2021-01-21 18:46:13 +0100238struct si_cache_entry *si_cache_add(struct si_cache *cache,
239 const struct osmo_cell_global_id_ps *key,
240 const struct si_cache_value *value)
241{
242 struct si_cache_entry *it;
243
244 /* First check if it already exists. If so, simply update timer+value */
245 it = si_cache_lookup_entry(cache, key);
246 if (!it) {
247 LOGP(DNACC, LOGL_DEBUG, "si_cache: Inserting new entry %s\n",
248 osmo_cgi_ps_name(key));
249 it = talloc_zero(cache, struct si_cache_entry);
250 OSMO_ASSERT(it);
251 memcpy(&it->key, key, sizeof(it->key));
252 } else {
253 LOGP(DNACC, LOGL_DEBUG, "si_cache: Updating entry %s\n",
254 osmo_cgi_ps_name(&it->key));
255 /* remove item, we'll add it to the end to have them sorted by last update */
256 llist_del(&it->list);
257 }
258
259 memcpy(&it->value, value, sizeof(it->value));
260 OSMO_ASSERT(osmo_clock_gettime(CLOCK_MONOTONIC, &it->update_ts) == 0);
261 llist_add_tail(&it->list, &cache->list);
262 si_cache_schedule_cleanup(cache);
263 return it;
264}
265
266struct si_cache_entry *si_cache_lookup_entry(struct si_cache *cache,
267 const struct osmo_cell_global_id_ps *key)
268{
269 struct si_cache_entry *tmp;
270 llist_for_each_entry(tmp, &cache->list, list) {
271 if (osmo_cgi_ps_cmp(&tmp->key, key) == 0)
272 return tmp;
273 }
274 return NULL;
275}
276
277const struct si_cache_value *si_cache_lookup_value(struct si_cache *cache,
278 const struct osmo_cell_global_id_ps *key)
279{
280 struct si_cache_entry *it = si_cache_lookup_entry(cache, key);
281 if (it)
282 return &it->value;
283 return NULL;
284}
285
286void si_cache_free(struct si_cache *cache)
287{
288 struct si_cache_entry *it, *tmp;
289 if (!cache)
290 return;
291
292 llist_for_each_entry_safe(it, tmp, &cache->list, list) {
293 llist_del(&it->list);
294 talloc_free(it);
295 }
296 osmo_timer_del(&cache->cleanup_timer);
297 talloc_free(cache);
298}