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