1 /*- 2 * Copyright (c) 2010-2020 The NetBSD Foundation, Inc. 3 * All rights reserved. 4 * 5 * This material is based upon work partially supported by The 6 * NetBSD Foundation under a contract with Mindaugas Rasiukevicius. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #ifdef _KERNEL 31 #include <sys/cdefs.h> 32 __KERNEL_RCSID(0, "$NetBSD: npf_worker.c,v 1.10 2020/08/27 18:49:36 riastradh Exp $"); 33 34 #include <sys/param.h> 35 #include <sys/types.h> 36 37 #include <sys/mutex.h> 38 #include <sys/kmem.h> 39 #include <sys/kernel.h> 40 #include <sys/kthread.h> 41 #include <sys/cprng.h> 42 #endif 43 44 #include "npf_impl.h" 45 46 typedef struct npf_worker { 47 kmutex_t lock; 48 kcondvar_t cv; 49 kcondvar_t exit_cv; 50 bool exit; 51 LIST_HEAD(, npf) instances; 52 unsigned worker_count; 53 lwp_t * worker[]; 54 } npf_workerinfo_t; 55 56 #define NPF_GC_MINWAIT (10) // 10 ms 57 #define NPF_GC_MAXWAIT (10 * 1000) // 10 sec 58 59 /* 60 * Flags for the npf_t::worker_flags field. 61 */ 62 #define WFLAG_ACTIVE 0x01 // instance enqueued for workers 63 #define WFLAG_INITED 0x02 // worker setup the instance 64 #define WFLAG_REMOVE 0x04 // remove the instance 65 66 static void npf_worker(void *) __dead; 67 static npf_workerinfo_t * worker_info __read_mostly; 68 69 int 70 npf_worker_sysinit(unsigned nworkers) 71 { 72 const size_t len = offsetof(npf_workerinfo_t, worker[nworkers]); 73 npf_workerinfo_t *winfo; 74 75 KASSERT(worker_info == NULL); 76 77 if (!nworkers) { 78 return 0; 79 } 80 81 winfo = kmem_zalloc(len, KM_SLEEP); 82 winfo->worker_count = nworkers; 83 mutex_init(&winfo->lock, MUTEX_DEFAULT, IPL_SOFTNET); 84 cv_init(&winfo->exit_cv, "npfgcx"); 85 cv_init(&winfo->cv, "npfgcw"); 86 LIST_INIT(&winfo->instances); 87 worker_info = winfo; 88 89 for (unsigned i = 0; i < nworkers; i++) { 90 if (kthread_create(PRI_NONE, KTHREAD_MPSAFE | KTHREAD_MUSTJOIN, 91 NULL, npf_worker, winfo, &winfo->worker[i], "npfgc%u", i)) { 92 npf_worker_sysfini(); 93 return ENOMEM; 94 } 95 } 96 return 0; 97 } 98 99 void 100 npf_worker_sysfini(void) 101 { 102 npf_workerinfo_t *winfo = worker_info; 103 unsigned nworkers; 104 105 if (!winfo) { 106 return; 107 } 108 109 /* Notify the workers to exit. */ 110 mutex_enter(&winfo->lock); 111 winfo->exit = true; 112 cv_broadcast(&winfo->cv); 113 mutex_exit(&winfo->lock); 114 115 /* Wait for them to finish and then destroy. */ 116 nworkers = winfo->worker_count; 117 for (unsigned i = 0; i < nworkers; i++) { 118 lwp_t *worker; 119 120 if ((worker = winfo->worker[i]) != NULL) { 121 kthread_join(worker); 122 } 123 } 124 cv_destroy(&winfo->cv); 125 cv_destroy(&winfo->exit_cv); 126 mutex_destroy(&winfo->lock); 127 kmem_free(winfo, offsetof(npf_workerinfo_t, worker[nworkers])); 128 worker_info = NULL; 129 } 130 131 int 132 npf_worker_addfunc(npf_t *npf, npf_workfunc_t work) 133 { 134 KASSERTMSG(npf->worker_flags == 0, 135 "the task must be added before the npf_worker_enlist() call"); 136 137 for (unsigned i = 0; i < NPF_MAX_WORKS; i++) { 138 if (npf->worker_funcs[i] == NULL) { 139 npf->worker_funcs[i] = work; 140 return 0; 141 } 142 } 143 return -1; 144 } 145 146 void 147 npf_worker_signal(npf_t *npf) 148 { 149 npf_workerinfo_t *winfo = worker_info; 150 151 if ((npf->worker_flags & WFLAG_ACTIVE) == 0) { 152 return; 153 } 154 KASSERT(winfo != NULL); 155 156 mutex_enter(&winfo->lock); 157 cv_signal(&winfo->cv); 158 mutex_exit(&winfo->lock); 159 } 160 161 /* 162 * npf_worker_enlist: add the NPF instance for worker(s) to process. 163 */ 164 void 165 npf_worker_enlist(npf_t *npf) 166 { 167 npf_workerinfo_t *winfo = worker_info; 168 169 KASSERT(npf->worker_flags == 0); 170 if (!winfo) { 171 return; 172 } 173 174 mutex_enter(&winfo->lock); 175 LIST_INSERT_HEAD(&winfo->instances, npf, worker_entry); 176 npf->worker_flags |= WFLAG_ACTIVE; 177 mutex_exit(&winfo->lock); 178 } 179 180 /* 181 * npf_worker_discharge: remove the NPF instance the list for workers. 182 * 183 * => May block waiting for a worker to finish processing the instance. 184 */ 185 void 186 npf_worker_discharge(npf_t *npf) 187 { 188 npf_workerinfo_t *winfo = worker_info; 189 190 if ((npf->worker_flags & WFLAG_ACTIVE) == 0) { 191 return; 192 } 193 KASSERT(winfo != NULL); 194 195 /* 196 * Notify the worker(s) that we are removing this instance. 197 */ 198 mutex_enter(&winfo->lock); 199 KASSERT(npf->worker_flags & WFLAG_ACTIVE); 200 npf->worker_flags |= WFLAG_REMOVE; 201 cv_broadcast(&winfo->cv); 202 203 /* Wait for a worker to process this request. */ 204 while (npf->worker_flags & WFLAG_ACTIVE) { 205 cv_wait(&winfo->exit_cv, &winfo->lock); 206 } 207 mutex_exit(&winfo->lock); 208 KASSERT(npf->worker_flags == 0); 209 } 210 211 static void 212 remove_npf_instance(npf_workerinfo_t *winfo, npf_t *npf) 213 { 214 KASSERT(mutex_owned(&winfo->lock)); 215 KASSERT(npf->worker_flags & WFLAG_ACTIVE); 216 KASSERT(npf->worker_flags & WFLAG_REMOVE); 217 218 /* 219 * Remove the NPF instance: 220 * - Release any structures owned by the worker. 221 * - Remove the instance from the list. 222 * - Notify any thread waiting for removal to complete. 223 */ 224 if (npf->worker_flags & WFLAG_INITED) { 225 npfk_thread_unregister(npf); 226 } 227 LIST_REMOVE(npf, worker_entry); 228 npf->worker_flags = 0; 229 cv_broadcast(&winfo->exit_cv); 230 } 231 232 static unsigned 233 process_npf_instance(npf_workerinfo_t *winfo, npf_t *npf) 234 { 235 npf_workfunc_t work; 236 237 KASSERT(mutex_owned(&winfo->lock)); 238 239 if (npf->worker_flags & WFLAG_REMOVE) { 240 remove_npf_instance(winfo, npf); 241 return NPF_GC_MAXWAIT; 242 } 243 244 if ((npf->worker_flags & WFLAG_INITED) == 0) { 245 npfk_thread_register(npf); 246 npf->worker_flags |= WFLAG_INITED; 247 } 248 249 /* Run the jobs. */ 250 for (unsigned i = 0; i < NPF_MAX_WORKS; i++) { 251 if ((work = npf->worker_funcs[i]) == NULL) { 252 break; 253 } 254 work(npf); 255 } 256 257 return MAX(MIN(npf->worker_wait_time, NPF_GC_MAXWAIT), NPF_GC_MINWAIT); 258 } 259 260 /* 261 * npf_worker: the main worker loop, processing enlisted NPF instances. 262 * 263 * XXX: Currently, npf_workerinfo_t::lock would serialize all workers, 264 * so there is no point to have more than one worker; but there might 265 * not be much point anyway. 266 */ 267 static void 268 npf_worker(void *arg) 269 { 270 npf_workerinfo_t *winfo = arg; 271 npf_t *npf; 272 273 mutex_enter(&winfo->lock); 274 for (;;) { 275 unsigned wait_time = NPF_GC_MAXWAIT; 276 277 /* 278 * Iterate all instances. We do not use LIST_FOREACH here, 279 * since the instance can be removed. 280 */ 281 npf = LIST_FIRST(&winfo->instances); 282 while (npf) { 283 npf_t *next = LIST_NEXT(npf, worker_entry); 284 unsigned i_wait_time = process_npf_instance(winfo, npf); 285 wait_time = MIN(wait_time, i_wait_time); 286 npf = next; 287 } 288 289 /* 290 * Sleep and periodically wake up, unless we get notified. 291 */ 292 if (winfo->exit) { 293 break; 294 } 295 cv_timedwait(&winfo->cv, &winfo->lock, mstohz(wait_time)); 296 } 297 mutex_exit(&winfo->lock); 298 299 KASSERTMSG(LIST_EMPTY(&winfo->instances), 300 "NPF instances must be discharged before the npfk_sysfini() call"); 301 302 kthread_exit(0); 303 } 304