1 /* $NetBSD: opl.c,v 1.7 1998/12/08 14:26:56 augustss Exp $ */ 2 3 /* 4 * Copyright (c) 1998 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Lennart Augustsson (augustss@netbsd.org). 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 /* 40 * The OPL3 (YMF262) manual can be found at 41 * ftp://ftp.yamahayst.com/pub/Fax_Back_Doc/Sound/YMF262.PDF 42 */ 43 44 #include <sys/param.h> 45 #include <sys/systm.h> 46 #include <sys/errno.h> 47 #include <sys/ioctl.h> 48 #include <sys/syslog.h> 49 #include <sys/device.h> 50 #include <sys/select.h> 51 52 #include <machine/cpu.h> 53 #include <machine/bus.h> 54 55 #include <sys/audioio.h> 56 #include <sys/midiio.h> 57 #include <dev/audio_if.h> 58 59 #include <dev/midi_if.h> 60 #include <dev/midivar.h> 61 #include <dev/midisynvar.h> 62 63 #include <dev/ic/oplreg.h> 64 #include <dev/ic/oplvar.h> 65 66 #ifdef AUDIO_DEBUG 67 #define DPRINTF(x) if (opldebug) printf x 68 #define DPRINTFN(n,x) if (opldebug >= (n)) printf x 69 int opldebug = 0; 70 #else 71 #define DPRINTF(x) 72 #define DPRINTFN(n,x) 73 #endif 74 75 struct real_voice { 76 u_int8_t voice_num; 77 u_int8_t voice_mode; /* 0=unavailable, 2=2 OP, 4=4 OP */ 78 u_int8_t iooffs; /* I/O port (left or right side) */ 79 u_int8_t op[4]; /* Operator offsets */ 80 }; 81 82 struct opl_voice voicetab[] = { 83 /* No I/O offs OP1 OP2 OP3 OP4 */ 84 /* --------------------------------------------------- */ 85 { 0, OPL_L, {0x00, 0x03, 0x08, 0x0b}}, 86 { 1, OPL_L, {0x01, 0x04, 0x09, 0x0c}}, 87 { 2, OPL_L, {0x02, 0x05, 0x0a, 0x0d}}, 88 89 { 3, OPL_L, {0x08, 0x0b, 0x00, 0x00}}, 90 { 4, OPL_L, {0x09, 0x0c, 0x00, 0x00}}, 91 { 5, OPL_L, {0x0a, 0x0d, 0x00, 0x00}}, 92 93 { 6, OPL_L, {0x10, 0x13, 0x00, 0x00}}, 94 { 7, OPL_L, {0x11, 0x14, 0x00, 0x00}}, 95 { 8, OPL_L, {0x12, 0x15, 0x00, 0x00}}, 96 97 { 0, OPL_R, {0x00, 0x03, 0x08, 0x0b}}, 98 { 1, OPL_R, {0x01, 0x04, 0x09, 0x0c}}, 99 { 2, OPL_R, {0x02, 0x05, 0x0a, 0x0d}}, 100 { 3, OPL_R, {0x08, 0x0b, 0x00, 0x00}}, 101 { 4, OPL_R, {0x09, 0x0c, 0x00, 0x00}}, 102 { 5, OPL_R, {0x0a, 0x0d, 0x00, 0x00}}, 103 104 { 6, OPL_R, {0x10, 0x13, 0x00, 0x00}}, 105 { 7, OPL_R, {0x11, 0x14, 0x00, 0x00}}, 106 { 8, OPL_R, {0x12, 0x15, 0x00, 0x00}} 107 }; 108 109 static void opl_command(struct opl_softc *, int, int, int); 110 void opl_reset(struct opl_softc *); 111 void opl_freq_to_fnum (int freq, int *block, int *fnum); 112 113 int oplsyn_open __P((midisyn *ms, int)); 114 void oplsyn_close __P((midisyn *)); 115 void oplsyn_reset __P((void *)); 116 void oplsyn_noteon __P((midisyn *, u_int32_t, u_int32_t, u_int32_t)); 117 void oplsyn_noteoff __P((midisyn *, u_int32_t, u_int32_t, u_int32_t)); 118 void oplsyn_keypressure __P((midisyn *, u_int32_t, u_int32_t, u_int32_t)); 119 void oplsyn_ctlchange __P((midisyn *, u_int32_t, u_int32_t, u_int32_t)); 120 void oplsyn_pitchbend __P((midisyn *, u_int32_t, u_int32_t, u_int32_t)); 121 void oplsyn_loadpatch __P((midisyn *, struct sysex_info *, struct uio *)); 122 123 124 void opl_set_op_reg __P((struct opl_softc *, int, int, int, u_char)); 125 void opl_set_ch_reg __P((struct opl_softc *, int, int, u_char)); 126 void opl_load_patch __P((struct opl_softc *, int)); 127 u_int32_t opl_get_block_fnum __P((int freq)); 128 int opl_calc_vol __P((int regbyte, int volume, int main_vol)); 129 130 struct midisyn_methods opl3_midi = { 131 oplsyn_open, 132 oplsyn_close, 133 0, 134 0, 135 oplsyn_noteon, 136 oplsyn_noteoff, 137 oplsyn_keypressure, 138 oplsyn_ctlchange, 139 0, 140 0, 141 oplsyn_pitchbend, 142 0 143 }; 144 145 void 146 opl_attach(sc) 147 struct opl_softc *sc; 148 { 149 int i; 150 151 if (!opl_find(sc)) { 152 printf("\nopl: find failed\n"); 153 return; 154 } 155 156 sc->syn.mets = &opl3_midi; 157 sprintf(sc->syn.name, "%sYamaha OPL%d", sc->syn.name, sc->model); 158 sc->syn.data = sc; 159 sc->syn.nvoice = sc->model == OPL_2 ? OPL2_NVOICE : OPL3_NVOICE; 160 sc->syn.flags = MS_DOALLOC | MS_FREQXLATE; 161 midisyn_attach(&sc->mididev, &sc->syn); 162 163 /* Set up voice table */ 164 for (i = 0; i < OPL3_NVOICE; i++) 165 sc->voices[i] = voicetab[i]; 166 167 opl_reset(sc); 168 169 printf(": model OPL%d\n", sc->model); 170 171 midi_attach_mi(&midisyn_hw_if, &sc->syn, &sc->mididev.dev); 172 } 173 174 static void 175 opl_command(sc, offs, addr, data) 176 struct opl_softc *sc; 177 int offs; 178 int addr, data; 179 { 180 DPRINTFN(4, ("opl_command: sc=%p, offs=%d addr=0x%02x data=0x%02x\n", 181 sc, offs, addr, data)); 182 offs += sc->offs; 183 bus_space_write_1(sc->iot, sc->ioh, OPL_ADDR+offs, addr); 184 if (sc->model == OPL_2) 185 delay(10); 186 else 187 delay(6); 188 bus_space_write_1(sc->iot, sc->ioh, OPL_DATA+offs, data); 189 if (sc->model == OPL_2) 190 delay(30); 191 else 192 delay(6); 193 } 194 195 int 196 opl_find(sc) 197 struct opl_softc *sc; 198 { 199 u_int8_t status1, status2; 200 201 DPRINTFN(2,("opl_find: ioh=0x%x\n", (int)sc->ioh)); 202 sc->model = OPL_2; /* worst case assumtion */ 203 204 /* Reset timers 1 and 2 */ 205 opl_command(sc, OPL_L, OPL_TIMER_CONTROL, 206 OPL_TIMER1_MASK | OPL_TIMER2_MASK); 207 /* Reset the IRQ of the FM chip */ 208 opl_command(sc, OPL_L, OPL_TIMER_CONTROL, OPL_IRQ_RESET); 209 210 /* get status bits */ 211 status1 = bus_space_read_1(sc->iot,sc->ioh,OPL_STATUS+OPL_L+sc->offs); 212 213 opl_command(sc, OPL_L, OPL_TIMER1, -2); /* wait 2 ticks */ 214 opl_command(sc, OPL_L, OPL_TIMER_CONTROL, /* start timer1 */ 215 OPL_TIMER1_START | OPL_TIMER2_MASK); 216 delay(1000); /* wait for timer to expire */ 217 218 /* get status bits again */ 219 status2 = bus_space_read_1(sc->iot,sc->ioh,OPL_STATUS+OPL_L+sc->offs); 220 221 opl_command(sc, OPL_L, OPL_TIMER_CONTROL, 222 OPL_TIMER1_MASK | OPL_TIMER2_MASK); 223 opl_command(sc, OPL_L, OPL_TIMER_CONTROL, OPL_IRQ_RESET); 224 225 DPRINTFN(2,("opl_find: %02x %02x\n", status1, status2)); 226 227 if ((status1 & OPL_STATUS_MASK) != 0 || 228 (status2 & OPL_STATUS_MASK) != (OPL_STATUS_IRQ | OPL_STATUS_FT1)) 229 return (0); 230 231 switch(status1) { 232 case 0x00: 233 case 0x0f: 234 sc->model = OPL_3; 235 break; 236 case 0x06: 237 sc->model = OPL_2; 238 break; 239 default: 240 return 0; 241 } 242 243 DPRINTFN(2,("opl_find: OPL%d at 0x%x detected\n", 244 sc->model, (int)sc->ioh)); 245 return (1); 246 } 247 248 void 249 opl_set_op_reg(sc, base, voice, op, value) 250 struct opl_softc *sc; 251 int base; 252 int voice; 253 int op; 254 u_char value; 255 { 256 struct opl_voice *v = &sc->voices[voice]; 257 opl_command(sc, v->iooffs, base + v->op[op], value); 258 } 259 260 void 261 opl_set_ch_reg(sc, base, voice, value) 262 struct opl_softc *sc; 263 int base; 264 int voice; 265 u_char value; 266 { 267 struct opl_voice *v = &sc->voices[voice]; 268 opl_command(sc, v->iooffs, base + v->voiceno, value); 269 } 270 271 272 void 273 opl_load_patch(sc, v) 274 struct opl_softc *sc; 275 int v; 276 { 277 struct opl_operators *p = sc->voices[v].patch; 278 279 opl_set_op_reg(sc, OPL_AM_VIB, v, 0, p->ops[OO_CHARS+0]); 280 opl_set_op_reg(sc, OPL_AM_VIB, v, 1, p->ops[OO_CHARS+1]); 281 opl_set_op_reg(sc, OPL_KSL_LEVEL, v, 0, p->ops[OO_KSL_LEV+0]); 282 opl_set_op_reg(sc, OPL_KSL_LEVEL, v, 1, p->ops[OO_KSL_LEV+1]); 283 opl_set_op_reg(sc, OPL_ATTACK_DECAY, v, 0, p->ops[OO_ATT_DEC+0]); 284 opl_set_op_reg(sc, OPL_ATTACK_DECAY, v, 1, p->ops[OO_ATT_DEC+1]); 285 opl_set_op_reg(sc, OPL_SUSTAIN_RELEASE, v, 0, p->ops[OO_SUS_REL+0]); 286 opl_set_op_reg(sc, OPL_SUSTAIN_RELEASE, v, 1, p->ops[OO_SUS_REL+1]); 287 opl_set_op_reg(sc, OPL_WAVE_SELECT, v, 0, p->ops[OO_WAV_SEL+0]); 288 opl_set_op_reg(sc, OPL_WAVE_SELECT, v, 1, p->ops[OO_WAV_SEL+1]); 289 opl_set_ch_reg(sc, OPL_FEEDBACK_CONNECTION, v, p->ops[OO_FB_CONN]); 290 } 291 292 #define OPL_FNUM_FAIL 0xffff 293 u_int32_t 294 opl_get_block_fnum(freq) 295 int freq; 296 { 297 u_int32_t f_num = freq / 3125; 298 u_int32_t block = 0; 299 300 while (f_num > 0x3FF && block < 8) { 301 block++; 302 f_num >>= 1; 303 } 304 305 if (block > 7) 306 return (OPL_FNUM_FAIL); 307 else 308 return ((block << 10) | f_num); 309 } 310 311 312 void 313 opl_reset(sc) 314 struct opl_softc *sc; 315 { 316 int i; 317 318 for (i = 1; i <= OPL_MAXREG; i++) 319 opl_command(sc, OPL_L, OPL_KEYON_BLOCK + i, 0); 320 321 opl_command(sc, OPL_L, OPL_TEST, OPL_ENABLE_WAVE_SELECT); 322 opl_command(sc, OPL_L, OPL_PERCUSSION, 0); 323 if (sc->model == OPL_3) { 324 opl_command(sc, OPL_R, OPL_MODE, OPL3_ENABLE); 325 opl_command(sc, OPL_R,OPL_CONNECTION_SELECT,OPL_NOCONNECTION); 326 } 327 328 sc->volume = 64; 329 } 330 331 int 332 oplsyn_open(ms, flags) 333 midisyn *ms; 334 int flags; 335 { 336 struct opl_softc *sc = ms->data; 337 338 DPRINTFN(2, ("oplsyn_open: %d\n", flags)); 339 340 opl_reset(ms->data); 341 if (sc->spkrctl) 342 sc->spkrctl(sc->spkrarg, 1); 343 return (0); 344 } 345 346 void 347 oplsyn_close(ms) 348 midisyn *ms; 349 { 350 struct opl_softc *sc = ms->data; 351 352 DPRINTFN(2, ("oplsyn_close:\n")); 353 354 /*opl_reset(ms->data);*/ 355 if (sc->spkrctl) 356 sc->spkrctl(sc->spkrarg, 0); 357 } 358 359 #if 0 360 void 361 oplsyn_getinfo(addr, sd) 362 void *addr; 363 struct synth_dev *sd; 364 { 365 struct opl_softc *sc = addr; 366 367 sd->name = sc->model == OPL_2 ? "Yamaha OPL2" : "Yamaha OPL3"; 368 sd->type = SYNTH_TYPE_FM; 369 sd->subtype = sc->model == OPL_2 ? SYNTH_SUB_FM_TYPE_ADLIB 370 : SYNTH_SUB_FM_TYPE_OPL3; 371 sd->capabilities = 0; 372 } 373 #endif 374 375 void 376 oplsyn_reset(addr) 377 void *addr; 378 { 379 struct opl_softc *sc = addr; 380 DPRINTFN(3, ("oplsyn_reset:\n")); 381 opl_reset(sc); 382 } 383 384 int8_t opl_volume_table[128] = 385 {-64, -48, -40, -35, -32, -29, -27, -26, 386 -24, -23, -21, -20, -19, -18, -18, -17, 387 -16, -15, -15, -14, -13, -13, -12, -12, 388 -11, -11, -10, -10, -10, -9, -9, -8, 389 -8, -8, -7, -7, -7, -6, -6, -6, 390 -5, -5, -5, -5, -4, -4, -4, -4, 391 -3, -3, -3, -3, -2, -2, -2, -2, 392 -2, -1, -1, -1, -1, 0, 0, 0, 393 0, 0, 0, 1, 1, 1, 1, 1, 394 1, 2, 2, 2, 2, 2, 2, 2, 395 3, 3, 3, 3, 3, 3, 3, 4, 396 4, 4, 4, 4, 4, 4, 4, 5, 397 5, 5, 5, 5, 5, 5, 5, 5, 398 6, 6, 6, 6, 6, 6, 6, 6, 399 6, 7, 7, 7, 7, 7, 7, 7, 400 7, 7, 7, 8, 8, 8, 8, 8}; 401 402 int 403 opl_calc_vol(regbyte, volume, mainvol) 404 int regbyte; 405 int volume; 406 int mainvol; 407 { 408 int level = ~regbyte & OPL_TOTAL_LEVEL_MASK; 409 410 if (mainvol > 127) 411 mainvol = 127; 412 413 volume = (volume * mainvol) / 127; 414 415 if (level) 416 level += opl_volume_table[volume]; 417 418 if (level > OPL_TOTAL_LEVEL_MASK) 419 level = OPL_TOTAL_LEVEL_MASK; 420 if (level < 0) 421 level = 0; 422 423 return (~level & OPL_TOTAL_LEVEL_MASK); 424 } 425 426 void 427 oplsyn_noteon(ms, voice, freq, vel) 428 midisyn *ms; 429 u_int32_t voice, freq, vel; 430 { 431 struct opl_softc *sc = ms->data; 432 struct opl_voice *v; 433 struct opl_operators *p; 434 u_int32_t block_fnum; 435 int mult; 436 int c_mult, m_mult; 437 u_int8_t chars0, chars1, ksl0, ksl1, fbc; 438 u_int8_t r20m, r20c, r40m, r40c, rA0, rB0; 439 u_int8_t vol0, vol1; 440 441 DPRINTFN(3, ("oplsyn_noteon: %p %d %d\n", sc, voice, 442 MIDISYN_FREQ_TO_HZ(freq))); 443 444 #ifdef DIAGNOSTIC 445 if (voice < 0 || voice >= sc->syn.nvoice) { 446 printf("oplsyn_noteon: bad voice %d\n", voice); 447 return; 448 } 449 #endif 450 /* Turn off old note */ 451 opl_set_op_reg(sc, OPL_KSL_LEVEL, voice, 0, 0xff); 452 opl_set_op_reg(sc, OPL_KSL_LEVEL, voice, 1, 0xff); 453 opl_set_ch_reg(sc, OPL_KEYON_BLOCK, voice, 0); 454 455 v = &sc->voices[voice]; 456 457 p = &opl2_instrs[MS_GETPGM(ms, voice)]; 458 v->patch = p; 459 opl_load_patch(sc, voice); 460 461 mult = 1; 462 for (;;) { 463 block_fnum = opl_get_block_fnum(freq / mult); 464 if (block_fnum != OPL_FNUM_FAIL) 465 break; 466 mult *= 2; 467 if (mult == 16) 468 mult = 15; 469 } 470 471 chars0 = p->ops[OO_CHARS+0]; 472 chars1 = p->ops[OO_CHARS+1]; 473 m_mult = (chars0 & OPL_MULTIPLE_MASK) * mult; 474 c_mult = (chars1 & OPL_MULTIPLE_MASK) * mult; 475 if ((block_fnum == OPL_FNUM_FAIL) || (m_mult > 15) || (c_mult > 15)) { 476 printf("oplsyn_noteon: frequence out of range %d\n", 477 MIDISYN_FREQ_TO_HZ(freq)); 478 return; 479 } 480 r20m = (chars0 &~ OPL_MULTIPLE_MASK) | m_mult; 481 r20c = (chars1 &~ OPL_MULTIPLE_MASK) | c_mult; 482 483 /* 2 voice */ 484 ksl0 = p->ops[OO_KSL_LEV+0]; 485 ksl1 = p->ops[OO_KSL_LEV+1]; 486 if (p->ops[OO_FB_CONN] & 0x01) { 487 vol0 = opl_calc_vol(ksl0, vel, sc->volume); 488 vol1 = opl_calc_vol(ksl1, vel, sc->volume); 489 } else { 490 vol0 = ksl0; 491 vol1 = opl_calc_vol(ksl1, vel, sc->volume); 492 } 493 r40m = (ksl0 & OPL_KSL_MASK) | vol0; 494 r40c = (ksl1 & OPL_KSL_MASK) | vol1; 495 496 rA0 = block_fnum & 0xFF; 497 rB0 = (block_fnum >> 8) | OPL_KEYON_BIT; 498 499 v->rB0 = rB0; 500 501 fbc = p->ops[OO_FB_CONN]; 502 if (sc->model == OPL_3) { 503 fbc &= ~OPL_STEREO_BITS; 504 /* XXX use pan */ 505 fbc |= OPL_VOICE_TO_LEFT | OPL_VOICE_TO_RIGHT; 506 } 507 opl_set_ch_reg(sc, OPL_FEEDBACK_CONNECTION, voice, fbc); 508 509 opl_set_op_reg(sc, OPL_AM_VIB, voice, 0, r20m); 510 opl_set_op_reg(sc, OPL_AM_VIB, voice, 1, r20c); 511 opl_set_op_reg(sc, OPL_KSL_LEVEL, voice, 0, r40m); 512 opl_set_op_reg(sc, OPL_KSL_LEVEL, voice, 1, r40c); 513 opl_set_ch_reg(sc, OPL_FNUM_LOW, voice, rA0); 514 opl_set_ch_reg(sc, OPL_KEYON_BLOCK, voice, rB0); 515 } 516 517 void 518 oplsyn_noteoff(ms, voice, note, vel) 519 midisyn *ms; 520 u_int32_t voice, note, vel; 521 { 522 struct opl_softc *sc = ms->data; 523 struct opl_voice *v; 524 525 DPRINTFN(3, ("oplsyn_noteoff: %p %d %d\n", sc, voice, 526 MIDISYN_FREQ_TO_HZ(note))); 527 528 #ifdef DIAGNOSTIC 529 if (voice < 0 || voice >= sc->syn.nvoice) { 530 printf("oplsyn_noteoff: bad voice %d\n", voice); 531 return; 532 } 533 #endif 534 v = &sc->voices[voice]; 535 opl_set_ch_reg(sc, 0xB0, voice, v->rB0 & ~OPL_KEYON_BIT); 536 } 537 538 void 539 oplsyn_keypressure(ms, voice, note, vel) 540 midisyn *ms; 541 u_int32_t voice, note, vel; 542 { 543 #ifdef AUDIO_DEBUG 544 struct opl_softc *sc = ms->data; 545 DPRINTFN(1, ("oplsyn_keypressure: %p %d\n", sc, note)); 546 #endif 547 } 548 549 void 550 oplsyn_ctlchange(ms, voice, parm, w14) 551 midisyn *ms; 552 u_int32_t voice, parm, w14; 553 { 554 #ifdef AUDIO_DEBUG 555 struct opl_softc *sc = ms->data; 556 DPRINTFN(1, ("oplsyn_ctlchange: %p %d\n", sc, voice)); 557 #endif 558 } 559 560 void 561 oplsyn_pitchbend(ms, voice, parm, x) 562 midisyn *ms; 563 u_int32_t voice, parm, x; 564 { 565 #ifdef AUDIO_DEBUG 566 struct opl_softc *sc = ms->data; 567 DPRINTFN(1, ("oplsyn_pitchbend: %p %d\n", sc, voice)); 568 #endif 569 } 570 571 void 572 oplsyn_loadpatch(ms, sysex, uio) 573 midisyn *ms; 574 struct sysex_info *sysex; 575 struct uio *uio; 576 { 577 #if 0 578 struct opl_softc *sc = ms->data; 579 struct sbi_instrument ins; 580 581 DPRINTFN(1, ("oplsyn_loadpatch: %p\n", sc)); 582 583 memcpy(&ins, sysex, sizeof *sysex); 584 if (uio->uio_resid >= sizeof ins - sizeof *sysex) 585 return EINVAL; 586 uiomove((char *)&ins + sizeof *sysex, sizeof ins - sizeof *sysex, uio); 587 /* XXX */ 588 #endif 589 } 590