|
|
1.1 root 1: #define _DDI_DKI 1
2: #define _DDI_DKI_IMPL 1
3: #define _SYSV3 1
4:
5: /*
6: * This file contains the implementations of the abstract scheduling
7: * interface functions defined in <sys/v_proc.h>.
8: *
9: * The functions defined here are used only by the code that needs to sleep
10: * processes to implement the DDI/DKI sleep lock and synchronization variable
11: * functionality. The code here has been broken out into a separate file and
12: * a formal interface specified purely to separate the abstract and
13: * implementation-dependent aspects of the DDI/DKI locking functions, ie to
14: * stop the original code being infected with #ifdefs.
15: *
16: * This module runs in the _SYSV3 compilation mode to interface with the iBCS2
17: * Coherent kernel.
18: */
19:
20: /*
21: *-IMPORTS:
22: * <common/ccompat.h>
23: * __CONST__
24: * __USE_PROTO__
25: * __ARGS ()
26: * <common/xdebug.h>
27: * __LOCAL__
28: * <kernel/ddi_proc.h>
29: * ddi_proc_data ()
30: * <kernel/ddi_glob.h>
31: * ddi_global_data ()
32: * <kernel/ddi_lock.h>
33: * proc_global_priority
34: * <sys/debug.h>
35: * ASSERT ()
36: * <sys/types.h>
37: * plhi
38: * <sys/inline.h>
39: * splbase ()
40: * <sys/ksynch.h>
41: * LOCK ()
42: * UNLOCK ()
43: * <sys/signal.h>
44: * SIGTSTP
45: */
46:
47: #include <common/ccompat.h>
48: #include <common/xdebug.h>
49: #include <kernel/ddi_proc.h>
50: #include <kernel/ddi_glob.h>
51: #include <kernel/ddi_lock.h>
52: #include <sys/debug.h>
53: #include <sys/types.h>
54: #include <sys/inline.h>
55: #include <sys/ksynch.h>
56: #include <sys/signal.h>
57: #include <stddef.h>
58:
59: #include <kernel/v_proc.h>
60:
61: /*
62: * Type used in comparisons of process priority.
63: */
64:
65: typedef unsigned short pri_t;
66:
67:
68: #define PROCESS_PNODE() (& ddi_proc_data ()->dp_pnode)
69:
70:
71: /*
72: * Here we deal with the implementation-specifics.
73: */
74:
75: #if __COHERENT__
76:
77: #define __KERNEL__ 2
78: #include <sys/sched.h>
79: #undef __KERNEL__
80:
81: #define PNODE_TO_PROC(pnode) __DOWNCAST (PROC, p_nigel, \
82: __DOWNCAST (dpdata_t, dp_pnode, pnode))
83:
84:
85: /*
86: * Implementation-specific version of KERNEL_SLEEP ().
87: */
88:
89: #if __USE_PROTO__
90: __LOCAL__ int (KERNEL_SLEEP) (plist_t * plistp, int priority, int flag)
91: #else
92: __LOCAL__ int
93: (KERNEL_SLEEP) __ARGS ((plistp, priority, flag))
94: plist_t * plistp;
95: int priority;
96: int flag;
97: #endif
98: {
99: pnode_t * pnode = PROCESS_PNODE ();
100: int state;
101:
102: /*
103: * Since Coherent's signal-checking causes a longjump out to the
104: * top-level, we'll check for signals here ourself.
105: */
106:
107: if (flag == SLEEP_INTERRUPTIBLE && nondsig ()) {
108: /*
109: * We have pending signals that need processing, dequeue us
110: * from the plist, ulock the plist, and return an appropriate
111: * indication to the caller.
112: */
113:
114: plistp->pl_head = pnode->pn_next;
115: pnode->pn_next->pn_prev = NULL;
116:
117: ATOMIC_CLEAR_PTR (pnode->pn_plistp);
118:
119: PLIST_UNLOCK (plistp, plbase);
120:
121: return PROCESS_SIGNALLED;
122: }
123:
124:
125: /*
126: * We expect to be running with interrupts disabled, so there's no
127: * reason not to just unlock the basic lock now and use the standard
128: * off-the-shelf Coherent kernel sleep function.
129: */
130:
131: for (;;) {
132: PLIST_UNLOCK (plistp, plhi);
133:
134: x_sleep (pnode, priority, slpriNoSig, "STREAMS");
135:
136: /*
137: * The Coherent kernel won't dequeue us from the process list
138: * if it awakens us via a signal, so we use that fact to
139: * figure out why we are awake (sleep () does not return any
140: * useful value).
141: *
142: * To do anything about this in a multiprocessor-friendly
143: * fashion, we have to re-acquire the process list lock.
144: */
145:
146: (void) PLIST_LOCK (plistp, "KERNEL_SLEEP");
147:
148: if (ATOMIC_FETCH_PTR (pnode->pn_plistp) == 0) {
149: state = PROCESS_NORMAL_WAKE;
150: break; /* dequeued, normal wakeup */
151: }
152:
153:
154: /*
155: * We were signalled; our caller may have requested non-
156: * interruptible sleep, so we go back to sleep on the caller's
157: * behalf (Coherent will always wake up a sleeper on receipt
158: * of a signal).
159: */
160:
161: if (flag == SLEEP_INTERRUPTIBLE) {
162: /*
163: * We were woken by a signal and the caller wants to
164: * know about it, so dequeue us from the list and
165: * return the indication.
166: */
167:
168: plistp->pl_head = pnode->pn_next;
169: pnode->pn_next->pn_prev = NULL;
170:
171: ATOMIC_CLEAR_PTR (pnode->pn_plistp);
172:
173: state = PROCESS_SIGNALLED;
174: break;
175: }
176: }
177:
178: /*
179: * Since we acquired a lock on the process list in order to safely
180: * test the reason why we awoke, release that lock now. Since we can,
181: * we'll set the interrupt priority down low.
182: */
183:
184: PLIST_UNLOCK (plistp, plbase);
185:
186: return state;
187: }
188:
189:
190: /*
191: * Implementation-specific version of KERNEL_WAKE ().
192: */
193:
194: #if __USE_PROTO__
195: __LOCAL__ void (KERNEL_WAKE) (pnode_t * pnodep)
196: #else
197: __LOCAL__ void
198: KERNEL_WAKE __ARGS ((pnodep))
199: pnode_t * pnodep;
200: #endif
201: {
202: dwakeup (pnodep); /* don't defer this */
203: }
204:
205:
206: /*
207: * Implementation-specific code to send a signal to a process.
208: */
209:
210: #if __USE_PROTO__
211: __LOCAL__ void (KERNEL_SIGNAL) (dpdata_t * dpdatap, int sig)
212: #else
213: __LOCAL__ void
214: KERNEL_SIGNAL __ARGS ((dpdatap, sig))
215: dpdata_t * dpdatap;
216: int sig;
217: #endif
218: {
219: /*
220: * Since the Coherent process management system is ... not very
221: * sophisticated, and fixing it is less important than getting STREAMS
222: * out the door, we punt on some issues such as process reference
223: * counts and state checking. This is just glue to the old, broken
224: * system.
225: */
226:
227: }
228:
229:
230: #else
231:
232:
233: uarea_t _dos_uarea_;
234: proc_t _dos_proc_;
235:
236: #define PNODE_TO_PROC(pnodep) __DOWNCAST (proc_t, p_nigel, \
237: __DOWNCAST (dpdata_t, dp_pnode, pnodep))
238:
239: #include <sys/cmn_err.h>
240: #include <bios.h>
241:
242:
243: /*
244: * Implementation-specific version of KERNEL_SLEEP ().
245: */
246:
247: #if __USE_PROTO__
248: __LOCAL__ int (KERNEL_SLEEP) (plist_t * plistp, int priority, int flag)
249: #else
250: __LOCAL__ int
251: (KERNEL_SLEEP) __ARGS ((plistp, priority, flag))
252: plist_t * plistp;
253: int priority;
254: int flag;
255: #endif
256: {
257: int state;
258:
259:
260: CURRENT_PROCESS ()->p_state = PS_SLEEP;
261:
262: PLIST_UNLOCK (plistp, plhi);
263: (void) splbase ();
264:
265: while ((state = CURRENT_PROCESS ()->p_state) != PS_RUN &&
266: state != PS_SIGNALLED)
267: if (_bios_keybrd (_KEYBRD_READY))
268: switch (_bios_keybrd (_KEYBRD_READ) & 0xFF) {
269:
270: case 0x1B:
271: cmn_err (CE_PANIC, "Abort!");
272: break;
273:
274: default:
275: break;
276: }
277:
278: return (state == PS_RUN ? PROCESS_NORMAL_WAKE : PROCESS_SIGNALLED);
279: }
280:
281:
282: /*
283: * Implementation-specific version of KERNEL_WAKE ()
284: */
285:
286: #if __USE_PROTO__
287: __LOCAL__ void (KERNEL_WAKE) (pnode_t * pnodep)
288: #else
289: __LOCAL__ void
290: KERNEL_WAKE __ARGS ((pnodep))
291: pnode_t * pnodep;
292: #endif
293: {
294: ASSERT (pnodep == PROCESS_PNODE ());
295:
296: if (CURRENT_PROCESS ()->p_state == PS_SLEEP ||
297: CURRENT_PROCESS ()->p_state == PS_SLEEP_NO_SIG)
298: CURRENT_PROCESS ()->p_state = PS_RUN;
299: }
300:
301:
302: /*
303: * Implementation-specific code to send a signal to a process.
304: */
305:
306: #if __USE_PROTO__
307: __LOCAL__ int (KERNEL_SIGNAL) (dpdata_t * dpdatap, int sig)
308: #else
309: __LOCAL__ int
310: KERNEL_SIGNAL __ARGS ((dpdatap, sig))
311: dpdata_t * dpdatap;
312: int sig;
313: #endif
314: {
315: if (CURRENT_PROCESS ()->p_state == PS_SLEEP)
316: CURRENT_PROCESS ()->p_state = PS_SIGNALLED;
317:
318: return 0;
319: }
320:
321: #endif
322:
323:
324: /*
325: * Table to define the mapping between abstract processor priority and the
326: * priority levels used in the comparisons between priority levels.
327: */
328:
329: __LOCAL__ pri_t _pri_map [] = {
330: 0, 1, 2, 3, 4, 5, 6, /* prilo */
331: 7, 8, 9, 10, 11, 12, 13, /* pritape */
332: 14, 15, 16, 17, 18, 19, 20, /* primed */
333: 21, 22, 23, 24, 25, 26, 27, /* pritty */
334: 28, 29, 30, 31, 32, 33, 34, /* pridisk */
335: 35, 36, 37, 38, 39, 40, 41, /* prinet */
336: 42, 43, 44, 45, 46, 47, 48 /* prihi */
337: };
338:
339:
340:
341: /*
342: * This function is the abstract interface used by the DDI/DKI functions to
343: * invoke kernel sleep.
344: *
345: * This function is charged with sleeping the current process (possibly such
346: * that it may be interrupted by signals). It is passed a pointer to a locked
347: * process list structure on which the current process is to be threaded if
348: * it is actually going to sleep. After moving the process to a "slept"
349: * state and saving the process context the process list is unlocked before
350: * the main kernel dispatched is invoked.
351: */
352:
353: #define ARRAY_MAX(array) (sizeof (array) / sizeof ((array) [0]))
354:
355: #if __USE_PROTO__
356: int (MAKE_SLEEPING) (plist_t * plistp, int priority, int flag)
357: #else
358: int
359: MAKE_SLEEPING __ARGS ((plistp, priority, flag))
360: plist_t * plistp;
361: int priority;
362: int flag;
363: #endif
364: {
365: pnode_t * pnodep = PROCESS_PNODE ();
366: pnode_t * pscan;
367: pnode_t * pprev;
368: unsigned my_pri = _pri_map [priority];
369:
370: ASSERT (plistp != NULL);
371: PLIST_ASSERT_LOCKED (plistp);
372:
373: ASSERT (priority >= 0 && priority < ARRAY_MAX (_pri_map));
374: ASSERT (flag == SLEEP_NO_SIGNALS || flag == SLEEP_INTERRUPTIBLE);
375:
376: /*
377: * Walk down the list of processes until one is found with a lower
378: * abstract priority than the process we are going to put to sleep.
379: */
380:
381: for (pscan = plistp->pl_head, pprev = NULL ; pscan != NULL ;
382: pscan = (pprev = pscan)->pn_next) {
383: /*
384: * Don't forget to map the stored priority...
385: */
386:
387: if (_pri_map [pscan->pn_priority] < my_pri)
388: break;
389: }
390:
391:
392: /*
393: * Now insert the current process between pprev and pscan.
394: */
395:
396: if (pprev == NULL)
397: plistp->pl_head = pnodep;
398: else
399: pprev->pn_next = pnodep;
400:
401: if (pscan != NULL)
402: pscan->pn_prev = pnodep;
403:
404: pnodep->pn_prev = pprev;
405: pnodep->pn_next = pscan;
406:
407:
408: /*
409: * We store the unmapped (abstract) priority since it should make more
410: * sense for a "ps"-like utility, and mapping it is cheap.
411: */
412:
413: pnodep->pn_priority = priority;
414: pnodep->pn_flag = flag;
415: ATOMIC_STORE_PTR (pnodep->pn_plistp, plistp);
416:
417:
418: /*
419: * Now we are going to actually perform the low-level sleep.
420: *
421: * This thing gets to perform the kernel-specific bit of sleeping the
422: * process, possibly with signal checks and whatnot, unlocking the
423: * plist, and running the next process.
424: */
425:
426: return KERNEL_SLEEP (plistp, priority, flag);
427: }
428:
429:
430: /*
431: * This function wakes one of the processes queued on a plist; since the
432: * MAKE_SLEEPING () function queues them in priority order, let's wake up
433: * the first...
434: */
435:
436: #if __USE_PROTO__
437: __VOID__ * (WAKE_ONE) (plist_t * plistp)
438: #else
439: __VOID__ *
440: WAKE_ONE __ARGS ((plistp))
441: plist_t * plistp;
442: #endif
443: {
444: pnode_t * pnodep;
445:
446: ASSERT (plistp != NULL);
447: PLIST_ASSERT_LOCKED (plistp);
448:
449: if ((pnodep = plistp->pl_head) != NULL) {
450: /*
451: * Given that we have something to awaken, dequeue it and
452: * make it runnable.
453: */
454:
455: plistp->pl_head = pnodep->pn_next;
456: pnodep->pn_next->pn_prev = NULL;
457:
458: KERNEL_WAKE (pnodep);
459:
460: /*
461: * Return the identity of the person we gave the lock to.
462: */
463:
464: return pnodep;
465: }
466:
467: /*
468: * Indicate that we found no-one to take over the mantle...
469: */
470:
471: return NULL;
472: }
473:
474:
475: /*
476: * This function wakes all of the processes queued on a plist.
477: */
478:
479: #if __USE_PROTO__
480: void (WAKE_ALL) (plist_t * plistp)
481: #else
482: void
483: WAKE_ALL __ARGS ((plistp))
484: plist_t * plistp;
485: #endif
486: {
487: pnode_t * pnodep;
488: pnode_t * pnext;
489:
490: ASSERT (plistp != NULL);
491: PLIST_ASSERT_LOCKED (plistp);
492:
493: for (pnodep = plistp->pl_head ; pnodep != NULL ; pnodep = pnext) {
494: /*
495: * We take the value of the "pn_next" member now, because the
496: * MAKE_RUNNABLE () call has the freedom to alter any of the
497: * members of the pnode to deal with such things as
498: * signalling.
499: */
500:
501: pnext = pnodep->pn_next;
502:
503: KERNEL_WAKE (pnodep);
504: }
505:
506: plistp->pl_head = NULL;
507: }
508:
509:
510: /*
511: * This function returns a value suitable for use as a process identifier for
512: * the DDI/DKI SLEEP_LOCKOWNED () function to identify the current context.
513: */
514:
515: #if __USE_PROTO__
516: __VOID__ * (PROC_HANDLE) (void)
517: #else
518: __VOID__ *
519: PROC_HANDLE __ARGS (())
520: #endif
521: {
522: return PROCESS_PNODE ();
523: }
524:
525:
526: /*
527: *-STATUS:
528: * DDI/DKI
529: *
530: *-NAME:
531: * proc_ref Obtain a reference to a process for signalling.
532: *
533: *-SYNOPSIS:
534: * #include <sys/types.h>
535: *
536: * void * proc_ref ();
537: *
538: *-DESCRIPTION:
539: * A non-STREAMS character driver can call proc_ref () to obtain a
540: * reference to the process in whose context it is running. The value
541: * returned can be used in subsequent calls to proc_signal () to post a
542: * signal to the process. The return value should not be used in any
543: * other way (ie, the driver should not attempt to interpret its
544: * meaning).
545: *
546: *-RETURN VALUE:
547: * An identifier that can be used in calls to proc_signal () and
548: * proc_unref ().
549: *
550: *-LEVEL:
551: * Base only.
552: *
553: *-NOTES:
554: * Processes can exit even though they are referenced by drivers. In this
555: * event, reuse of the identifier will be deferred until all driver
556: * references are given up.
557: *
558: * There must be a matching call to proc_unref () for every call to
559: * proc_ref (), when the driver no longer needs to reference the process.
560: * This is typically done as part of close () processing.
561: *
562: * Requires user context.
563: *
564: * Does not sleep.
565: *
566: * Driver-defined basic locks, read/write locks, and sleep locks may be
567: * held across calls to this function.
568: *
569: *-SEE ALSO:
570: * proc_signal (), proc_unref ()
571: */
572:
573: #if __USE_PROTO__
574: __VOID__ * (proc_ref) (void)
575: #else
576: __VOID__ *
577: proc_ref __ARGS (())
578: #endif
579: {
580: pl_t prev_pl;
581: dpdata_t * dpdatap;
582:
583: ASSERT_BASE_LEVEL ();
584:
585:
586: /*
587: * This implementation is a really just a stub for testing. Later
588: * revisions of the code (especially in proc_unref () will deal with
589: * the synchronization and storage management issues properly.
590: *
591: * In addition, leaving the multiprocessor rewrite of the process
592: * system for another time will be good for ensuring that proper
593: * encapsulation boundaries are created and respected, and will ensure
594: * that the ideas prototypes here have been properly tested before
595: * they are incorporated into other kernel areas.
596: */
597:
598: dpdatap = ddi_proc_data ();
599:
600: prev_pl = LOCK (ddi_global_data ()->dg_proclock,
601: proc_global_priority);
602:
603: dpdatap->dp_refcount ++;
604:
605: UNLOCK (ddi_global_data ()->dg_proclock, prev_pl);
606:
607: return dpdatap;
608: }
609:
610:
611: /*
612: *-STATUS:
613: * DDI/DKI
614: *
615: *-NAME:
616: * proc_signal Send a signal to a process.
617: *
618: *-SYNOPSIS:
619: * #include <sys/types.h>
620: * #include <sys/signal.h>
621: *
622: * int proc_signal (void * pref, int sig);
623: *
624: *-ARGUMENTS:
625: * pref Identifier obtained by a previous call to proc_ref ().
626: *
627: * sig Signal number to be sent. Valid signal numbers to be
628: * sent are:
629: * SIGHUP The device has been disconnected.
630: * SIGINT The interrupt character has been
631: * received.
632: * SIGQUIT The quit character has been received.
633: * SIGWINCH The window size has changed.
634: * SIGURG Urgent data are available.
635: * SIGPOLL A pollable event has occurred.
636: *
637: *-DESCRIPTION:
638: * The proc_signal () function can be used to post a signal to the
639: * process represented by "pref". This will interrupt any process blocked
640: * in SV_WAIT_SIG () or SLEEP_LOCK_SIG () at the time the signal is
641: * posted, causing those functions to return prematurely in most cases.
642: * If the process has exited then this function has no effect.
643: *
644: *-RETURN VALUE:
645: * If the process still exists, 0 is returned. Otherwise, -1 is returned
646: * to indicate that the process no longer exists.
647: *
648: *-LEVEL:
649: * Base or interrupt.
650: *
651: *-NOTES:
652: * STREAMS drivers and modules should not use this mechanism for
653: * signalling processes. Instead, they can send M_SIG or M_PCSIG STREAMS
654: * messages to the stream head.
655: *
656: * proc_signal () must not be used to send SIGTSTP to a process.
657: *
658: * Does not sleep.
659: *
660: * Driver-defined basic locks, read/write locks, and sleep locks may be
661: * held across calls to this function.
662: *
663: *-SEE ALSO:
664: * proc_ref (), proc_unref ()
665: */
666:
667: #if __USE_PROTO__
668: int (proc_signal) (__VOID__ * pref, int sig)
669: #else
670: int
671: proc_signal __ARGS ((pref, sig))
672: __VOID__ * pref;
673: int sig;
674: #endif
675: {
676: dpdata_t * dpdatap = (dpdata_t *) pref;
677:
678: ASSERT (pref != NULL);
679: ASSERT (sig != SIGTSTP);
680:
681: return KERNEL_SIGNAL (dpdatap, sig);
682: }
683:
684:
685: /*
686: *-STATUS:
687: * DDI/DKI
688: *
689: *-SYNOPSIS:
690: * #include <sys/types.h>
691: *
692: * void proc_unref (void * pref);
693: *
694: *-ARGUMENTS:
695: * pref Identifier obtained by a previous call to proc_ref ().
696: *
697: *-DESCRIPTION:
698: * The proc_unref () function can be used to release a reference to a
699: * process identified by the parameter "pref". There must be a matching
700: * call to proc_unref () for every previous call to proc_ref ().
701: *
702: *-RETURN VALUE:
703: * None.
704: *
705: *-LEVEL:
706: * Base or interrupt.
707: *
708: *-NOTE:
709: * Processes can exit even though they are referenced by drivers. In this
710: * event, reuse of "pref" will be deferred until all driver references
711: * are given up.
712: *
713: * Does not sleep.
714: *
715: * Driver-defined basic locks, read/write locks, and sleep locks may be
716: * held across calls to this function.
717: *
718: *-SEE ALSO:
719: * proc_ref (), proc_signal ()
720: */
721:
722: #if __USE_PROTO__
723: void (proc_unref) (__VOID__ * pref)
724: #else
725: void
726: proc_unref __ARGS ((pref))
727: __VOID__ * pref;
728: #endif
729: {
730: pl_t prev_pl;
731: dpdata_t * dpdatap = (dpdata_t *) pref;
732:
733: ASSERT (pref != NULL);
734:
735: /*
736: * This implementation is a really just a stub for testing. Later
737: * revisions of the code will deal with the synchronization and
738: * storage management issues properly.
739: */
740:
741: prev_pl = LOCK (ddi_global_data ()->dg_proclock,
742: proc_global_priority);
743:
744: dpdatap->dp_refcount --;
745:
746: UNLOCK (ddi_global_data ()->dg_proclock, prev_pl);
747: }
748:
749:
750: /*
751: *-STATUS:
752: * Internal
753: *
754: *-SYNOPSIS:
755: * #include <sys/types.h>
756: *
757: * void proc_kill_group (pid_t group, int sig);
758: *
759: *-ARGUMENTS:
760: * group Process group ID to which the signal will be sent. All
761: * members of the indicated process group will be sent
762: * the signal.
763: *
764: * sig Signal number to be sent. Valid signal numbers are:
765: * SIGHUP The device has been disconnected.
766: * SIGINT The interrupt character has been
767: * received.
768: * SIGQUIT The quit character has been received.
769: * SIGWINCH The window size has changed.
770: * SIGURG Urgent data are available.
771: * SIGPOLL A pollable event has occurred.
772: *
773: *-DESCRIPTION:
774: * The proc_kill_group () function can be used to post a signal to a
775: * group of processes. This will interrupt any process blocked in
776: * SV_WAIT_SIG () or SLEEP_LOCK_SIG () at the time the signal is posted,
777: * causing those functions to return prematurely in most cases.
778: *
779: *-RETURN VALUE:
780: * None.
781: *
782: *-LEVEL:
783: * Base or interrupt.
784: *
785: *-NOTES:
786: * STREAMS drivers and modules should not use this mechanism for
787: * signalling processes. Instead, they can send M_SIG or M_PCSIG STREAMS
788: * messages to the stream head.
789: *
790: * proc_kill_group () must not be used to send SIGTSTP to a process.
791: *
792: * Does not sleep.
793: *
794: * Driver-defined basic locks, read/write locks, and sleep locks may be
795: * held across calls to this function.
796: *
797: *-SEE ALSO:
798: * proc_ref (), proc_signal (), proc_unref ()
799: */
800:
801: #if __USE_PROTO__
802: void (proc_kill_group) (pid_t group, int sig)
803: #else
804: void
805: proc_kill_group __ARGS ((group, sig))
806: pid_t group;
807: int sig;
808: #endif
809: {
810: ASSERT ("proc_kill_group () : not implemented" == 0);
811: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.