Commit | Line | Data |
---|---|---|
558a398b | 1 | /*- |
2a1ad637 FT |
2 | * Copyright (c) 2005-2009 Ariff Abdullah <ariff@FreeBSD.org> |
3 | * Copyright (c) 1999 Cameron Grant <cg@FreeBSD.org> | |
984263bc MD |
4 | * All rights reserved. |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions | |
8 | * are met: | |
9 | * 1. Redistributions of source code must retain the above copyright | |
10 | * notice, this list of conditions and the following disclaimer. | |
11 | * 2. Redistributions in binary form must reproduce the above copyright | |
12 | * notice, this list of conditions and the following disclaimer in the | |
13 | * documentation and/or other materials provided with the distribution. | |
14 | * | |
15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
25 | * SUCH DAMAGE. | |
26 | */ | |
27 | ||
2a1ad637 FT |
28 | #ifdef HAVE_KERNEL_OPTION_HEADERS |
29 | #include "opt_snd.h" | |
30 | #endif | |
31 | ||
984263bc MD |
32 | #include <dev/sound/pcm/sound.h> |
33 | ||
34 | #include "feeder_if.h" | |
35 | ||
2a1ad637 | 36 | SND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pcm/feeder.c 227293 2011-11-07 06:44:47Z ed $"); |
984263bc | 37 | |
2a1ad637 | 38 | static MALLOC_DEFINE(M_FEEDER, "feeder", "pcm feeder"); |
984263bc MD |
39 | |
40 | #define MAXFEEDERS 256 | |
41 | #undef FEEDER_DEBUG | |
42 | ||
43 | struct feedertab_entry { | |
44 | SLIST_ENTRY(feedertab_entry) link; | |
45 | struct feeder_class *feederclass; | |
46 | struct pcm_feederdesc *desc; | |
47 | ||
48 | int idx; | |
49 | }; | |
50 | static SLIST_HEAD(, feedertab_entry) feedertab; | |
51 | ||
52 | /*****************************************************************************/ | |
53 | ||
54 | void | |
55 | feeder_register(void *p) | |
56 | { | |
57 | static int feedercnt = 0; | |
58 | ||
59 | struct feeder_class *fc = p; | |
60 | struct feedertab_entry *fte; | |
61 | int i; | |
62 | ||
63 | if (feedercnt == 0) { | |
64 | KASSERT(fc->desc == NULL, ("first feeder not root: %s", fc->name)); | |
65 | ||
66 | SLIST_INIT(&feedertab); | |
4e8e900c | 67 | fte = kmalloc(sizeof(*fte), M_FEEDER, M_WAITOK | M_ZERO); |
2a1ad637 | 68 | if (fte == NULL) { |
67931cc4 | 69 | kprintf("can't allocate memory for root feeder: %s\n", |
2a1ad637 FT |
70 | fc->name); |
71 | ||
72 | return; | |
73 | } | |
984263bc MD |
74 | fte->feederclass = fc; |
75 | fte->desc = NULL; | |
76 | fte->idx = feedercnt; | |
77 | SLIST_INSERT_HEAD(&feedertab, fte, link); | |
78 | feedercnt++; | |
79 | ||
2a1ad637 FT |
80 | /* initialize global variables */ |
81 | ||
82 | if (snd_verbose < 0 || snd_verbose > 4) | |
83 | snd_verbose = 1; | |
84 | ||
85 | /* initialize unit numbering */ | |
86 | snd_unit_init(); | |
87 | if (snd_unit < 0 || snd_unit > PCMMAXUNIT) | |
88 | snd_unit = -1; | |
89 | ||
90 | if (snd_maxautovchans < 0 || | |
91 | snd_maxautovchans > SND_MAXVCHANS) | |
92 | snd_maxautovchans = 0; | |
93 | ||
94 | if (chn_latency < CHN_LATENCY_MIN || | |
95 | chn_latency > CHN_LATENCY_MAX) | |
96 | chn_latency = CHN_LATENCY_DEFAULT; | |
97 | ||
98 | if (chn_latency_profile < CHN_LATENCY_PROFILE_MIN || | |
99 | chn_latency_profile > CHN_LATENCY_PROFILE_MAX) | |
100 | chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; | |
101 | ||
102 | if (feeder_rate_min < FEEDRATE_MIN || | |
103 | feeder_rate_max < FEEDRATE_MIN || | |
104 | feeder_rate_min > FEEDRATE_MAX || | |
105 | feeder_rate_max > FEEDRATE_MAX || | |
106 | !(feeder_rate_min < feeder_rate_max)) { | |
107 | feeder_rate_min = FEEDRATE_RATEMIN; | |
108 | feeder_rate_max = FEEDRATE_RATEMAX; | |
109 | } | |
110 | ||
111 | if (feeder_rate_round < FEEDRATE_ROUNDHZ_MIN || | |
112 | feeder_rate_round > FEEDRATE_ROUNDHZ_MAX) | |
113 | feeder_rate_round = FEEDRATE_ROUNDHZ; | |
114 | ||
115 | if (bootverbose) | |
67931cc4 | 116 | kprintf("%s: snd_unit=%d snd_maxautovchans=%d " |
2a1ad637 FT |
117 | "latency=%d " |
118 | "feeder_rate_min=%d feeder_rate_max=%d " | |
119 | "feeder_rate_round=%d\n", | |
120 | __func__, snd_unit, snd_maxautovchans, | |
121 | chn_latency, | |
122 | feeder_rate_min, feeder_rate_max, | |
123 | feeder_rate_round); | |
124 | ||
984263bc MD |
125 | /* we've got our root feeder so don't veto pcm loading anymore */ |
126 | pcm_veto_load = 0; | |
127 | ||
128 | return; | |
129 | } | |
130 | ||
131 | KASSERT(fc->desc != NULL, ("feeder '%s' has no descriptor", fc->name)); | |
132 | ||
133 | /* beyond this point failure is non-fatal but may result in some translations being unavailable */ | |
134 | i = 0; | |
135 | while ((feedercnt < MAXFEEDERS) && (fc->desc[i].type > 0)) { | |
2a1ad637 | 136 | /* printf("adding feeder %s, %x -> %x\n", fc->name, fc->desc[i].in, fc->desc[i].out); */ |
4e8e900c | 137 | fte = kmalloc(sizeof(*fte), M_FEEDER, M_WAITOK | M_ZERO); |
2a1ad637 | 138 | if (fte == NULL) { |
67931cc4 | 139 | kprintf("can't allocate memory for feeder '%s', %x -> %x\n", fc->name, fc->desc[i].in, fc->desc[i].out); |
2a1ad637 FT |
140 | |
141 | return; | |
142 | } | |
984263bc MD |
143 | fte->feederclass = fc; |
144 | fte->desc = &fc->desc[i]; | |
145 | fte->idx = feedercnt; | |
146 | fte->desc->idx = feedercnt; | |
147 | SLIST_INSERT_HEAD(&feedertab, fte, link); | |
148 | i++; | |
149 | } | |
150 | feedercnt++; | |
151 | if (feedercnt >= MAXFEEDERS) | |
67931cc4 | 152 | kprintf("MAXFEEDERS (%d >= %d) exceeded\n", feedercnt, MAXFEEDERS); |
984263bc MD |
153 | } |
154 | ||
155 | static void | |
156 | feeder_unregisterall(void *p) | |
157 | { | |
158 | struct feedertab_entry *fte, *next; | |
159 | ||
160 | next = SLIST_FIRST(&feedertab); | |
161 | while (next != NULL) { | |
162 | fte = next; | |
163 | next = SLIST_NEXT(fte, link); | |
67931cc4 | 164 | kfree(fte, M_FEEDER); |
984263bc MD |
165 | } |
166 | } | |
167 | ||
168 | static int | |
169 | cmpdesc(struct pcm_feederdesc *n, struct pcm_feederdesc *m) | |
170 | { | |
171 | return ((n->type == m->type) && | |
172 | ((n->in == 0) || (n->in == m->in)) && | |
173 | ((n->out == 0) || (n->out == m->out)) && | |
174 | (n->flags == m->flags)); | |
175 | } | |
176 | ||
177 | static void | |
178 | feeder_destroy(struct pcm_feeder *f) | |
179 | { | |
180 | FEEDER_FREE(f); | |
181 | kobj_delete((kobj_t)f, M_FEEDER); | |
182 | } | |
183 | ||
184 | static struct pcm_feeder * | |
185 | feeder_create(struct feeder_class *fc, struct pcm_feederdesc *desc) | |
186 | { | |
187 | struct pcm_feeder *f; | |
188 | int err; | |
189 | ||
4e8e900c | 190 | f = (struct pcm_feeder *)kobj_create((kobj_class_t)fc, M_FEEDER, M_WAITOK | M_ZERO); |
984263bc MD |
191 | if (f == NULL) |
192 | return NULL; | |
193 | ||
984263bc MD |
194 | f->data = fc->data; |
195 | f->source = NULL; | |
196 | f->parent = NULL; | |
197 | f->class = fc; | |
198 | f->desc = &(f->desc_static); | |
199 | ||
200 | if (desc) { | |
201 | *(f->desc) = *desc; | |
202 | } else { | |
203 | f->desc->type = FEEDER_ROOT; | |
204 | f->desc->in = 0; | |
205 | f->desc->out = 0; | |
206 | f->desc->flags = 0; | |
207 | f->desc->idx = 0; | |
208 | } | |
209 | ||
210 | err = FEEDER_INIT(f); | |
211 | if (err) { | |
67931cc4 | 212 | kprintf("feeder_init(%p) on %s returned %d\n", f, fc->name, err); |
984263bc MD |
213 | feeder_destroy(f); |
214 | ||
215 | return NULL; | |
216 | } | |
217 | ||
218 | return f; | |
219 | } | |
220 | ||
221 | struct feeder_class * | |
222 | feeder_getclass(struct pcm_feederdesc *desc) | |
223 | { | |
224 | struct feedertab_entry *fte; | |
225 | ||
226 | SLIST_FOREACH(fte, &feedertab, link) { | |
227 | if ((desc == NULL) && (fte->desc == NULL)) | |
228 | return fte->feederclass; | |
229 | if ((fte->desc != NULL) && (desc != NULL) && cmpdesc(desc, fte->desc)) | |
230 | return fte->feederclass; | |
231 | } | |
232 | return NULL; | |
233 | } | |
234 | ||
235 | int | |
236 | chn_addfeeder(struct pcm_channel *c, struct feeder_class *fc, struct pcm_feederdesc *desc) | |
237 | { | |
238 | struct pcm_feeder *nf; | |
239 | ||
240 | nf = feeder_create(fc, desc); | |
241 | if (nf == NULL) | |
242 | return ENOSPC; | |
243 | ||
244 | nf->source = c->feeder; | |
245 | ||
558a398b SS |
246 | if (c->feeder != NULL) |
247 | c->feeder->parent = nf; | |
984263bc MD |
248 | c->feeder = nf; |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
253 | int | |
254 | chn_removefeeder(struct pcm_channel *c) | |
255 | { | |
256 | struct pcm_feeder *f; | |
257 | ||
258 | if (c->feeder == NULL) | |
259 | return -1; | |
260 | f = c->feeder; | |
261 | c->feeder = c->feeder->source; | |
262 | feeder_destroy(f); | |
263 | ||
264 | return 0; | |
265 | } | |
266 | ||
267 | struct pcm_feeder * | |
268 | chn_findfeeder(struct pcm_channel *c, u_int32_t type) | |
269 | { | |
270 | struct pcm_feeder *f; | |
271 | ||
272 | f = c->feeder; | |
273 | while (f != NULL) { | |
274 | if (f->desc->type == type) | |
275 | return f; | |
276 | f = f->source; | |
277 | } | |
278 | ||
279 | return NULL; | |
280 | } | |
281 | ||
2a1ad637 FT |
282 | /* |
283 | * 14bit format scoring | |
284 | * -------------------- | |
285 | * | |
286 | * 13 12 11 10 9 8 2 1 0 offset | |
287 | * +---+---+---+---+---+---+-------------+---+---+ | |
288 | * | X | X | X | X | X | X | X X X X X X | X | X | | |
289 | * +---+---+---+---+---+---+-------------+---+---+ | |
290 | * | | | | | | | | | | |
291 | * | | | | | | | | +--> signed? | |
292 | * | | | | | | | | | |
293 | * | | | | | | | +------> bigendian? | |
294 | * | | | | | | | | |
295 | * | | | | | | +---------------> total channels | |
296 | * | | | | | | | |
297 | * | | | | | +------------------------> AFMT_A_LAW | |
298 | * | | | | | | |
299 | * | | | | +----------------------------> AFMT_MU_LAW | |
300 | * | | | | | |
301 | * | | | +--------------------------------> AFMT_8BIT | |
302 | * | | | | |
303 | * | | +------------------------------------> AFMT_16BIT | |
304 | * | | | |
305 | * | +----------------------------------------> AFMT_24BIT | |
306 | * | | |
307 | * +--------------------------------------------> AFMT_32BIT | |
308 | */ | |
309 | #define score_signeq(s1, s2) (((s1) & 0x1) == ((s2) & 0x1)) | |
310 | #define score_endianeq(s1, s2) (((s1) & 0x2) == ((s2) & 0x2)) | |
311 | #define score_cheq(s1, s2) (((s1) & 0xfc) == ((s2) & 0xfc)) | |
312 | #define score_chgt(s1, s2) (((s1) & 0xfc) > ((s2) & 0xfc)) | |
313 | #define score_chlt(s1, s2) (((s1) & 0xfc) < ((s2) & 0xfc)) | |
314 | #define score_val(s1) ((s1) & 0x3f00) | |
315 | #define score_cse(s1) ((s1) & 0x7f) | |
984263bc | 316 | |
2a1ad637 FT |
317 | u_int32_t |
318 | snd_fmtscore(u_int32_t fmt) | |
558a398b | 319 | { |
2a1ad637 FT |
320 | u_int32_t ret; |
321 | ||
322 | ret = 0; | |
323 | if (fmt & AFMT_SIGNED) | |
324 | ret |= 1 << 0; | |
325 | if (fmt & AFMT_BIGENDIAN) | |
326 | ret |= 1 << 1; | |
327 | /*if (fmt & AFMT_STEREO) | |
328 | ret |= (2 & 0x3f) << 2; | |
329 | else | |
330 | ret |= (1 & 0x3f) << 2;*/ | |
331 | ret |= (AFMT_CHANNEL(fmt) & 0x3f) << 2; | |
558a398b | 332 | if (fmt & AFMT_A_LAW) |
2a1ad637 FT |
333 | ret |= 1 << 8; |
334 | else if (fmt & AFMT_MU_LAW) | |
335 | ret |= 1 << 9; | |
336 | else if (fmt & AFMT_8BIT) | |
337 | ret |= 1 << 10; | |
338 | else if (fmt & AFMT_16BIT) | |
339 | ret |= 1 << 11; | |
340 | else if (fmt & AFMT_24BIT) | |
341 | ret |= 1 << 12; | |
342 | else if (fmt & AFMT_32BIT) | |
343 | ret |= 1 << 13; | |
344 | ||
345 | return ret; | |
558a398b SS |
346 | } |
347 | ||
2a1ad637 FT |
348 | static u_int32_t |
349 | snd_fmtbestfunc(u_int32_t fmt, u_int32_t *fmts, int cheq) | |
558a398b | 350 | { |
2a1ad637 FT |
351 | u_int32_t best, score, score2, oldscore; |
352 | int i; | |
558a398b | 353 | |
2a1ad637 FT |
354 | if (fmt == 0 || fmts == NULL || fmts[0] == 0) |
355 | return 0; | |
558a398b | 356 | |
2a1ad637 FT |
357 | if (snd_fmtvalid(fmt, fmts)) |
358 | return fmt; | |
558a398b SS |
359 | |
360 | best = 0; | |
2a1ad637 | 361 | score = snd_fmtscore(fmt); |
558a398b SS |
362 | oldscore = 0; |
363 | for (i = 0; fmts[i] != 0; i++) { | |
2a1ad637 FT |
364 | score2 = snd_fmtscore(fmts[i]); |
365 | if (cheq && !score_cheq(score, score2) && | |
366 | (score_chlt(score2, score) || | |
367 | (oldscore != 0 && score_chgt(score2, oldscore)))) | |
368 | continue; | |
369 | if (oldscore == 0 || | |
370 | (score_val(score2) == score_val(score)) || | |
371 | (score_val(score2) == score_val(oldscore)) || | |
372 | (score_val(score2) > score_val(oldscore) && | |
373 | score_val(score2) < score_val(score)) || | |
374 | (score_val(score2) < score_val(oldscore) && | |
375 | score_val(score2) > score_val(score)) || | |
376 | (score_val(oldscore) < score_val(score) && | |
377 | score_val(score2) > score_val(oldscore))) { | |
378 | if (score_val(oldscore) != score_val(score2) || | |
379 | score_cse(score) == score_cse(score2) || | |
380 | ((score_cse(oldscore) != score_cse(score) && | |
381 | !score_endianeq(score, oldscore) && | |
382 | (score_endianeq(score, score2) || | |
383 | (!score_signeq(score, oldscore) && | |
384 | score_signeq(score, score2)))))) { | |
558a398b SS |
385 | best = fmts[i]; |
386 | oldscore = score2; | |
387 | } | |
388 | } | |
389 | } | |
390 | return best; | |
391 | } | |
392 | ||
393 | u_int32_t | |
2a1ad637 FT |
394 | snd_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) |
395 | { | |
396 | return snd_fmtbestfunc(fmt, fmts, 0); | |
397 | } | |
398 | ||
399 | u_int32_t | |
400 | snd_fmtbestchannel(u_int32_t fmt, u_int32_t *fmts) | |
401 | { | |
402 | return snd_fmtbestfunc(fmt, fmts, 1); | |
403 | } | |
404 | ||
405 | u_int32_t | |
406 | snd_fmtbest(u_int32_t fmt, u_int32_t *fmts) | |
558a398b SS |
407 | { |
408 | u_int32_t best1, best2; | |
2a1ad637 FT |
409 | u_int32_t score, score1, score2; |
410 | ||
411 | if (snd_fmtvalid(fmt, fmts)) | |
412 | return fmt; | |
558a398b | 413 | |
2a1ad637 FT |
414 | best1 = snd_fmtbestchannel(fmt, fmts); |
415 | best2 = snd_fmtbestbit(fmt, fmts); | |
558a398b | 416 | |
2a1ad637 FT |
417 | if (best1 != 0 && best2 != 0 && best1 != best2) { |
418 | /*if (fmt & AFMT_STEREO)*/ | |
419 | if (AFMT_CHANNEL(fmt) > 1) | |
558a398b SS |
420 | return best1; |
421 | else { | |
2a1ad637 FT |
422 | score = score_val(snd_fmtscore(fmt)); |
423 | score1 = score_val(snd_fmtscore(best1)); | |
424 | score2 = score_val(snd_fmtscore(best2)); | |
558a398b SS |
425 | if (score1 == score2 || score1 == score) |
426 | return best1; | |
427 | else if (score2 == score) | |
428 | return best2; | |
429 | else if (score1 > score2) | |
430 | return best1; | |
431 | return best2; | |
432 | } | |
433 | } else if (best2 == 0) | |
434 | return best1; | |
435 | else | |
436 | return best2; | |
437 | } | |
438 | ||
558a398b SS |
439 | void |
440 | feeder_printchain(struct pcm_feeder *head) | |
441 | { | |
442 | struct pcm_feeder *f; | |
443 | ||
67931cc4 | 444 | kprintf("feeder chain (head @%p)\n", head); |
558a398b SS |
445 | f = head; |
446 | while (f != NULL) { | |
67931cc4 | 447 | kprintf("%s/%d @ %p\n", f->class->name, f->desc->idx, f); |
558a398b SS |
448 | f = f->source; |
449 | } | |
67931cc4 | 450 | kprintf("[end]\n\n"); |
984263bc MD |
451 | } |
452 | ||
453 | /*****************************************************************************/ | |
454 | ||
455 | static int | |
456 | feed_root(struct pcm_feeder *feeder, struct pcm_channel *ch, u_int8_t *buffer, u_int32_t count, void *source) | |
457 | { | |
458 | struct snd_dbuf *src = source; | |
2a1ad637 | 459 | int l, offset; |
984263bc MD |
460 | |
461 | KASSERT(count > 0, ("feed_root: count == 0")); | |
2a1ad637 FT |
462 | |
463 | if (++ch->feedcount == 0) | |
464 | ch->feedcount = 2; | |
984263bc MD |
465 | |
466 | l = min(count, sndbuf_getready(src)); | |
984263bc MD |
467 | |
468 | /* When recording only return as much data as available */ | |
2a1ad637 FT |
469 | if (ch->direction == PCMDIR_REC) { |
470 | sndbuf_dispose(src, buffer, l); | |
984263bc | 471 | return l; |
2a1ad637 | 472 | } |
984263bc | 473 | |
984263bc | 474 | |
2a1ad637 FT |
475 | offset = count - l; |
476 | ||
477 | if (offset > 0) { | |
478 | if (snd_verbose > 3) | |
67931cc4 | 479 | kprintf("%s: (%s) %spending %d bytes " |
2a1ad637 FT |
480 | "(count=%d l=%d feed=%d)\n", |
481 | __func__, | |
482 | (ch->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", | |
483 | (ch->feedcount == 1) ? "pre" : "ap", | |
484 | offset, count, l, ch->feedcount); | |
485 | ||
486 | if (ch->feedcount == 1) { | |
487 | memset(buffer, | |
488 | sndbuf_zerodata(sndbuf_getfmt(src)), | |
489 | offset); | |
490 | if (l > 0) | |
491 | sndbuf_dispose(src, buffer + offset, l); | |
492 | else | |
493 | ch->feedcount--; | |
494 | } else { | |
495 | if (l > 0) | |
496 | sndbuf_dispose(src, buffer, l); | |
497 | memset(buffer + l, | |
498 | sndbuf_zerodata(sndbuf_getfmt(src)), | |
499 | offset); | |
500 | if (!(ch->flags & CHN_F_CLOSING)) | |
501 | ch->xruns++; | |
502 | } | |
503 | } else if (l > 0) | |
504 | sndbuf_dispose(src, buffer, l); | |
984263bc MD |
505 | |
506 | return count; | |
507 | } | |
508 | ||
509 | static kobj_method_t feeder_root_methods[] = { | |
510 | KOBJMETHOD(feeder_feed, feed_root), | |
7774cda2 | 511 | KOBJMETHOD_END |
984263bc MD |
512 | }; |
513 | static struct feeder_class feeder_root_class = { | |
558a398b SS |
514 | .name = "feeder_root", |
515 | .methods = feeder_root_methods, | |
516 | .size = sizeof(struct pcm_feeder), | |
558a398b SS |
517 | .desc = NULL, |
518 | .data = NULL, | |
984263bc MD |
519 | }; |
520 | SYSINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_register, &feeder_root_class); | |
521 | SYSUNINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_unregisterall, NULL); |