diff options
-rw-r--r-- | Documentation/RCU/torture.txt | 22 | ||||
-rw-r--r-- | kernel/rcutorture.c | 163 |
2 files changed, 133 insertions, 52 deletions
diff --git a/Documentation/RCU/torture.txt b/Documentation/RCU/torture.txt index d896685a9100..a4948591607d 100644 --- a/Documentation/RCU/torture.txt +++ b/Documentation/RCU/torture.txt | |||
@@ -7,7 +7,7 @@ The CONFIG_RCU_TORTURE_TEST config option is available for all RCU | |||
7 | implementations. It creates an rcutorture kernel module that can | 7 | implementations. It creates an rcutorture kernel module that can |
8 | be loaded to run a torture test. The test periodically outputs | 8 | be loaded to run a torture test. The test periodically outputs |
9 | status messages via printk(), which can be examined via the dmesg | 9 | status messages via printk(), which can be examined via the dmesg |
10 | command (perhaps grepping for "rcutorture"). The test is started | 10 | command (perhaps grepping for "torture"). The test is started |
11 | when the module is loaded, and stops when the module is unloaded. | 11 | when the module is loaded, and stops when the module is unloaded. |
12 | 12 | ||
13 | However, actually setting this config option to "y" results in the system | 13 | However, actually setting this config option to "y" results in the system |
@@ -44,6 +44,10 @@ test_no_idle_hz Whether or not to test the ability of RCU to operate in | |||
44 | a kernel that disables the scheduling-clock interrupt to | 44 | a kernel that disables the scheduling-clock interrupt to |
45 | idle CPUs. Boolean parameter, "1" to test, "0" otherwise. | 45 | idle CPUs. Boolean parameter, "1" to test, "0" otherwise. |
46 | 46 | ||
47 | torture_type The type of RCU to test: "rcu" for the rcu_read_lock() | ||
48 | API, "rcu_bh" for the rcu_read_lock_bh() API, and "srcu" | ||
49 | for the "srcu_read_lock()" API. | ||
50 | |||
47 | verbose Enable debug printk()s. Default is disabled. | 51 | verbose Enable debug printk()s. Default is disabled. |
48 | 52 | ||
49 | 53 | ||
@@ -51,14 +55,14 @@ OUTPUT | |||
51 | 55 | ||
52 | The statistics output is as follows: | 56 | The statistics output is as follows: |
53 | 57 | ||
54 | rcutorture: --- Start of test: nreaders=16 stat_interval=0 verbose=0 | 58 | rcu-torture: --- Start of test: nreaders=16 stat_interval=0 verbose=0 |
55 | rcutorture: rtc: 0000000000000000 ver: 1916 tfle: 0 rta: 1916 rtaf: 0 rtf: 1915 | 59 | rcu-torture: rtc: 0000000000000000 ver: 1916 tfle: 0 rta: 1916 rtaf: 0 rtf: 1915 |
56 | rcutorture: Reader Pipe: 1466408 9747 0 0 0 0 0 0 0 0 0 | 60 | rcu-torture: Reader Pipe: 1466408 9747 0 0 0 0 0 0 0 0 0 |
57 | rcutorture: Reader Batch: 1464477 11678 0 0 0 0 0 0 0 0 | 61 | rcu-torture: Reader Batch: 1464477 11678 0 0 0 0 0 0 0 0 |
58 | rcutorture: Free-Block Circulation: 1915 1915 1915 1915 1915 1915 1915 1915 1915 1915 0 | 62 | rcu-torture: Free-Block Circulation: 1915 1915 1915 1915 1915 1915 1915 1915 1915 1915 0 |
59 | rcutorture: --- End of test | 63 | rcu-torture: --- End of test |
60 | 64 | ||
61 | The command "dmesg | grep rcutorture:" will extract this information on | 65 | The command "dmesg | grep torture:" will extract this information on |
62 | most systems. On more esoteric configurations, it may be necessary to | 66 | most systems. On more esoteric configurations, it may be necessary to |
63 | use other commands to access the output of the printk()s used by | 67 | use other commands to access the output of the printk()s used by |
64 | the RCU torture test. The printk()s use KERN_ALERT, so they should | 68 | the RCU torture test. The printk()s use KERN_ALERT, so they should |
@@ -124,7 +128,7 @@ The following script may be used to torture RCU: | |||
124 | modprobe rcutorture | 128 | modprobe rcutorture |
125 | sleep 100 | 129 | sleep 100 |
126 | rmmod rcutorture | 130 | rmmod rcutorture |
127 | dmesg | grep rcutorture: | 131 | dmesg | grep torture: |
128 | 132 | ||
129 | The output can be manually inspected for the error flag of "!!!". | 133 | The output can be manually inspected for the error flag of "!!!". |
130 | One could of course create a more elaborate script that automatically | 134 | One could of course create a more elaborate script that automatically |
diff --git a/kernel/rcutorture.c b/kernel/rcutorture.c index 2a65bd8a3d10..c96b5edd6ed1 100644 --- a/kernel/rcutorture.c +++ b/kernel/rcutorture.c | |||
@@ -53,6 +53,7 @@ static int stat_interval; /* Interval between stats, in seconds. */ | |||
53 | static int verbose; /* Print more debug info. */ | 53 | static int verbose; /* Print more debug info. */ |
54 | static int test_no_idle_hz; /* Test RCU's support for tickless idle CPUs. */ | 54 | static int test_no_idle_hz; /* Test RCU's support for tickless idle CPUs. */ |
55 | static int shuffle_interval = 5; /* Interval between shuffles (in sec)*/ | 55 | static int shuffle_interval = 5; /* Interval between shuffles (in sec)*/ |
56 | static char *torture_type = "rcu"; /* What to torture. */ | ||
56 | 57 | ||
57 | module_param(nreaders, int, 0); | 58 | module_param(nreaders, int, 0); |
58 | MODULE_PARM_DESC(nreaders, "Number of RCU reader threads"); | 59 | MODULE_PARM_DESC(nreaders, "Number of RCU reader threads"); |
@@ -64,13 +65,16 @@ module_param(test_no_idle_hz, bool, 0); | |||
64 | MODULE_PARM_DESC(test_no_idle_hz, "Test support for tickless idle CPUs"); | 65 | MODULE_PARM_DESC(test_no_idle_hz, "Test support for tickless idle CPUs"); |
65 | module_param(shuffle_interval, int, 0); | 66 | module_param(shuffle_interval, int, 0); |
66 | MODULE_PARM_DESC(shuffle_interval, "Number of seconds between shuffles"); | 67 | MODULE_PARM_DESC(shuffle_interval, "Number of seconds between shuffles"); |
67 | #define TORTURE_FLAG "rcutorture: " | 68 | module_param(torture_type, charp, 0); |
69 | MODULE_PARM_DESC(torture_type, "Type of RCU to torture (rcu)"); | ||
70 | |||
71 | #define TORTURE_FLAG "-torture:" | ||
68 | #define PRINTK_STRING(s) \ | 72 | #define PRINTK_STRING(s) \ |
69 | do { printk(KERN_ALERT TORTURE_FLAG s "\n"); } while (0) | 73 | do { printk(KERN_ALERT "%s" TORTURE_FLAG s "\n", torture_type); } while (0) |
70 | #define VERBOSE_PRINTK_STRING(s) \ | 74 | #define VERBOSE_PRINTK_STRING(s) \ |
71 | do { if (verbose) printk(KERN_ALERT TORTURE_FLAG s "\n"); } while (0) | 75 | do { if (verbose) printk(KERN_ALERT "%s" TORTURE_FLAG s "\n", torture_type); } while (0) |
72 | #define VERBOSE_PRINTK_ERRSTRING(s) \ | 76 | #define VERBOSE_PRINTK_ERRSTRING(s) \ |
73 | do { if (verbose) printk(KERN_ALERT TORTURE_FLAG "!!! " s "\n"); } while (0) | 77 | do { if (verbose) printk(KERN_ALERT "%s" TORTURE_FLAG "!!! " s "\n", torture_type); } while (0) |
74 | 78 | ||
75 | static char printk_buf[4096]; | 79 | static char printk_buf[4096]; |
76 | 80 | ||
@@ -139,28 +143,6 @@ rcu_torture_free(struct rcu_torture *p) | |||
139 | spin_unlock_bh(&rcu_torture_lock); | 143 | spin_unlock_bh(&rcu_torture_lock); |
140 | } | 144 | } |
141 | 145 | ||
142 | static void | ||
143 | rcu_torture_cb(struct rcu_head *p) | ||
144 | { | ||
145 | int i; | ||
146 | struct rcu_torture *rp = container_of(p, struct rcu_torture, rtort_rcu); | ||
147 | |||
148 | if (fullstop) { | ||
149 | /* Test is ending, just drop callbacks on the floor. */ | ||
150 | /* The next initialization will pick up the pieces. */ | ||
151 | return; | ||
152 | } | ||
153 | i = rp->rtort_pipe_count; | ||
154 | if (i > RCU_TORTURE_PIPE_LEN) | ||
155 | i = RCU_TORTURE_PIPE_LEN; | ||
156 | atomic_inc(&rcu_torture_wcount[i]); | ||
157 | if (++rp->rtort_pipe_count >= RCU_TORTURE_PIPE_LEN) { | ||
158 | rp->rtort_mbtest = 0; | ||
159 | rcu_torture_free(rp); | ||
160 | } else | ||
161 | call_rcu(p, rcu_torture_cb); | ||
162 | } | ||
163 | |||
164 | struct rcu_random_state { | 146 | struct rcu_random_state { |
165 | unsigned long rrs_state; | 147 | unsigned long rrs_state; |
166 | unsigned long rrs_count; | 148 | unsigned long rrs_count; |
@@ -191,6 +173,83 @@ rcu_random(struct rcu_random_state *rrsp) | |||
191 | } | 173 | } |
192 | 174 | ||
193 | /* | 175 | /* |
176 | * Operations vector for selecting different types of tests. | ||
177 | */ | ||
178 | |||
179 | struct rcu_torture_ops { | ||
180 | void (*init)(void); | ||
181 | void (*cleanup)(void); | ||
182 | int (*readlock)(void); | ||
183 | void (*readunlock)(int idx); | ||
184 | int (*completed)(void); | ||
185 | void (*deferredfree)(struct rcu_torture *p); | ||
186 | int (*stats)(char *page); | ||
187 | char *name; | ||
188 | }; | ||
189 | static struct rcu_torture_ops *cur_ops = NULL; | ||
190 | |||
191 | /* | ||
192 | * Definitions for rcu torture testing. | ||
193 | */ | ||
194 | |||
195 | static int rcu_torture_read_lock(void) | ||
196 | { | ||
197 | rcu_read_lock(); | ||
198 | return 0; | ||
199 | } | ||
200 | |||
201 | static void rcu_torture_read_unlock(int idx) | ||
202 | { | ||
203 | rcu_read_unlock(); | ||
204 | } | ||
205 | |||
206 | static int rcu_torture_completed(void) | ||
207 | { | ||
208 | return rcu_batches_completed(); | ||
209 | } | ||
210 | |||
211 | static void | ||
212 | rcu_torture_cb(struct rcu_head *p) | ||
213 | { | ||
214 | int i; | ||
215 | struct rcu_torture *rp = container_of(p, struct rcu_torture, rtort_rcu); | ||
216 | |||
217 | if (fullstop) { | ||
218 | /* Test is ending, just drop callbacks on the floor. */ | ||
219 | /* The next initialization will pick up the pieces. */ | ||
220 | return; | ||
221 | } | ||
222 | i = rp->rtort_pipe_count; | ||
223 | if (i > RCU_TORTURE_PIPE_LEN) | ||
224 | i = RCU_TORTURE_PIPE_LEN; | ||
225 | atomic_inc(&rcu_torture_wcount[i]); | ||
226 | if (++rp->rtort_pipe_count >= RCU_TORTURE_PIPE_LEN) { | ||
227 | rp->rtort_mbtest = 0; | ||
228 | rcu_torture_free(rp); | ||
229 | } else | ||
230 | cur_ops->deferredfree(rp); | ||
231 | } | ||
232 | |||
233 | static void rcu_torture_deferred_free(struct rcu_torture *p) | ||
234 | { | ||
235 | call_rcu(&p->rtort_rcu, rcu_torture_cb); | ||
236 | } | ||
237 | |||
238 | static struct rcu_torture_ops rcu_ops = { | ||
239 | .init = NULL, | ||
240 | .cleanup = NULL, | ||
241 | .readlock = rcu_torture_read_lock, | ||
242 | .readunlock = rcu_torture_read_unlock, | ||
243 | .completed = rcu_torture_completed, | ||
244 | .deferredfree = rcu_torture_deferred_free, | ||
245 | .stats = NULL, | ||
246 | .name = "rcu" | ||
247 | }; | ||
248 | |||
249 | static struct rcu_torture_ops *torture_ops[] = | ||
250 | { &rcu_ops, NULL }; | ||
251 | |||
252 | /* | ||
194 | * RCU torture writer kthread. Repeatedly substitutes a new structure | 253 | * RCU torture writer kthread. Repeatedly substitutes a new structure |
195 | * for that pointed to by rcu_torture_current, freeing the old structure | 254 | * for that pointed to by rcu_torture_current, freeing the old structure |
196 | * after a series of grace periods (the "pipeline"). | 255 | * after a series of grace periods (the "pipeline"). |
@@ -209,8 +268,6 @@ rcu_torture_writer(void *arg) | |||
209 | 268 | ||
210 | do { | 269 | do { |
211 | schedule_timeout_uninterruptible(1); | 270 | schedule_timeout_uninterruptible(1); |
212 | if (rcu_batches_completed() == oldbatch) | ||
213 | continue; | ||
214 | if ((rp = rcu_torture_alloc()) == NULL) | 271 | if ((rp = rcu_torture_alloc()) == NULL) |
215 | continue; | 272 | continue; |
216 | rp->rtort_pipe_count = 0; | 273 | rp->rtort_pipe_count = 0; |
@@ -225,10 +282,10 @@ rcu_torture_writer(void *arg) | |||
225 | i = RCU_TORTURE_PIPE_LEN; | 282 | i = RCU_TORTURE_PIPE_LEN; |
226 | atomic_inc(&rcu_torture_wcount[i]); | 283 | atomic_inc(&rcu_torture_wcount[i]); |
227 | old_rp->rtort_pipe_count++; | 284 | old_rp->rtort_pipe_count++; |
228 | call_rcu(&old_rp->rtort_rcu, rcu_torture_cb); | 285 | cur_ops->deferredfree(old_rp); |
229 | } | 286 | } |
230 | rcu_torture_current_version++; | 287 | rcu_torture_current_version++; |
231 | oldbatch = rcu_batches_completed(); | 288 | oldbatch = cur_ops->completed(); |
232 | } while (!kthread_should_stop() && !fullstop); | 289 | } while (!kthread_should_stop() && !fullstop); |
233 | VERBOSE_PRINTK_STRING("rcu_torture_writer task stopping"); | 290 | VERBOSE_PRINTK_STRING("rcu_torture_writer task stopping"); |
234 | while (!kthread_should_stop()) | 291 | while (!kthread_should_stop()) |
@@ -246,6 +303,7 @@ static int | |||
246 | rcu_torture_reader(void *arg) | 303 | rcu_torture_reader(void *arg) |
247 | { | 304 | { |
248 | int completed; | 305 | int completed; |
306 | int idx; | ||
249 | DEFINE_RCU_RANDOM(rand); | 307 | DEFINE_RCU_RANDOM(rand); |
250 | struct rcu_torture *p; | 308 | struct rcu_torture *p; |
251 | int pipe_count; | 309 | int pipe_count; |
@@ -254,12 +312,12 @@ rcu_torture_reader(void *arg) | |||
254 | set_user_nice(current, 19); | 312 | set_user_nice(current, 19); |
255 | 313 | ||
256 | do { | 314 | do { |
257 | rcu_read_lock(); | 315 | idx = cur_ops->readlock(); |
258 | completed = rcu_batches_completed(); | 316 | completed = cur_ops->completed(); |
259 | p = rcu_dereference(rcu_torture_current); | 317 | p = rcu_dereference(rcu_torture_current); |
260 | if (p == NULL) { | 318 | if (p == NULL) { |
261 | /* Wait for rcu_torture_writer to get underway */ | 319 | /* Wait for rcu_torture_writer to get underway */ |
262 | rcu_read_unlock(); | 320 | cur_ops->readunlock(idx); |
263 | schedule_timeout_interruptible(HZ); | 321 | schedule_timeout_interruptible(HZ); |
264 | continue; | 322 | continue; |
265 | } | 323 | } |
@@ -273,14 +331,14 @@ rcu_torture_reader(void *arg) | |||
273 | pipe_count = RCU_TORTURE_PIPE_LEN; | 331 | pipe_count = RCU_TORTURE_PIPE_LEN; |
274 | } | 332 | } |
275 | ++__get_cpu_var(rcu_torture_count)[pipe_count]; | 333 | ++__get_cpu_var(rcu_torture_count)[pipe_count]; |
276 | completed = rcu_batches_completed() - completed; | 334 | completed = cur_ops->completed() - completed; |
277 | if (completed > RCU_TORTURE_PIPE_LEN) { | 335 | if (completed > RCU_TORTURE_PIPE_LEN) { |
278 | /* Should not happen, but... */ | 336 | /* Should not happen, but... */ |
279 | completed = RCU_TORTURE_PIPE_LEN; | 337 | completed = RCU_TORTURE_PIPE_LEN; |
280 | } | 338 | } |
281 | ++__get_cpu_var(rcu_torture_batch)[completed]; | 339 | ++__get_cpu_var(rcu_torture_batch)[completed]; |
282 | preempt_enable(); | 340 | preempt_enable(); |
283 | rcu_read_unlock(); | 341 | cur_ops->readunlock(idx); |
284 | schedule(); | 342 | schedule(); |
285 | } while (!kthread_should_stop() && !fullstop); | 343 | } while (!kthread_should_stop() && !fullstop); |
286 | VERBOSE_PRINTK_STRING("rcu_torture_reader task stopping"); | 344 | VERBOSE_PRINTK_STRING("rcu_torture_reader task stopping"); |
@@ -311,7 +369,7 @@ rcu_torture_printk(char *page) | |||
311 | if (pipesummary[i] != 0) | 369 | if (pipesummary[i] != 0) |
312 | break; | 370 | break; |
313 | } | 371 | } |
314 | cnt += sprintf(&page[cnt], "rcutorture: "); | 372 | cnt += sprintf(&page[cnt], "%s%s ", torture_type, TORTURE_FLAG); |
315 | cnt += sprintf(&page[cnt], | 373 | cnt += sprintf(&page[cnt], |
316 | "rtc: %p ver: %ld tfle: %d rta: %d rtaf: %d rtf: %d " | 374 | "rtc: %p ver: %ld tfle: %d rta: %d rtaf: %d rtf: %d " |
317 | "rtmbe: %d", | 375 | "rtmbe: %d", |
@@ -324,7 +382,7 @@ rcu_torture_printk(char *page) | |||
324 | atomic_read(&n_rcu_torture_mberror)); | 382 | atomic_read(&n_rcu_torture_mberror)); |
325 | if (atomic_read(&n_rcu_torture_mberror) != 0) | 383 | if (atomic_read(&n_rcu_torture_mberror) != 0) |
326 | cnt += sprintf(&page[cnt], " !!!"); | 384 | cnt += sprintf(&page[cnt], " !!!"); |
327 | cnt += sprintf(&page[cnt], "\nrcutorture: "); | 385 | cnt += sprintf(&page[cnt], "\n%s%s ", torture_type, TORTURE_FLAG); |
328 | if (i > 1) { | 386 | if (i > 1) { |
329 | cnt += sprintf(&page[cnt], "!!! "); | 387 | cnt += sprintf(&page[cnt], "!!! "); |
330 | atomic_inc(&n_rcu_torture_error); | 388 | atomic_inc(&n_rcu_torture_error); |
@@ -332,17 +390,19 @@ rcu_torture_printk(char *page) | |||
332 | cnt += sprintf(&page[cnt], "Reader Pipe: "); | 390 | cnt += sprintf(&page[cnt], "Reader Pipe: "); |
333 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) | 391 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) |
334 | cnt += sprintf(&page[cnt], " %ld", pipesummary[i]); | 392 | cnt += sprintf(&page[cnt], " %ld", pipesummary[i]); |
335 | cnt += sprintf(&page[cnt], "\nrcutorture: "); | 393 | cnt += sprintf(&page[cnt], "\n%s%s ", torture_type, TORTURE_FLAG); |
336 | cnt += sprintf(&page[cnt], "Reader Batch: "); | 394 | cnt += sprintf(&page[cnt], "Reader Batch: "); |
337 | for (i = 0; i < RCU_TORTURE_PIPE_LEN; i++) | 395 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) |
338 | cnt += sprintf(&page[cnt], " %ld", batchsummary[i]); | 396 | cnt += sprintf(&page[cnt], " %ld", batchsummary[i]); |
339 | cnt += sprintf(&page[cnt], "\nrcutorture: "); | 397 | cnt += sprintf(&page[cnt], "\n%s%s ", torture_type, TORTURE_FLAG); |
340 | cnt += sprintf(&page[cnt], "Free-Block Circulation: "); | 398 | cnt += sprintf(&page[cnt], "Free-Block Circulation: "); |
341 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) { | 399 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) { |
342 | cnt += sprintf(&page[cnt], " %d", | 400 | cnt += sprintf(&page[cnt], " %d", |
343 | atomic_read(&rcu_torture_wcount[i])); | 401 | atomic_read(&rcu_torture_wcount[i])); |
344 | } | 402 | } |
345 | cnt += sprintf(&page[cnt], "\n"); | 403 | cnt += sprintf(&page[cnt], "\n"); |
404 | if (cur_ops->stats != NULL) | ||
405 | cnt += cur_ops->stats(&page[cnt]); | ||
346 | return cnt; | 406 | return cnt; |
347 | } | 407 | } |
348 | 408 | ||
@@ -444,11 +504,11 @@ rcu_torture_shuffle(void *arg) | |||
444 | static inline void | 504 | static inline void |
445 | rcu_torture_print_module_parms(char *tag) | 505 | rcu_torture_print_module_parms(char *tag) |
446 | { | 506 | { |
447 | printk(KERN_ALERT TORTURE_FLAG "--- %s: nreaders=%d " | 507 | printk(KERN_ALERT "%s" TORTURE_FLAG "--- %s: nreaders=%d " |
448 | "stat_interval=%d verbose=%d test_no_idle_hz=%d " | 508 | "stat_interval=%d verbose=%d test_no_idle_hz=%d " |
449 | "shuffle_interval = %d\n", | 509 | "shuffle_interval = %d\n", |
450 | tag, nrealreaders, stat_interval, verbose, test_no_idle_hz, | 510 | torture_type, tag, nrealreaders, stat_interval, verbose, |
451 | shuffle_interval); | 511 | test_no_idle_hz, shuffle_interval); |
452 | } | 512 | } |
453 | 513 | ||
454 | static void | 514 | static void |
@@ -493,6 +553,9 @@ rcu_torture_cleanup(void) | |||
493 | rcu_barrier(); | 553 | rcu_barrier(); |
494 | 554 | ||
495 | rcu_torture_stats_print(); /* -After- the stats thread is stopped! */ | 555 | rcu_torture_stats_print(); /* -After- the stats thread is stopped! */ |
556 | |||
557 | if (cur_ops->cleanup != NULL) | ||
558 | cur_ops->cleanup(); | ||
496 | if (atomic_read(&n_rcu_torture_error)) | 559 | if (atomic_read(&n_rcu_torture_error)) |
497 | rcu_torture_print_module_parms("End of test: FAILURE"); | 560 | rcu_torture_print_module_parms("End of test: FAILURE"); |
498 | else | 561 | else |
@@ -508,6 +571,20 @@ rcu_torture_init(void) | |||
508 | 571 | ||
509 | /* Process args and tell the world that the torturer is on the job. */ | 572 | /* Process args and tell the world that the torturer is on the job. */ |
510 | 573 | ||
574 | for (i = 0; cur_ops = torture_ops[i], cur_ops != NULL; i++) { | ||
575 | cur_ops = torture_ops[i]; | ||
576 | if (strcmp(torture_type, cur_ops->name) == 0) { | ||
577 | break; | ||
578 | } | ||
579 | } | ||
580 | if (cur_ops == NULL) { | ||
581 | printk(KERN_ALERT "rcutorture: invalid torture type: \"%s\"\n", | ||
582 | torture_type); | ||
583 | return (-EINVAL); | ||
584 | } | ||
585 | if (cur_ops->init != NULL) | ||
586 | cur_ops->init(); /* no "goto unwind" prior to this point!!! */ | ||
587 | |||
511 | if (nreaders >= 0) | 588 | if (nreaders >= 0) |
512 | nrealreaders = nreaders; | 589 | nrealreaders = nreaders; |
513 | else | 590 | else |