[USB]
[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 #include <windows.h>
45 #define NTOS_MODE_USER
46 #include <ndk/iofuncs.h>
47 #include <ndk/obfuncs.h>
48 #include <ndk/rtlfuncs.h>
49 #include <stdio.h>
50 #include <ntddbeep.h>
51 #include <math.h>
52
53 #include <mmddk.h>
54 #include <mmsystem.h>
55
56 /*#define DPRINT printf*/
57 #define DPRINT FakePrintf
58
59 /* A few MIDI command categories */
60 #define MIDI_NOTE_OFF 0x80
61 #define MIDI_NOTE_ON 0x90
62 #define MIDI_CONTROL_CHANGE 0xB0
63 #define MIDI_PROGRAM 0xC0
64 #define MIDI_PITCH_BEND 0xE0
65 #define MIDI_SYSTEM 0xFF
66
67 /* Specific commands */
68 #define MIDI_RESET 0xFF
69
70
71 typedef struct _NoteNode
72 {
73 struct _NoteNode* next;
74 struct _NoteNode* previous;
75
76 UCHAR note;
77 UCHAR velocity; /* 0 is note-off */
78 } NoteNode;
79
80 typedef struct _DeviceInfo
81 {
82 HDRVR mme_handle;
83 HANDLE kernel_device;
84
85 DWORD callback;
86 DWORD instance;
87 DWORD flags;
88
89 UCHAR running_status;
90
91 DWORD playing_notes_count;
92 NoteNode* note_list;
93 BOOL refresh_notes;
94
95 HANDLE thread_handle;
96 BOOL terminate_thread;
97 HANDLE thread_termination_complete;
98 } DeviceInfo;
99
100 DeviceInfo* the_device;
101 CRITICAL_SECTION device_lock;
102
103 void
104 FakePrintf(char* str, ...)
105 {
106 /* Just to shut the compiler up */
107 }
108
109
110 /*
111 This is designed to be treated as a thread, however it behaves as a
112 normal function if CONTINUOUS_NOTES is not defined.
113 */
114
115 DWORD WINAPI
116 ProcessPlayingNotes(
117 LPVOID parameter)
118 {
119 DeviceInfo* device_info = (DeviceInfo*) parameter;
120 NTSTATUS status;
121 IO_STATUS_BLOCK io_status_block;
122 DWORD arp_notes;
123
124 DPRINT("Note processing started\n");
125
126 /* We lock the note list only while accessing it */
127
128 #ifdef CONTINUOUS_NOTES
129 while ( ! device_info->terminate_thread )
130 #endif
131 {
132 NoteNode* node;
133
134 /* Number of notes being arpeggiated */
135 arp_notes = 1;
136
137 EnterCriticalSection(&device_lock);
138
139 /* Calculate how much time to allocate to each playing note */
140
141 DPRINT("%d notes active\n", (int) device_info->playing_notes_count);
142
143 node = device_info->note_list;
144
145 while ( ( node != NULL ) && ( arp_notes <= POLYPHONY ) )
146 {
147 BEEP_SET_PARAMETERS beep_data;
148 DWORD actually_playing = 0;
149
150 double frequency = node->note;
151
152 DPRINT("playing..\n");
153
154 frequency = frequency / 12;
155 frequency = pow(2, frequency);
156 frequency = 8.1758 * frequency;
157
158 if (device_info->playing_notes_count > POLYPHONY)
159 actually_playing = POLYPHONY;
160 else
161 actually_playing = device_info->playing_notes_count;
162
163 DPRINT("Frequency %f\n", frequency);
164
165 // TODO
166 beep_data.Frequency = (DWORD) frequency;
167 beep_data.Duration = TIMESLICE_SIZE / actually_playing; /* device_info->playing_notes_count; */
168
169 status = NtDeviceIoControlFile(device_info->kernel_device,
170 NULL,
171 NULL,
172 NULL,
173 &io_status_block,
174 IOCTL_BEEP_SET,
175 &beep_data,
176 sizeof(BEEP_SET_PARAMETERS),
177 NULL,
178 0);
179
180 if ( ! NT_SUCCESS(status) )
181 {
182 DPRINT("ERROR %d\n", (int) GetLastError());
183 }
184
185 SleepEx(beep_data.Duration, TRUE);
186
187 if ( device_info->refresh_notes )
188 {
189 device_info->refresh_notes = FALSE;
190 break;
191 }
192
193 arp_notes ++;
194 node = node->next;
195 }
196
197 LeaveCriticalSection(&device_lock);
198 }
199
200 #ifdef CONTINUOUS_NOTES
201 SetEvent(device_info->thread_termination_complete);
202 #endif
203
204 return 0;
205 }
206
207
208 /*
209 Fills a MIDIOUTCAPS structure with information about our device.
210 */
211
212 MMRESULT
213 GetDeviceCapabilities(
214 MIDIOUTCAPS* caps)
215 {
216 /* These are ignored for now */
217 caps->wMid = 0;
218 caps->wPid = 0;
219
220 caps->vDriverVersion = 0x0100;
221
222 memset(caps->szPname, 0, sizeof(caps->szPname));
223 memcpy(caps->szPname, L"PC speaker\0", strlen("PC speaker\0") * 2);
224
225 caps->wTechnology = MOD_SQSYNTH;
226
227 caps->wVoices = 1; /* We only have one voice */
228 caps->wNotes = POLYPHONY;
229 caps->wChannelMask = 0xFFBF; /* Ignore channel 10 */
230
231 caps->dwSupport = 0;
232
233 return MMSYSERR_NOERROR;
234 }
235
236
237 /*
238 Helper function that just simplifies calling the application making use
239 of us.
240 */
241
242 BOOL
243 CallClient(
244 DeviceInfo* device_info,
245 DWORD_PTR message,
246 DWORD_PTR parameter1,
247 DWORD_PTR parameter2)
248 {
249 DPRINT("Calling client - callback 0x%x mmhandle 0x%x\n", device_info->callback, device_info->mme_handle);
250 return DriverCallback(device_info->callback,
251 HIWORD(device_info->flags),
252 device_info->mme_handle,
253 message,
254 device_info->instance,
255 parameter1,
256 parameter2);
257
258 }
259
260
261 /*
262 Open the kernel-mode device and allocate resources. This opens the
263 BEEP.SYS kernel device.
264 */
265
266 MMRESULT
267 OpenDevice(
268 DeviceInfo** private_data,
269 MIDIOPENDESC* open_desc,
270 DWORD flags)
271 {
272 NTSTATUS status;
273 HANDLE heap;
274 HANDLE kernel_device;
275 UNICODE_STRING beep_device_name;
276 OBJECT_ATTRIBUTES attribs;
277 IO_STATUS_BLOCK status_block;
278
279 /* One at a time.. */
280 if ( the_device )
281 {
282 DPRINT("Already allocated\n");
283 return MMSYSERR_ALLOCATED;
284 }
285
286 /* Make the device name into a unicode string and open it */
287
288 RtlInitUnicodeString(&beep_device_name,
289 L"\\Device\\Beep");
290
291 InitializeObjectAttributes(&attribs,
292 &beep_device_name,
293 0,
294 NULL,
295 NULL);
296
297 status = NtCreateFile(&kernel_device,
298 FILE_READ_DATA | FILE_WRITE_DATA,
299 &attribs,
300 &status_block,
301 NULL,
302 0,
303 FILE_SHARE_READ | FILE_SHARE_WRITE,
304 FILE_OPEN_IF,
305 0,
306 NULL,
307 0);
308
309 if ( ! NT_SUCCESS(status) )
310 {
311 DPRINT("Could not connect to BEEP device - %d\n", (int) GetLastError());
312 return MMSYSERR_ERROR;
313 }
314
315 DPRINT("Opened!\n");
316
317 /* Allocate and initialize the device info */
318
319 heap = GetProcessHeap();
320
321 the_device = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(DeviceInfo));
322
323 if ( ! the_device )
324 {
325 DPRINT("Out of memory\n");
326 return MMSYSERR_NOMEM;
327 }
328
329 /* Initialize */
330 the_device->kernel_device = kernel_device;
331 the_device->playing_notes_count = 0;
332 the_device->note_list = NULL;
333 the_device->thread_handle = 0;
334 the_device->terminate_thread = FALSE;
335 the_device->running_status = 0;
336
337 // TODO
338 the_device->mme_handle = (HDRVR) open_desc->hMidi;
339 the_device->callback = open_desc->dwCallback;
340 the_device->instance = open_desc->dwInstance;
341 the_device->flags = flags;
342
343 /* Store the pointer in the user data */
344 *private_data = the_device;
345
346 /* This is threading-related code */
347 #ifdef CONTINUOUS_NOTES
348 the_device->thread_termination_complete = CreateEvent(NULL, FALSE, FALSE, NULL);
349
350 if ( ! the_device->thread_termination_complete )
351 {
352 DPRINT("CreateEvent failed\n");
353 HeapFree(heap, 0, the_device);
354 return MMSYSERR_NOMEM;
355 }
356
357 the_device->thread_handle = CreateThread(NULL,
358 0,
359 ProcessPlayingNotes,
360 (PVOID) the_device,
361 0,
362 NULL);
363
364 if ( ! the_device->thread_handle )
365 {
366 DPRINT("CreateThread failed\n");
367 CloseHandle(the_device->thread_termination_complete);
368 HeapFree(heap, 0, the_device);
369 return MMSYSERR_NOMEM;
370 }
371 #endif
372
373 /* Now we call the client application to say the device is open */
374 DPRINT("Sending MOM_OPEN\n");
375 DPRINT("Success? %d\n", (int) CallClient(the_device, MOM_OPEN, 0, 0));
376
377 return MMSYSERR_NOERROR;
378 }
379
380
381 /*
382 Close the kernel-mode device.
383 */
384
385 MMRESULT
386 CloseDevice(DeviceInfo* device_info)
387 {
388 HANDLE heap = GetProcessHeap();
389
390 /* If we're working in threaded mode we need to wait for thread to die */
391 #ifdef CONTINUOUS_NOTES
392 the_device->terminate_thread = TRUE;
393
394 WaitForSingleObject(the_device->thread_termination_complete, INFINITE);
395
396 CloseHandle(the_device->thread_termination_complete);
397 #endif
398
399 /* Let the client application know the device is closing */
400 DPRINT("Sending MOM_CLOSE\n");
401 CallClient(device_info, MOM_CLOSE, 0, 0);
402
403 NtClose(device_info->kernel_device);
404
405 /* Free resources */
406 HeapFree(heap, 0, device_info);
407
408 the_device = NULL;
409
410 return MMSYSERR_NOERROR;
411 }
412
413
414 /*
415 Removes a note from the playing notes list. If the note is not playing,
416 we just pretend nothing happened.
417 */
418
419 MMRESULT
420 StopNote(
421 DeviceInfo* device_info,
422 UCHAR note)
423 {
424 HANDLE heap = GetProcessHeap();
425 NoteNode* node;
426 NoteNode* prev_node = NULL;
427
428 DPRINT("StopNote\n");
429
430 EnterCriticalSection(&device_lock);
431
432 node = device_info->note_list;
433
434 while ( node != NULL )
435 {
436 if ( node->note == note )
437 {
438 /* Found the note - just remove the node from the list */
439
440 DPRINT("Stopping note %d\n", (int) node->note);
441
442 if ( prev_node != NULL )
443 prev_node->next = node->next;
444 else
445 device_info->note_list = node->next;
446
447 HeapFree(heap, 0, node);
448
449 device_info->playing_notes_count --;
450
451 DPRINT("Note stopped - now playing %d notes\n", (int) device_info->playing_notes_count);
452
453 LeaveCriticalSection(&device_lock);
454 device_info->refresh_notes = TRUE;
455
456 return MMSYSERR_NOERROR;
457 }
458
459 prev_node = node;
460 node = node->next;
461 }
462
463 LeaveCriticalSection(&device_lock);
464
465 /* Hmm, a good idea? */
466 #ifndef CONTINUOUS_NOTES
467 ProcessPlayingNotes((PVOID) device_info);
468 #endif
469
470 return MMSYSERR_NOERROR;
471 }
472
473
474 /*
475 Adds a note to the playing notes list. If the note is already playing,
476 the definition of ALLOW_DUPLICATE_NOTES determines if an existing note
477 may be duplicated. Otherwise, duplicate notes are ignored.
478 */
479
480 MMRESULT
481 PlayNote(
482 DeviceInfo* device_info,
483 UCHAR note,
484 UCHAR velocity)
485 {
486 HANDLE heap = GetProcessHeap();
487
488 NoteNode* node;
489
490 DPRINT("PlayNote\n");
491
492 if ( velocity == 0 )
493 {
494 DPRINT("Zero velocity\n");
495
496 /* Velocity zero is effectively a "note off" */
497 StopNote(device_info, note);
498 }
499 else
500 {
501 /* Start playing the note */
502 NoteNode* new_node;
503
504 EnterCriticalSection(&device_lock);
505
506 node = device_info->note_list;
507
508 while ( node != NULL )
509 {
510 #ifndef ALLOW_DUPLICATE_NOTES
511 if ( ( node->note == note ) && ( velocity > 0 ) )
512 {
513 /* The note is already playing - do nothing */
514 DPRINT("Duplicate note playback request ignored\n");
515 LeaveCriticalSection(&device_lock);
516 return MMSYSERR_NOERROR;
517 }
518 #endif
519
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 }