xref: /reactos/dll/win32/beepmidi/beepmidi.c (revision c2c66aff)
1 /*
2     BeepMidi :: beep.sys MIDI player
3 
4     (c) Andrew Greenwood, 2007.
5 
6     Released as open-source software. You may copy, re-distribute and modify
7     this software, provided this copyright notice remains intact.
8 
9     Please see the included README.TXT for more information
10 
11     HISTORY :
12         16th January 2007   Started
13         17th January 2007   Polyphony support and threading added
14         18th January 2007   Made threading optional, added comments
15 */
16 
17 /* The timeslice to allocate for all playing notes (in milliseconds) */
18 #define TIMESLICE_SIZE  60
19 
20 /*
21     If this is defined, notes are added to the playing list, even if
22     they already exist. As a result, the note will sound twice during
23     each timeslice. Also each note on will require a corresponding note
24     off event.
25 */
26 #define ALLOW_DUPLICATE_NOTES
27 
28 /*
29     The maximum number of notes that may be playing at any one time.
30     Higher values result in a messier sound as all the frequencies get
31     mashed together. Do not set this below 2. Recommended = 4
32 */
33 #define POLYPHONY   3
34 
35 /*
36     Define CONTINUOUS_NOTES to perform note playback in a separate thread.
37     This was originally the intended behaviour, but after experimentation
38     doesn't sound as good for MIDI files which have a lot going on. If not
39     defined, all playing notes are output in sequence as a new note starts.
40 */
41 #define CONTINUOUS_NOTES
42 
43 #define WIN32_NO_STATUS
44 #define _INC_WINDOWS
45 #define COM_NO_WINDOWS_H
46 #include <stdarg.h>
47 #include <windef.h>
48 #include <winbase.h>
49 #define NTOS_MODE_USER
50 #include <ndk/iofuncs.h>
51 #include <ndk/obfuncs.h>
52 #include <ndk/rtlfuncs.h>
53 #include <ntddbeep.h>
54 #include <math.h>
55 #include <mmddk.h>
56 
57 /*#define DPRINT printf*/
58 #define DPRINT FakePrintf
59 
60 /* A few MIDI command categories */
61 #define MIDI_NOTE_OFF       0x80
62 #define MIDI_NOTE_ON        0x90
63 #define MIDI_CONTROL_CHANGE 0xB0
64 #define MIDI_PROGRAM        0xC0
65 #define MIDI_PITCH_BEND     0xE0
66 #define MIDI_SYSTEM         0xFF
67 
68 /* Specific commands */
69 #define MIDI_RESET          0xFF
70 
71 
72 typedef struct _NoteNode
73 {
74     struct _NoteNode* next;
75     struct _NoteNode* previous;
76 
77     UCHAR note;
78     UCHAR velocity; /* 0 is note-off */
79 } NoteNode;
80 
81 typedef struct _DeviceInfo
82 {
83     HDRVR mme_handle;
84     HANDLE kernel_device;
85 
86     DWORD callback;
87     DWORD instance;
88     DWORD flags;
89 
90     UCHAR running_status;
91 
92     DWORD playing_notes_count;
93     NoteNode* note_list;
94     BOOL refresh_notes;
95 
96     HANDLE thread_handle;
97     BOOL terminate_thread;
98     HANDLE thread_termination_complete;
99 } DeviceInfo;
100 
101 DeviceInfo* the_device;
102 CRITICAL_SECTION device_lock;
103 
104 void
105 FakePrintf(char* str, ...)
106 {
107     /* Just to shut the compiler up */
108 }
109 
110 
111 /*
112     This is designed to be treated as a thread, however it behaves as a
113     normal function if CONTINUOUS_NOTES is not defined.
114 */
115 
116 DWORD WINAPI
117 ProcessPlayingNotes(
118     LPVOID parameter)
119 {
120     DeviceInfo* device_info = (DeviceInfo*) parameter;
121     NTSTATUS status;
122     IO_STATUS_BLOCK io_status_block;
123     DWORD arp_notes;
124 
125     DPRINT("Note processing started\n");
126 
127     /* We lock the note list only while accessing it */
128 
129 #ifdef CONTINUOUS_NOTES
130     while ( ! device_info->terminate_thread )
131 #endif
132     {
133         NoteNode* node;
134 
135         /* Number of notes being arpeggiated */
136         arp_notes = 1;
137 
138         EnterCriticalSection(&device_lock);
139 
140         /* Calculate how much time to allocate to each playing note */
141 
142         DPRINT("%d notes active\n", (int) device_info->playing_notes_count);
143 
144         node = device_info->note_list;
145 
146         while ( ( node != NULL ) && ( arp_notes <= POLYPHONY ) )
147         {
148             BEEP_SET_PARAMETERS beep_data;
149             DWORD actually_playing = 0;
150 
151             double frequency = node->note;
152 
153             DPRINT("playing..\n");
154 
155             frequency = frequency / 12;
156             frequency = pow(2, frequency);
157             frequency = 8.1758 * frequency;
158 
159             if (device_info->playing_notes_count > POLYPHONY)
160                 actually_playing = POLYPHONY;
161             else
162                 actually_playing = device_info->playing_notes_count;
163 
164             DPRINT("Frequency %f\n", frequency);
165 
166             // TODO
167             beep_data.Frequency = (DWORD) frequency;
168             beep_data.Duration = TIMESLICE_SIZE / actually_playing; /* device_info->playing_notes_count; */
169 
170             status = NtDeviceIoControlFile(device_info->kernel_device,
171                                            NULL,
172                                            NULL,
173                                            NULL,
174                                            &io_status_block,
175                                            IOCTL_BEEP_SET,
176                                            &beep_data,
177                                            sizeof(BEEP_SET_PARAMETERS),
178                                            NULL,
179                                            0);
180 
181             if ( ! NT_SUCCESS(status) )
182             {
183                 DPRINT("ERROR %d\n", (int) GetLastError());
184             }
185 
186             SleepEx(beep_data.Duration, TRUE);
187 
188             if ( device_info->refresh_notes )
189             {
190                 device_info->refresh_notes = FALSE;
191                 break;
192             }
193 
194             arp_notes ++;
195             node = node->next;
196         }
197 
198         LeaveCriticalSection(&device_lock);
199     }
200 
201 #ifdef CONTINUOUS_NOTES
202     SetEvent(device_info->thread_termination_complete);
203 #endif
204 
205     return 0;
206 }
207 
208 
209 /*
210     Fills a MIDIOUTCAPS structure with information about our device.
211 */
212 
213 MMRESULT
214 GetDeviceCapabilities(
215     MIDIOUTCAPS* caps)
216 {
217     /* These are ignored for now */
218     caps->wMid = 0;
219     caps->wPid = 0;
220 
221     caps->vDriverVersion = 0x0100;
222 
223     memset(caps->szPname, 0, sizeof(caps->szPname));
224     wcscpy(caps->szPname, L"PC speaker");
225 
226     caps->wTechnology = MOD_SQSYNTH;
227 
228     caps->wVoices = 1;              /* We only have one voice */
229     caps->wNotes = POLYPHONY;
230     caps->wChannelMask = 0xFFBF;    /* Ignore channel 10 */
231 
232     caps->dwSupport = 0;
233 
234     return MMSYSERR_NOERROR;
235 }
236 
237 
238 /*
239     Helper function that just simplifies calling the application making use
240     of us.
241 */
242 
243 BOOL
244 CallClient(
245     DeviceInfo* device_info,
246     DWORD_PTR message,
247     DWORD_PTR parameter1,
248     DWORD_PTR parameter2)
249 {
250     DPRINT("Calling client - callback 0x%x mmhandle 0x%x\n", device_info->callback, device_info->mme_handle);
251     return DriverCallback(device_info->callback,
252                           HIWORD(device_info->flags),
253                           device_info->mme_handle,
254                           message,
255                           device_info->instance,
256                           parameter1,
257                           parameter2);
258 
259 }
260 
261 
262 /*
263     Open the kernel-mode device and allocate resources. This opens the
264     BEEP.SYS kernel device.
265 */
266 
267 MMRESULT
268 OpenDevice(
269     DeviceInfo** private_data,
270     MIDIOPENDESC* open_desc,
271     DWORD flags)
272 {
273     NTSTATUS status;
274     HANDLE heap;
275     HANDLE kernel_device;
276     UNICODE_STRING beep_device_name;
277     OBJECT_ATTRIBUTES attribs;
278     IO_STATUS_BLOCK status_block;
279 
280     /* One at a time.. */
281     if ( the_device )
282     {
283         DPRINT("Already allocated\n");
284         return MMSYSERR_ALLOCATED;
285     }
286 
287     /* Make the device name into a unicode string and open it */
288 
289     RtlInitUnicodeString(&beep_device_name,
290                             L"\\Device\\Beep");
291 
292     InitializeObjectAttributes(&attribs,
293                                 &beep_device_name,
294                                 0,
295                                 NULL,
296                                 NULL);
297 
298     status = NtCreateFile(&kernel_device,
299                             FILE_READ_DATA | FILE_WRITE_DATA,
300                             &attribs,
301                             &status_block,
302                             NULL,
303                             0,
304                             FILE_SHARE_READ | FILE_SHARE_WRITE,
305                             FILE_OPEN_IF,
306                             0,
307                             NULL,
308                             0);
309 
310     if ( ! NT_SUCCESS(status) )
311     {
312         DPRINT("Could not connect to BEEP device - %d\n", (int) GetLastError());
313         return MMSYSERR_ERROR;
314     }
315 
316     DPRINT("Opened!\n");
317 
318     /* Allocate and initialize the device info */
319 
320     heap = GetProcessHeap();
321 
322     the_device = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(DeviceInfo));
323 
324     if ( ! the_device )
325     {
326         DPRINT("Out of memory\n");
327         return MMSYSERR_NOMEM;
328     }
329 
330     /* Initialize */
331     the_device->kernel_device = kernel_device;
332     the_device->playing_notes_count = 0;
333     the_device->note_list = NULL;
334     the_device->thread_handle = 0;
335     the_device->terminate_thread = FALSE;
336     the_device->running_status = 0;
337 
338     // TODO
339     the_device->mme_handle = (HDRVR) open_desc->hMidi;
340     the_device->callback = open_desc->dwCallback;
341     the_device->instance = open_desc->dwInstance;
342     the_device->flags = flags;
343 
344     /* Store the pointer in the user data */
345     *private_data = the_device;
346 
347     /* This is threading-related code */
348 #ifdef CONTINUOUS_NOTES
349     the_device->thread_termination_complete = CreateEvent(NULL, FALSE, FALSE, NULL);
350 
351     if ( ! the_device->thread_termination_complete )
352     {
353         DPRINT("CreateEvent failed\n");
354         HeapFree(heap, 0, the_device);
355         return MMSYSERR_NOMEM;
356     }
357 
358     the_device->thread_handle = CreateThread(NULL,
359                                              0,
360                                              ProcessPlayingNotes,
361                                              (PVOID) the_device,
362                                              0,
363                                              NULL);
364 
365     if ( ! the_device->thread_handle )
366     {
367         DPRINT("CreateThread failed\n");
368         CloseHandle(the_device->thread_termination_complete);
369         HeapFree(heap, 0, the_device);
370         return MMSYSERR_NOMEM;
371     }
372 #endif
373 
374     /* Now we call the client application to say the device is open */
375     DPRINT("Sending MOM_OPEN\n");
376     DPRINT("Success? %d\n", (int) CallClient(the_device, MOM_OPEN, 0, 0));
377 
378     return MMSYSERR_NOERROR;
379 }
380 
381 
382 /*
383     Close the kernel-mode device.
384 */
385 
386 MMRESULT
387 CloseDevice(DeviceInfo* device_info)
388 {
389     HANDLE heap = GetProcessHeap();
390 
391     /* If we're working in threaded mode we need to wait for thread to die */
392 #ifdef CONTINUOUS_NOTES
393     the_device->terminate_thread = TRUE;
394 
395     WaitForSingleObject(the_device->thread_termination_complete, INFINITE);
396 
397     CloseHandle(the_device->thread_termination_complete);
398 #endif
399 
400     /* Let the client application know the device is closing */
401     DPRINT("Sending MOM_CLOSE\n");
402     CallClient(device_info, MOM_CLOSE, 0, 0);
403 
404     NtClose(device_info->kernel_device);
405 
406     /* Free resources */
407     HeapFree(heap, 0, device_info);
408 
409     the_device = NULL;
410 
411     return MMSYSERR_NOERROR;
412 }
413 
414 
415 /*
416     Removes a note from the playing notes list. If the note is not playing,
417     we just pretend nothing happened.
418 */
419 
420 MMRESULT
421 StopNote(
422     DeviceInfo* device_info,
423     UCHAR note)
424 {
425     HANDLE heap = GetProcessHeap();
426     NoteNode* node;
427     NoteNode* prev_node = NULL;
428 
429     DPRINT("StopNote\n");
430 
431     EnterCriticalSection(&device_lock);
432 
433     node = device_info->note_list;
434 
435     while ( node != NULL )
436     {
437         if ( node->note == note )
438         {
439             /* Found the note - just remove the node from the list */
440 
441             DPRINT("Stopping note %d\n", (int) node->note);
442 
443             if ( prev_node != NULL )
444                 prev_node->next = node->next;
445             else
446                 device_info->note_list = node->next;
447 
448             HeapFree(heap, 0, node);
449 
450             device_info->playing_notes_count --;
451 
452             DPRINT("Note stopped - now playing %d notes\n", (int) device_info->playing_notes_count);
453 
454             LeaveCriticalSection(&device_lock);
455             device_info->refresh_notes = TRUE;
456 
457             return MMSYSERR_NOERROR;
458         }
459 
460         prev_node = node;
461         node = node->next;
462     }
463 
464     LeaveCriticalSection(&device_lock);
465 
466     /* Hmm, a good idea? */
467 #ifndef CONTINUOUS_NOTES
468     ProcessPlayingNotes((PVOID) device_info);
469 #endif
470 
471     return MMSYSERR_NOERROR;
472 }
473 
474 
475 /*
476     Adds a note to the playing notes list. If the note is already playing,
477     the definition of ALLOW_DUPLICATE_NOTES determines if an existing note
478     may be duplicated. Otherwise, duplicate notes are ignored.
479 */
480 
481 MMRESULT
482 PlayNote(
483     DeviceInfo* device_info,
484     UCHAR note,
485     UCHAR velocity)
486 {
487     HANDLE heap = GetProcessHeap();
488 
489     NoteNode* node;
490 
491     DPRINT("PlayNote\n");
492 
493     if ( velocity == 0 )
494     {
495         DPRINT("Zero velocity\n");
496 
497         /* Velocity zero is effectively a "note off" */
498         StopNote(device_info, note);
499     }
500     else
501     {
502         /* Start playing the note */
503         NoteNode* new_node;
504 
505         EnterCriticalSection(&device_lock);
506 
507         node = device_info->note_list;
508 
509         while ( node != NULL )
510         {
511 #ifndef ALLOW_DUPLICATE_NOTES
512             if ( ( node->note == note ) && ( velocity > 0 ) )
513             {
514                 /* The note is already playing - do nothing */
515                 DPRINT("Duplicate note playback request ignored\n");
516                 LeaveCriticalSection(&device_lock);
517                 return MMSYSERR_NOERROR;
518             }
519 #endif
520 
521             node = node->next;
522         }
523 
524         new_node = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(NoteNode));
525 
526         if ( ! new_node )
527         {
528             LeaveCriticalSection(&device_lock);
529             return MMSYSERR_NOMEM;
530         }
531 
532         new_node->note = note;
533         new_node->velocity = velocity;
534 
535         /*
536             Prepend to the playing notes list. If exceeding polyphony,
537             remove the oldest note (which will be at the tail.)
538         */
539 
540         if ( device_info->note_list )
541             device_info->note_list->previous = new_node;
542 
543         new_node->next = device_info->note_list;
544         new_node->previous = NULL;
545 
546         device_info->note_list = new_node;
547         device_info->playing_notes_count ++;
548 
549 /*
550         if ( device_info->playing_notes_count > POLYPHONY )
551         {
552             ASSERT(tail_node);
553 
554             DPRINT("Polyphony exceeded\n");
555 
556             tail_node->previous->next = NULL;
557 
558             HeapFree(heap, 0, tail_node);
559 
560             device_info->playing_notes_count --;
561         }
562 */
563 
564         LeaveCriticalSection(&device_lock);
565 
566         DPRINT("Note started - now playing %d notes\n", (int) device_info->playing_notes_count);
567         device_info->refresh_notes = TRUE;
568     }
569 
570 #ifndef CONTINUOUS_NOTES
571     ProcessPlayingNotes((PVOID) device_info);
572 #endif
573 
574     return MMSYSERR_NOERROR;
575 }
576 
577 /*
578     Decipher a short MIDI message (which is a MIDI message packed into a DWORD.)
579     This will set "running status", but does not take this into account when
580     processing messages (is this necessary?)
581 */
582 
583 MMRESULT
584 ProcessShortMidiMessage(
585     DeviceInfo* device_info,
586     DWORD message)
587 {
588     DWORD status;
589 
590     DWORD category;
591     DWORD channel;
592     DWORD data1, data2;
593 
594     status = message & 0x000000FF;
595 
596     /* Deal with running status */
597 
598     if ( status < MIDI_NOTE_OFF )
599     {
600         status = device_info->running_status;
601     }
602 
603     /* Ensure the status is sane! */
604 
605     if ( status < MIDI_NOTE_OFF )
606     {
607         /* It's garbage, ignore it */
608         return MMSYSERR_NOERROR;
609     }
610 
611     /* Figure out the message category and channel */
612 
613     category = status & 0xF0;
614     channel = status & 0x0F;    /* we don't use this */
615 
616     data1 = (message & 0x0000FF00) >> 8;
617     data2 = (message & 0x00FF0000) >> 16;
618 
619     DPRINT("0x%x, %d, %d\n", (int) status, (int) data1, (int) data2);
620 
621     /* Filter drums (which are *usually* on channel 10) */
622     if ( channel == 10 )
623     {
624         return MMSYSERR_NOERROR;
625     }
626 
627     /* Pass to the appropriate message handler */
628 
629     switch ( category )
630     {
631         case MIDI_NOTE_ON :
632         {
633             PlayNote(device_info, data1, data2);
634             break;
635         }
636 
637         case MIDI_NOTE_OFF :
638         {
639             StopNote(device_info, data1);
640             break;
641         }
642     }
643 
644     return MMSYSERR_NOERROR;
645 }
646 
647 
648 #define PACK_MIDI(b1, b2, b3) \
649     ((b3 * 65536) + (b2 * 256) + b1);
650 
651 
652 /*
653     Processes a "long" MIDI message (ie, a MIDI message contained within a
654     buffer.) This is intended for supporting SysEx data, or blocks of MIDI
655     events. However in our case we're only interested in short MIDI messages,
656     so we scan the buffer, and each time we encounter a valid status byte
657     we start recording it as a new event. Once 3 bytes or a new status is
658     received, the event is passed to the short message handler.
659 */
660 
661 MMRESULT
662 ProcessLongMidiMessage(
663     DeviceInfo* device_info,
664     MIDIHDR* header)
665 {
666     unsigned int index = 0;
667     UCHAR* midi_bytes = (UCHAR*) header->lpData;
668 
669     unsigned int msg_index = 0;
670     UCHAR msg[3];
671 
672     /* Initialize the buffer */
673     msg[0] = msg[1] = msg[2] = 0;
674 
675     if ( ! ( header->dwFlags & MHDR_PREPARED ) )
676     {
677         DPRINT("Not prepared!\n");
678         return MIDIERR_UNPREPARED;
679     }
680 
681     DPRINT("Processing %d bytes of MIDI\n", (int) header->dwBufferLength);
682 
683     while ( index < header->dwBufferLength )
684     {
685         /* New status byte? ( = new event) */
686         if ( midi_bytes[index] & 0x80 )
687         {
688             DWORD short_msg;
689 
690             /* Deal with the existing event */
691 
692             if ( msg[0] & 0x80 )
693             {
694                 short_msg = PACK_MIDI(msg[0], msg[1], msg[2]);
695 
696                 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg[0], (int) msg[1], (int) msg[2]);
697                 ProcessShortMidiMessage(device_info, short_msg);
698             }
699 
700             /* Set new running status and start recording the event */
701             DPRINT("Set new running status\n");
702             device_info->running_status = midi_bytes[index];
703             msg[0] = midi_bytes[index];
704             msg_index = 1;
705         }
706 
707         /* Unexpected data byte? ( = re-use previous status) */
708         else if ( msg_index == 0 )
709         {
710             if ( device_info->running_status & 0x80 )
711             {
712                 DPRINT("Retrieving running status\n");
713                 msg[0] = device_info->running_status;
714                 msg[1] = midi_bytes[index];
715                 msg_index = 2;
716             }
717             else
718                 DPRINT("garbage\n");
719         }
720 
721         /* Expected data ( = append to message until buffer full) */
722         else
723         {
724             DPRINT("Next byte...\n");
725             msg[msg_index] = midi_bytes[index];
726             msg_index ++;
727 
728             if ( msg_index > 2 )
729             {
730                 DWORD short_msg;
731 
732                 short_msg = PACK_MIDI(msg[0], msg[1], msg[2]);
733 
734                 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg[0], (int) msg[1], (int) msg[2]);
735                 ProcessShortMidiMessage(device_info, short_msg);
736 
737                 /* Reinit */
738                 msg_index = 0;
739                 msg[0] = msg[1] = msg[2] = 0;
740             }
741         }
742 
743         index ++;
744     }
745 
746     /*
747         We're meant to clear MHDR_DONE and set MHDR_INQUEUE but since we
748         deal with everything here and now we might as well just say so.
749     */
750     header->dwFlags |= MHDR_DONE;
751     header->dwFlags &= ~ MHDR_INQUEUE;
752 
753     DPRINT("Success? %d\n", CallClient(the_device, MOM_DONE, (DWORD_PTR) header, 0));
754 
755     return MMSYSERR_NOERROR;
756 }
757 
758 
759 /*
760     Exported function that receives messages from WINMM (the MME API.)
761 */
762 
763 MMRESULT
764 FAR PASCAL
765 modMessage(
766     UINT device_id,
767     UINT message,
768     DWORD_PTR private_data,
769     DWORD_PTR parameter1,
770     DWORD_PTR parameter2)
771 {
772     switch ( message )
773     {
774         case MODM_GETNUMDEVS :
775         {
776             /* Only one internal PC speaker device (and even that's too much) */
777             DPRINT("MODM_GETNUMDEVS\n");
778             return 1;
779         }
780 
781         case MODM_GETDEVCAPS :
782         {
783             DPRINT("MODM_GETDEVCAPS\n");
784             return GetDeviceCapabilities((MIDIOUTCAPS*) parameter1);
785         }
786 
787         case MODM_OPEN :
788         {
789             DPRINT("MODM_OPEN\n");
790 
791             return OpenDevice((DeviceInfo**) private_data,
792                               (MIDIOPENDESC*) parameter1,
793                               parameter2);
794         }
795 
796         case MODM_CLOSE :
797         {
798             DPRINT("MODM_CLOSE\n");
799             return CloseDevice((DeviceInfo*) private_data);
800         }
801 
802         case MODM_DATA :
803         {
804             return ProcessShortMidiMessage((DeviceInfo*) private_data, parameter1);
805         }
806 
807         case MODM_PREPARE :
808         {
809             /* We don't bother with this */
810             MIDIHDR* hdr = (MIDIHDR*) parameter1;
811             hdr->dwFlags |= MHDR_PREPARED;
812             return MMSYSERR_NOERROR;
813         }
814 
815         case MODM_UNPREPARE :
816         {
817             MIDIHDR* hdr = (MIDIHDR*) parameter1;
818             hdr->dwFlags &= ~MHDR_PREPARED;
819             return MMSYSERR_NOERROR;
820         }
821 
822         case MODM_LONGDATA :
823         {
824             DPRINT("LONGDATA\n");
825             return ProcessLongMidiMessage((DeviceInfo*) private_data, (MIDIHDR*) parameter1);
826         }
827 
828         case MODM_RESET :
829         {
830             /* TODO */
831             break;
832         }
833     }
834 
835     DPRINT("Not supported %d\n", message);
836 
837     return MMSYSERR_NOTSUPPORTED;
838 }
839 
840 
841 /*
842     Driver entrypoint.
843 */
844 
845 LONG
846 FAR PASCAL
847 DriverProc(
848     DWORD driver_id,
849     HDRVR driver_handle,
850     UINT message,
851     LONG parameter1,
852     LONG parameter2)
853 {
854     switch ( message )
855     {
856         case DRV_LOAD :
857             DPRINT("DRV_LOAD\n");
858             the_device = NULL;
859             return 1L;
860 
861         case DRV_FREE :
862             DPRINT("DRV_FREE\n");
863             return 1L;
864 
865         case DRV_OPEN :
866             DPRINT("DRV_OPEN\n");
867             InitializeCriticalSection(&device_lock);
868             return 1L;
869 
870         case DRV_CLOSE :
871             DPRINT("DRV_CLOSE\n");
872             return 1L;
873 
874         case DRV_ENABLE :
875             DPRINT("DRV_ENABLE\n");
876             return 1L;
877 
878         case DRV_DISABLE :
879             DPRINT("DRV_DISABLE\n");
880             return 1L;
881 
882         /*
883             We don't provide configuration capabilities. This used to be
884             for things like I/O port, IRQ, DMA settings, etc.
885         */
886 
887         case DRV_QUERYCONFIGURE :
888             DPRINT("DRV_QUERYCONFIGURE\n");
889             return 0L;
890 
891         case DRV_CONFIGURE :
892             DPRINT("DRV_CONFIGURE\n");
893             return 0L;
894 
895         case DRV_INSTALL :
896             DPRINT("DRV_INSTALL\n");
897             return DRVCNF_RESTART;
898     };
899 
900     DPRINT("???\n");
901 
902     return DefDriverProc(driver_id,
903                          driver_handle,
904                          message,
905                          parameter1,
906                          parameter2);
907 }
908