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