xref: /reactos/dll/win32/beepmidi/beepmidi.c (revision d5b576b2)
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 work_available;
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 ( WaitForSingleObject(the_device->work_available, INFINITE), !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     return 0;
202 }
203 
204 
205 /*
206     Fills a MIDIOUTCAPS structure with information about our device.
207 */
208 
209 MMRESULT
210 GetDeviceCapabilities(
211     MIDIOUTCAPS* caps)
212 {
213     /* These are ignored for now */
214     caps->wMid = 0;
215     caps->wPid = 0;
216 
217     caps->vDriverVersion = 0x0100;
218 
219     memset(caps->szPname, 0, sizeof(caps->szPname));
220     wcscpy(caps->szPname, L"PC speaker");
221 
222     caps->wTechnology = MOD_SQSYNTH;
223 
224     caps->wVoices = 1;              /* We only have one voice */
225     caps->wNotes = POLYPHONY;
226     caps->wChannelMask = 0xFFBF;    /* Ignore channel 10 */
227 
228     caps->dwSupport = 0;
229 
230     return MMSYSERR_NOERROR;
231 }
232 
233 
234 /*
235     Helper function that just simplifies calling the application making use
236     of us.
237 */
238 
239 BOOL
240 CallClient(
241     DeviceInfo* device_info,
242     DWORD_PTR message,
243     DWORD_PTR parameter1,
244     DWORD_PTR parameter2)
245 {
246     DPRINT("Calling client - callback 0x%x mmhandle 0x%x\n", device_info->callback, device_info->mme_handle);
247     return DriverCallback(device_info->callback,
248                           HIWORD(device_info->flags),
249                           device_info->mme_handle,
250                           message,
251                           device_info->instance,
252                           parameter1,
253                           parameter2);
254 
255 }
256 
257 
258 /*
259     Open the kernel-mode device and allocate resources. This opens the
260     BEEP.SYS kernel device.
261 */
262 
263 MMRESULT
264 OpenDevice(
265     DeviceInfo** private_data,
266     MIDIOPENDESC* open_desc,
267     DWORD flags)
268 {
269     NTSTATUS status;
270     HANDLE heap;
271     HANDLE kernel_device;
272     UNICODE_STRING beep_device_name;
273     OBJECT_ATTRIBUTES attribs;
274     IO_STATUS_BLOCK status_block;
275 
276     /* One at a time.. */
277     if ( the_device )
278     {
279         DPRINT("Already allocated\n");
280         return MMSYSERR_ALLOCATED;
281     }
282 
283     /* Make the device name into a unicode string and open it */
284 
285     RtlInitUnicodeString(&beep_device_name,
286                             L"\\Device\\Beep");
287 
288     InitializeObjectAttributes(&attribs,
289                                 &beep_device_name,
290                                 0,
291                                 NULL,
292                                 NULL);
293 
294     status = NtCreateFile(&kernel_device,
295                             FILE_READ_DATA | FILE_WRITE_DATA,
296                             &attribs,
297                             &status_block,
298                             NULL,
299                             0,
300                             FILE_SHARE_READ | FILE_SHARE_WRITE,
301                             FILE_OPEN_IF,
302                             0,
303                             NULL,
304                             0);
305 
306     if ( ! NT_SUCCESS(status) )
307     {
308         DPRINT("Could not connect to BEEP device - %d\n", (int) GetLastError());
309         return MMSYSERR_ERROR;
310     }
311 
312     DPRINT("Opened!\n");
313 
314     /* Allocate and initialize the device info */
315 
316     heap = GetProcessHeap();
317 
318     the_device = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(DeviceInfo));
319 
320     if ( ! the_device )
321     {
322         DPRINT("Out of memory\n");
323         return MMSYSERR_NOMEM;
324     }
325 
326     /* Initialize */
327     the_device->kernel_device = kernel_device;
328     the_device->playing_notes_count = 0;
329     the_device->note_list = NULL;
330     the_device->thread_handle = 0;
331     the_device->terminate_thread = FALSE;
332     the_device->running_status = 0;
333 
334     // TODO
335     the_device->mme_handle = (HDRVR) open_desc->hMidi;
336     the_device->callback = open_desc->dwCallback;
337     the_device->instance = open_desc->dwInstance;
338     the_device->flags = flags;
339 
340     /* Store the pointer in the user data */
341     *private_data = the_device;
342 
343     /* This is threading-related code */
344 #ifdef CONTINUOUS_NOTES
345     the_device->work_available = CreateEvent(NULL, TRUE, FALSE, NULL);
346 
347     if ( ! the_device->work_available )
348     {
349         DPRINT("CreateEvent failed\n");
350         HeapFree(heap, 0, the_device);
351         return MMSYSERR_NOMEM;
352     }
353 
354     the_device->thread_handle = CreateThread(NULL,
355                                              0,
356                                              ProcessPlayingNotes,
357                                              (PVOID) the_device,
358                                              0,
359                                              NULL);
360 
361     if ( ! the_device->thread_handle )
362     {
363         DPRINT("CreateThread failed\n");
364         CloseHandle(the_device->work_available);
365         HeapFree(heap, 0, the_device);
366         return MMSYSERR_NOMEM;
367     }
368 #endif
369 
370     /* Now we call the client application to say the device is open */
371     DPRINT("Sending MOM_OPEN\n");
372     DPRINT("Success? %d\n", (int) CallClient(the_device, MOM_OPEN, 0, 0));
373 
374     return MMSYSERR_NOERROR;
375 }
376 
377 
378 /*
379     Close the kernel-mode device.
380 */
381 
382 MMRESULT
383 CloseDevice(DeviceInfo* device_info)
384 {
385     HANDLE heap = GetProcessHeap();
386 
387     /* If we're working in threaded mode we need to wait for thread to die */
388 #ifdef CONTINUOUS_NOTES
389     the_device->terminate_thread = TRUE;
390     SetEvent(device_info->work_available);
391 
392     WaitForSingleObject(the_device->thread_handle, INFINITE);
393     CloseHandle(the_device->thread_handle);
394     CloseHandle(the_device->work_available);
395 #endif
396 
397     /* Let the client application know the device is closing */
398     DPRINT("Sending MOM_CLOSE\n");
399     CallClient(device_info, MOM_CLOSE, 0, 0);
400 
401     NtClose(device_info->kernel_device);
402 
403     /* Free resources */
404     HeapFree(heap, 0, device_info);
405 
406     the_device = NULL;
407 
408     return MMSYSERR_NOERROR;
409 }
410 
411 
412 /*
413     Removes a note from the playing notes list. If the note is not playing,
414     we just pretend nothing happened.
415 */
416 
417 MMRESULT
418 StopNote(
419     DeviceInfo* device_info,
420     UCHAR note)
421 {
422     HANDLE heap = GetProcessHeap();
423     NoteNode* node;
424     NoteNode* prev_node = NULL;
425 
426     DPRINT("StopNote\n");
427 
428     EnterCriticalSection(&device_lock);
429 
430     node = device_info->note_list;
431 
432     while ( node != NULL )
433     {
434         if ( node->note == note )
435         {
436             /* Found the note - just remove the node from the list */
437 
438             DPRINT("Stopping note %d\n", (int) node->note);
439 
440             if ( prev_node != NULL )
441                 prev_node->next = node->next;
442             else
443                 device_info->note_list = node->next;
444 
445             HeapFree(heap, 0, node);
446 
447             device_info->playing_notes_count --;
448 
449             DPRINT("Note stopped - now playing %d notes\n", (int) device_info->playing_notes_count);
450 
451 #ifdef CONTINUOUS_NOTES
452             if (device_info->playing_notes_count == 0)
453                 ResetEvent(device_info->work_available);
454 #endif
455 
456             LeaveCriticalSection(&device_lock);
457             device_info->refresh_notes = TRUE;
458 
459             return MMSYSERR_NOERROR;
460         }
461 
462         prev_node = node;
463         node = node->next;
464     }
465 
466     LeaveCriticalSection(&device_lock);
467 
468     /* Hmm, a good idea? */
469 #ifndef CONTINUOUS_NOTES
470     ProcessPlayingNotes((PVOID) device_info);
471 #endif
472 
473     return MMSYSERR_NOERROR;
474 }
475 
476 
477 /*
478     Adds a note to the playing notes list. If the note is already playing,
479     the definition of ALLOW_DUPLICATE_NOTES determines if an existing note
480     may be duplicated. Otherwise, duplicate notes are ignored.
481 */
482 
483 MMRESULT
484 PlayNote(
485     DeviceInfo* device_info,
486     UCHAR note,
487     UCHAR velocity)
488 {
489     HANDLE heap = GetProcessHeap();
490 
491     NoteNode* node;
492 
493     DPRINT("PlayNote\n");
494 
495     if ( velocity == 0 )
496     {
497         DPRINT("Zero velocity\n");
498 
499         /* Velocity zero is effectively a "note off" */
500         StopNote(device_info, note);
501     }
502     else
503     {
504         /* Start playing the note */
505         NoteNode* new_node;
506 
507         EnterCriticalSection(&device_lock);
508 
509         node = device_info->note_list;
510 
511         while ( node != NULL )
512         {
513 #ifndef ALLOW_DUPLICATE_NOTES
514             if ( ( node->note == note ) && ( velocity > 0 ) )
515             {
516                 /* The note is already playing - do nothing */
517                 DPRINT("Duplicate note playback request ignored\n");
518                 LeaveCriticalSection(&device_lock);
519                 return MMSYSERR_NOERROR;
520             }
521 #endif
522 
523             node = node->next;
524         }
525 
526         new_node = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(NoteNode));
527 
528         if ( ! new_node )
529         {
530             LeaveCriticalSection(&device_lock);
531             return MMSYSERR_NOMEM;
532         }
533 
534         new_node->note = note;
535         new_node->velocity = velocity;
536 
537         /*
538             Prepend to the playing notes list. If exceeding polyphony,
539             remove the oldest note (which will be at the tail.)
540         */
541 
542         if ( device_info->note_list )
543             device_info->note_list->previous = new_node;
544 
545         new_node->next = device_info->note_list;
546         new_node->previous = NULL;
547 
548         device_info->note_list = new_node;
549         device_info->playing_notes_count ++;
550 
551 /*
552         if ( device_info->playing_notes_count > POLYPHONY )
553         {
554             ASSERT(tail_node);
555 
556             DPRINT("Polyphony exceeded\n");
557 
558             tail_node->previous->next = NULL;
559 
560             HeapFree(heap, 0, tail_node);
561 
562             device_info->playing_notes_count --;
563         }
564 */
565 
566 #ifdef CONTINUOUS_NOTES
567         SetEvent(device_info->work_available);
568 #endif
569 
570         LeaveCriticalSection(&device_lock);
571 
572         DPRINT("Note started - now playing %d notes\n", (int) device_info->playing_notes_count);
573         device_info->refresh_notes = TRUE;
574     }
575 
576 #ifndef CONTINUOUS_NOTES
577     ProcessPlayingNotes((PVOID) device_info);
578 #endif
579 
580     return MMSYSERR_NOERROR;
581 }
582 
583 /*
584     Decipher a short MIDI message (which is a MIDI message packed into a DWORD.)
585     This will set "running status", but does not take this into account when
586     processing messages (is this necessary?)
587 */
588 
589 MMRESULT
590 ProcessShortMidiMessage(
591     DeviceInfo* device_info,
592     DWORD message)
593 {
594     DWORD status;
595 
596     DWORD category;
597     DWORD channel;
598     DWORD data1, data2;
599 
600     status = message & 0x000000FF;
601 
602     /* Deal with running status */
603 
604     if ( status < MIDI_NOTE_OFF )
605     {
606         status = device_info->running_status;
607     }
608 
609     /* Ensure the status is sane! */
610 
611     if ( status < MIDI_NOTE_OFF )
612     {
613         /* It's garbage, ignore it */
614         return MMSYSERR_NOERROR;
615     }
616 
617     /* Figure out the message category and channel */
618 
619     category = status & 0xF0;
620     channel = status & 0x0F;    /* we don't use this */
621 
622     data1 = (message & 0x0000FF00) >> 8;
623     data2 = (message & 0x00FF0000) >> 16;
624 
625     DPRINT("0x%x, %d, %d\n", (int) status, (int) data1, (int) data2);
626 
627     /* Filter drums (which are *usually* on channel 10) */
628     if ( channel == 10 )
629     {
630         return MMSYSERR_NOERROR;
631     }
632 
633     /* Pass to the appropriate message handler */
634 
635     switch ( category )
636     {
637         case MIDI_NOTE_ON :
638         {
639             PlayNote(device_info, data1, data2);
640             break;
641         }
642 
643         case MIDI_NOTE_OFF :
644         {
645             StopNote(device_info, data1);
646             break;
647         }
648     }
649 
650     return MMSYSERR_NOERROR;
651 }
652 
653 
654 #define PACK_MIDI(b1, b2, b3) \
655     ((b3 * 65536) + (b2 * 256) + b1);
656 
657 
658 /*
659     Processes a "long" MIDI message (ie, a MIDI message contained within a
660     buffer.) This is intended for supporting SysEx data, or blocks of MIDI
661     events. However in our case we're only interested in short MIDI messages,
662     so we scan the buffer, and each time we encounter a valid status byte
663     we start recording it as a new event. Once 3 bytes or a new status is
664     received, the event is passed to the short message handler.
665 */
666 
667 MMRESULT
668 ProcessLongMidiMessage(
669     DeviceInfo* device_info,
670     MIDIHDR* header)
671 {
672     unsigned int index = 0;
673     UCHAR* midi_bytes = (UCHAR*) header->lpData;
674 
675     unsigned int msg_index = 0;
676     UCHAR msg[3];
677 
678     /* Initialize the buffer */
679     msg[0] = msg[1] = msg[2] = 0;
680 
681     if ( ! ( header->dwFlags & MHDR_PREPARED ) )
682     {
683         DPRINT("Not prepared!\n");
684         return MIDIERR_UNPREPARED;
685     }
686 
687     DPRINT("Processing %d bytes of MIDI\n", (int) header->dwBufferLength);
688 
689     while ( index < header->dwBufferLength )
690     {
691         /* New status byte? ( = new event) */
692         if ( midi_bytes[index] & 0x80 )
693         {
694             DWORD short_msg;
695 
696             /* Deal with the existing event */
697 
698             if ( msg[0] & 0x80 )
699             {
700                 short_msg = PACK_MIDI(msg[0], msg[1], msg[2]);
701 
702                 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg[0], (int) msg[1], (int) msg[2]);
703                 ProcessShortMidiMessage(device_info, short_msg);
704             }
705 
706             /* Set new running status and start recording the event */
707             DPRINT("Set new running status\n");
708             device_info->running_status = midi_bytes[index];
709             msg[0] = midi_bytes[index];
710             msg_index = 1;
711         }
712 
713         /* Unexpected data byte? ( = re-use previous status) */
714         else if ( msg_index == 0 )
715         {
716             if ( device_info->running_status & 0x80 )
717             {
718                 DPRINT("Retrieving running status\n");
719                 msg[0] = device_info->running_status;
720                 msg[1] = midi_bytes[index];
721                 msg_index = 2;
722             }
723             else
724                 DPRINT("garbage\n");
725         }
726 
727         /* Expected data ( = append to message until buffer full) */
728         else
729         {
730             DPRINT("Next byte...\n");
731             msg[msg_index] = midi_bytes[index];
732             msg_index ++;
733 
734             if ( msg_index > 2 )
735             {
736                 DWORD short_msg;
737 
738                 short_msg = PACK_MIDI(msg[0], msg[1], msg[2]);
739 
740                 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg[0], (int) msg[1], (int) msg[2]);
741                 ProcessShortMidiMessage(device_info, short_msg);
742 
743                 /* Reinit */
744                 msg_index = 0;
745                 msg[0] = msg[1] = msg[2] = 0;
746             }
747         }
748 
749         index ++;
750     }
751 
752     /*
753         We're meant to clear MHDR_DONE and set MHDR_INQUEUE but since we
754         deal with everything here and now we might as well just say so.
755     */
756     header->dwFlags |= MHDR_DONE;
757     header->dwFlags &= ~ MHDR_INQUEUE;
758 
759     DPRINT("Success? %d\n", CallClient(the_device, MOM_DONE, (DWORD_PTR) header, 0));
760 
761     return MMSYSERR_NOERROR;
762 }
763 
764 
765 /*
766     Exported function that receives messages from WINMM (the MME API.)
767 */
768 
769 MMRESULT
770 FAR PASCAL
771 modMessage(
772     UINT device_id,
773     UINT message,
774     DWORD_PTR private_data,
775     DWORD_PTR parameter1,
776     DWORD_PTR parameter2)
777 {
778     switch ( message )
779     {
780         case MODM_GETNUMDEVS :
781         {
782             /* Only one internal PC speaker device (and even that's too much) */
783             DPRINT("MODM_GETNUMDEVS\n");
784             return 1;
785         }
786 
787         case MODM_GETDEVCAPS :
788         {
789             DPRINT("MODM_GETDEVCAPS\n");
790             return GetDeviceCapabilities((MIDIOUTCAPS*) parameter1);
791         }
792 
793         case MODM_OPEN :
794         {
795             DPRINT("MODM_OPEN\n");
796 
797             return OpenDevice((DeviceInfo**) private_data,
798                               (MIDIOPENDESC*) parameter1,
799                               parameter2);
800         }
801 
802         case MODM_CLOSE :
803         {
804             DPRINT("MODM_CLOSE\n");
805             return CloseDevice((DeviceInfo*) private_data);
806         }
807 
808         case MODM_DATA :
809         {
810             return ProcessShortMidiMessage((DeviceInfo*) private_data, parameter1);
811         }
812 
813         case MODM_PREPARE :
814         {
815             /* We don't bother with this */
816             MIDIHDR* hdr = (MIDIHDR*) parameter1;
817             hdr->dwFlags |= MHDR_PREPARED;
818             return MMSYSERR_NOERROR;
819         }
820 
821         case MODM_UNPREPARE :
822         {
823             MIDIHDR* hdr = (MIDIHDR*) parameter1;
824             hdr->dwFlags &= ~MHDR_PREPARED;
825             return MMSYSERR_NOERROR;
826         }
827 
828         case MODM_LONGDATA :
829         {
830             DPRINT("LONGDATA\n");
831             return ProcessLongMidiMessage((DeviceInfo*) private_data, (MIDIHDR*) parameter1);
832         }
833 
834         case MODM_RESET :
835         {
836             /* TODO */
837             break;
838         }
839     }
840 
841     DPRINT("Not supported %d\n", message);
842 
843     return MMSYSERR_NOTSUPPORTED;
844 }
845 
846 
847 /*
848     Driver entrypoint.
849 */
850 
851 LONG
852 FAR PASCAL
853 DriverProc(
854     DWORD driver_id,
855     HDRVR driver_handle,
856     UINT message,
857     LONG parameter1,
858     LONG parameter2)
859 {
860     switch ( message )
861     {
862         case DRV_LOAD :
863             DPRINT("DRV_LOAD\n");
864             the_device = NULL;
865             return 1L;
866 
867         case DRV_FREE :
868             DPRINT("DRV_FREE\n");
869             return 1L;
870 
871         case DRV_OPEN :
872             DPRINT("DRV_OPEN\n");
873             InitializeCriticalSection(&device_lock);
874             return 1L;
875 
876         case DRV_CLOSE :
877             DPRINT("DRV_CLOSE\n");
878             return 1L;
879 
880         case DRV_ENABLE :
881             DPRINT("DRV_ENABLE\n");
882             return 1L;
883 
884         case DRV_DISABLE :
885             DPRINT("DRV_DISABLE\n");
886             return 1L;
887 
888         /*
889             We don't provide configuration capabilities. This used to be
890             for things like I/O port, IRQ, DMA settings, etc.
891         */
892 
893         case DRV_QUERYCONFIGURE :
894             DPRINT("DRV_QUERYCONFIGURE\n");
895             return 0L;
896 
897         case DRV_CONFIGURE :
898             DPRINT("DRV_CONFIGURE\n");
899             return 0L;
900 
901         case DRV_INSTALL :
902             DPRINT("DRV_INSTALL\n");
903             return DRVCNF_RESTART;
904     };
905 
906     DPRINT("???\n");
907 
908     return DefDriverProc(driver_id,
909                          driver_handle,
910                          message,
911                          parameter1,
912                          parameter2);
913 }
914